automation wip
This commit is contained in:
250
twitch.js
250
twitch.js
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user