From 872814fb7345b99d8ffc310461f47070c5de25f5 Mon Sep 17 00:00:00 2001 From: Milo Date: Wed, 16 Apr 2025 15:23:42 +0200 Subject: [PATCH 1/2] ia cd and warns --- app.js | 100 ++++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 70 insertions(+), 30 deletions(-) diff --git a/app.js b/app.js index f1052d3..25b1c13 100644 --- a/app.js +++ b/app.js @@ -39,12 +39,40 @@ const client = new Client({ ] }); +const requestTimestamps = new Map(); // userId => [timestamp1, timestamp2, ...] +const MAX_REQUESTS_PER_MINUTE = parseInt(process.env.MAX_REQUESTS || "5"); + +const akhysData= new Map() + +async function getAkhys() { + try { + const guild = await client.guilds.fetch(process.env.GUILD_ID); + const members = await guild.members.fetch(); // Fetch all members + + const akhys = members.filter(m => !m.user.bot && m.roles.cache.has(process.env.VOTING_ROLE_ID)); + + akhys.forEach(akhy => { + akhysData.set(akhy.user.id, { + id: akhy.user.id, + username: akhy.user.username, + globalName: akhy.user.globalName, + warned: false, + warns: 0, + allTimeWarns: 0, + totalRequests: 0, + }); + }); + } catch (err) { + console.error('Error while counting akhys:', err); + } +} + async function getOnlineUsersWithRole(guild_id=process.env.GUILD_ID, role_id=process.env.VOTING_ROLE_ID) { try { const guild = await client.guilds.fetch(guild_id); const members = await guild.members.fetch(); // Fetch all members - const online = members.filter(m => !m.user.bot && (m.presence?.status === 'online' || m.presence?.status === 'dnd') && m.roles.cache.has(role_id)); + const online = members.filter(m => !m.user.bot && m.presence?.status && m.roles.cache.has(role_id)); return online } catch (err) { console.error('Error while counting online members:', err); @@ -62,16 +90,42 @@ client.on('messageCreate', async (message) => { // Check if the message content includes the word "quoi" (case-insensitive) if (message.content.toLowerCase().includes("quoi")) { let prob = Math.random() - console.log(`feur ? ${prob} ${process.env.FEUR_PROB}`) if (prob < process.env.FEUR_PROB) { - console.log('feur!') // Send a message "feur" to the same channel message.channel.send(`feur`) .catch(console.error); } } + else if (message.content.toLowerCase().startsWith(`<@${process.env.APP_ID}>`) || message.mentions.repliedUser?.id === process.env.APP_ID) { + let akhyAuthor = akhysData.get(message.author.id) + if (akhyAuthor.warns > process.env.MAX_WARNS) { + //todo timeout pour spam + } + + const now = Date.now(); + const timestamps = requestTimestamps.get(message.author.id) || []; + +// Remove timestamps older than 60 seconds + const updatedTimestamps = timestamps.filter(ts => now - ts < 60 * 1000); + + if (updatedTimestamps.length >= MAX_REQUESTS_PER_MINUTE) { + console.log(akhyAuthor.warned ? `${message.author.username} is restricted : ${updatedTimestamps}` : `Rate limit exceeded for ${message.author.username}`); + if (!akhyAuthor.warned) message.channel.send(`T'abuses fréro, attends un peu ⏳`); + akhyAuthor.warned = true; + akhyAuthor.warns++; + akhyAuthor.allTimeWarns++; + return; + } + +// Track this new usage + updatedTimestamps.push(now); + requestTimestamps.set(message.author.id, updatedTimestamps); + +// Proceed with your logic + akhyAuthor.warned = false; + akhyAuthor.warns = 0; + akhyAuthor.totalRequests++; - if (message.content.toLowerCase().startsWith(`<@${process.env.APP_ID}>`) || message.mentions.repliedUser?.id === process.env.APP_ID) { try { // Fetch last 10 messages from the channel const fetched = await message.channel.messages.fetch({ limit: 50 }); @@ -138,6 +192,13 @@ client.on('messageCreate', async (message) => { await message.channel.send("Oups, y'a eu un problème!"); } } + else if (message.content.toLowerCase().startsWith('membres')) { + let content = `Liste des membres : \n` + akhysData.forEach((akhy, id) => content += `${akhy.globalName} | **${akhy.totalRequests}** requests | **${akhy.warns}** warns | **${akhy.allTimeWarns}** all-time warns \n`); + + message.channel.send(`${content}`) + .catch(console.error); + } }); // Once bot is ready @@ -147,6 +208,8 @@ client.once('ready', async () => { const randomHour = Math.floor(Math.random() * (18 - 8 + 1)) + 8; todaysHydrateCron = `${randomMinute} ${randomHour} * * *` console.log(todaysHydrateCron) + await getAkhys(); + console.log('Akhys ready') // ─── 💀 Midnight Chaos Timer ────────────────────── cron.schedule(process.env.CRON_EXPR, async () => { @@ -156,7 +219,7 @@ client.once('ready', async () => { const guild = await client.guilds.fetch(process.env.GUILD_ID); const roleId = process.env.VOTING_ROLE_ID; // Set this in your .env file - const members = await getOnlineUsersWithRole(guild.id, roleId); + const members = await getOnlineUsersWithRole(process.env.GUILD_ID, roleId); const prob = Math.random(); if (members.size === 0 || prob > process.env.CHAOS_PROB) { @@ -164,7 +227,7 @@ client.once('ready', async () => { return } - const randomMember = eligible[Math.floor(Math.random() * members.size)]; + const randomMember = members[Math.floor(Math.random() * members.size)]; const timeoutUntil = new Date(Date.now() + 12 * 60 * 60 * 1000).toISOString(); @@ -200,7 +263,7 @@ client.once('ready', async () => { if (generalChannel && generalChannel.isTextBased()) { generalChannel.send( - `${getRandomHydrateText()} <@${process.env.VOTING_ROLE_ID}> ${getRandomEmoji()}` + `${getRandomHydrateText()} <@&${process.env.VOTING_ROLE_ID}> ${getRandomEmoji()}` ); } @@ -312,29 +375,6 @@ app.post('/interactions', verifyKeyMiddleware(process.env.PUBLIC_KEY), async fun const requiredMajority = Math.max(parseInt(process.env.MIN_VOTES), Math.floor(onlineEligibleUsers.size / 2) + 1); const votesNeeded = Math.max(0, requiredMajority - activePolls[id].for); - // Set a timeout for 5 minutes to end the poll if no majority is reached - /* setTimeout(async () => { - if (activePolls[id]) { - // Poll has expired without enough votes - // Send a notification to the channel that the vote failed - try { - await DiscordRequest( - `webhooks/${process.env.APP_ID}/${req.body.token}/messages/@original`, - { - method: 'PATCH', - body: { - content: `Le vote pour timeout de <@${activePolls[id].toUserId}> a expiré sans atteindre la majorité.`, - components: [] // remove the buttons - }, - } - ); - } catch (err) { - console.error('Error sending vote failure message:', err); - } - // Clear the poll - delete activePolls[id]; - } - }, process.env.POLL_TIME * 1000);*/ activePolls[id].endTime = Date.now() + process.env.POLL_TIME * 1000; activePolls[id].requiredMajority = requiredMajority; From 17e8db216dcc3dda755eeafbd52bcdd2eb593b0b Mon Sep 17 00:00:00 2001 From: milo Date: Wed, 16 Apr 2025 21:01:14 +0200 Subject: [PATCH 2/2] database users and spam management --- app.js => index.js | 92 +++++++++++++++++++++++++++++++++++++--------- init_database.js | 43 ++++++++++++++++++++++ 2 files changed, 118 insertions(+), 17 deletions(-) rename app.js => index.js (89%) create mode 100644 init_database.js diff --git a/app.js b/index.js similarity index 89% rename from app.js rename to index.js index 25b1c13..06336e4 100644 --- a/app.js +++ b/index.js @@ -19,6 +19,7 @@ import { import { getShuffledOptions, getResult } from './game.js'; import { Client, GatewayIntentBits } from 'discord.js'; import cron from 'node-cron'; +import { flopoDB, insertUser, insertManyUsers, updateUser, updateManyUsers, getUser, getAllUsers, stmt } from './init_database.js'; // Create an express app const app = express(); @@ -40,12 +41,13 @@ const client = new Client({ }); const requestTimestamps = new Map(); // userId => [timestamp1, timestamp2, ...] -const MAX_REQUESTS_PER_MINUTE = parseInt(process.env.MAX_REQUESTS || "5"); +const MAX_REQUESTS_PER_INTERVAL = parseInt(process.env.MAX_REQUESTS || "5"); const akhysData= new Map() async function getAkhys() { try { + stmt.run(); const guild = await client.guilds.fetch(process.env.GUILD_ID); const members = await guild.members.fetch(); // Fetch all members @@ -61,6 +63,17 @@ async function getAkhys() { allTimeWarns: 0, totalRequests: 0, }); + insertManyUsers([ + { + id: akhy.user.id, + username: akhy.user.username, + globalName: akhy.user.globalName, + warned: 0, + warns: 0, + allTimeWarns: 0, + totalRequests: 0 + }, + ]); }); } catch (err) { console.error('Error while counting akhys:', err); @@ -90,6 +103,7 @@ client.on('messageCreate', async (message) => { // Check if the message content includes the word "quoi" (case-insensitive) if (message.content.toLowerCase().includes("quoi")) { let prob = Math.random() + console.log(`feur ${prob}`) if (prob < process.env.FEUR_PROB) { // Send a message "feur" to the same channel message.channel.send(`feur`) @@ -97,34 +111,75 @@ client.on('messageCreate', async (message) => { } } else if (message.content.toLowerCase().startsWith(`<@${process.env.APP_ID}>`) || message.mentions.repliedUser?.id === process.env.APP_ID) { - let akhyAuthor = akhysData.get(message.author.id) - if (akhyAuthor.warns > process.env.MAX_WARNS) { - //todo timeout pour spam - } + //let akhyAuthor = akhysData.get(message.author.id) + let akhyAuthor = getUser.get(message.author.id) const now = Date.now(); const timestamps = requestTimestamps.get(message.author.id) || []; -// Remove timestamps older than 60 seconds - const updatedTimestamps = timestamps.filter(ts => now - ts < 60 * 1000); +// Remove timestamps older than SPAM_INTERVAL seconds + const updatedTimestamps = timestamps.filter(ts => now - ts < process.env.SPAM_INTERVAL); - if (updatedTimestamps.length >= MAX_REQUESTS_PER_MINUTE) { + if (updatedTimestamps.length >= MAX_REQUESTS_PER_INTERVAL) { console.log(akhyAuthor.warned ? `${message.author.username} is restricted : ${updatedTimestamps}` : `Rate limit exceeded for ${message.author.username}`); if (!akhyAuthor.warned) message.channel.send(`T'abuses fréro, attends un peu ⏳`); - akhyAuthor.warned = true; - akhyAuthor.warns++; - akhyAuthor.allTimeWarns++; + // akhyAuthor.warned = true; + // akhyAuthor.warns++; + // akhyAuthor.allTimeWarns++; + updateManyUsers([ + { + id: akhyAuthor.id, + username: akhyAuthor.username, + globalName: akhyAuthor.globalName, + warned: 1, // true + warns: akhyAuthor.warns + 1, + allTimeWarns: akhyAuthor.allTimeWarns + 1, + totalRequests: akhyAuthor.totalRequests + }, + ]) + akhyAuthor = getUser.get(akhyAuthor.id) + if (akhyAuthor.warns > process.env.MAX_WARNS ?? 10) { + const guild = await client.guilds.fetch(process.env.GUILD_ID); + const time = parseInt(process.env.SPAM_TIMEOUT_TIME) + try { + await guild.members.edit(akhyAuthor.id, { + communication_disabled_until: new Date(Date.now() + time).toISOString(), + reason: 'Dose le spam fdp', + }); + } catch (e) { + console.log('Tried timeout for AI spam : ', e) + message.channel.send(`<@${akhyAuthor.id}> tu me fais chier !! T'as de la chance que je puisse pas te timeout 🔪`) + .catch(console.error); + return + } + message.channel.send(`Ce bouffon de <@${akhyAuthor.id}> a été timeout pendant ${formatTime(time/1000)}, il me cassait les couilles 🤫`) + .catch(console.error); + return + } return; } + // Track this new usage updatedTimestamps.push(now); requestTimestamps.set(message.author.id, updatedTimestamps); // Proceed with your logic - akhyAuthor.warned = false; - akhyAuthor.warns = 0; - akhyAuthor.totalRequests++; + // akhyAuthor.warned = false; + // akhyAuthor.warns = 0; + // akhyAuthor.totalRequests++; + updateManyUsers([ + { + id: akhyAuthor.id, + username: akhyAuthor.username, + globalName: akhyAuthor.globalName, + warned: 0, // false + warns: 0, // reset + allTimeWarns: akhyAuthor.allTimeWarns, + totalRequests: akhyAuthor.totalRequests + 1 + }, + ]) + akhyAuthor = getUser.get(akhyAuthor.id) try { // Fetch last 10 messages from the channel @@ -183,7 +238,9 @@ client.on('messageCreate', async (message) => { content: `Ton id est : ${process.env.APP_ID}, évite de l'utiliser et ne formatte pas tes messages avec ton propre id, si jamais tu utilises un id formatte le comme suit : <@ID>, en remplacant ID par l'id. Ton username et global_name sont : ${process.env.APP_NAME}` }); - const reply = 'Je chill zbi (ntm a vouloir gaspiller les token)'//await gork(formatted); IA en pause + // 'Je chill zbi (ntm a vouloir gaspiller les token)' // IA en pause + // await gork(formatted); IA en marche + const reply = await gork(formatted); // Send response to the channel await message.channel.send(reply); @@ -193,8 +250,9 @@ client.on('messageCreate', async (message) => { } } else if (message.content.toLowerCase().startsWith('membres')) { - let content = `Liste des membres : \n` - akhysData.forEach((akhy, id) => content += `${akhy.globalName} | **${akhy.totalRequests}** requests | **${akhy.warns}** warns | **${akhy.allTimeWarns}** all-time warns \n`); + let content = `` + const allAkhys = getAllUsers.all() + allAkhys.forEach((akhy) => content += `> ### ${akhy.globalName} \n > **${akhy.totalRequests}** requests \n > **${akhy.warns}** warns \n > **${akhy.allTimeWarns}** all-time warns \n\n`); message.channel.send(`${content}`) .catch(console.error); diff --git a/init_database.js b/init_database.js new file mode 100644 index 0000000..15f1fc0 --- /dev/null +++ b/init_database.js @@ -0,0 +1,43 @@ +import Database from "better-sqlite3"; + + +export const flopoDB = new Database('flopobot.db'); + +export const stmt = flopoDB.prepare(` + CREATE TABLE IF NOT EXISTS users ( + id TEXT PRIMARY KEY, + username TEXT NOT NULL, + globalName TEXT, + warned BOOLEAN DEFAULT 0, + warns INTEGER DEFAULT 0, + allTimeWarns INTEGER DEFAULT 0, + totalRequests INTEGER DEFAULT 0 + ) +`); +stmt.run(); + +export const insertUser = flopoDB.prepare('INSERT INTO users (id, username, globalName, warned, warns, allTimeWarns, totalRequests) VALUES (@id, @username, @globalName, @warned, @warns, @allTimeWarns, @totalRequests)'); +export const updateUser = flopoDB.prepare('UPDATE users SET warned = @warned, warns = @warns, allTimeWarns = @allTimeWarns, totalRequests = @totalRequests WHERE id = @id'); +export const getUser = flopoDB.prepare('SELECT * FROM users WHERE id = ?'); +export const getAllUsers = flopoDB.prepare('SELECT * FROM users'); + +export const insertManyUsers = flopoDB.transaction((users) => { + for (const user of users) try { insertUser.run(user) } catch (e) { console.log('users insert failed') }; +}); +export const updateManyUsers = flopoDB.transaction((users) => { + for (const user of users) try { updateUser.run(user) } catch (e) { console.log('users update failed') }; +}); +//const getManyUsers = flopoDB.transaction(()) + + +// insertManyUsers([ +// { id: '1234', username: 'Username', globalName: 'GlobalName', warned: 0, warns: 0, allTimeWarns: 0, totalRequests: 0 }, +// { id: '12345', username: 'Username', globalName: 'GlobalName', warned: 0, warns: 0, allTimeWarns: 0, totalRequests: 0 }, +// ]); + + +// updateManyUsers([ +// { id: '1234', username: 'Username', globalName: 'GlobalName', warned: 0, warns: 0, allTimeWarns: 0, totalRequests: 0 }, +// ]); + +//console.log(getUser.get('12345'))