mirror of
https://github.com/streamwall/streamwall.git
synced 2026-01-31 01:12:48 -05:00
Add experimental Twitch plays functionality
This commit is contained in:
@@ -54,6 +54,10 @@ json-url = ["https://woke.net/api/streams.json"]
|
|||||||
#interval = 60
|
#interval = 60
|
||||||
#delay = 30
|
#delay = 30
|
||||||
|
|
||||||
|
[twitch.vote]
|
||||||
|
#template = "Switching to #<%- selectedIdx %> (with <%- voteCount %> votes)"
|
||||||
|
#interval = 15
|
||||||
|
|
||||||
[cert]
|
[cert]
|
||||||
# SSL certificates (optional)
|
# SSL certificates (optional)
|
||||||
# If you specify an https:// URL for the "webserver" option, a certificate will be automatically generated and signed by Let's Encrypt.
|
# If you specify an https:// URL for the "webserver" option, a certificate will be automatically generated and signed by Let's Encrypt.
|
||||||
|
|||||||
@@ -4,10 +4,12 @@ import ejs from 'ejs'
|
|||||||
import { State } from 'xstate'
|
import { State } from 'xstate'
|
||||||
import { ChatClient, SlowModeRateLimiter, LoginError } from 'dank-twitch-irc'
|
import { ChatClient, SlowModeRateLimiter, LoginError } from 'dank-twitch-irc'
|
||||||
|
|
||||||
|
const VOTE_RE = /^!(\d+)$/
|
||||||
|
|
||||||
export default class TwitchBot extends EventEmitter {
|
export default class TwitchBot extends EventEmitter {
|
||||||
constructor(config) {
|
constructor(config) {
|
||||||
super()
|
super()
|
||||||
const { username, token } = config
|
const { username, token, vote } = config
|
||||||
this.config = config
|
this.config = config
|
||||||
this.announceTemplate = ejs.compile(config.announce.template)
|
this.announceTemplate = ejs.compile(config.announce.template)
|
||||||
const client = new ChatClient({
|
const client = new ChatClient({
|
||||||
@@ -23,6 +25,12 @@ export default class TwitchBot extends EventEmitter {
|
|||||||
this.dwellTimeout = null
|
this.dwellTimeout = null
|
||||||
this.announceTimeouts = new Map()
|
this.announceTimeouts = new Map()
|
||||||
|
|
||||||
|
if (vote.interval) {
|
||||||
|
this.voteTemplate = ejs.compile(config.vote.template)
|
||||||
|
this.votes = new Map()
|
||||||
|
setInterval(this.tallyVotes.bind(this), vote.interval * 1000)
|
||||||
|
}
|
||||||
|
|
||||||
client.on('ready', () => {
|
client.on('ready', () => {
|
||||||
this.onReady()
|
this.onReady()
|
||||||
})
|
})
|
||||||
@@ -38,6 +46,9 @@ export default class TwitchBot extends EventEmitter {
|
|||||||
console.error('Twitch bot disconnected due to error:', err)
|
console.error('Twitch bot disconnected due to error:', err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
client.on('PRIVMSG', (msg) => {
|
||||||
|
this.onMsg(msg)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
connect() {
|
connect() {
|
||||||
@@ -105,4 +116,50 @@ export default class TwitchBot extends EventEmitter {
|
|||||||
}, announce.interval * 1000)
|
}, announce.interval * 1000)
|
||||||
this.announceTimeouts.set(listeningURL, timeout)
|
this.announceTimeouts.set(listeningURL, timeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async tallyVotes() {
|
||||||
|
const { client } = this
|
||||||
|
const { channel } = this.config
|
||||||
|
if (this.votes.size === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let voteCount = 0
|
||||||
|
let selectedIdx = null
|
||||||
|
for (const [idx, value] of this.votes) {
|
||||||
|
if (value > voteCount) {
|
||||||
|
voteCount = value
|
||||||
|
selectedIdx = idx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const msg = this.voteTemplate({ selectedIdx, voteCount })
|
||||||
|
await client.say(channel, msg)
|
||||||
|
|
||||||
|
// Index spaces starting with 1
|
||||||
|
this.emit('setListeningView', selectedIdx - 1)
|
||||||
|
|
||||||
|
this.votes = new Map()
|
||||||
|
}
|
||||||
|
|
||||||
|
onMsg(msg) {
|
||||||
|
const { grid, vote } = this.config
|
||||||
|
if (!vote.interval) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const match = msg.messageText.match(VOTE_RE)
|
||||||
|
if (!match) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let idx
|
||||||
|
try {
|
||||||
|
idx = Number(match[1])
|
||||||
|
} catch (err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.votes.set(idx, (this.votes.get(idx) || 0) + 1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -98,6 +98,8 @@ function parseArgs() {
|
|||||||
'twitch.color',
|
'twitch.color',
|
||||||
'twitch.announce.template',
|
'twitch.announce.template',
|
||||||
'twitch.announce.interval',
|
'twitch.announce.interval',
|
||||||
|
'twitch.vote.template',
|
||||||
|
'twitch.vote.interval',
|
||||||
],
|
],
|
||||||
'Twitch Chat',
|
'Twitch Chat',
|
||||||
)
|
)
|
||||||
@@ -133,6 +135,15 @@ function parseArgs() {
|
|||||||
number: true,
|
number: true,
|
||||||
default: 30,
|
default: 30,
|
||||||
})
|
})
|
||||||
|
.option('twitch.vote.template', {
|
||||||
|
describe: 'Message template for vote result announcements',
|
||||||
|
default: 'Switching to #<%- selectedIdx %> (with <%- voteCount %> votes)',
|
||||||
|
})
|
||||||
|
.option('twitch.vote.interval', {
|
||||||
|
describe: 'Time interval (in seconds) between votes (0 to disable)',
|
||||||
|
number: true,
|
||||||
|
default: 0,
|
||||||
|
})
|
||||||
.group(
|
.group(
|
||||||
[
|
[
|
||||||
'control.username',
|
'control.username',
|
||||||
@@ -382,6 +393,9 @@ async function main() {
|
|||||||
|
|
||||||
if (argv.twitch.token) {
|
if (argv.twitch.token) {
|
||||||
twitchBot = new TwitchBot(argv.twitch)
|
twitchBot = new TwitchBot(argv.twitch)
|
||||||
|
twitchBot.on('setListeningView', (idx) => {
|
||||||
|
streamWindow.setListeningView(idx)
|
||||||
|
})
|
||||||
twitchBot.connect()
|
twitchBot.connect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user