diff --git a/config.json b/config.json index 5d65212..33ba804 100755 --- a/config.json +++ b/config.json @@ -54,18 +54,19 @@ ], "defaultPlaylist": "room-grind" }, - "initialQueueSize": 3, "defaultSceneName": "fgfm", "commercialSceneName": "commercial", - "videoPollSize": 5, "currentActivitySceneItemName": "now-showing-txt", - "commercialInterval": 3600, - "auwChance": 25, + "initialQueueSize": 3, "recentlyPlayedMemory": 5, "roomGrindChance": 25, "roomGrindPlaytime": 1800, - "defaultSRVolume": 75, + "videoPollSize": 5, + "videoPollIntervalMinutes": 15, "commercialsEnabled": false, + "commercialInterval": 3600, + "auwChance": 25, + "defaultSRVolume": 75, "vods": [ { "id": "ot-seg-escape", @@ -80,7 +81,8 @@ "scale.x": 960 }, "sceneItem": "4x3ph", - "length": 352 + "length": 352, + "includeInShuffle": true }, { "id": "ot-seg-eastern", @@ -95,7 +97,8 @@ "scale.x": 960 }, "sceneItem": "4x3ph", - "length": 277 + "length": 277, + "includeInShuffle": true }, { "id": "ot-seg-desert", @@ -110,7 +113,8 @@ "scale.x": 960 }, "sceneItem": "4x3ph", - "length": 345 + "length": 345, + "includeInShuffle": true }, { "id": "ot-seg-hera", @@ -125,7 +129,8 @@ "scale.x": 960 }, "sceneItem": "4x3ph", - "length": 299 + "length": 299, + "includeInShuffle": true }, { "id": "ot-seg-atower", @@ -140,7 +145,8 @@ "scale.x": 960 }, "sceneItem": "4x3ph", - "length": 352 + "length": 352, + "includeInShuffle": true }, { "id": "ot-seg-pod", @@ -155,7 +161,8 @@ "scale.x": 960 }, "sceneItem": "4x3ph", - "length": 309 + "length": 309, + "includeInShuffle": true }, { "id": "ot-seg-thieves", @@ -170,7 +177,8 @@ "scale.x": 960 }, "sceneItem": "4x3ph", - "length": 377 + "length": 377, + "includeInShuffle": true }, { "id": "ot-seg-skull", @@ -185,7 +193,8 @@ "scale.x": 960 }, "sceneItem": "4x3ph", - "length": 267 + "length": 267, + "includeInShuffle": true }, { "id": "ot-seg-ice", @@ -200,7 +209,8 @@ "scale.x": 960 }, "sceneItem": "4x3ph", - "length": 318 + "length": 318, + "includeInShuffle": true }, { "id": "ot-seg-swamp", @@ -215,7 +225,8 @@ "scale.x": 960 }, "sceneItem": "4x3ph", - "length": 345 + "length": 345, + "includeInShuffle": true }, { "id": "ot-seg-mire", @@ -230,7 +241,8 @@ "scale.x": 960 }, "sceneItem": "4x3ph", - "length": 365 + "length": 365, + "includeInShuffle": true }, { "id": "ot-seg-trock", @@ -245,7 +257,8 @@ "scale.x": 960 }, "sceneItem": "4x3ph", - "length": 361 + "length": 361, + "includeInShuffle": true }, { "id": "ot-seg-gtower", @@ -260,7 +273,8 @@ "scale.x": 960 }, "sceneItem": "4x3ph", - "length": 396 + "length": 396, + "includeInShuffle": true }, { "id": "ot-seg-ganon", @@ -275,7 +289,8 @@ "scale.x": 960 }, "sceneItem": "4x3ph", - "length": 108 + "length": 108, + "includeInShuffle": true }, { "id": "st-seg-escape", @@ -290,7 +305,8 @@ "scale.x": 960 }, "sceneItem": "4x3ph", - "length": 354 + "length": 354, + "includeInShuffle": true }, { "id": "st-seg-eastern", @@ -305,7 +321,8 @@ "scale.x": 960 }, "sceneItem": "4x3ph", - "length": 281 + "length": 281, + "includeInShuffle": true }, { "id": "st-seg-desert", @@ -320,7 +337,8 @@ "scale.x": 960 }, "sceneItem": "4x3ph", - "length": 347 + "length": 347, + "includeInShuffle": true }, { "id": "st-seg-hera", @@ -335,7 +353,8 @@ "scale.x": 960 }, "sceneItem": "4x3ph", - "length": 303 + "length": 303, + "includeInShuffle": true }, { "id": "st-seg-atower", @@ -350,7 +369,8 @@ "scale.x": 960 }, "sceneItem": "4x3ph", - "length": 354 + "length": 354, + "includeInShuffle": true }, { "id": "st-seg-pod", @@ -365,7 +385,8 @@ "scale.x": 960 }, "sceneItem": "4x3ph", - "length": 310 + "length": 310, + "includeInShuffle": true }, { "id": "st-seg-thieves", @@ -380,7 +401,8 @@ "scale.x": 960 }, "sceneItem": "4x3ph", - "length": 379 + "length": 379, + "includeInShuffle": true }, { "id": "st-seg-skull", @@ -395,7 +417,8 @@ "scale.x": 960 }, "sceneItem": "4x3ph", - "length": 270 + "length": 270, + "includeInShuffle": true }, { "id": "st-seg-ice", @@ -410,7 +433,8 @@ "scale.x": 960 }, "sceneItem": "4x3ph", - "length": 321 + "length": 321, + "includeInShuffle": true }, { "id": "st-seg-swamp", @@ -425,7 +449,8 @@ "scale.x": 960 }, "sceneItem": "4x3ph", - "length": 351 + "length": 351, + "includeInShuffle": true }, { "id": "st-seg-mire", @@ -440,7 +465,8 @@ "scale.x": 960 }, "sceneItem": "4x3ph", - "length": 370 + "length": 370, + "includeInShuffle": true }, { "id": "st-seg-trock", @@ -455,7 +481,8 @@ "scale.x": 960 }, "sceneItem": "4x3ph", - "length": 364 + "length": 364, + "includeInShuffle": true }, { "id": "st-seg-gtower", @@ -470,7 +497,8 @@ "scale.x": 960 }, "sceneItem": "4x3ph", - "length": 408 + "length": 408, + "includeInShuffle": true }, { "id": "nmg-gold-escape", @@ -485,7 +513,8 @@ "scale.x": 960 }, "sceneItem": "4x3ph", - "length": 360 + "length": 360, + "includeInShuffle": true }, { "id": "nmg-gold-eastern", @@ -500,7 +529,8 @@ "scale.x": 960 }, "sceneItem": "4x3ph", - "length": 302 + "length": 302, + "includeInShuffle": true }, { "id": "nmg-gold-desert", @@ -515,7 +545,8 @@ "scale.x": 960 }, "sceneItem": "4x3ph", - "length": 374 + "length": 374, + "includeInShuffle": true }, { "id": "nmg-gold-hera", @@ -530,7 +561,8 @@ "scale.x": 960 }, "sceneItem": "4x3ph", - "length": 332 + "length": 332, + "includeInShuffle": true }, { "id": "nmg-gold-atower", @@ -545,7 +577,8 @@ "scale.x": 960 }, "sceneItem": "4x3ph", - "length": 314 + "length": 314, + "includeInShuffle": true }, { "id": "nmg-gold-pod", @@ -560,7 +593,8 @@ "scale.x": 960 }, "sceneItem": "4x3ph", - "length": 376 + "length": 376, + "includeInShuffle": true }, { "id": "nmg-gold-thieves", @@ -575,7 +609,8 @@ "scale.x": 960 }, "sceneItem": "4x3ph", - "length": 432 + "length": 432, + "includeInShuffle": true }, { "id": "nmg-gold-skull", @@ -590,7 +625,8 @@ "scale.x": 960 }, "sceneItem": "4x3ph", - "length": 328 + "length": 328, + "includeInShuffle": true }, { "id": "nmg-gold-ice", @@ -605,7 +641,8 @@ "scale.x": 960 }, "sceneItem": "4x3ph", - "length": 379 + "length": 379, + "includeInShuffle": true }, { "id": "nmg-gold-swamp", @@ -620,7 +657,8 @@ "scale.x": 960 }, "sceneItem": "4x3ph", - "length": 418 + "length": 418, + "includeInShuffle": true }, { "id": "nmg-gold-mire", @@ -635,7 +673,8 @@ "scale.x": 960 }, "sceneItem": "4x3ph", - "length": 432 + "length": 432, + "includeInShuffle": true }, { "id": "nmg-gold-trock", @@ -650,7 +689,8 @@ "scale.x": 960 }, "sceneItem": "4x3ph", - "length": 434 + "length": 434, + "includeInShuffle": true }, { "id": "nmg-gold-gtower", @@ -665,7 +705,8 @@ "scale.x": 960 }, "sceneItem": "4x3ph", - "length": 433 + "length": 433, + "includeInShuffle": true }, { "id": "nmg-gold-ganon", @@ -680,7 +721,8 @@ "scale.x": 960 }, "sceneItem": "4x3ph", - "length": 117 + "length": 117, + "includeInShuffle": true }, { "id": "pb-100-ahp", @@ -695,7 +737,8 @@ "scale.x": 1280 }, "sceneItem": "16x9ph", - "length": 4786 + "length": 4786, + "includeInShuffle": true }, { "id": "pb-ab", @@ -710,7 +753,8 @@ "scale.x": 1280 }, "sceneItem": "16x9ph", - "length": 4200 + "length": 4200, + "includeInShuffle": false }, { "id": "pb-ad", @@ -725,7 +769,8 @@ "scale.x": 1280 }, "sceneItem": "16x9ph", - "length": 4555 + "length": 4555, + "includeInShuffle": true }, { "id": "pb-any-nmg", @@ -740,7 +785,8 @@ "scale.x": 1280 }, "sceneItem": "16x9ph", - "length": 5190 + "length": 5190, + "includeInShuffle": true }, { "id": "pb-any-no-eg", @@ -755,7 +801,8 @@ "scale.x": 1280 }, "sceneItem": "16x9ph", - "length": 1833 + "length": 1833, + "includeInShuffle": true }, { "id": "pb-master-sword", @@ -770,7 +817,8 @@ "scale.x": 1280 }, "sceneItem": "16x9ph", - "length": 1409 + "length": 1409, + "includeInShuffle": true }, { "id": "pb-ms", @@ -785,7 +833,8 @@ "scale.x": 1280 }, "sceneItem": "16x9ph", - "length": 3068 + "length": 3068, + "includeInShuffle": true }, { "id": "pb-ms-no-eg", @@ -800,7 +849,8 @@ "scale.x": 1280 }, "sceneItem": "16x9ph", - "length": 738 + "length": 738, + "includeInShuffle": true }, { "id": "pb-rbo", @@ -815,7 +865,8 @@ "scale.x": 1280 }, "sceneItem": "16x9ph", - "length": 4725 + "length": 4725, + "includeInShuffle": true }, { "id": "pb-100-nmg", @@ -825,7 +876,8 @@ "chatName": "100% NMG (PB)", "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\personal-bests\\100%\\2017-01-31-hundo-14930.mp4", "sceneItem": "legacyph", - "length": 6598 + "length": 6598, + "includeInShuffle": false }, { "id": "pb-dg", @@ -835,7 +887,8 @@ "chatName": "Defeat Ganon (PB)", "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\personal-bests\\defeat-ganon\\2017-02-20_12-55-40-defeat-ganon-1341.mp4", "sceneItem": "legacyph", - "length": 868 + "length": 868, + "includeInShuffle": true } ], "memes": [ @@ -844,98 +897,112 @@ "name": "archery", "filePath": "Y:\\media\\videos\\ALttP\\memes\\archery-contest.mp4", "sceneItem": "meme1", - "length": 27 + "length": 27, + "includeInShuffle": true }, { "id": "69blazeit", "name": "69blazeit", "filePath": "Y:\\media\\videos\\ALttP\\memes\\69BlazeIt.mp4", "sceneItem": "meme1", - "length": 92 + "length": 92, + "includeInShuffle": true }, { "id": "rpgfarm", "name": "rpgfarm", "filePath": "Y:\\media\\videos\\ALttP\\memes\\2016-07-24-1424-06-rpg-race-sniped.mp4", "sceneItem": "meme1", - "length": 144 + "length": 144, + "includeInShuffle": true }, { "id": "emmapeg", "name": "emmapeg", "filePath": "Y:\\media\\videos\\ALttP\\memes\\emma-pegging.mp4", "sceneItem": "meme1", - "length": 8 + "length": 8, + "includeInShuffle": true }, { "id": "handy", "name": "handy", "filePath": "Y:\\media\\videos\\ALttP\\memes\\handy-in-the-mothhole.mp4", "sceneItem": "meme1", - "length": 39 + "length": 39, + "includeInShuffle": true }, { "id": "bodyguard", "name": "bodyguard", "filePath": "Y:\\media\\videos\\ALttP\\memes\\heroic-popo.mp4", "sceneItem": "meme1", - "length": 14 + "length": 14, + "includeInShuffle": true }, { "id": "whowillitbe", "name": "whowillitbe", "filePath": "Y:\\media\\videos\\ALttP\\memes\\its-gonna-be-may.mp4", "sceneItem": "meme1", - "length": 30 + "length": 30, + "includeInShuffle": true }, { "id": "mindblown", "name": "mindblown", "filePath": "Y:\\media\\videos\\ALttP\\memes\\mindblowing-and-lifechanging.mp4", "sceneItem": "meme1", - "length": 55 + "length": 55, + "includeInShuffle": true }, { "id": "nerd-nookie", "name": "nerd-nookie", "filePath": "Y:\\media\\videos\\ALttP\\memes\\nerd-bizkit.mp4", "sceneItem": "meme1", - "length": 27 + "length": 27, + "includeInShuffle": true }, { "id": "curling", "name": "curling", "filePath": "Y:\\media\\videos\\ALttP\\curling-bored-janitors.mp4", "sceneItem": "meme1", - "length": 16 + "length": 16, + "includeInShuffle": true }, { "id": "airplane", "name": "airplane", "filePath": "Y:\\media\\videos\\ALttP\\emetaPlane.mp4", "sceneItem": "meme1", - "length": 5 + "length": 5, + "includeInShuffle": true }, { "id": "hard-things", "name": "hard-things", "filePath": "Y:\\media\\videos\\ALttP\\questions-about-hard-things.mp4", "sceneItem": "meme1", - "length": 39 + "length": 39, + "includeInShuffle": true }, { "id": "18arrows", "name": "18arrows", "filePath": "Y:\\media\\videos\\ALttP\\screevo-18-arrows-fine.mp4", "sceneItem": "meme1", - "length": 41 + "length": 41, + "includeInShuffle": true }, { "id": "quake", "name": "quake", "filePath": "Y:\\media\\videos\\ALttP\\trock-indoor-quake.mp4", "sceneItem": "meme1", - "length": 17 + "length": 17, + "includeInShuffle": true } ], "debug": false diff --git a/fgfm.TODO b/fgfm.TODO index 0fc8af3..f124925 100755 --- a/fgfm.TODO +++ b/fgfm.TODO @@ -16,6 +16,10 @@ TODO: ☐ Command to stop video rotation / timers (shutdown) ☐ Ability to include/exclude vods from shuffle in config +Ideas: + ☐ Web interface for viewers to issue commands -- twitch extension?!?!?! + ☐ Support songrequests -- play through discord? + ___________________ Archive: ✔ show commercials after a video length cap is hit -- show at conclusion of video @done (18-09-19 11:11) @project(TODO) diff --git a/fgfm.js b/fgfm.js index 0280cb1..660f655 100755 --- a/fgfm.js +++ b/fgfm.js @@ -12,7 +12,6 @@ const util = require('./lib/util'); let config = require('./config.json'); const snesGames = require('./conf/snesgames.json'); const twitchChannel = config.twitch.channels[0].toLowerCase(); -const randSort = () => { return 0.5 - Math.random() }; let videoQueue = recentlyPlayed = []; let currentVideo; @@ -40,71 +39,50 @@ obs.on('error', err => { console.error(`OBS socket error: ${JSON.stringify(err)}`); }); -// Initialize Twitch chat +// Connect to twitch, set up basic event listeners const twitchInit = (config, obs) => { return new Promise((resolve, reject) => { console.log('Connecting to Twitch...'); - - // Connect to Twitch IRC server with the Bot - let twitchChat = new irc.Client(config.twitch.ircServer, config.twitch.username, { - password: config.twitch.oauth, + let defaultTwitchConfig = { autoRejoin: true, retryCount: 10, channels: config.twitch.channels, debug: config.debug - }); + }; - // Also connect with an editor account - let editorChat = new irc.Client(config.twitch.ircServer, config.twitch.editorLogin.username, { - password: config.twitch.editorLogin.oauth, - autoRejoin: true, - retryCount: 10, - channels: config.twitch.channels, - debug: config.debug - }); + // Connect to Twitch with the bot account + let botChat = new irc.Client( + config.twitch.ircServer, + config.twitch.username, + Object.assign({password: config.twitch.oauth}, defaultTwitchConfig) + ); - // Set up event listeners for Twitch - twitchChat.addListener('error', message => { + // Connect to Twitch with an editor account + let editorChat = new irc.Client( + config.twitch.ircServer, + config.twitch.editorLogin.username, + Object.assign({password: config.twitch.editorLogin.oauth}, defaultTwitchConfig) + ); + + let twitchErrorHandler = message => { if (message.command != 'err_unknowncommand') { - console.error('error from Twitch IRC Server: ', message); + console.error('Error from Twitch IRC Server: ', message); } - }); - editorChat.addListener('error', message => { - if (message.command != 'err_unknowncommand') { - console.error('error from Twitch IRC Server: ', message); - } - }); + }; - twitchChat.addListener('registered', message => { - console.log(`Connected to ${message.server}`); - }); + // Set up bare minimum event listeners for Twitch + botChat.addListener('error', twitchErrorHandler); + editorChat.addListener('error', twitchErrorHandler); - twitchChat.addListener('join', (channel, nick, message) => { - if (nick === config.twitch.username) { - console.log(`Joined channel ${channel}`); - } - }); - - twitchChat.addListener('part', (channel, nick, message) => { - if (nick === config.twitch.username) { - console.log(`Left channel ${channel}`); - } - }); - - twitchChat.addListener('motd', motd => { - //console.log(`Received MOTD: ${motd}`); - }); - - resolve({"botChat": twitchChat, "editorChat": editorChat}); + resolve({"botChat": botChat, "editorChat": editorChat}); }); } // Initialize Stream automation const streamInit = (config, obs, twitch) => { return new Promise((resolve, reject) => { - console.log(`Setting up initial video queue...`); - videoQueue = config.vods.sort(randSort).slice(0, config.initialQueueSize); - console.log(`Initial queue: ${videoQueue.map((c, i) => `[${i+1}] ${c.chatName}`).join(' | ')}`); + videoQueue = config.vods.sort(util.randSort).slice(0, config.initialQueueSize); + console.log(`Initial video queue: ${videoQueue.map((c, i) => `[${i+1}] ${c.chatName}`).join(' | ')}`); // Shows a video in the given scene and triggers a callback when it's finished const playVideoInScene = (video, scene, callback) => { @@ -168,7 +146,7 @@ const streamInit = (config, obs, twitch) => { nextVideo(); }); } else { - let commercial = config.memes.sort(randSort)[0]; + let commercial = config.memes.sort(util.randSort)[0]; console.log(`Showing random meme: ${commercial.name}`); obs.setCurrentScene({"scene-name": config.commercialSceneName}) @@ -230,7 +208,7 @@ const streamInit = (config, obs, twitch) => { let freshVods = config.vods.filter(e => { return !recentlyPlayed.includes(e.id); }); - currentVideo = freshVods.sort(randSort).slice(0, 1).shift(); + currentVideo = freshVods.sort(util.randSort).slice(0, 1).shift(); } showVideo(currentVideo); @@ -383,7 +361,7 @@ const streamInit = (config, obs, twitch) => { // memes on-demand } else if (commandNoPrefix === 'meme') { commercialPlaying = true; - let commercial = config.memes.sort(randSort)[0]; + let commercial = config.memes.sort(util.randSort)[0]; obs.setCurrentScene({"scene-name": config.commercialSceneName}) .then(res => { return playVideoInScene(commercial, config.commercialSceneName, () => { @@ -588,7 +566,7 @@ const streamInit = (config, obs, twitch) => { // RNGAMES } else if (commandNoPrefix === 'rngames') { - twitch.botChat.say(to, snesGames.sort(randSort).slice(0, 10).join(' | ')); + twitch.botChat.say(to, snesGames.sort(util.randSort).slice(0, 10).join(' | ')); } //////////////// } @@ -648,7 +626,7 @@ const streamInit = (config, obs, twitch) => { let inQueue = videoQueue.findIndex(q => q.id === e.id) !== -1; return !inQueue; }); - currentChoices = vodsNotInQueue.sort(randSort).slice(0, config.videoPollSize); + currentChoices = vodsNotInQueue.sort(util.randSort).slice(0, config.videoPollSize); // Poll the chat let chatChoices = currentChoices.map((c, i) => { diff --git a/lib/fgfm.js b/lib/fgfm.js new file mode 100755 index 0000000..e69de29 diff --git a/lib/util.js b/lib/util.js index 05dcf31..941853d 100755 --- a/lib/util.js +++ b/lib/util.js @@ -46,4 +46,6 @@ exports.average = function(e) { let avg = sum / e.length; return avg; -}; \ No newline at end of file +}; + +exports.randSort = () => { return 0.5 - Math.random() }; \ No newline at end of file diff --git a/sfx/okusa.mp3 b/sfx/okusa.mp3 new file mode 100755 index 0000000..3086761 Binary files /dev/null and b/sfx/okusa.mp3 differ diff --git a/sfx/tasty.mp3 b/sfx/tasty.mp3 new file mode 100755 index 0000000..abd3741 Binary files /dev/null and b/sfx/tasty.mp3 differ diff --git a/sfx/watchyourback.mp3 b/sfx/watchyourback.mp3 new file mode 100755 index 0000000..d015276 Binary files /dev/null and b/sfx/watchyourback.mp3 differ