diff --git a/app.js b/app.js index 622c809..17d74cc 100644 --- a/app.js +++ b/app.js @@ -5,9 +5,11 @@ require('dotenv').config() // UTILS -const getReward = require('./utils/getReward') +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') @@ -25,11 +27,13 @@ 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 redirect_uri = 'https://angels-dev.fr/twitch/oauth/login/' +const uri = 'https://angels-dev.fr/twitch/' +const redirect_uri = uri + 'oauth/login/' const chatBeginMsg = `PRIVMSG #${channel_name}` @@ -37,33 +41,32 @@ const chatBeginMsg = `PRIVMSG #${channel_name}` const port = 3000 const app = express() app.use(express.json()) -app.use(express.static('panel')) +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 : []) + 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) => { - console.log(req.query) let type = req.params.type if (type === 'user') { - user_access_token = await getUserAccessToken(req.query.code, client_id, client_secret, redirect_uri + type) + 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) + 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(req.query.code, client_id, client_secret, redirect_uri + type) + 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) + channel_name = await getUserName(client_id, channel_access_token).login writeEnv('TWITCH_CHANNEL_USERNAME', channel_name) clientEventSub.connect('wss://eventsub.wss.twitch.tv/ws') @@ -71,7 +74,27 @@ app.get('/twitch/oauth/login/:type', async (req, res) => { return res.send('Login successful !') }) -app.listen(port, () => { console.log(`Listening at ${redirect_uri}`) }) +// 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 @@ -103,6 +126,10 @@ clientChatBot.on('connect', async connection => { 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 !`) } @@ -133,8 +160,10 @@ const clientEventSub = new WebSocketClient().on('connect', async connection => { 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, channel_name) - let reward_id = await getReward(client_id, channel_access_token, broadcaster_user_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 } } @@ -145,7 +174,7 @@ const clientEventSub = new WebSocketClient().on('connect', async connection => { console.log(`Creating ${type}...`) let { version, condition } = topics[type] - let status = await subscribeToEvents(channel_access_token, data.payload.session.id, client_id, type, version, condition) + 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) { @@ -158,15 +187,13 @@ const clientEventSub = new WebSocketClient().on('connect', async connection => { // Handle notification messages for reward redemption else if (data.metadata.message_type === 'notification') { - let{ subscription, event } = data.payload + let { subscription, event } = data.payload - //if (subscription.type === 'channel.channel_points_custom_reward_redemption.add' && event.reward.id === reward_id) { if (subscription.type === 'channel.channel_points_custom_reward_redemption.add') { - let { user_id, user_name } = event - console.log(`User ${user_name} claimed reward ${event.reward.title} !`) - rewardRedemption(user_id, user_name) + console.log(`User ${{ user_name } = event} claimed reward ${event.reward.title} !`) + await rewardRedemption({ user_id, user_name } = event) } - else if (subscription.type === 'stream.online' && event.broadcaster_user_login === channel_name) { + 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') } diff --git a/package.json b/package.json index b73e497..bcdbde9 100644 --- a/package.json +++ b/package.json @@ -20,10 +20,8 @@ "dependencies": { "axios": "^1.4.0", "dotenv": "^16.0.3", - "envfile": "^6.18.0", "express": "^4.18.2", - "mysql": "^2.18.1", - "tmi.js": "^1.8.5", + "mysql2": "^3.3.0", "websocket": "^1.0.34" } } diff --git a/public/panel/panel.css b/public/panel/panel.css new file mode 100644 index 0000000..ce59694 --- /dev/null +++ b/public/panel/panel.css @@ -0,0 +1,16 @@ +p { + color: white; + text-align: center; +} +th { + color: white; + text-align: center; +} +td { + color: white; + text-align: center; +} +table .center { + margin-left: auto; + margin-right: auto; +} \ No newline at end of file diff --git a/public/panel/panel.html b/public/panel/panel.html new file mode 100644 index 0000000..9c6fe81 --- /dev/null +++ b/public/panel/panel.html @@ -0,0 +1,16 @@ + + + + + +Panel Laytho +

Classement des Daily Arrows

