automation wip

This commit is contained in:
Chris Ham
2018-09-13 15:51:30 -07:00
parent bd93260e21
commit 950ad92364
6 changed files with 339 additions and 44 deletions

250
twitch.js
View File

@@ -2,39 +2,44 @@
* GHBot4Twitch
*/
// @TODO: modularize OBS and Twitch code
// @TODO: Make the bot aware of what video is current active
// @TODO: Change video playlist source on an interval
// @TODO: Rotating background images (leftside)
// Import modules
const irc = require('irc');
const OBSWebSocket = require('obs-websocket-js');
const schedule = require('node-schedule');
const util = require('./lib/util');
// Read internal configuration
let config = require('./config.json');
let twitchChat;
let currentPlaylist = config.obs.defaultPlaylist;
let twitchChannel = '#' + config.twitch.channels[0].toLowerCase();
const init = (config) => {
let botChannel = '#' + config.twitch.username.toLowerCase();
// Connect to OBS Websocket
const obs = new OBSWebSocket();
console.log(`Connecting to OBS...`);
obs.connect({ address: config.obs.websocket.address, password: config.obs.websocket.password })
.then(() => {
console.log(`Success! We're connected to OBS!`);
//obs.getSourcesList().then(data => {console.log(data.sources)}).catch(console.error);
twitchInit(config, obs);
})
.catch(err => {
console.log(err);
});
obs.on('error', err => {
console.error('OBS socket error:', err);
// Connect to OBS Websocket
const obs = new OBSWebSocket();
console.log(`Connecting to OBS...`);
obs.connect({ address: config.obs.websocket.address, password: config.obs.websocket.password })
.then(() => {
console.log(`Success! We're connected to OBS!`);
return twitchInit(config, obs);
})
.then(data => {
return streamInit(config, obs, data);
})
.catch(err => {
console.log(err);
});
const twitchInit = (config, obs) => {
obs.on('error', err => {
console.error('OBS socket error:', err);
});
const twitchInit = (config, obs) => {
return new Promise((resolve, reject) => {
console.log('Connecting to Twitch...');
// Connect to Twitch IRC server with the Bot
@@ -67,7 +72,9 @@ const init = (config) => {
// Listen for specific commands from admins
if (config.twitch.admins.includes(from) || from === config.twitch.username.toLowerCase()) {
if (commandNoPrefix === 'show' || commandNoPrefix === 'hide') {
let newVisibility = (commandNoPrefix === 'show');
let visibleTerm = (newVisibility ? 'visible' : 'hidden');
@@ -100,32 +107,76 @@ const init = (config) => {
.catch(err => {
twitchChat.say(to, JSON.stringify(err));
});
} else if (commandNoPrefix === 'auw') {
obs.setSceneItemProperties({"item": "everybody-wow", "visible": true})
.then(res => {
// fade out headphone audio
/*for (i = 100; i >= 0; i--) {
obs.setVolume({"source": "headphones", "volume": i/100});
}*/
// @TODO: send command to mute the songrequest audio
editorChat.say(to, '!volume 0');
} else if (commandNoPrefix === 't') {
let target = commandParts[1] || false;
if (!target) {
twitchChat.say(to, `A scene item name is required!`);
return;
}
let sceneItem = {"item": target};
obs.getSceneItemProperties(sceneItem)
.then(data => {
let newVisibility = !data.visible;
let visibleTerm = (newVisibility ? 'visible' : 'hidden');
sceneItem.visible = newVisibility;
obs.setSceneItemProperties(sceneItem)
.then(res => {
twitchChat.say(to, `${target} is now ${visibleTerm}.`);
})
.catch(console.error);
})
.catch(err => {
twitchChat.say(to, JSON.stringify(err));
});
} else if (commandNoPrefix === 'swap') {
// hide first argument, show second argument
let targetToHide = commandParts[1] || false;
let targetToShow = commandParts[2] || false;
if (targetToHide === false || targetToShow == false) {
twitchChat.say(to, `Format: ${config.twitch.cmdPrefix}swap <item-to-hide> <item-to-show>`);
return
}
obs.setSceneItemProperties({"item": targetToHide, "visible": false})
.then(res => {
obs.setSceneItemProperties({"item": targetToShow, "visible": true});
})
.catch(console.error);
} else if (commandNoPrefix === 'auw') {
// @TODO: switch to 'commercial' scene and show appropriate items, then switch back
// this way, playing a commercial doesn't have to know what's playing in the other scene
obs.setCurrentScene({"scene-name": "commercial"})
.then(res => {
// show the video
return obs.setSceneItemProperties({"item": "everybody-wow", "scene-name": "commercial", "visible": true});
})
.then(res => {
// mute songrequest audio
editorChat.say(to, '!volume 0');
// show owen
obs.setSceneItemProperties({"item": "owen", "scene-name": "commercial", "visible": true});
// tell chat what's up
twitchChat.say(to, 'Everybody OwenWow');
// hide the source after a certain amount of time (248s in this case)
// swap back to fgfm scene after the video ends
setTimeout(() => {
obs.setSceneItemProperties({"item": "everybody-wow", "visible": false})
.then(res => {
// fade in headphone audio
/*for (i = 1; i <= 100; i++) {
obs.setVolume({"source": "headphones", "volume": i/100});
}*/
// @TODO: send command to unmute the songrequest audio
editorChat.say(to, '!volume 75');
twitchChat.say(to, 'OwenWow');
}).catch(console.error);
// hide video
obs.setSceneItemProperties({"item": "everybody-wow", "scene-name": "commercial", "visible": false})
// hide owen
obs.setSceneItemProperties({"item": "owen", "scene-name": "commercial", "visible": false});
// unmute songrequest audio
editorChat.say(to, '!volume 75');
// swap back to fgfm
obs.setCurrentScene({"scene-name": "fgfm"});
}, 248000);
}).catch(console.error);
})
.catch(console.error);
} else if (commandNoPrefix === 'switch') {
let target = commandParts[1] || false;
if (!target) {
twitchChat.say(to, `A scene name is required!`);
@@ -182,10 +233,123 @@ const init = (config) => {
twitchChat.addListener('motd', motd => {
console.log(`Received MOTD: ${motd}`);
});
}
resolve({"botChat": twitchChat, "editorChat": editorChat});
});
}
init(config);
const streamInit = (config, obs, twitch) => {
return new Promise((resolve, reject) => {
console.log(`Initializing stream timers...`);
// When: Hourly at 55 past
// What: AUW
let auwJob = schedule.scheduleJob({minute: 55}, (fireDate) => {
// AUW
twitch.editorChat.say(twitchChannel, `${config.twitch.cmdPrefix}auw`);
});
console.log(`AUW is scheduled to be shown at ${auwJob.nextInvocation()}`);
let userVotes = [];
let playlistChoices = config.obs.availablePlaylists.map((e, i, a) => {
return `[${i+1}] ${e.chatName}`;
});
setTimeout(() => {
twitch.botChat.say(twitchChannel, `Vote for which video playlist you'd like to see next using ${config.twitch.cmdPrefix}vote #: ${playlistChoices.join(' | ')}`);
}, 5000);
// When: Every 2 Hours
// What: Change the video playlist
let changePlaylistJob = schedule.scheduleJob("*/5 * * * *", () => {
// Base the selection on user votes collected since the last invocation (unless there are 0 votes, then choose randomly)
let newPlaylist;
if (userVotes.length === 0) {
// choose a random item other than currentPlaylist from config.obs.availablePlaylists
let choices = config.obs.availablePlaylists.slice(0);
currentChoice = choices.indexOf(e => e.sceneItem === currentPlaylist);
choices.splice(currentChoice, 1);
newPlaylist = util.randElement(choices);
console.log(`PLAYLIST CHOSEN RANDOMLY: ${newPlaylist.chatName}`);
} else {
// tally and sort votes
let tallied = userVotes.reduce((voteTallies, currentValue) => {
tallyIndex = voteTallies.find(e.id === currentValue.vote);
if (tallyIndex !== -1) {
voteTallies[tallyIndex].count++;
} else {
voteTallies.push({id: currentValue.vote, count: 1});
}
}).sort((a, b) => {
if (a.count < b.count) {
return -1;
}
if (a.count > b.count) {
return 1;
}
// a must be equal to b
return 0;
});
console.log(`[TEST] Voting Results: ${JSON.stringify(tallied)}`);
newPlaylist = config.obs.availablePlaylists[tallied[0].id-1];
console.log(`WINNER OF THE VOTE: ${newPlaylist.chatName}`);
//twitch.botChat.say(twitchChannel, `[TEST] Voting Results: ${JSON.stringify(tallied)}`);
// clear user votes
userVotes = [];
}
/*twitch.botChat.say(twitchChannel, `[TEST] Changing playlist from ${currentPlaylist} to ${newPlaylist.chatName}`);
twitch.editorChat.say(twitchChannel, `[TEST] ${config.twitch.cmdPrefix}swap ${currentPlaylist} ${newPlaylist.sceneItem}`);
twitch.editorChat.say(twitchChannel, `[TEST] !setcurrent NOW SHOWING: ${newPlaylist.activity}`);*/
console.log(`Changing playlist from ${currentPlaylist} to ${newPlaylist.chatName}`);
console.log(`${config.twitch.cmdPrefix}swap ${currentPlaylist} ${newPlaylist.sceneItem}`);
console.log(`!setcurrent NOW SHOWING: ${newPlaylist.activity}`);
currentPlaylist = newPlaylist.sceneItem;
});
console.log(`Playlist will be changed at ${changePlaylistJob.nextInvocation()}`);
// Track user votes for playlist
twitch.botChat.addListener('message', (from, to, message) => {
// Ignore everything from blacklisted users
if (config.twitch.blacklistedUsers.includes(from)) return;
// Listen for commands that start with the designated prefix
if (message.startsWith(config.twitch.cmdPrefix)) {
let commandParts = message.slice(config.twitch.cmdPrefix.length).split(' ');
let commandNoPrefix = commandParts[0] || '';
if (commandNoPrefix === 'vote') {
let userVote = commandParts[1] || false;
if (userVote === false) {
return twitch.botChat.say(to, `Vote for which video playlist you'd like to see next using ${config.twitch.cmdPrefix}vote #: ${playlistChoices.join(' | ')}`);
}
userVote = Number.parseInt(userVote);
if (!Number.isInteger(userVote) || userVote < 1 || userVote > playlistChoices.length) {
return twitch.botChat.say(to, `@${from}, please choose an option from 1 - ${playlistChoices.length}!`);
}
// Check for uniqueness of vote
// if it's not unique, update the vote
let prevVote = userVotes.findIndex(e => e.from === from);
if (prevVote !== -1) {
if (userVotes[prevVote].vote !== userVote) {
// update vote and inform the user
userVotes[prevVote].vote = userVote;
twitch.botChat.say(to, `@${from}, your vote has been updated!`);
}
} else {
// log user vote
userVotes.push({"from": from, "vote": userVote});
twitch.botChat.say(to, `@${from}, your vote has been registered!`);
}
}
}
});
});
}
// catches Promise errors
process.on('unhandledRejection', console.error);