// PACKAGES const WebSocketClient = require('websocket').client const express = require('express') require('dotenv').config() // UTILS const getRewardData = require('./utils/getRewardData') const getRewardID = require('./utils/getRewardID') const getUserAccessToken = require('./utils/getUserAccessToken') const getUserID = require('./utils/getUserID') const getUserInfo = require('./utils/getUserInfo') const getUserName = require('./utils/getUserName') const oauthGen = require('./utils/oauthGen') const parseMessage = require('./utils/parseMessage') const rewardRedemption = require('./utils/rewardRedemption') const subscribeToEvents = require('./utils/subscribeToEvents') const writeEnv = require('./utils/writeEnv') // VARIABLES let client_id = process.env.TWITCH_APP_ID let client_secret = process.env.TWITCH_APP_SECRET let user_access_token = process.env.TWITCH_USER_ACCESS_TOKEN let user_name = process.env.TWITCH_USER_USERNAME let channel_access_token = process.env.TWITCH_CHANNEL_ACCESS_TOKEN let channel_name = process.env.TWITCH_CHANNEL_USERNAME let channel_reward_name = process.env.TWITCH_CHANNEL_REWARD_NAME const user_scope = ['chat:read', 'chat:edit', 'channel:moderate'] const channel_scope = ['channel:manage:redemptions'] const uri = 'https://angels-dev.fr/twitch/' const redirect_uri = uri + 'oauth/login/' const chatBeginMsg = `PRIVMSG #${channel_name}` // EXPRESS const port = 3000 const app = express() app.use(express.json()) app.use(express.static('public')) // Twitch OAuth app.get('/twitch/oauth/:type', async (req, res) => { let type = req.params.type let url = await oauthGen(client_id, redirect_uri + type, type === 'user' ? user_scope : type === 'channel' ? channel_scope : []) return res.redirect(url) }) app.get('/twitch/oauth/login/:type', async (req, res) => { let type = req.params.type if (type === 'user') { user_access_token = await getUserAccessToken(client_id, client_secret, req.query.code, redirect_uri + type) writeEnv('TWITCH_USER_ACCESS_TOKEN', user_access_token) user_name = await getUserName(client_id, user_access_token).login writeEnv('TWITCH_USER_USERNAME', user_name) clientChatBot.connect('wss://irc-ws.chat.twitch.tv:443') } else if (type === 'channel') { channel_access_token = await getUserAccessToken(client_id, client_secret, req.query.code, redirect_uri + type) writeEnv('TWITCH_CHANNEL_ACCESS_TOKEN', channel_access_token) channel_name = await getUserName(client_id, channel_access_token).login writeEnv('TWITCH_CHANNEL_USERNAME', channel_name) clientEventSub.connect('wss://eventsub.wss.twitch.tv/ws') } return res.send('Login successful !') }) // Twitch Panel app.get('/twitch/panel/:file', async (req, res) => { let file = req.params.file if (file === 'data') { //let { panel_user_id } = req.query let panel_user_id = '44322889' //let event_user_id = '55833896' //let event_user_name = 'angelskimi' //await rewardRedemption(event_user_id, event_user_name) let panel_data = await getRewardData() let response = { scoreboard: panel_data, user: panel_data.find(entry => entry.user_id === panel_user_id) } return res.json(response) } else return res.sendFile(__dirname + '/public/panel/' + file) }) app.listen(port, () => { console.log(`Express listening at port ${port} !`) }) // CHATBOT const clientChatBot = new WebSocketClient() let connectionChatBot clientChatBot.on('connect', async connection => { console.log('Twitch ChatBot WebSocket Connected !') connectionChatBot = connection // Authenticate to Twitch IRC and join channel connection.sendUTF('CAP REQ :twitch.tv/commands twitch.tv/membership twitch.tv/tags') connection.sendUTF(`PASS oauth:${user_access_token}`) connection.sendUTF(`NICK ${user_name}`) connection.sendUTF(`JOIN #${channel_name}`) connection.sendUTF(`PRIVMSG #${channel_name} :Salut tout le monde !`) connection.on('message', async message => { if (message.type === 'utf8') { try { let data = parseMessage(message.utf8Data) // Handle incoming messages if (data.command.command === 'PRIVMSG') { let message = data.parameters.split('\r\n')[0] console.log(`${data.source.nick}: ${message}`) if (message.includes('@Bot_Laytho')) { connection.sendUTF(`@reply-parent-msg-id=${data.tags.id} ${chatBeginMsg} :Kestuveu @${data.tags['display-name']} ?`) connection.sendUTF(`${chatBeginMsg} :/timeout ${data.tags['display-name']} 60 T'as pas à me parler comme ça !`) } else if (message.toLowerCase().includes('quoi')) { connection.sendUTF(`@reply-parent-msg-id=${data.tags.id} ${chatBeginMsg} :@${data.tags['display-name']} Coubeh !`) connection.sendUTF(`${chatBeginMsg} :/timeout ${data.tags['display-name']} 60 T'as pas à me parler comme ça !`) } else if (message === '!ping') { connection.sendUTF(`@reply-parent-msg-id=${data.tags.id} ${chatBeginMsg} :Pong !`) } } else if (data.command.command === 'NOTICE') { if (data.parameters.includes('Login authentication failed')) { console.log('Erreur de connexion ChatBot, veuillez vous reconnecter !\nhttps://angels-dev.fr/twitch/oauth/user') } } } catch (error) { } // catch (error) { console.error(error) } } }) .on('error', error => { console.error(error) }) .on('close', () => { console.log('Twitch ChatBot Connection Closed !') }) }).on('connectFailed', error => { console.error(error) }) clientChatBot.connect('wss://irc-ws.chat.twitch.tv:443') // EVENTSUB const clientEventSub = new WebSocketClient().on('connect', async connection => { console.log('Twitch EventSub WebSocket Connected !') connection.on('message', async message => { if (message.type === 'utf8') { try { let data = JSON.parse(message.utf8Data) // Check when Twitch asks to login if (data.metadata.message_type === 'session_welcome') { // Get broadcaster user id and reward id let broadcaster_user_id = await getUserID(client_id, channel_access_token) writeEnv('TWITCH_CHANNEL_BROADCASTER_ID', broadcaster_user_id) let reward_id = await getRewardID(client_id, channel_access_token, broadcaster_user_id, channel_reward_name) writeEnv('TWITCH_CHANNEL_REWARD_ID', reward_id) let topics = { 'channel.channel_points_custom_reward_redemption.add': { version: '1', condition: { broadcaster_user_id, reward_id } }, 'stream.online': { version: '1', condition: { broadcaster_user_id } } } // Subscribe to all events required for (let type in topics) { console.log(`Creating ${type}...`) let { version, condition } = topics[type] let status = await subscribeToEvents(client_id, channel_access_token, data.payload.session.id, type, version, condition) if (!status) return console.error(`Failed to create ${type}`) else if (status.error) { console.error(status) console.log('Erreur de connexion EventSub, veuillez vous reconnecter !\nhttps://angels-dev.fr/twitch/oauth/channel') return connection.sendUTF(`${chatBeginMsg} :@${channel_name} Erreur de connexion EventSub, veuillez vous reconnecter !\nhttps://angels-dev.fr/twitch/oauth/channel`) } else console.log(`Successfully created ${type}`) } } // Handle notification messages for reward redemption else if (data.metadata.message_type === 'notification') { let { subscription, event } = data.payload if (subscription.type === 'channel.channel_points_custom_reward_redemption.add') { console.log(`User ${{ user_name } = event} claimed reward ${event.reward.title} !`) await rewardRedemption({ user_id, user_name } = event) } else if (subscription.type === 'stream.online') { console.log(`Stream from ${event.broadcaster_user_name} is now online, connecting to chat...`) clientChatBot.connect('wss://irc-ws.chat.twitch.tv:443') } } // Don't log ping/pong messages else if (data.metadata.message_type === 'session_keepalive') return // Log unknown messages else console.log(data) } catch (error) { console.error(error) } } }) .on('error', error => { console.error(error) }) .on('close', () => { console.log('Twitch EventSub Connection Closed !') }) }).on('connectFailed', error => { console.error(error) }) clientEventSub.connect('wss://eventsub.wss.twitch.tv/ws')