+ + + + + + + + +
PseudoDaily Arrows
\ No newline at end of file diff --git a/public/panel/script.js b/public/panel/script.js new file mode 100644 index 0000000..def9cbf --- /dev/null +++ b/public/panel/script.js @@ -0,0 +1,19 @@ +const xhr = new XMLHttpRequest() +const url = 'https://angels-dev.fr/twitch/panel/data' + +xhr.open('GET', url, true) +xhr.onload = () => { + if (xhr.status === 200) { + let data = JSON.parse(xhr.responseText) + + let tbodyRef = document.getElementById('table').getElementsByTagName('tbody')[0] + + for (let entry of data.scoreboard) { + let row = tbodyRef.insertRow() + row.insertCell().appendChild(document.createTextNode(entry.user_name)) + row.insertCell().appendChild(document.createTextNode(entry.count)) + } + } else console.error('Error:', xhr.statusText) +} +xhr.onerror = () => { console.error('Error:', xhr.statusText) } +xhr.send() \ No newline at end of file diff --git a/utils/getRewardData.js b/utils/getRewardData.js new file mode 100644 index 0000000..9d70f7e --- /dev/null +++ b/utils/getRewardData.js @@ -0,0 +1,23 @@ +const mysql = require('mysql2/promise') + +module.exports = async function () { + // Create a connection to the MySQL database + const connection = await mysql.createConnection({ + host: process.env.MYSQL_HOST, + port: process.env.MYSQL_PORT, + user: process.env.MYSQL_USER, + password: process.env.MYSQL_PASSWORD, + database: process.env.MYSQL_DATABASE + }) + + // Retrieve the count of rewards claimed by each user, sorted by count + let results = await connection.execute('SELECT * FROM rewards ORDER BY count DESC') + .then(async ([rows, fields]) => { return rows }) + .catch(error => { console.error(error) }) + + // Terminate the connection to the database + await connection.end() + + if (!results) return { error: 'No scoreboard data found' } + return results +} \ No newline at end of file diff --git a/utils/getReward.js b/utils/getRewardID.js similarity index 73% rename from utils/getReward.js rename to utils/getRewardID.js index 445d5ac..10bb1fa 100644 --- a/utils/getReward.js +++ b/utils/getRewardID.js @@ -1,13 +1,13 @@ const axios = require('axios') -module.exports = async function (client_id, access_token, broadcaster_id) { +module.exports = async function (client_id, access_token, broadcaster_id, reward_name) { return await axios.get(`https://api.twitch.tv/helix/channel_points/custom_rewards?broadcaster_id=${broadcaster_id}`, { headers: { 'Authorization': `Bearer ${access_token}`, 'Client-Id': client_id } }).then(response => { - console.log(response.data) - return response.data.data[0].id + let reward = response.data.data.find(reward => reward.title === reward_name) + return reward.id }).catch(error => { console.log(error.response.data) }) } \ No newline at end of file diff --git a/utils/getUserAccessToken.js b/utils/getUserAccessToken.js index 5d31394..203247b 100644 --- a/utils/getUserAccessToken.js +++ b/utils/getUserAccessToken.js @@ -1,6 +1,6 @@ const axios = require('axios') -module.exports = async function (code, client_id, client_secret, redirect_uri) { +module.exports = async function (client_id, client_secret, code, redirect_uri) { return await axios.post('https://id.twitch.tv/oauth2/token', { code, client_id, diff --git a/utils/getUserID.js b/utils/getUserID.js index d855c96..2d37d81 100644 --- a/utils/getUserID.js +++ b/utils/getUserID.js @@ -1,7 +1,7 @@ const axios = require('axios') -module.exports = async function (client_id, access_token, login) { - return await axios.get(`https://api.twitch.tv/helix/users?login=${login}`, { +module.exports = async function (client_id, access_token) { + return await axios.get(`https://api.twitch.tv/helix/users`, { headers: { 'Authorization': `Bearer ${access_token}`, 'Client-Id': client_id diff --git a/utils/getUserInfo.js b/utils/getUserInfo.js new file mode 100644 index 0000000..e24410e --- /dev/null +++ b/utils/getUserInfo.js @@ -0,0 +1,13 @@ +const axios = require('axios') + +module.exports = async function (client_id, access_token) { + return await axios.get(`https://api.twitch.tv/helix/users`, { + headers: { + 'Authorization': `Bearer ${access_token}`, + 'Client-Id': client_id + } + }).then(response => { + //console.log(response.data) + return response.data.data[0] + }).catch(error => { console.log(error.response.data) }) +} \ No newline at end of file diff --git a/utils/getUserName.js b/utils/getUserName.js index f30ea33..83e5e2f 100644 --- a/utils/getUserName.js +++ b/utils/getUserName.js @@ -7,7 +7,7 @@ module.exports = async function (client_id, access_token) { 'Client-Id': client_id } }).then(response => { - console.log(response.data) + //console.log(response.data) return response.data.data[0].login }).catch(error => { console.log(error.response.data) }) } \ No newline at end of file diff --git a/utils/parseMessage.js b/utils/parseMessage.js index d276ec9..a2dcd0e 100644 --- a/utils/parseMessage.js +++ b/utils/parseMessage.js @@ -3,7 +3,7 @@ // Expects the caller to pass a single message. (Remember, the Twitch // IRC server may send one or more IRC messages in a single message.) -module.exports = function parseMessage(message) { +module.exports = function (message) { let parsedMessage = { // Contains the component parts. tags: null, diff --git a/utils/rewardRedemption.js b/utils/rewardRedemption.js index 2e6d4a3..02dd7d6 100644 --- a/utils/rewardRedemption.js +++ b/utils/rewardRedemption.js @@ -1,38 +1,30 @@ -const mysql = require('mysql') +const mysql = require('mysql2/promise') -module.exports = function rewardRedemption(user_id, user_name) { +module.exports = async function (user_id, user_name) { // Create a connection to the MySQL database - const connection = mysql.createConnection({ + const connection = await mysql.createConnection({ host: process.env.MYSQL_HOST, + port: process.env.MYSQL_PORT, user: process.env.MYSQL_USER, password: process.env.MYSQL_PASSWORD, database: process.env.MYSQL_DATABASE }) - // Connect to the database - connection.connect(error => { - if (error) return console.error(error) - console.log(`Connected to MySql database as id ${connection.threadId} !`) - }) - // Check if the user already exists in the rewards table - connection.query('SELECT * FROM rewards WHERE user_id = ?', [user_id], (error, results) => { - if (error) return console.error(error) - - if (results.length === 0) { - // User doesn't exist, insert a new row - connection.query('INSERT INTO rewards SET ?', { user_id, user_name, count: 1 }, error => { - if (error) return console.error(error) - }) - } else { - // User exists, update the count - const newRow = { count: results[0].count + 1 } - connection.query('UPDATE rewards SET ? WHERE user_id = ?', [newRow, user_id], error => { - if (error) return console.error(error) - }) - } - }) + await connection.query('SELECT * FROM rewards WHERE user_id = ?', [user_id]) + .then(async ([rows, fields]) => { + if (rows.length === 0) { + // User doesn't exist, insert a new row + await connection.query('INSERT INTO rewards SET ?', { user_id, user_name, count: 1, current_count: 1 }) + .catch(error => { console.error(error) }) + } else { + // User exists, update the count + const newRow = { count: rows[0].count + 1, current_count: rows[0].current_count + 1 } + await connection.query('UPDATE rewards SET ? WHERE user_id = ?', [newRow, user_id]) + .catch(error => { console.error(error) }) + } + }).catch(error => { console.error(error) }) // Terminate the connection to the database - connection.end() + await connection.end() } \ No newline at end of file diff --git a/utils/subscribeToEvents.js b/utils/subscribeToEvents.js index af73f94..96c8c8b 100644 --- a/utils/subscribeToEvents.js +++ b/utils/subscribeToEvents.js @@ -1,6 +1,6 @@ const axios = require('axios') -module.exports = async function (access_token, session_id, client_id, type, version, condition) { +module.exports = async function (client_id, access_token, session_id, type, version, condition) { return await axios.post('https://api.twitch.tv/helix/eventsub/subscriptions', { type, version,