Raw Dogged that custom shoutout event and made HelixRequest easier to handle universally

This commit is contained in:
DeSqBlocki
2025-02-08 13:42:18 +01:00
parent e499308e64
commit 0c6deae544
8 changed files with 165 additions and 62 deletions
-1
View File
@@ -1,3 +1,2 @@
MONGO_URI= MONGO_URI=
MONGO_DB=
PREFIX= PREFIX=
+1 -1
View File
@@ -25,7 +25,7 @@ const initializeClients = async () => {
], ],
}); });
const db = mClient.db(process.env.MONGO_DB); const db = mClient.db("client");
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' });
+13 -36
View File
@@ -3,41 +3,19 @@ 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, makeTwitchRequest } = require('../../index') const { dClient, mClient, getIDByName, updateChatMode} = 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 emitShoutoutInfo(channel, username) {
let state // Function sends information to the Shoutout Handler if they haven't been shouted out before, default target: VIPs
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 db = mClient.db("shoutouts")
let col = db.collection(channel) let col = db.collection(channel)
let status = await col.findOne({ user: username })
let query = { user: username }
let status = await col.findOne(query)
if (!status) { if (!status) {
dClient.emit('TwitchShoutout', "VIP", channel, username) dClient.emit('Twitch/Shoutout', "VIP", channel, username, tClient)
await col.insertOne({ await col.insertOne({
user: username user: username,
created_at: Date()
}) })
} }
} }
@@ -61,6 +39,7 @@ module.exports = {
return tClient.say(channel, positive_Messages[rdm]) return tClient.say(channel, positive_Messages[rdm])
} }
async function sendDonationAd() { async function sendDonationAd() {
// Function to send static donate ad
const links = ["https://bit.ly/heltwittergoals", "https://bit.ly/donatetohel", "http://bit.ly/donothon_goals"] 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, "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 see goals and incentives here: ${links[0]}`)
@@ -85,9 +64,6 @@ module.exports = {
const args = message.substring(1).split(" ") const args = message.substring(1).split(" ")
const cmd = args[0] const cmd = args[0]
switch (cmd) { switch (cmd) {
// case "test":
// tClient.say(channel, "/me This is a test message")
// break;
case "mode": case "mode":
if (userstate.badges.moderator != 1 && userstate.badges.broadcaster != 1) { break } // return stops all further operations, so you should just break out of switch if (userstate.badges.moderator != 1 && userstate.badges.broadcaster != 1) { break } // return stops all further operations, so you should just break out of switch
let regex = /^(on|off)$/ let regex = /^(on|off)$/
@@ -98,7 +74,8 @@ module.exports = {
sendDonationAd() sendDonationAd()
break; break;
case "test": case "test":
console.log(await makeTwitchRequest('games/top')) await emitShoutoutInfo(channel, userstate.username)
// console.log(await getIDByName(userstate))
break; break;
case "positivity": case "positivity":
sendPositivity() sendPositivity()
@@ -109,8 +86,8 @@ module.exports = {
} }
// ---------------VIP Handler------------------- // ---------------VIP Handler-------------------
// if (!userstate.badges?.vip) { return } if (!userstate.badges?.vip) { return }
// verifyUser(channel, userstate.username) emitShoutoutInfo(channel, userstate.username)
// --------------------------------------------- // ---------------------------------------------
} }
+4 -2
View File
@@ -2,7 +2,9 @@
module.exports = { module.exports = {
name: 'Twitch/Raided', name: 'Twitch/Raided',
once: false, once: false,
execute(stream) { execute(channel, username, raiders, tClient) {
console.log(`Connected to ${stream.address}`) const { dClient } = require('../../index')
tClient.say(channel, `${username} entrusted us with ${raiders} people of their community, welcome xhelxBlankie`)
dClient.emit('Twitch/Shoutout', "RAID", channel, username)
} }
} }
+65
View File
@@ -0,0 +1,65 @@
// Event triggered by Custom Shoutout emitted in Messages.js
const soBuffer = new Map();
let isShoutoutInProgress = false; // Track shoutout status to avoid overlapping shoutouts
module.exports = {
name: 'Twitch/Shoutout',
once: false,
async execute(reason, channel, username, tClient) {
const { getIDByName, getChannelInformation } = require('../../index');
// Function to perform shoutout (Nightbot or custom)
async function doShoutouts(channel, user) {
try {
// Use Nightbot command !so
// tClient.say(channel, `!so ${user}`); // Send the shoutout command
// Use custom shoutout
let channelID = await getIDByName(user)
let res = await getChannelInformation(channelID)
let channelInfo = res?.data[0]
let shoutoutMessage = `Check out ${channelInfo.broadcaster_name} over at https://twitch.tv/${channelInfo.broadcaster_login}! They were last playing ${channelInfo.game_name}`
tClient.say(channel, shoutoutMessage)
console.log(`Shouting out ${user} in channel ${channel} for reason: ${reason}`);
} catch (error) {
console.error('Error during shoutout:', error);
}
}
// Handle buffered shoutout queue
async function processShoutoutQueue() {
if (soBuffer.size === 0 || isShoutoutInProgress) {
return; // Exit if no users are in the buffer or a shoutout is in progress
}
// Set shoutout in progress flag
isShoutoutInProgress = true;
// Get the next user in the buffer
const [user, channel] = soBuffer.entries().next().value;
try {
await doShoutouts(channel, user); // Perform the shoutout
soBuffer.delete(user); // Remove user after shoutout is done
// console.log(`Shoutout for ${user} completed in channel ${channel}`);
} catch (error) {
console.error('Error while processing shoutout:', error);
} finally {
// Reset shoutout in progress flag and schedule next shoutout
isShoutoutInProgress = false;
setTimeout(processShoutoutQueue, 5000); // Schedule next shoutout after cooldown
}
}
// Add user to the buffer if not already present
if (!soBuffer.has(username)) {
soBuffer.set(username, channel);
console.log(`Added ${username} to the shoutout buffer. Reason: ${reason}`);
}
// Call the shoutout queue processing
setTimeout(processShoutoutQueue, 5000); // Ensure 5-second cooldown between shoutouts
}
};
+12 -2
View File
@@ -2,7 +2,17 @@
module.exports = { module.exports = {
name: 'Twitch/Subscribers', name: 'Twitch/Subscribers',
once: false, once: false,
execute(stream) { async execute(channel, enabled) {
console.log(`Connected to ${stream.address}`) const { mClient } = require('../..')
let db = mClient.db('shoutouts')
let col = db.collection(channel)
// Drop Shoutout Collection to reset
try {
await col.drop()
} catch (error) {
return
}
} }
} }
+2 -2
View File
@@ -12,10 +12,10 @@ module.exports = (dClient) => {
const filePath = path.join(eventsPath, file) const filePath = path.join(eventsPath, file)
const event = require(filePath) const event = require(filePath)
if (event.once) { if (event.once) {
dClient.once(event.name, (...args) => event.execute(...args, dClient)) dClient.once(event.name, (...args) => event.execute(...args))
// added client to commomerate global usage // added client to commomerate global usage
} else { } else {
dClient.on(event.name, (...args) => event.execute(...args, dClient)) dClient.on(event.name, (...args) => event.execute(...args))
// added client to commomerate global usage // added client to commomerate global usage
} }
} }
+68 -18
View File
@@ -7,7 +7,7 @@ const axios = require('axios');
// Validate required environment variables // Validate required environment variables
function validateEnvVariables() { function validateEnvVariables() {
const requiredVars = ['MONGO_URI', 'MONGO_DB']; const requiredVars = ['MONGO_URI'];
const missingVars = requiredVars.filter((varName) => !process.env[varName]); const missingVars = requiredVars.filter((varName) => !process.env[varName]);
if (missingVars.length > 0) { if (missingVars.length > 0) {
@@ -36,7 +36,7 @@ initializeMongoClient();
// Function to save tokens to MongoDB // Function to save tokens to MongoDB
async function saveToken(access_token, refresh_token, expiresIn, scope) { async function saveToken(access_token, refresh_token, expiresIn, scope) {
const db = mClient.db(process.env.MONGO_DB); const db = mClient.db("clients");
const credentialCollection = db.collection('credentials'); const credentialCollection = db.collection('credentials');
const tokenData = { const tokenData = {
@@ -57,7 +57,7 @@ async function saveToken(access_token, refresh_token, expiresIn, scope) {
// Function to retrieve stored tokens from MongoDB // Function to retrieve stored tokens from MongoDB
async function getStoredToken() { async function getStoredToken() {
const db = mClient.db(process.env.MONGO_DB); const db = mClient.db("clients");
const credentialCollection = db.collection('credentials'); const credentialCollection = db.collection('credentials');
const tCreds = await credentialCollection.findOne({ service: 'twitch' }); const tCreds = await credentialCollection.findOne({ service: 'twitch' });
@@ -82,7 +82,7 @@ async function refreshAccessToken() {
isRefreshingToken = true; isRefreshingToken = true;
refreshPromise = (async () => { refreshPromise = (async () => {
try { try {
const db = mClient.db(process.env.MONGO_DB); const db = mClient.db("clients");
const credentialCollection = db.collection('credentials'); const credentialCollection = db.collection('credentials');
const credentials = await credentialCollection.findOne({ service: 'twitch' }); const credentials = await credentialCollection.findOne({ service: 'twitch' });
const token = await getStoredToken(); const token = await getStoredToken();
@@ -121,8 +121,8 @@ async function refreshAccessToken() {
} }
// Function to make requests to the Twitch API // Function to make requests to the Twitch API
async function makeTwitchRequest(endpoint) { async function makeHelixRequest(requestType, endpoint, params, data) {
const db = mClient.db(process.env.MONGO_DB); const db = mClient.db("clients");
const credentialCollection = db.collection('credentials'); const credentialCollection = db.collection('credentials');
const credentials = await credentialCollection.findOne({ service: 'twitch' }); const credentials = await credentialCollection.findOne({ service: 'twitch' });
@@ -133,27 +133,77 @@ async function makeTwitchRequest(endpoint) {
} }
try { try {
const response = await axios.get(`https://api.twitch.tv/helix/${endpoint}`, { var response
headers: { var headers = {
'Client-Id': credentials.client_id, 'Client-Id': credentials.client_id,
'Authorization': `Bearer ${token.access_token}`, 'Authorization': `Bearer ${token.access_token}`,
}, }
}); switch (requestType) {
case 'GET':
response = await axios.get(`https://api.twitch.tv/helix/${endpoint}`, { headers, params });
return response.data;
break;
case 'PATCH':
response = await axios.patch(`https://api.twitch.tv/helix/${endpoint}`, data, { headers, params })
return response.data;
break;
default:
break;
}
return response.data;
} catch (error) { } catch (error) {
if (error.response?.status === 401) { if (error.response?.status === 401) {
console.log('Access token expired. Refreshing...'); console.log('Access token expired. Refreshing...');
const newToken = await refreshAccessToken(); const newToken = await refreshAccessToken();
if (newToken) { if (newToken) {
return makeTwitchRequest(endpoint); // Retry with new token return makeHelixRequest(requestType, endpoint, params, data); // Retry with new token
} }
} }
console.error('Twitch API request failed:', error.response?.data || error.message); console.error('Twitch API request failed:', error.response?.data || error.message);
} }
} }
async function getIDByName(user) {
// Function to convert Names to ID
let endpoint = `users`
let params = {
login: user
}
let res = await makeHelixRequest('GET', endpoint, params)
return res?.data[0]?.id
}
async function updateChatMode(broadcaster_id, setTo) {
let state
if (setTo === 'on') {
state = true
} else if (setTo === 'off') {
state = false
} else {
return
}
let db = mClient.db('clients')
let credentialCollection = db.collection('credentials')
let credentials = await credentialCollection.findOne({ service: 'twitch' })
let endpoint = `chat/settings`
let data = {
"follower_mode": state,
"follower_mode_duration": 0,
"subscriber_mode": state,
"emote_mode": state
}
let params = {
broadcaster_id: broadcaster_id,
moderator_id: credentials.mod_id
}
makeHelixRequest('PATCH', endpoint, params, data)
}
async function getChannelInformation(broadcaster_id){
return response = await makeHelixRequest('GET', 'channels', { broadcaster_id: broadcaster_id })
}
// Initialize Discord Client // Initialize Discord Client
const dClient = new Client({ const dClient = new Client({
intents: [ intents: [
@@ -171,7 +221,7 @@ fs.readdirSync('./handlers').forEach((handler) => {
// Function to log in to Discord // Function to log in to Discord
async function dLogin() { async function dLogin() {
try { try {
const db = mClient.db(process.env.MONGO_DB); const db = mClient.db("clients");
const credentialCollection = db.collection('credentials'); const credentialCollection = db.collection('credentials');
const dCreds = await credentialCollection.findOne({ service: 'discord' }); const dCreds = await credentialCollection.findOne({ service: 'discord' });
@@ -191,7 +241,7 @@ dLogin();
// Function to log in to Twitch // Function to log in to Twitch
async function tLogin() { async function tLogin() {
const channels = ['desq_blocki']; const channels = ['desq_blocki'];
const db = mClient.db(process.env.MONGO_DB); const db = mClient.db("clients");
const credentialCollection = db.collection('credentials'); const credentialCollection = db.collection('credentials');
const tCreds = await credentialCollection.findOne({ service: 'twitch' }); const tCreds = await credentialCollection.findOne({ service: 'twitch' });
@@ -246,7 +296,7 @@ async function tLogin() {
console.warn('Twitch client already disconnected:', err.message); console.warn('Twitch client already disconnected:', err.message);
} }
const db = mClient.db(process.env.MONGO_DB); const db = mClient.db("clients");
const credentialCollection = db.collection('credentials'); const credentialCollection = db.collection('credentials');
const tCreds = await credentialCollection.findOne({ service: 'twitch' }); const tCreds = await credentialCollection.findOne({ service: 'twitch' });
@@ -277,4 +327,4 @@ process.on('unhandledRejection', (reason, promise) => {
// Prevent app from crashing // Prevent app from crashing
}); });
module.exports = { mClient, dClient, makeTwitchRequest }; module.exports = { mClient, dClient, makeHelixRequest, getIDByName, updateChatMode, getChannelInformation };