Skip to content

Instantly share code, notes, and snippets.

@KararTY
Last active April 9, 2022 13:01
Show Gist options
  • Save KararTY/124fea0de6d8c722290ccd1e1837fdf0 to your computer and use it in GitHub Desktop.
Save KararTY/124fea0de6d8c722290ccd1e1837fdf0 to your computer and use it in GitHub Desktop.
Spurpsbot
const { writeFileSync } = require('fs')
const path = require('path')
global.appSettings = null
const defaultSettings = {
username: '',
token: 'oauth:',
channels: ['twitchdev'],
admins: ['twitchdev'],
logEverySeconds: 60
}
try {
global.appSettings = require('./settings.json')
} catch (err) {
if (err.code === 'MODULE_NOT_FOUND') {
writeFileSync(path.join(__dirname, 'settings.json'), JSON.stringify(defaultSettings, null, 2))
console.log('Created settings.json file, please edit it before proceeding.')
process.exit(0)
}
}
if (global.appSettings.username.length > 0 && global.appSettings.token.startsWith('oauth:') && global.appSettings.token.length > 6) {
console.log('Loaded settings.json')
} else {
console.log('settings.json is not fully setup. Please edit it before proceeding.')
process.exit(0)
}
const { ChatClient, AlternateMessageModifier } = require('dank-twitch-irc')
const client = new ChatClient({ username: global.appSettings.username, password: global.appSettings.token })
client.use(new AlternateMessageModifier(client)) // Allows same messages to bypass 30 seconds timeout.
const react = require('./react')
const logToFile = require('./logger')
client.on('ready', async () => {
console.log('Twitch: Successfully connected to Twitch IRC.')
// Join the channels.
for (let index = 0; index < global.appSettings.channels.length; index++) {
const channelName = global.appSettings.channels[index]
await client.join(channelName)
console.log('Successfully joined', channelName)
}
})
client.on('close', err => {
if (err != null) {
console.error('Twitch: Client closed due to error', err)
}
})
client.on('PRIVMSG', async msg => {
// Maybe react to it, if it's a command?
await react(client, 'PRIVMSG', msg)
// Log it to a text file
await logToFile(msg, 'PRIVMSG')
})
client.on('USERNOTICE', async event => {
// React
await react(client, 'USERNOTICE', event)
// Log to text file
await logToFile(event, 'USERNOTICE')
})
client.connect()
const { promises: { writeFile, stat }, existsSync, mkdirSync } = require('fs')
const path = require('path')
const logDateFormat = { timeZone: 'UTC', year: 'numeric', month: '2-digit', day: '2-digit' }
const logsFolder = path.join(__dirname, 'logs')
if (!existsSync(logsFolder)) mkdirSync(logsFolder)
let logQueue = []
async function addTologQueue (msg, type) {
console.log(formatMessage(msg, type))
logQueue.push({ msg, type })
}
setInterval(async () => {
let tempLogs = [...logQueue].sort((a, b) => {
return new Date(a.msg.serverTimestamp).getTime() - new Date(b.msg.serverTimestamp).getTime()
})
logQueue = []
for (let index = 0; index < tempLogs.length; index++) {
const log = tempLogs[index]
await logToFile(log.msg, log.type)
}
if (tempLogs.length > 0) console.log(`Logged ${tempLogs.length} messages.`)
tempLogs = undefined
}, global.appSettings.logEverySeconds * 1000)
async function logToFile (msg, type) {
const formattedMsg = formatMessage(msg, type)
const formattedFolderDate = formatTimeString(new Date(msg.serverTimestamp).toLocaleDateString('en-GB', logDateFormat))
const folder = path.join(logsFolder.toString(), msg.channelName)
try {
await stat(folder)
} catch (err) {
if (err.code === 'ENOENT') mkdirSync(folder, { recursive: true })
}
// Log it to a text file
await writeFile(path.join(folder.toString(), `${formattedFolderDate}.txt`), formattedMsg + '\r\n', { flag: 'a+' })
return Promise.resolve()
}
function formatMessage (msg, type) {
const date = new Date(msg.serverTimestamp)
let formattedMsg
const dateFormatted = date.toLocaleTimeString('en-GB', { timeZone: 'UTC' })
if (type === 'PRIVMSG') {
formattedMsg = `[${formatTimeString(dateFormatted)}] ${msg.displayName}${msg.bits ? ` (Bits: ${msg.bits})` : ''}: ${msg.messageText}`
} else if (type === 'USERNOTICE') {
formattedMsg = `[${formatTimeString(dateFormatted)}] ${msg.displayName} [EVENT]: ${msg.systemMessage}`
}
return formattedMsg
}
function formatTimeString (date) {
return date.toString().replace(/\//g, '-')
}
module.exports = addTologQueue
{
"name": "harm",
"version": "1.0.0",
"description": "",
"main": "app.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node ./app.js"
},
"author": "",
"license": "MIT",
"dependencies": {
"dank-twitch-irc": "^3.0.7"
}
}
async function react (client, type, msg) {
if (type === 'PRIVMSG') {
// Remove this, or add it selectively to enable admin-only commands.
if (!global.appSettings.admins.includes(msg.senderUsername.toLowerCase())) return Promise.resolve()
if (msg.channelName.toLowerCase() === 'twitchdev') {
if (msg.messageText.charAt(0) === '!') {
const args = msg.messageText.substr(1).split(' ')
const command = args[0]
if (command === 'ping') {
await client.say(msg.channelName, 'Pong!')
}
}
}
} else if (type === 'USERNOTICE') {
let parsedEvent
switch (msg.messageTypeID) {
case 'sub':
case 'resub':
case 'extendsub':
parsedEvent = {
type: msg.messageTypeID,
subPlan: msg.eventParams.subPlan,
subPlanName: msg.eventParams.subPlanName,
months: msg.eventParams.cumulativeMonths,
streakMonths: msg.eventParams.streakMonths,
shouldShareStreak: msg.eventParams.shouldShareStreak
}
break
case 'subgift':
case 'anonsubgift':
parsedEvent = {
type: msg.messageTypeID,
subPlan: msg.eventParams.subPlan,
subPlanName: msg.eventParams.subPlanName,
months: msg.eventParams.months,
recipient: {
id: msg.eventParams.recipientID,
name: msg.eventParams.recipientDisplayName,
username: msg.eventParams.recipientUsername
}
}
break
case 'giftpaidupgrade':
case 'anongiftpaidupgrade':
case 'primepaidupgrade':
parsedEvent = {
type: msg.messageTypeID,
promoGiftTotal: msg.eventParams.promoGiftTotal,
promoName: msg.eventParams.promoName,
senderLogin: msg.eventParams.senderLogin
}
break
case 'raid':
parsedEvent = {
type: msg.messageTypeID,
viewerCount: msg.eventParams.viewerCount,
displayName: msg.eventParams.displayName
}
break
case 'ritual':
// New chatter!
parsedEvent = {
type: msg.messageTypeID,
name: msg.eventParams.ritualName
}
break
case 'bitsbadgetier':
parsedEvent = {
type: msg.messageTypeID,
threshold: msg.eventParams.threshold
}
break
case 'submysterygift':
parsedEvent = {
type: msg.messageTypeID,
amount: msg.eventParams.massGiftCount,
subPlan: msg.eventParams.subPlan
}
break
case 'standardpayforward':
parsedEvent = {
type: msg.messageTypeID,
priorGifterUserName: msg.eventParams.priorGifterUserName,
priorGifterDisplayName: msg.eventParams.priorGifterDisplayName,
recipientDisplayName: msg.eventParams.recipientDisplayName,
recipientUserName: msg.eventParams.recipientUserName
}
break
case 'rewardgift':
parsedEvent = {
type: msg.messageTypeID,
domain: msg.eventParams.domain,
triggerType: msg.eventParams.triggerType,
triggerAmount: msg.eventParams.triggerAmount,
totalRewardCount: msg.eventParams.totalRewardCount
}
break
case 'communitypayforward':
parsedEvent = {
type: msg.messageTypeID,
priorGifterDisplayName: msg.eventParams.priorGifterDisplayName
}
break
default:
console.log('[Twitch] Unhandled messageType!', msg)
parsedEvent = {
type: msg.messageTypeID
}
break
}
if (msg.channelName.toLowerCase() === 'twitchdev') {
switch (parsedEvent.type) {
case 'sub':
case 'resub':
case 'extendsub':
case 'subgift':
case 'anonsubgift':
case 'giftpaidupgrade':
case 'anongiftpaidupgrade':
case 'primepaidupgrade':
// React!
await client.say(msg.channelName, 'gat gat gat gat gat gat gat')
break
}
}
}
return Promise.resolve()
}
module.exports = react

Put all of the files into one folder, make sure they're all named properly:

  • app.js
  • logger.js
  • react.js
  • package.json

Or you can click on "Download ZIP".

You will need NodeJS and the node package manager that comes with it, it's pretty big for a tiny script like this but you might as well start somewhere. It's like installing python, I guess.

Run npm init in the folder, and then npm install dank-twitch-irc and then you can run the script with npm start or node ./app.js.

It'll create a settings.json on its very first launch which you'll need to edit it and then run the script again.

You can get the OAuth token from Chatterino's "login" page. https://chatterino.com/client_login

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment