tClient will now try to successfully reconnect and NOT crash the entire app

This commit is contained in:
DeSqBlocki
2025-02-08 11:32:08 +01:00
parent c9c382d1bf
commit e499308e64
5 changed files with 135 additions and 91 deletions
+3 -3
View File
@@ -1,3 +1,3 @@
mongo_uri= MONGO_URI=
mongo_db= MONGO_DB=
prefix= PREFIX=
+2 -1
View File
@@ -1 +1,2 @@
.env .env
TODO.txt
+15 -15
View File
@@ -2,15 +2,22 @@
const { MongoClient } = require('mongodb'); const { MongoClient } = require('mongodb');
const { Client, GatewayIntentBits } = require('discord.js'); const { Client, GatewayIntentBits } = require('discord.js');
const tmi = require('tmi.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 () => { 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 // Initialize MongoDB Client
const mClient = new MongoClient(process.env.mongo_uri); mClient = new MongoClient(process.env.MONGO_URI);
await mClient.connect(); await mClient.connect();
// Initialize Discord Client // Initialize Discord Client
const dClient = new Client({ dClient = new Client({
intents: [ intents: [
GatewayIntentBits.Guilds, GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMembers, 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 credentialCollection = db.collection('credentials');
const dCreds = await credentialCollection.findOne({ service: 'discord' }); const dCreds = await credentialCollection.findOne({ service: 'discord' });
const tCreds = await credentialCollection.findOne({ service: 'twitch' }); const tCreds = await credentialCollection.findOne({ service: 'twitch' });
await dClient.login(dCreds.token); await dClient.login(dCreds.token);
// Initialize Twitch Client // Initialize Twitch Client
const tClient = new tmi.Client({ let tClient = new tmi.Client({
options: { debug: false }, options: { debug: false },
connection: { connection: { reconnect: true, secure: true },
reconnect: true,
secure: true,
},
identity: { identity: {
username: tCreds.username, username: tCreds.username,
password: `oauth:${tCreds.token.access_token}`, password: `oauth:${tCreds.token.access_token}`,
}, },
channels: channels, // Replace with your channel channels: channels,
}); });
await tClient.connect();
return { mClient, dClient, tClient }; return { mClient, dClient, tClient };
}; };
+76 -63
View File
@@ -3,42 +3,72 @@ module.exports = {
name: 'Twitch/Message', name: 'Twitch/Message',
once: false, once: false,
async execute(channel, userstate, message, self, tClient) { 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']) const knownBots = new Set(['streamlabs', 'nightbot', 'moobot', 'soundalerts', 'streamelements', 'remasuri_bot', 'commanderroot', 'x__hel_bot__x'])
async function updateChatMode(userID, setTo) { async function updateChatMode(userID, setTo) {
let state let state
if (setTo === 'on') { if (setTo === 'on') {
state = true state = true
} else if (setTo === 'off') { } else if (setTo === 'off') {
state = false state = false
} else { } else {
return return
} }
await Helix.chat.updateSettings(userID, process.env.mod_id, { await Helix.chat.updateSettings(userID, process.env.MOD_ID, {
follower_mode: state, follower_mode: state,
follower_mode_duration: 0, follower_mode_duration: 0,
subscriber_mode: state, subscriber_mode: state,
emote_mode: state emote_mode: state
}); });
} }
async function getIDByName(user) { async function getIDByName(user) {
const result = await Helix.users.get(user); // Function to convert Names to ID
return result.data[0].id const result = await Helix.users.get(user);
} return result.data[0].id
async function verifyUser(channel, username) { }
let db = mClient.db("shoutouts") async function verifyUser(channel, username) {
let col = db.collection(channel) // 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 (self) { return }
if (knownBots.has(userstate.username)) { return } if (knownBots.has(userstate.username)) { return }
@@ -50,8 +80,8 @@ async function verifyUser(channel, username) {
tClient.say(channel, 'KAW!') tClient.say(channel, 'KAW!')
} }
if (message.startsWith(process.env.prefix)) { if (message.startsWith(process.env.PREFIX)) {
const args = message.substring(1).split(" ") const args = message.substring(1).split(" ")
const cmd = args[0] const cmd = args[0]
switch (cmd) { switch (cmd) {
@@ -64,40 +94,23 @@ async function verifyUser(channel, username) {
if (!args[1].match(regex)) { break } if (!args[1].match(regex)) { break }
updateChatMode(await getIDByName(channel.substring(1)), args[1]) updateChatMode(await getIDByName(channel.substring(1)), args[1])
break; break;
// case "donate": case "donate":
// const links = ["https://bit.ly/heltwittergoals", "https://bit.ly/donatetohel","http://bit.ly/donothon_goals"] sendDonationAd()
// tClient.say(channel, "Hel is currently celebrating her birthday with a donathon!") break;
// tClient.say(channel, `You can see goals and incentives here: ${links[0]}`) case "test":
// tClient.say(channel, `You can donate here: ${links[1]}`) console.log(await makeTwitchRequest('games/top'))
// tClient.say(channel, `Terms, conditions, rules and how rewards work are found here: ${links[2]}`) break;
// tClient.say(channel, `All donations and gifts are greatly appreciated, but please do so responsibly!`)
// break;
case "positivity": case "positivity":
let positive_Messages = ["You are so very worthy of all the love and support that may come your way.", sendPositivity()
"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])
break; break;
default: default:
break; break;
} }
} }
// ---------------VIP Handler------------------- // ---------------VIP Handler-------------------
if (!userstate.badges?.vip) { return } // if (!userstate.badges?.vip) { return }
verifyUser(channel, userstate.username) // verifyUser(channel, userstate.username)
// --------------------------------------------- // ---------------------------------------------
} }
+39 -9
View File
@@ -200,7 +200,7 @@ async function tLogin() {
return; return;
} }
const tClient = new tmi.Client({ var tClient = new tmi.Client({
options: { debug: false }, options: { debug: false },
connection: { connection: {
reconnect: true, reconnect: true,
@@ -214,7 +214,7 @@ async function tLogin() {
}); });
tClient.connect(); tClient.connect();
// Handle Twitch events // Handle Twitch events
tClient.on('connected', (address, port) => { tClient.on('connected', (address, port) => {
@@ -233,18 +233,48 @@ async function tLogin() {
dClient.emit('Twitch/Subscribers', channel, enabled, tClient); dClient.emit('Twitch/Subscribers', channel, enabled, tClient);
}); });
tClient.on('disconnected', (reason) => { tClient.on('disconnected', async (reason) => {
console.log('Twitch client disconnected:', reason); console.log('Twitch disconnected:', reason);
if (reason.includes('Login authentication failed')) { if (reason.includes('Login authentication failed')) {
refreshAccessToken() try {
.then(() => tClient.connect()) await refreshAccessToken();
.catch((error) => { console.log('Reinitializing Twitch client after token refresh...');
console.error('Failed to refresh token and reconnect:', error);
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(); tLogin();
module.exports = { mClient, dClient }; process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
// Prevent app from crashing
});
module.exports = { mClient, dClient, makeTwitchRequest };