diff --git a/.env.sample b/.env.sample index 044ba9c..2a99c12 100644 --- a/.env.sample +++ b/.env.sample @@ -1,3 +1,3 @@ -mongo_uri= -mongo_db= -prefix= \ No newline at end of file +MONGO_URI= +MONGO_DB= +PREFIX= \ No newline at end of file diff --git a/.gitignore b/.gitignore index 2eea525..5596c37 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -.env \ No newline at end of file +.env +TODO.txt \ No newline at end of file diff --git a/clients.js b/clients.js index 911df90..859d2da 100644 --- a/clients.js +++ b/clients.js @@ -2,15 +2,22 @@ const { MongoClient } = require('mongodb'); const { Client, GatewayIntentBits } = require('discord.js'); const tmi = require('tmi.js'); -const channels = ['x__hel__x','desq_blocki'] +const channels = ['x__hel__x', 'desq_blocki']; + +let mClient, dClient, tClient; // Global references const initializeClients = async () => { + // Disconnect existing clients if they exist + if (tClient) await tClient.disconnect().catch(() => {}); + if (dClient) await dClient.destroy().catch(() => {}); + if (mClient) await mClient.close().catch(() => {}); + // Initialize MongoDB Client - const mClient = new MongoClient(process.env.mongo_uri); + mClient = new MongoClient(process.env.MONGO_URI); await mClient.connect(); // Initialize Discord Client - const dClient = new Client({ + dClient = new Client({ intents: [ GatewayIntentBits.Guilds, GatewayIntentBits.GuildMembers, @@ -18,31 +25,24 @@ const initializeClients = async () => { ], }); - // Fetch Discord credentials from the database - const db = mClient.db(process.env.mongo_db); + const db = mClient.db(process.env.MONGO_DB); const credentialCollection = db.collection('credentials'); const dCreds = await credentialCollection.findOne({ service: 'discord' }); - const tCreds = await credentialCollection.findOne({ service: 'twitch' }); await dClient.login(dCreds.token); // Initialize Twitch Client - const tClient = new tmi.Client({ + let tClient = new tmi.Client({ options: { debug: false }, - connection: { - reconnect: true, - secure: true, - }, + connection: { reconnect: true, secure: true }, identity: { username: tCreds.username, password: `oauth:${tCreds.token.access_token}`, }, - channels: channels, // Replace with your channel + channels: channels, }); - - await tClient.connect(); - + return { mClient, dClient, tClient }; }; diff --git a/events/Twitch/Message.js b/events/Twitch/Message.js index a960b92..c6ab2c4 100644 --- a/events/Twitch/Message.js +++ b/events/Twitch/Message.js @@ -3,42 +3,72 @@ module.exports = { name: 'Twitch/Message', once: false, async execute(channel, userstate, message, self, tClient) { - const { dClient, mClient } = require('../../index') + const { dClient, mClient, makeTwitchRequest } = require('../../index') const knownBots = new Set(['streamlabs', 'nightbot', 'moobot', 'soundalerts', 'streamelements', 'remasuri_bot', 'commanderroot', 'x__hel_bot__x']) -async function updateChatMode(userID, setTo) { - let state - if (setTo === 'on') { - state = true - } else if (setTo === 'off') { - state = false - } else { - return - } - await Helix.chat.updateSettings(userID, process.env.mod_id, { - follower_mode: state, - follower_mode_duration: 0, - subscriber_mode: state, - emote_mode: state - }); - -} -async function getIDByName(user) { - const result = await Helix.users.get(user); - return result.data[0].id -} -async function verifyUser(channel, username) { - let db = mClient.db("shoutouts") - let col = db.collection(channel) + async function updateChatMode(userID, setTo) { + let state + if (setTo === 'on') { + state = true + } else if (setTo === 'off') { + state = false + } else { + return + } + await Helix.chat.updateSettings(userID, process.env.MOD_ID, { + follower_mode: state, + follower_mode_duration: 0, + subscriber_mode: state, + emote_mode: state + }); + + } + async function getIDByName(user) { + // Function to convert Names to ID + const result = await Helix.users.get(user); + return result.data[0].id + } + async function verifyUser(channel, username) { + // Function checks if user is a target to shout out, default target: VIPs + let db = mClient.db("shoutouts") + let col = db.collection(channel) + + let query = { user: username } + let status = await col.findOne(query) + if (!status) { + dClient.emit('TwitchShoutout', "VIP", channel, username) + await col.insertOne({ + user: username + }) + } + } + async function sendPositivity() { + // Function sends a random string of predetermined positivity, Donothon Top Donator Reward + let positive_Messages = ["You are so very worthy of all the love and support that may come your way.", + "There are many things which draw us together. In this time and place, that lovely subject is you, Hel.", + "The happiness and joy you add to the world is worth more than most anything.", + "You deserve every warm laugh, bright smile, and all the genuine kindness given to you.", + "There are few things as precious as your time. Thank you for sharing with us.", + "Your feelings are valid and you are not less than for having them.", + "I've seen all the work you do and I'm so very proud of you.", + "You are dearly cherished and I want to thank you for being so wonderfully you.", + "You. Are. Worthy. Enough. And I will tell you as many times as it takes for you to believe it.", + "Every day you give your best and that is so admirable. Don't let anyone tell you otherwise.", + "Your light is like that of the full moon, brightening our lives even in our darkest moments.", + "It's okay to take a moment to catch your breath. Even machines require maintenance from time to time.", + "Watching you succeed and achieve your heart's desires gives me so much motivation and I am very grateful for it."] + + let rdm = Math.floor(Math.random() * positive_Messages.length) + return tClient.say(channel, positive_Messages[rdm]) + } + async function sendDonationAd() { + const links = ["https://bit.ly/heltwittergoals", "https://bit.ly/donatetohel", "http://bit.ly/donothon_goals"] + tClient.say(channel, "Hel is currently celebrating her birthday with a donathon!") + tClient.say(channel, `You can see goals and incentives here: ${links[0]}`) + tClient.say(channel, `You can donate here: ${links[1]}`) + tClient.say(channel, `Terms, conditions, rules and how rewards work are found here: ${links[2]}`) + tClient.say(channel, `All donations and gifts are greatly appreciated, but please do so responsibly!`) + } - let query = { user: username } - let status = await col.findOne(query) - if (!status) { - dClient.emit('TwitchShoutout', "VIP", channel, username) - await col.insertOne({ - user: username - }) - } -} if (self) { return } if (knownBots.has(userstate.username)) { return } @@ -50,8 +80,8 @@ async function verifyUser(channel, username) { tClient.say(channel, 'KAW!') } - - if (message.startsWith(process.env.prefix)) { + + if (message.startsWith(process.env.PREFIX)) { const args = message.substring(1).split(" ") const cmd = args[0] switch (cmd) { @@ -64,40 +94,23 @@ async function verifyUser(channel, username) { if (!args[1].match(regex)) { break } updateChatMode(await getIDByName(channel.substring(1)), args[1]) break; - // case "donate": - // const links = ["https://bit.ly/heltwittergoals", "https://bit.ly/donatetohel","http://bit.ly/donothon_goals"] - // tClient.say(channel, "Hel is currently celebrating her birthday with a donathon!") - // tClient.say(channel, `You can see goals and incentives here: ${links[0]}`) - // tClient.say(channel, `You can donate here: ${links[1]}`) - // tClient.say(channel, `Terms, conditions, rules and how rewards work are found here: ${links[2]}`) - // tClient.say(channel, `All donations and gifts are greatly appreciated, but please do so responsibly!`) - // break; + case "donate": + sendDonationAd() + break; + case "test": + console.log(await makeTwitchRequest('games/top')) + break; case "positivity": - let positive_Messages = ["You are so very worthy of all the love and support that may come your way.", - "There are many things which draw us together. In this time and place, that lovely subject is you, Hel.", - "The happiness and joy you add to the world is worth more than most anything.", - "You deserve every warm laugh, bright smile, and all the genuine kindness given to you.", - "There are few things as precious as your time. Thank you for sharing with us.", - "Your feelings are valid and you are not less than for having them.", - "I've seen all the work you do and I'm so very proud of you.", - "You are dearly cherished and I want to thank you for being so wonderfully you.", - "You. Are. Worthy. Enough. And I will tell you as many times as it takes for you to believe it.", - "Every day you give your best and that is so admirable. Don't let anyone tell you otherwise.", - "Your light is like that of the full moon, brightening our lives even in our darkest moments.", - "It's okay to take a moment to catch your breath. Even machines require maintenance from time to time.", - "Watching you succeed and achieve your heart's desires gives me so much motivation and I am very grateful for it."] - - let rdm = Math.floor(Math.random() * positive_Messages.length) - tClient.say(channel, positive_Messages[rdm]) - + sendPositivity() break; default: break; } } + // ---------------VIP Handler------------------- - if (!userstate.badges?.vip) { return } - verifyUser(channel, userstate.username) + // if (!userstate.badges?.vip) { return } + // verifyUser(channel, userstate.username) // --------------------------------------------- } diff --git a/index.js b/index.js index a2ee150..ae50133 100644 --- a/index.js +++ b/index.js @@ -200,7 +200,7 @@ async function tLogin() { return; } - const tClient = new tmi.Client({ + var tClient = new tmi.Client({ options: { debug: false }, connection: { reconnect: true, @@ -214,7 +214,7 @@ async function tLogin() { }); tClient.connect(); - + // Handle Twitch events tClient.on('connected', (address, port) => { @@ -233,18 +233,48 @@ async function tLogin() { dClient.emit('Twitch/Subscribers', channel, enabled, tClient); }); - tClient.on('disconnected', (reason) => { - console.log('Twitch client disconnected:', reason); + tClient.on('disconnected', async (reason) => { + console.log('Twitch disconnected:', reason); if (reason.includes('Login authentication failed')) { - refreshAccessToken() - .then(() => tClient.connect()) - .catch((error) => { - console.error('Failed to refresh token and reconnect:', error); + try { + await refreshAccessToken(); + console.log('Reinitializing Twitch client after token refresh...'); + + try { + await tClient.disconnect(); + } catch (err) { + console.warn('Twitch client already disconnected:', err.message); + } + + const db = mClient.db(process.env.MONGO_DB); + const credentialCollection = db.collection('credentials'); + const tCreds = await credentialCollection.findOne({ service: 'twitch' }); + + // Recreate the client + tClient = new tmi.Client({ + options: { debug: false }, + connection: { reconnect: true, secure: true }, + identity: { + username: tCreds.username, + password: `oauth:${tCreds.token.access_token}`, + }, + channels: channels, }); + + await tClient.connect(); + console.log('Twitch client reconnected successfully.'); + } catch (error) { + console.error('Failed to refresh token and reconnect:', error); + } } }); } tLogin(); -module.exports = { mClient, dClient }; \ No newline at end of file +process.on('unhandledRejection', (reason, promise) => { + console.error('Unhandled Rejection at:', promise, 'reason:', reason); + // Prevent app from crashing +}); + +module.exports = { mClient, dClient, makeTwitchRequest }; \ No newline at end of file