diff --git a/conf/timers.json b/conf/timers.json new file mode 100755 index 0000000..668933d --- /dev/null +++ b/conf/timers.json @@ -0,0 +1,7 @@ +[ + { + "name": "vr", + "interval": 1800, + "value": "Video and room requests are on! Use $vr to request a video from this list [https://pastebin.com/qv0wDkvB] or $room to request a specific room (looped for a few minutes) from this list [https://goo.gl/qoNmuH]" + } +] \ No newline at end of file diff --git a/fgfm.TODO b/fgfm.TODO index e1ea65c..492d488 100755 --- a/fgfm.TODO +++ b/fgfm.TODO @@ -1,42 +1,24 @@ -- Ability to control stream from web or twitch - - The OBS "director" and its state need to be accessible from both - - How to accomplish this? Websocket? - TODO: - ✔ Add cooldowns @done (18-10-02 10:16) - ☐ Silence detection - - If stream has been silent for more than X minutes, try skipping song + ☐ Have director emit events for bots to listen to + - show status changing + - video starting/ending/skipped + ☐ Auto-enable vrmode timer when show starts (listen for event) ☐ Start/stop stream automation - ☐ Set up the queue upon init so it can be managed during startup + ☐ Support scheduled start/stop + - instead of countdown for X seconds, use datetime argument ☐ Start - ✔ Start stream @done (18-10-26 09:44) - ✔ Starting Soon is shown until countdown is triggered @done (18-10-26 09:44) - - Add parameter for countdown - Countdown for X minutes is triggered and shown - Once countdown finishes, switch to intro scene and play - Switch to fgfm once intro finishes - ☐ Stop - - Parameter for how long until the stream should end - - Switch to credits with 1 minute remaining - - Fade out audio sources with 5 seconds left - - Stop Stream - ☐ Fix commercial playing issue (switches back to scene early) - ☐ Change $auw and $meme to queue up the videos just like the others (don't switch scenes) - ☐ Don't auto-init GHOBS or FGFM, make them on-demand ☐ Decouple twitch chat from GHOBS ☐ Move anything that calls director.state from app into fgfm lib ☐ Restrict # of requests a user can have in the queue at once - ☐ Move vrmode timer to this bot, delete from SLCB ☐ Room vid requests / import ☐ Improved interface for viewer requests ☐ Web interface? Twitch extension? ☐ Improvements ☐ When playing a room back, loop it at slower speeds for a few iterations - ☐ Support viewer $skip voting - ☐ Remove currently playing video from vote choices - ☐ Command to add sets of videos to the queue at once (like the entire ttas or all gold segments) - ☐ Command to stop video rotation / timers (shutdown) - ☐ Support for $pause (pauses queue after current video finishes) + ☐ Support adding sets of videos to the queue at once (like the entire ttas or all gold segments) ☐ Tool to output list of video ID's / descriptions ☐ Stream alerts for chat ☐ Rotating background images (leftside) @@ -45,6 +27,8 @@ TODO: - !bender - !carlton - !weeb + ☐ Fix commercial playing issue (switches back to scene early) + ☐ Change $auw and $meme to queue up the videos just like the others (don't switch scenes) Ideas: ☐ Web interface for viewers to issue commands -- twitch extension?! @@ -62,6 +46,27 @@ StreamWebRemote: ___________________ Archive: + ✔ Remove currently playing video from vote choices @done (18-10-30 11:51) @project(TODO) + ✔ track votes for the current playing video @done (18-10-30 11:48) @project(TODO) + ✔ if threshold is met, skip @done (18-10-30 11:48) @project(TODO) + ✔ config item for how many votes are required to skip @done (18-10-30 11:48) @project(TODO) + ✔ clear votes after video finishes @done (18-10-30 11:48) @project(TODO) + ✔ Support viewer $skip voting @done (18-10-30 11:48) @project(TODO) + ✔ update director showStatus to 'PAUSED' @done (18-10-30 11:33) @project(TODO) + ✔ have nextVideo check for PAUSED @done (18-10-30 11:33) @project(TODO) + ✔ Support for $pause (pauses queue after current video finishes) @done (18-10-30 11:33) @project(TODO) + ✔ Move vrmode timer to this bot, delete from SLCB @done (18-10-30 11:20) @project(TODO) + ✔ Don't auto-init GHOBS or FGFM, make them on-demand @done (18-10-30 08:26) @project(TODO) + ✔ Switch to credits with 1 minute remaining @done (18-10-30 08:15) @project(TODO) + ✔ Stop Stream @done (18-10-30 08:15) @project(TODO) + ✔ Stop @done (18-10-30 08:15) @project(TODO) + ✔ Parameter for how long until the stream should end @done (18-10-30 08:15) @project(TODO) + ✔ Fade out audio sources with 5 seconds left @done (18-10-30 08:15) @project(TODO) + ✔ Add parameter for countdown @done (18-10-30 08:15) @project(TODO) + ✔ Set up the queue upon init so it can be managed during startup @done (18-10-30 08:14) @project(TODO) + ✔ Starting Soon is shown until countdown is triggered @done (18-10-26 09:44) @project(TODO) + ✔ Start stream @done (18-10-26 09:44) @project(TODO) + ✔ Add cooldowns @done (18-10-02 10:16) @project(TODO) ✔ video length @done (18-09-28 09:33) @project(TODO) ✔ root folder name @done (18-09-28 09:33) @project(TODO) ✔ room ID @done (18-09-28 09:33) @project(TODO) diff --git a/fgfm.js b/fgfm.js index 3925450..658115d 100755 --- a/fgfm.js +++ b/fgfm.js @@ -6,6 +6,7 @@ const irc = require('irc'); const schedule = require('node-schedule'); const md5 = require('md5'); +const moment = require('moment'); // Import local packages const GHOBS = require('./lib/ghobs'); @@ -18,6 +19,15 @@ let config = require('./config.json'); config.vods = require(config.vodConfigFile); config.rooms = require(config.roomConfigFile); let snesGames = require('./conf/snesgames.json'); +let timersList = require('./conf/timers.json'); + +let activeTimers = []; +let skipVote = { + target: null, + count: 0 +}; +// @TODO: Move to config +config.skipVoteThreshold = 2; // Main screen turn on const obs = new GHOBS(config); @@ -80,6 +90,7 @@ const streamInit = (config, twitch) => { init: (cmd) => { let delaySeconds = cmd.args[1] || 300; + director.startingSoon(delaySeconds); }, @@ -129,6 +140,49 @@ const streamInit = (config, twitch) => { }, + timer: (cmd) => { + let timerName = cmd.args[1] || false; + if (!timerName) { + twitch.botChat.say(cmd.to, `A timer name is required!`); + return; + } + + // search timers for matching name + let theTimerIndex = timersList.findIndex(e => e.name === timerName); + if (theTimerIndex === -1) { + twitch.botChat.say(cmd.to, `Invalid timer name!`); + return; + } + + let theTimer = timersList[theTimerIndex]; + + // look in activeTimers for current status + let currentTimerIndex = activeTimers.findIndex(e => e.name === timerName); + + let timerStatus = cmd.args[2] || false; + if (!timerStatus || timerStatus !== 'on' || timerStatus !== 'off') { + // toggle by default + if (currentTimerIndex === -1) { + timerStatus = 'on'; + } else { + timerStatus = 'off'; + } + } + + if (currentTimerIndex === -1 && timerStatus === 'on') { + let timerFunc = () => { + twitch.botChat.say(config.twitch.channel, theTimer.value); + }; + let timerInterval = setInterval(timerFunc, theTimer.interval*1000); + activeTimers.push({name: theTimer.name, timer: timerInterval}); + timerFunc(); + } else if (timerStatus === 'off') { + clearInterval(activeTimers[currentTimerIndex].timer); + activeTimers.splice(currentTimerIndex, 1); + } + }, + + auw: (cmd) => { director.showMeme('auw'); }, @@ -220,6 +274,16 @@ const streamInit = (config, twitch) => { }, + pause: (cmd) => { + director.pause(); + }, + + + resume: (cmd) => { + director.resume(); + }, + + clear: (cmd) => { director.clearQueue(); }, @@ -374,7 +438,25 @@ const streamInit = (config, twitch) => { rngames: (cmd) => { twitch.botChat.say(cmd.to, snesGames.sort(util.randSort).slice(0, 10).join(' | ')); - } + }, + + + // voting to skip current video + skip: (cmd) => { + // check if there is an existing vote to skip for the director.state.currentVideo + if (skipVote.target === director.state.currentVideo.id) { + // if yes, add the vote, check if threshold is met, skip if necessary + skipVote.count++; + } else { + skipVote.target = director.state.currentVideo.id; + skipVote.count = 1; + } + + if (skipVote.count >= config.skipVoteThreshold) { + director.skip(); + skipVote.target = null; + } + }, } }; @@ -425,6 +507,8 @@ const streamInit = (config, twitch) => { .catch(console.error); }); + + // @TODO: Modularize timed events //console.log(`Initializing stream timers...`); let userVotes = currentChoices = []; @@ -477,7 +561,7 @@ const streamInit = (config, twitch) => { // choose more random videos from config.vods.alttp (that aren't already in the queue) // @TODO: Move into FGFM let vodsNotInQueue = config.vods.alttp.filter(e => { - let inQueue = director.state.videoQueue.findIndex(q => q.id === e.id) !== -1; + let inQueue = (director.state.videoQueue.findIndex(q => q.id === e.id) !== -1) && (director.state.currentVideo.id !== e.id); return !inQueue; }); currentChoices = vodsNotInQueue.sort(util.randSort).slice(0, config.videoPollSize); @@ -496,5 +580,11 @@ const streamInit = (config, twitch) => { }); }; +const startTimer = (timer) => { + setInterval(() => { + + }, timer.interval*1000); +}; + // catches Promise errors process.on('unhandledRejection', console.error); diff --git a/lib/fgfm.js b/lib/fgfm.js index ba053ec..d7883e4 100755 --- a/lib/fgfm.js +++ b/lib/fgfm.js @@ -157,7 +157,8 @@ function FGFM(config) { // Also handles "commercial breaks" if enabled this.nextVideo = () => { // @TODO: Validate this.state.showStatus -- make sure the "show" hasn't been paused or stopped - if (this.state.showStatus === 'ENDING' || this.state.showStatus === 'ENDED') { + let ignoreStates = ['ENDING', 'ENDED', 'PAUSED']; + if (ignoreStates.includes(this.state.showStatus)) { return; } @@ -282,7 +283,16 @@ function FGFM(config) { // Clears.. the... queue this.clearQueue = () => { this.state.videoQueue = []; - } + }; + + this.pause = () => { + this.state.showStatus = 'PAUSED'; + }; + + this.resume = () => { + this.state.showStatus = 'RUNNING'; + this.nextVideo(); + }; } module.exports = FGFM; diff --git a/sfx/mouthfeel.mp3 b/sfx/mouthfeel.mp3 new file mode 100755 index 0000000..f21ffaa Binary files /dev/null and b/sfx/mouthfeel.mp3 differ