From 10c807f611f3967f47ed56cef068404b18ce5dd5 Mon Sep 17 00:00:00 2001 From: guams Date: Sat, 28 Jun 2025 16:13:36 +0200 Subject: [PATCH 1/8] rebase --- .gitignore | 15 ++--- database.sql | 2 +- package.json | 18 ++++++ src/constants.js | 45 ++++++++++++++ src/database.js | 25 ++++++++ src/deploy-commands.js | 13 +++++ src/main.js | 129 +++++++++++++++++++++++++++++++++++++++++ 7 files changed, 235 insertions(+), 12 deletions(-) create mode 100644 package.json create mode 100644 src/constants.js create mode 100644 src/database.js create mode 100644 src/deploy-commands.js create mode 100644 src/main.js diff --git a/.gitignore b/.gitignore index a7ae25d..d9b79bd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,4 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Editor-based HTTP Client requests -/httpRequests/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml -configuration.json -.idea/ -.venv/* +node_modules +package-lock.json +config.json +.idea \ No newline at end of file diff --git a/database.sql b/database.sql index aff685d..f5d8435 100644 --- a/database.sql +++ b/database.sql @@ -35,7 +35,7 @@ CREATE TABLE member user_id VARCHAR(255), guild_id VARCHAR(255), username VARCHAR(255), - points bigint DEFAULT 50, + points bigint DEFAULT 50 NOT NULL CHECK (points >= 50), bet_value bigint DEFAULT 50, PRIMARY KEY (user_id), FOREIGN KEY (guild_id) REFERENCES guild (guild_id) diff --git a/package.json b/package.json new file mode 100644 index 0000000..46b45ec --- /dev/null +++ b/package.json @@ -0,0 +1,18 @@ +{ + "name": "gambling-bot", + "version": "1.0.0", + "description": "a simple gambling discord bot", + "license": "ISC", + "author": "", + "type": "commonjs", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "dependencies": { + "discord-api-types": "^0.38.13", + "discord.js": "^14.21.0", + "pg": "^8.16.3", + "uuid": "^11.1.0" + } +} diff --git a/src/constants.js b/src/constants.js new file mode 100644 index 0000000..993c2d2 --- /dev/null +++ b/src/constants.js @@ -0,0 +1,45 @@ +const {SlashCommandBuilder} = require("discord.js"); + +const POLL_COMMAND_NAME = 'poll'; +const BET_COMMAND_NAME = 'bet'; +const CLOSE_COMMAND_NAME = 'close'; +const PROFIL_COMMAND_NAME = 'profil'; + +const POLL_COMMAND = new SlashCommandBuilder() + .setName("poll") + .setDescription("Des votes sur lesquels gamble"); + +const BET_COMMAND = new SlashCommandBuilder() + .setName("bet") + .setDescription("Pour pouvoir planifier le prochain pari !") + .addNumberOption(option => option + .setName("somme") + .setDescription("La somme du prochain pari") + .setRequired(true) + .setMinValue(0)); + +const CLOSE_COMMAND = new SlashCommandBuilder() + .setName("close") + .setDescription("Pour clôturer un sondage") + .addStringOption(option => option + .setName("option") + .setDescription("L'issue du sondage") + .setRequired(true)); + +const PROFIL_COMMAND = new SlashCommandBuilder() + .setName("profil") + .setDescription("Afficher des informations sur un utilisateur") + .addUserOption(option => option + .setName("user") + .setDescription("La personne concernée") + .setRequired(false)); + +exports.POLL_COMMAND_NAME = POLL_COMMAND_NAME +exports.BET_COMMAND_NAME = BET_COMMAND_NAME +exports.CLOSE_COMMAND_NAME = CLOSE_COMMAND_NAME +exports.PROFIL_COMMAND_NAME = PROFIL_COMMAND_NAME + +exports.POLL_COMMAND = POLL_COMMAND +exports.BET_COMMAND = BET_COMMAND +exports.CLOSE_COMMAND = CLOSE_COMMAND +exports.PROFIL_COMMAND = PROFIL_COMMAND \ No newline at end of file diff --git a/src/database.js b/src/database.js new file mode 100644 index 0000000..155886e --- /dev/null +++ b/src/database.js @@ -0,0 +1,25 @@ +const { Client } = require('pg'); +const {DB_NAME, DB_USER, DB_PASSWORD, DB_ADDRESS, DB_PORT} = require('../config.json'); + +const DATABASE_CLIENT = new Client({ + user: DB_USER, + password: DB_PASSWORD, + host: DB_ADDRESS, + port: DB_PORT, + database: DB_NAME, +}); + +async function userExists() { + const res = await DATABASE_CLIENT.query("SELECT * FROM member"); + return res.rows; +} + +async function insertPollIntoDB(creatorId, pollId, guildId, questionLabel) { + const response = await DATABASE_CLIENT.query("INSERT INTO poll(creator, poll_id, guild_id, question, opened) VALUES ($1, $2, $3, $4, true)", [creatorId, pollId, guildId, questionLabel]) + return response.rows; +} + +exports.DATABASE_CLIENT = DATABASE_CLIENT; +exports.userExists = userExists; +exports.insertPollIntoDB = insertPollIntoDB; + diff --git a/src/deploy-commands.js b/src/deploy-commands.js new file mode 100644 index 0000000..c183d7d --- /dev/null +++ b/src/deploy-commands.js @@ -0,0 +1,13 @@ +const {POLL_COMMAND, BET_COMMAND, CLOSE_COMMAND, PROFIL_COMMAND} = require("./constants") +const {TOKEN, APPLICATION_ID} = require('../config.json') +const {REST} = require("discord.js"); +const { Routes } = require('discord-api-types/v10'); + +const commands = [POLL_COMMAND, BET_COMMAND, CLOSE_COMMAND, PROFIL_COMMAND].map(command => command.toJSON()); +const rest = new REST({version: '10'}).setToken(TOKEN); + +console.log(commands) + +rest.put(Routes.applicationCommands(APPLICATION_ID), { body: commands }) + .then(() => console.log('Successfully registered application commands.')) + .catch(console.error); \ No newline at end of file diff --git a/src/main.js b/src/main.js new file mode 100644 index 0000000..afe713d --- /dev/null +++ b/src/main.js @@ -0,0 +1,129 @@ +const { + Client, + Events, + GatewayIntentBits, + ModalBuilder, + TextInputBuilder, + ActionRowBuilder, + EmbedBuilder, + Colors, ButtonBuilder +} = require('discord.js'); +const {DATABASE_CLIENT, insertPollIntoDB} = require('./database'); +const {TOKEN} = require('../config.json'); +const {v4: uuidv4} = require('uuid'); +const {POLL_COMMAND_NAME} = require("./constants"); +const {ButtonStyle} = require("discord-api-types/v10"); + +const client = new Client({intents: [GatewayIntentBits.Guilds]}); + +client.once(Events.ClientReady, async readyClient => { + console.log(`Ready! Logged in as ${readyClient.user.tag}`); + await DATABASE_CLIENT.connect(); +}); + +client.on(Events.InteractionCreate, async interaction => { + if (interaction.isCommand()) { + const {commandName} = interaction; + + if (commandName === POLL_COMMAND_NAME) { + await interaction.showModal(buildPollModal()); + } + } else if (interaction.isModalSubmit()) { + const pollToCreate = interaction.components; + const sender = interaction.user; + const guildFromWhereUserSendedInteraction = interaction.member.guild; + const pollQuestionObj = pollToCreate[0].components[0]; + let voteOptions = [ + pollToCreate[1].components[0].value, + pollToCreate[2].components[0].value, + pollToCreate[3].components[0].value, + pollToCreate[4].components[0].value + ]; + + voteOptions = [...new Set(voteOptions)].filter(voteOption => voteOption !== ''); + const introEmbed = new EmbedBuilder() + .setTitle("Les paris sont ouverts !") + .setDescription("Vous pouvez parier vos points, ils ne passeront pas en dessous de 50") + .setColor(Colors.Aqua); + const buttons = new ActionRowBuilder(); + + let pollDesc = ''; + voteOptions.forEach((voteOption, index) => { + buttons.addComponents(new ButtonBuilder() + .setCustomId(`${interaction.customId}_${voteOption}_${index + 1}`) + .setLabel(voteOption) + .setStyle(ButtonStyle.Primary)); + pollDesc += `Option ${index + 1}: ${voteOption}\n` + }); + + const pollEmbed = new EmbedBuilder() + .setTitle(`Sondage: ${pollQuestionObj.value}`) + .setDescription(pollDesc) + .setColor(Colors.Green); + + insertPollIntoDB(sender.id, interaction.customId, guildFromWhereUserSendedInteraction.id, pollQuestionObj.value); + + await interaction.reply({embeds: [introEmbed, pollEmbed], components: [buttons]}); + } + +}); + +function buildPollModal() { + const pollId = uuidv4(); + + const pollModal = new ModalBuilder() + .setTitle("Créer un vote") + .setCustomId(pollId) + + const questionRow = new ActionRowBuilder().addComponents( + new TextInputBuilder() + .setCustomId("label") + .setLabel("Question du vote") + .setPlaceholder("Shy aime-t-il les robots?") + .setStyle(1) + .setMinLength(1) + .setRequired(true) + ); + + const firstOption = new ActionRowBuilder().addComponents( + new TextInputBuilder() + .setCustomId("vote1") + .setLabel("Première option") + .setPlaceholder("Oui") + .setStyle(1) + .setMinLength(1) + .setRequired(true) + ); + + const secondOption = new ActionRowBuilder().addComponents( + new TextInputBuilder() + .setCustomId("vote2") + .setLabel("Deuxième option") + .setPlaceholder("Non") + .setStyle(1) + .setMinLength(1) + .setRequired(false) + ); + const thirdOption = new ActionRowBuilder().addComponents( + new TextInputBuilder() + .setCustomId("vote3") + .setLabel("Troisième option") + .setPlaceholder("Peut-être") + .setStyle(1) + .setMinLength(1) + .setRequired(false) + ); + const fourthOption = new ActionRowBuilder().addComponents( + new TextInputBuilder() + .setCustomId("vote4") + .setLabel("Quatrième option") + .setPlaceholder("Pas du tout") + .setStyle(1) + .setMinLength(1) + .setRequired(false) + ); + + return pollModal.addComponents(questionRow, firstOption, secondOption, thirdOption, fourthOption); +} + +client.login(TOKEN); From e47bf9f4510e91822c4ae4d592fff670d228a9d9 Mon Sep 17 00:00:00 2001 From: guams Date: Sat, 28 Jun 2025 16:48:04 +0200 Subject: [PATCH 2/8] insert new poll --- src/database.js | 56 ++++++++++++++++++++++++++++++++++++++++++------- src/main.js | 14 +++++++++++-- 2 files changed, 60 insertions(+), 10 deletions(-) diff --git a/src/database.js b/src/database.js index 155886e..1d6a5a2 100644 --- a/src/database.js +++ b/src/database.js @@ -1,4 +1,4 @@ -const { Client } = require('pg'); +const {Client} = require('pg'); const {DB_NAME, DB_USER, DB_PASSWORD, DB_ADDRESS, DB_PORT} = require('../config.json'); const DATABASE_CLIENT = new Client({ @@ -9,17 +9,57 @@ const DATABASE_CLIENT = new Client({ database: DB_NAME, }); -async function userExists() { - const res = await DATABASE_CLIENT.query("SELECT * FROM member"); - return res.rows; +async function userExistsInDB(userId, guildId) { + const response = await DATABASE_CLIENT.query( + "SELECT user_id FROM member WHERE user_id=$1 AND guild_id=$2", + [userId, guildId] + ); + return response.rows.length > 0; +} + +async function isLastPollCreatedByUserOpen(userId) { + const response = await DATABASE_CLIENT.query( + "SELECT opened FROM poll WHERE creator=$1 AND opened=true", + [userId] + ); + return response.rows.length > 0; +} + +async function insertUserIntoDB(userId, guildId, username) { + await DATABASE_CLIENT.query( + "INSERT INTO member (user_id, guild_id, username) VALUES ($1, $2, $3)", + [userId, guildId, username] + ); } async function insertPollIntoDB(creatorId, pollId, guildId, questionLabel) { - const response = await DATABASE_CLIENT.query("INSERT INTO poll(creator, poll_id, guild_id, question, opened) VALUES ($1, $2, $3, $4, true)", [creatorId, pollId, guildId, questionLabel]) + const response = await DATABASE_CLIENT.query( + "INSERT INTO poll(creator, poll_id, guild_id, question, opened) " + + "VALUES ($1, $2, $3, $4, true)", + [creatorId, pollId, guildId, questionLabel]) return response.rows; } -exports.DATABASE_CLIENT = DATABASE_CLIENT; -exports.userExists = userExists; -exports.insertPollIntoDB = insertPollIntoDB; +async function guildExistsInDB(guildId) { + const response = await DATABASE_CLIENT.query( + "SELECT guild_id FROM guild WHERE guild_id=$1", + [guildId] + ); + return response.rows.length > 0; +} + +async function insertGuildIntoDB(guildId) { + await DATABASE_CLIENT.query( + "INSERT INTO guild (guild_id) VALUES ($1)", + [guildId] + ); +} + +exports.DATABASE_CLIENT = DATABASE_CLIENT; +exports.userExistsInDB = userExistsInDB; +exports.insertPollIntoDB = insertPollIntoDB; +exports.guildExistsInDB = guildExistsInDB; +exports.insertGuildIntoDB = insertGuildIntoDB; +exports.insertUserIntoDB = insertUserIntoDB; +exports.isLastPollCreatedByUserOpen = isLastPollCreatedByUserOpen; diff --git a/src/main.js b/src/main.js index afe713d..edba6c5 100644 --- a/src/main.js +++ b/src/main.js @@ -8,7 +8,9 @@ const { EmbedBuilder, Colors, ButtonBuilder } = require('discord.js'); -const {DATABASE_CLIENT, insertPollIntoDB} = require('./database'); +const {DATABASE_CLIENT, insertPollIntoDB, guildExistsInDB, insertGuildIntoDB, userExistsInDB, insertUserIntoDB, + isLastPollCreatedByUserOpen +} = require('./database'); const {TOKEN} = require('../config.json'); const {v4: uuidv4} = require('uuid'); const {POLL_COMMAND_NAME} = require("./constants"); @@ -26,7 +28,15 @@ client.on(Events.InteractionCreate, async interaction => { const {commandName} = interaction; if (commandName === POLL_COMMAND_NAME) { - await interaction.showModal(buildPollModal()); + if (!await guildExistsInDB(interaction.guildId)) { + insertGuildIntoDB(interaction.guildId); + } + if (!await userExistsInDB(interaction.user.id, interaction.guildId)) { + insertUserIntoDB(interaction.user.id, interaction.guildId, interaction.user.username); + } + if (isLastPollCreatedByUserOpen(interaction.user.id)) { + await interaction.showModal(buildPollModal()); + } } } else if (interaction.isModalSubmit()) { const pollToCreate = interaction.components; From 5ee5944a7869b3afdf8a9b5505a6700fb4d71f3e Mon Sep 17 00:00:00 2001 From: guams Date: Sat, 28 Jun 2025 19:25:03 +0200 Subject: [PATCH 3/8] added profil command --- src/database.js | 21 ++++++++++-- src/main.js | 85 +++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 88 insertions(+), 18 deletions(-) diff --git a/src/database.js b/src/database.js index 1d6a5a2..5e11951 100644 --- a/src/database.js +++ b/src/database.js @@ -17,12 +17,28 @@ async function userExistsInDB(userId, guildId) { return response.rows.length > 0; } +async function getPollCreatedByUserAndOpened(userId) { + const response = await DATABASE_CLIENT.query( + "SELECT poll_id, question FROM poll WHERE creator=$1 and opened=true", + [userId] + ); + return response.rows; +} + +async function findUserById(userId, guildId) { + const response = await DATABASE_CLIENT.query( + "SELECT username, points, bet_value FROM member WHERE user_id=$1 AND guild_id=$2", + [userId, guildId] + ); + return response.rows[0]; +} + async function isLastPollCreatedByUserOpen(userId) { const response = await DATABASE_CLIENT.query( "SELECT opened FROM poll WHERE creator=$1 AND opened=true", [userId] ); - return response.rows.length > 0; + return !response.rows.length > 0; } async function insertUserIntoDB(userId, guildId, username) { @@ -62,4 +78,5 @@ exports.guildExistsInDB = guildExistsInDB; exports.insertGuildIntoDB = insertGuildIntoDB; exports.insertUserIntoDB = insertUserIntoDB; exports.isLastPollCreatedByUserOpen = isLastPollCreatedByUserOpen; - +exports.getPollCreatedByUserAndOpened = getPollCreatedByUserAndOpened; +exports.findUserById = findUserById; diff --git a/src/main.js b/src/main.js index edba6c5..667b50d 100644 --- a/src/main.js +++ b/src/main.js @@ -8,13 +8,15 @@ const { EmbedBuilder, Colors, ButtonBuilder } = require('discord.js'); -const {DATABASE_CLIENT, insertPollIntoDB, guildExistsInDB, insertGuildIntoDB, userExistsInDB, insertUserIntoDB, - isLastPollCreatedByUserOpen +const { + DATABASE_CLIENT, insertPollIntoDB, guildExistsInDB, insertGuildIntoDB, userExistsInDB, insertUserIntoDB, + isLastPollCreatedByUserOpen, getPollCreatedByUserAndOpened, findUserById } = require('./database'); const {TOKEN} = require('../config.json'); const {v4: uuidv4} = require('uuid'); -const {POLL_COMMAND_NAME} = require("./constants"); +const {POLL_COMMAND_NAME, PROFIL_COMMAND_NAME, BET_COMMAND_NAME, CLOSE_COMMAND_NAME} = require("./constants"); const {ButtonStyle} = require("discord-api-types/v10"); +const {MessageFlags} = require("discord-api-types/v10"); const client = new Client({intents: [GatewayIntentBits.Guilds]}); @@ -26,17 +28,59 @@ client.once(Events.ClientReady, async readyClient => { client.on(Events.InteractionCreate, async interaction => { if (interaction.isCommand()) { const {commandName} = interaction; + await createGuildAndUserIfNecessary(interaction.guildId, interaction.user); + switch (commandName) { + case POLL_COMMAND_NAME: + if (await isLastPollCreatedByUserOpen(interaction.user.id)) { + await interaction.showModal(buildPollModal()); + } else { + await interaction.reply({ + flags: MessageFlags.Ephemeral, + content: `${interaction.user.username}, il faut d'abord clôturer ton sondage avant de pouvoir en créer un nouveau. (commande: **/close**)` + }) + } + break; + case CLOSE_COMMAND_NAME: + const response = getPollCreatedByUserAndOpened(interaction.user.id); + if (response.length) { + // Logique pour fermer le poll + // [ + // { + // poll_id: '9b02d63e-5e4b-411e-bb65-2f412f65b21f', + // question: 'coucou' + // } + // ] + } + break; + case PROFIL_COMMAND_NAME: + let concernedUser; + if (interaction.options.data.length) { + concernedUser = interaction.options.data[0].user; + if (!await userExistsInDB(concernedUser.id)) { + await insertUserIntoDB(concernedUser.id, interaction.guildId, concernedUser.username); + } + } else { + concernedUser = interaction.user; + } + const userFetched = await findUserById(concernedUser.id, interaction.guildId); - if (commandName === POLL_COMMAND_NAME) { - if (!await guildExistsInDB(interaction.guildId)) { - insertGuildIntoDB(interaction.guildId); - } - if (!await userExistsInDB(interaction.user.id, interaction.guildId)) { - insertUserIntoDB(interaction.user.id, interaction.guildId, interaction.user.username); - } - if (isLastPollCreatedByUserOpen(interaction.user.id)) { - await interaction.showModal(buildPollModal()); - } + + const profilEmbed = new EmbedBuilder() + .setTitle(`Profil de ${concernedUser.username}`) + .setThumbnail(`https://cdn.discordapp.com/avatars/${concernedUser.id}/${concernedUser.avatar}`) + .setFooter({text: 'https://guams.fr', iconURL: 'https://guams.fr/icon.webp'}) + .addFields( + {name: 'Solde du compte', value: `${userFetched.points}`, inline: true}, + {name: 'Valeur du prochain pari', value: `${userFetched.bet_value}`, inline: true}) + .setTimestamp() + .setColor(Colors.Aqua); + await interaction.reply({embeds: [profilEmbed]}); + break; + case BET_COMMAND_NAME: + break; + default: + console.log(`Commande [${commandName}] inconnue`); + break; } } else if (interaction.isModalSubmit()) { const pollToCreate = interaction.components; @@ -54,7 +98,7 @@ client.on(Events.InteractionCreate, async interaction => { const introEmbed = new EmbedBuilder() .setTitle("Les paris sont ouverts !") .setDescription("Vous pouvez parier vos points, ils ne passeront pas en dessous de 50") - .setColor(Colors.Aqua); + .setColor(Colors.Aqua) const buttons = new ActionRowBuilder(); let pollDesc = ''; @@ -69,15 +113,24 @@ client.on(Events.InteractionCreate, async interaction => { const pollEmbed = new EmbedBuilder() .setTitle(`Sondage: ${pollQuestionObj.value}`) .setDescription(pollDesc) - .setColor(Colors.Green); + .setColor(Colors.Green) + .setFooter({text: 'https://guams.fr', iconURL: 'https://guams.fr/icon.webp'}); insertPollIntoDB(sender.id, interaction.customId, guildFromWhereUserSendedInteraction.id, pollQuestionObj.value); await interaction.reply({embeds: [introEmbed, pollEmbed], components: [buttons]}); } - }); +async function createGuildAndUserIfNecessary(guildId, user) { + if (!await guildExistsInDB(guildId)) { + insertGuildIntoDB(guildId); + } + if (!await userExistsInDB(user.id, guildId)) { + insertUserIntoDB(user.id, guildId, user.username); + } +} + function buildPollModal() { const pollId = uuidv4(); From 45d8721c5a9cad8f61b7729e3c62527ac1109f78 Mon Sep 17 00:00:00 2001 From: guams Date: Sat, 28 Jun 2025 20:09:29 +0200 Subject: [PATCH 4/8] added bet feature --- src/database.js | 46 ++++++++++++++++++++++++++++++++++++++++++++-- src/main.js | 34 ++++++++++++++++++++++++++++++---- 2 files changed, 74 insertions(+), 6 deletions(-) diff --git a/src/database.js b/src/database.js index 5e11951..5c203b5 100644 --- a/src/database.js +++ b/src/database.js @@ -17,7 +17,7 @@ async function userExistsInDB(userId, guildId) { return response.rows.length > 0; } -async function getPollCreatedByUserAndOpened(userId) { +async function findPollCreatedByUserAndOpened(userId) { const response = await DATABASE_CLIENT.query( "SELECT poll_id, question FROM poll WHERE creator=$1 and opened=true", [userId] @@ -41,6 +41,43 @@ async function isLastPollCreatedByUserOpen(userId) { return !response.rows.length > 0; } +async function findUserVoteForPoll(userId, pollId) { + const response = await DATABASE_CLIENT.query( + "SELECT vote_id FROM vote_member WHERE user_id=$1 AND poll_id=$2", + [userId, pollId] + ); + return response.rows[0]; +} + +async function findBetValueForUser(userId, guildId) { + const response = await DATABASE_CLIENT.query( + "SELECT bet_value FROM member WHERE user_id=$1 AND member.guild_id=$2", + [userId, guildId] + ); + return response.rows[0].bet_value; +} + +async function substractUserPoints(userId, betValue) { + await DATABASE_CLIENT.query( + "UPDATE member SET points = points - $1 WHERE user_id = $2 AND points - $1 >= 50", + betValue, userId + ); +} + +async function bet(userId, pollId, guildId, voteId, betValue) { + await DATABASE_CLIENT.query( + "INSERT INTO vote_member (user_id, poll_id, guild_id, vote_id, bet_value) VALUES ($1, $2, $3, $4, $5)", + [userId, pollId, guildId, voteId, betValue] + ); +} + +async function insertVoteOptions(pollId, voteId, voteOption) { + await DATABASE_CLIENT.query( + "INSERT INTO vote (poll_id, vote_id, vote_option) VALUES ($1, $2, $3)", + [pollId, voteId, voteOption] + ); +} + async function insertUserIntoDB(userId, guildId, username) { await DATABASE_CLIENT.query( "INSERT INTO member (user_id, guild_id, username) VALUES ($1, $2, $3)", @@ -78,5 +115,10 @@ exports.guildExistsInDB = guildExistsInDB; exports.insertGuildIntoDB = insertGuildIntoDB; exports.insertUserIntoDB = insertUserIntoDB; exports.isLastPollCreatedByUserOpen = isLastPollCreatedByUserOpen; -exports.getPollCreatedByUserAndOpened = getPollCreatedByUserAndOpened; +exports.findPollCreatedByUserAndOpened = findPollCreatedByUserAndOpened; exports.findUserById = findUserById; +exports.findUserVoteForPoll = findUserVoteForPoll; +exports.findBetValueForUser = findBetValueForUser; +exports.bet = bet; +exports.substractUserPoints = substractUserPoints; +exports.insertVoteOptions = insertVoteOptions; diff --git a/src/main.js b/src/main.js index 667b50d..d99eb72 100644 --- a/src/main.js +++ b/src/main.js @@ -9,8 +9,15 @@ const { Colors, ButtonBuilder } = require('discord.js'); const { - DATABASE_CLIENT, insertPollIntoDB, guildExistsInDB, insertGuildIntoDB, userExistsInDB, insertUserIntoDB, - isLastPollCreatedByUserOpen, getPollCreatedByUserAndOpened, findUserById + DATABASE_CLIENT, + insertPollIntoDB, + guildExistsInDB, + insertGuildIntoDB, + userExistsInDB, + insertUserIntoDB, + isLastPollCreatedByUserOpen, + findPollCreatedByUserAndOpened, + findUserById, findUserVoteForPoll, findBetValueForUser, bet, insertVoteOptions } = require('./database'); const {TOKEN} = require('../config.json'); const {v4: uuidv4} = require('uuid'); @@ -41,7 +48,7 @@ client.on(Events.InteractionCreate, async interaction => { } break; case CLOSE_COMMAND_NAME: - const response = getPollCreatedByUserAndOpened(interaction.user.id); + const response = findPollCreatedByUserAndOpened(interaction.user.id); if (response.length) { // Logique pour fermer le poll // [ @@ -116,9 +123,28 @@ client.on(Events.InteractionCreate, async interaction => { .setColor(Colors.Green) .setFooter({text: 'https://guams.fr', iconURL: 'https://guams.fr/icon.webp'}); - insertPollIntoDB(sender.id, interaction.customId, guildFromWhereUserSendedInteraction.id, pollQuestionObj.value); + await insertPollIntoDB(sender.id, interaction.customId, guildFromWhereUserSendedInteraction.id, pollQuestionObj.value); + for (let i = 0; i < voteOptions.length; i++) { + await insertVoteOptions(interaction.customId, `${interaction.customId}_${voteOptions[i]}_${i + 1}`, voteOptions[i]); + } await interaction.reply({embeds: [introEmbed, pollEmbed], components: [buttons]}); + } else if (interaction.isMessageComponent()) { + console.log(interaction) + const userVote = await findUserVoteForPoll(interaction.user.id, interaction.customId.split("_")[0]) + if (userVote) { + await interaction.reply({ + flags: MessageFlags.Ephemeral, + content: `${interaction.user.username}, tu as déjà voté pour **${userVote.vote_id.split("_")[1]}**` + }); + } else { + const userBetValue = await findBetValueForUser(interaction.user.id, interaction.guildId); + await bet(interaction.user.id, interaction.customId.split("_")[0], interaction.guildId, interaction.customId, userBetValue) + await interaction.reply({ + flags: MessageFlags.Ephemeral, + content: `${interaction.user.username}, tu as misé ${userBetValue} sur l'option **${interaction.customId.split("_")[1]}** ! 🎉` + }); + } } }); From 2524b6830f1eb45d54599e605f2c59025d7c54ad Mon Sep 17 00:00:00 2001 From: guams Date: Sun, 29 Jun 2025 08:59:11 +0200 Subject: [PATCH 5/8] added closing poll feature --- src/database.js | 49 ++++++++++++++++++++++++++++++ src/main.js | 79 ++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 110 insertions(+), 18 deletions(-) diff --git a/src/database.js b/src/database.js index 5c203b5..391fa6f 100644 --- a/src/database.js +++ b/src/database.js @@ -22,9 +22,53 @@ async function findPollCreatedByUserAndOpened(userId) { "SELECT poll_id, question FROM poll WHERE creator=$1 and opened=true", [userId] ); + return response.rows[0]; +} + +async function findVoteIdByVoteOptionAndPollId(voteOption, pollId) { + const response = await DATABASE_CLIENT.query( + "SELECT vote_id FROM vote WHERE vote_option=$1 AND poll_id=$2", + [voteOption, pollId] + ); + return response.rows[0]; +} + +async function findBetValueMultiplierForPoll(pollId) { + const response = await DATABASE_CLIENT.query( + "SELECT vote_id, (1 + (1 - (SUM(vote_member.bet_value) / total.sum))) AS cote " + + "FROM vote_member " + + "CROSS JOIN (SELECT SUM(bet_value) " + + "AS sum FROM vote_member WHERE poll_id=$1) " + + "AS total " + + "WHERE poll_id=$1 " + + "GROUP BY vote_id, total.sum", + [pollId] + ); return response.rows; } +async function findUsersAndBetValueByVoteId(voteId) { + const response = await DATABASE_CLIENT.query( + "SELECT user_id, bet_value FROM vote_member WHERE vote_id=$1", + [voteId] + ); + return response.rows; +} + +async function addPointsToUser(userId, betValue) { + await DATABASE_CLIENT.query( + "UPDATE member SET points = points + $1 WHERE user_id = $2", + [betValue, userId] + ); +} + +async function closePoll(pollId) { + await DATABASE_CLIENT.query( + "UPDATE poll SET opened = false WHERE poll_id=$1", + [pollId] + ); +} + async function findUserById(userId, guildId) { const response = await DATABASE_CLIENT.query( "SELECT username, points, bet_value FROM member WHERE user_id=$1 AND guild_id=$2", @@ -122,3 +166,8 @@ exports.findBetValueForUser = findBetValueForUser; exports.bet = bet; exports.substractUserPoints = substractUserPoints; exports.insertVoteOptions = insertVoteOptions; +exports.findVoteIdByVoteOptionAndPollId = findVoteIdByVoteOptionAndPollId; +exports.findBetValueMultiplierForPoll = findBetValueMultiplierForPoll; +exports.findUsersAndBetValueByVoteId = findUsersAndBetValueByVoteId; +exports.addPointsToUser = addPointsToUser; +exports.closePoll = closePoll; \ No newline at end of file diff --git a/src/main.js b/src/main.js index d99eb72..8184866 100644 --- a/src/main.js +++ b/src/main.js @@ -17,7 +17,12 @@ const { insertUserIntoDB, isLastPollCreatedByUserOpen, findPollCreatedByUserAndOpened, - findUserById, findUserVoteForPoll, findBetValueForUser, bet, insertVoteOptions + findUserById, + findUserVoteForPoll, + findBetValueForUser, + bet, + insertVoteOptions, findVoteIdByVoteOptionAndPollId, findBetValueMultiplierForPoll, findUsersAndBetValueByVoteId, + addPointsToUser, closePoll } = require('./database'); const {TOKEN} = require('../config.json'); const {v4: uuidv4} = require('uuid'); @@ -33,11 +38,11 @@ client.once(Events.ClientReady, async readyClient => { }); client.on(Events.InteractionCreate, async interaction => { + await createGuildAndUserIfNecessary(interaction.guildId, interaction.user); if (interaction.isCommand()) { const {commandName} = interaction; - await createGuildAndUserIfNecessary(interaction.guildId, interaction.user); switch (commandName) { - case POLL_COMMAND_NAME: + case POLL_COMMAND_NAME: { if (await isLastPollCreatedByUserOpen(interaction.user.id)) { await interaction.showModal(buildPollModal()); } else { @@ -47,23 +52,59 @@ client.on(Events.InteractionCreate, async interaction => { }) } break; - case CLOSE_COMMAND_NAME: - const response = findPollCreatedByUserAndOpened(interaction.user.id); - if (response.length) { - // Logique pour fermer le poll - // [ - // { - // poll_id: '9b02d63e-5e4b-411e-bb65-2f412f65b21f', - // question: 'coucou' - // } - // ] + } + case CLOSE_COMMAND_NAME: { + const actualOpenedPollCreatedByUser = await findPollCreatedByUserAndOpened(interaction.user.id); + + if (!actualOpenedPollCreatedByUser) { + await interaction.reply({ + flags: MessageFlags.Ephemeral, + content: `<@${interaction.user.id}>, tu n'as actuellement aucun sondage en cours.` + }); + break; } + + const optionSendedByUser = interaction.options.data[0].value; + let winnerVoteId = await findVoteIdByVoteOptionAndPollId(optionSendedByUser, actualOpenedPollCreatedByUser.poll_id); + + if (!winnerVoteId) { + await interaction.reply({ + flags: MessageFlags.Ephemeral, + content: `<@${interaction.user.id}>, L'option **${optionSendedByUser}** n'existe pas.` + }); + break; + } + winnerVoteId = winnerVoteId.vote_id; + + const voteIdsWithMultipliers = await findBetValueMultiplierForPoll(actualOpenedPollCreatedByUser.poll_id); + let winnerCote; + + for (const voteIdWithMultiplier of voteIdsWithMultipliers) { + const usersThatVotedActualVoteId = await findUsersAndBetValueByVoteId(voteIdWithMultiplier.vote_id); + + if (voteIdWithMultiplier.vote_id === winnerVoteId) { + winnerCote = Number.parseFloat(voteIdWithMultiplier.cote); + for (const user of usersThatVotedActualVoteId) { + const gain = Math.round(user.bet_value * winnerCote); + await addPointsToUser(user.user_id, gain); + } + } + } + + await closePoll(actualOpenedPollCreatedByUser.poll_id); + + const out = winnerCote ? ((Math.round(winnerCote * 100) / 100 - 1) * 100) : 0; + + await interaction.reply({ + content: `@everyone le sondage **${actualOpenedPollCreatedByUser.question}** est terminé ! L'issue gagnante est **${optionSendedByUser}** avec une côte de ${out.toFixed(2)}%` + }); break; - case PROFIL_COMMAND_NAME: + } + case PROFIL_COMMAND_NAME: { let concernedUser; if (interaction.options.data.length) { concernedUser = interaction.options.data[0].user; - if (!await userExistsInDB(concernedUser.id)) { + if (!await userExistsInDB(concernedUser.id, interaction.guildId)) { await insertUserIntoDB(concernedUser.id, interaction.guildId, concernedUser.username); } } else { @@ -83,11 +124,14 @@ client.on(Events.InteractionCreate, async interaction => { .setColor(Colors.Aqua); await interaction.reply({embeds: [profilEmbed]}); break; - case BET_COMMAND_NAME: + } + case BET_COMMAND_NAME: { break; - default: + } + default: { console.log(`Commande [${commandName}] inconnue`); break; + } } } else if (interaction.isModalSubmit()) { const pollToCreate = interaction.components; @@ -130,7 +174,6 @@ client.on(Events.InteractionCreate, async interaction => { await interaction.reply({embeds: [introEmbed, pollEmbed], components: [buttons]}); } else if (interaction.isMessageComponent()) { - console.log(interaction) const userVote = await findUserVoteForPoll(interaction.user.id, interaction.customId.split("_")[0]) if (userVote) { await interaction.reply({ From 6217203967b9b6d1bde228e13d789672240f9adb Mon Sep 17 00:00:00 2001 From: guams Date: Sun, 29 Jun 2025 09:20:40 +0200 Subject: [PATCH 6/8] added bot presence --- src/database.js | 19 ++++++++++++++++++- src/main.js | 36 ++++++++++++++++++++++++++++++++---- 2 files changed, 50 insertions(+), 5 deletions(-) diff --git a/src/database.js b/src/database.js index 391fa6f..aa16211 100644 --- a/src/database.js +++ b/src/database.js @@ -33,6 +33,21 @@ async function findVoteIdByVoteOptionAndPollId(voteOption, pollId) { return response.rows[0]; } +async function findUserPoints(userId, guildId) { + const response = await DATABASE_CLIENT.query( + "SELECT points FROM member WHERE user_id=$1 AND guild_id=$2", + [userId, guildId] + ); + return response.rows[0]; +} + +async function changeNextBetValueForUser(userId, guildId, amount) { + await DATABASE_CLIENT.query( + "UPDATE member SET bet_value=$1 WHERE user_id=$2 AND guild_id=$3", + [amount, userId, guildId] + ); +} + async function findBetValueMultiplierForPoll(pollId) { const response = await DATABASE_CLIENT.query( "SELECT vote_id, (1 + (1 - (SUM(vote_member.bet_value) / total.sum))) AS cote " + @@ -170,4 +185,6 @@ exports.findVoteIdByVoteOptionAndPollId = findVoteIdByVoteOptionAndPollId; exports.findBetValueMultiplierForPoll = findBetValueMultiplierForPoll; exports.findUsersAndBetValueByVoteId = findUsersAndBetValueByVoteId; exports.addPointsToUser = addPointsToUser; -exports.closePoll = closePoll; \ No newline at end of file +exports.closePoll = closePoll; +exports.findUserPoints = findUserPoints; +exports.changeNextBetValueForUser = changeNextBetValueForUser; \ No newline at end of file diff --git a/src/main.js b/src/main.js index 8184866..add7a00 100644 --- a/src/main.js +++ b/src/main.js @@ -21,19 +21,34 @@ const { findUserVoteForPoll, findBetValueForUser, bet, - insertVoteOptions, findVoteIdByVoteOptionAndPollId, findBetValueMultiplierForPoll, findUsersAndBetValueByVoteId, - addPointsToUser, closePoll + insertVoteOptions, + findVoteIdByVoteOptionAndPollId, + findBetValueMultiplierForPoll, + findUsersAndBetValueByVoteId, + addPointsToUser, + closePoll, + findUserPoints, + changeNextBetValueForUser } = require('./database'); const {TOKEN} = require('../config.json'); const {v4: uuidv4} = require('uuid'); const {POLL_COMMAND_NAME, PROFIL_COMMAND_NAME, BET_COMMAND_NAME, CLOSE_COMMAND_NAME} = require("./constants"); -const {ButtonStyle} = require("discord-api-types/v10"); +const {ButtonStyle, ActivityType} = require("discord-api-types/v10"); const {MessageFlags} = require("discord-api-types/v10"); const client = new Client({intents: [GatewayIntentBits.Guilds]}); client.once(Events.ClientReady, async readyClient => { console.log(`Ready! Logged in as ${readyClient.user.tag}`); + client.user.setPresence({ + status: "dnd", + activities: [ + { + name: "son argent flamber", + type: ActivityType.Watching, + } + ] + }) await DATABASE_CLIENT.connect(); }); @@ -112,7 +127,6 @@ client.on(Events.InteractionCreate, async interaction => { } const userFetched = await findUserById(concernedUser.id, interaction.guildId); - const profilEmbed = new EmbedBuilder() .setTitle(`Profil de ${concernedUser.username}`) .setThumbnail(`https://cdn.discordapp.com/avatars/${concernedUser.id}/${concernedUser.avatar}`) @@ -126,6 +140,20 @@ client.on(Events.InteractionCreate, async interaction => { break; } case BET_COMMAND_NAME: { + const amountForNextBet = interaction.options.data[0].value; + const senderPoints = await findUserPoints(interaction.user.id, interaction.guildId); + if (senderPoints >= amountForNextBet) { + await changeNextBetValueForUser(interaction.user.id, interaction.guildId, amountForNextBet); + await interaction.reply({ + flags: MessageFlags.Ephemeral, + content: `${interaction.user.username}, ton prochain pari vaudra **${amountForNextBet}**` + }); + } else { + await interaction.reply({ + flags: MessageFlags.Ephemeral, + content: `${interaction.user.id}, solde insuffisant !` + }); + } break; } default: { From 323b7b3d11bb0a5cde8a01b9e2efbfc9d5abc1ac Mon Sep 17 00:00:00 2001 From: guams Date: Sun, 29 Jun 2025 09:32:04 +0200 Subject: [PATCH 7/8] rebasing --- README.md | 4 +- database.py | 301 ------------------------ enums.py | 57 ----- main.py | 598 ----------------------------------------------- requirements.txt | Bin 264 -> 0 bytes 5 files changed, 1 insertion(+), 959 deletions(-) delete mode 100644 database.py delete mode 100644 enums.py delete mode 100644 main.py delete mode 100644 requirements.txt diff --git a/README.md b/README.md index 697498a..8f1502e 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,4 @@ Readme in progress... ## TODO -- [ ] corriger le problème de seuil -- [ ] changer la bet_value après un vote perdant (on se retrouve parfois avec un bet_value > points) -- [ ] update l'username (quand l'utilisateur en a un nouveau...) pareil pour l'avatar....... +- [ ] update l'username (quand l'utilisateur en a un nouveau) diff --git a/database.py b/database.py deleted file mode 100644 index 680835b..0000000 --- a/database.py +++ /dev/null @@ -1,301 +0,0 @@ -from psycopg2.extensions import connection, cursor - - -async def user_exists(conn, user_id, guild_id): - cur = conn.cursor() - - cur.execute("SELECT user_id FROM member WHERE user_id=%s AND guild_id=%s", (user_id, guild_id)) - result = cur.fetchone() - - cur.close() - - return result is not None - - -async def insert_vote_options(conn, poll_id: str, vote_id: str, vote_option: str): - cur = conn.cursor() - - cur.execute( - "INSERT INTO " - "vote (poll_id, vote_id, vote_option) " - "VALUES (%s, %s, %s)", - (poll_id, vote_id, vote_option) - ) - - conn.commit() - cur.close() - - -async def change_bet(conn: connection, user_id: str, guild_id: str, new_bet_amount: int): - cur: cursor = conn.cursor() - - cur.execute("UPDATE member SET bet_value = %s WHERE user_id=%s AND guild_id=%s", - (new_bet_amount, user_id, guild_id)) - - conn.commit() - cur.close() - - -async def is_last_poll_created_opened(conn: connection, creator: str): - cur: cursor = conn.cursor() - - cur.execute("select opened " - "from poll " - "where creator=%s " - "and opened=true", - (creator,) - ) - result = cur.fetchone() - cur.close() - - if result is not None: - return result[0] - return - - -async def close_poll(conn: connection, poll_id: str): - cur: cursor = conn.cursor() - - cur.execute("UPDATE poll SET opened = false WHERE poll_id=%s", - (poll_id,)) - - conn.commit() - cur.close() - - -async def get_poll_id_opened(conn: connection, creator: str): - cur: cursor = conn.cursor() - - cur.execute("select opened, poll_id, question " - "from poll " - "where creator=%s " - "and opened=true", - (creator,) - ) - result = cur.fetchone() - cur.close() - - return result - - -async def get_user_vote(conn: connection, user_id: str, poll_id: str): - cur: cursor = conn.cursor() - - cur.execute("" - "SELECT vote_id " - "FROM vote_member " - "WHERE user_id=%s " - "AND poll_id=%s", - (user_id, poll_id) - ) - - result = cur.fetchone() - cur.close() - - return result - - -async def get_user_by_id(conn: connection, user_id: str, guild_id: str): - cur: cursor = conn.cursor() - - cur.execute("" - "SELECT username, points, bet_value " - "FROM member " - "WHERE user_id=%s " - "AND guild_id=%s", - (user_id, guild_id) - ) - - result = cur.fetchone() - cur.close() - - return result - - -async def get_user_points(conn: connection, user_id: str, guild_id: str): - cur: cursor = conn.cursor() - - cur.execute("" - "SELECT points " - "FROM member " - "WHERE user_id=%s " - "AND member.guild_id=%s", - (user_id, guild_id) - ) - - result = cur.fetchone() - cur.close() - - if result is not None: - return result[0] - return - - -async def get_bet_value(conn: connection, user_id: str, guild_id: str): - cur: cursor = conn.cursor() - - cur.execute("" - "SELECT bet_value " - "FROM member " - "WHERE user_id=%s " - "AND member.guild_id=%s", - (user_id, guild_id) - ) - - result = cur.fetchone() - cur.close() - - if result is not None: - return result[0] - return - - -async def bet(conn, vote_id: str, poll_id: str, user_id: str, guild_id: str, bet_value: int): - cur = conn.cursor() - - cur.execute( - "INSERT INTO " - "vote_member (user_id, poll_id, guild_id, vote_id, bet_value) " - "VALUES (%s, %s, %s, %s, %s)", - (user_id, poll_id, guild_id, vote_id, bet_value) - ) - - conn.commit() - cur.close() - - -async def get_bet_value_multipliers(conn: connection, poll_id: str): - cur = conn.cursor() - - cur.execute("SELECT vote_id,(1 + (1 - (SUM(vote_member.bet_value) / total.sum))) " - "FROM vote_member " - "CROSS JOIN (SELECT SUM(bet_value) AS sum FROM vote_member WHERE poll_id=%s) AS total " - "WHERE poll_id=%s " - "GROUP BY vote_id, total.sum", - (poll_id, poll_id)) - result = cur.fetchall() - - return result - - -async def get_vote_id_by_vote_option_and_poll_id(conn: connection, vote_option: str, poll_id: str): - cur = conn.cursor() - - cur.execute("SELECT vote_id FROM vote " - "WHERE vote_option=%s " - "AND poll_id=%s", - (vote_option, poll_id)) - result = cur.fetchone() - - if result is not None: - return result[0] - return - - -async def get_users_that_voted(conn: connection, vote_id: str): - cur = conn.cursor() - - cur.execute("select user_id " - "from vote_member " - "where vote_id=%s", - (vote_id,)) - results = cur.fetchall() - - return results - - -async def guild_exists(conn, guild_id): - cur = conn.cursor() - - cur.execute("SELECT guild_id FROM guild WHERE guild_id=%s", (guild_id,)) - result = cur.fetchone() - - cur.close() - - return result is not None - - -async def insert_poll(conn, creator, poll_id, guild_id, question): - cur = conn.cursor() - - cur.execute( - "INSERT INTO " - "poll (creator, poll_id, guild_id, question, opened) " - "VALUES (%s, %s, %s, %s, true)", - (creator, poll_id, guild_id, question) - ) - - conn.commit() - cur.close() - - -async def insert_guild(conn, guild_id): - cur = conn.cursor() - cur.execute( - "INSERT INTO " - "guild (guild_id) " - "VALUES (%s)", - (guild_id,) - ) - - conn.commit() - cur.close() - - -async def insert_user(conn, user_id, guild_id, username): - cur = conn.cursor() - - cur.execute( - "INSERT INTO " - "member (user_id, guild_id, username) " - "VALUES (%s, %s, %s)", - (user_id, guild_id, username) - ) - - conn.commit() - cur.close() - - -async def get_users_and_bet_value_by_vote_id(conn: connection, vote_id: str): - cur = conn.cursor() - - cur.execute("SELECT user_id, bet_value FROM vote_member " - "WHERE vote_id=%s", - (vote_id,) - ) - result = cur.fetchall() - - cur.close() - - return result - - -async def add_user_points(conn: connection, user_id: str, bet_value: int): - cur = conn.cursor() - - cur.execute( - "UPDATE member " - "SET points = points + %s " - "WHERE user_id=%s", - (bet_value, user_id) - ) - - conn.commit() - cur.close() - - -async def minus_user_points(conn: connection, user_id: str, bet_value: int): - try: - cur = conn.cursor() - - cur.execute( - "UPDATE member " - "SET points = points - %s " - "WHERE user_id = %s AND points - %s >= 50", - (bet_value, user_id, bet_value) - ) - - conn.commit() - cur.close() - except Exception: - conn.rollback() diff --git a/enums.py b/enums.py deleted file mode 100644 index bdc720d..0000000 --- a/enums.py +++ /dev/null @@ -1,57 +0,0 @@ -from enum import Enum - - -class OpCode(int, Enum): - DISPATCH = 0 - HEARTBEAT = 1 - IDENTIFY = 2 - PRESENCE_UPDATE = 3 - VOICE_STATE_UPDATE = 4 - RESUME = 6 - RECONNECT = 7 - REQUEST_GUILD_MEMBERS = 8 - INVALID_SESSION = 9 - HELLO = 10 - HEARTBEAT_ACK = 11 - REQUEST_SOUNDBOARD_SOUNDS = 31 - - -class MessageComponentType(str, Enum): - ACTION_ROW = 1 - BUTTON = 2 - STRING_SELECT = 3 - TEXT_INPUT = 4 - USER_SELECT = 5 - ROLE_SELECT = 6 - MENTIONNABLE_SELECT = 7 - CHANNEL_SELECT = 8 - MODAL = 9 - - -class EventTitle(str, Enum): - INTERACTION_CREATE = "INTERACTION_CREATE" - MESSAGE_CREATE = "MESSAGE_CREATE" - GUILD_CREATE = "GUILD_CREATE" - READY = "READY" - - -class InteractionType(int, Enum): - PING = 1 - APPLICATION_COMMAND = 2 - MESSAGE_COMPONENT = 3 - APPLICATION_COMMAND_AUTOCOMPLETE = 4 - MODAL_SUBMIT = 5 - - -class ApplicationCommand(int, Enum): - SUB_COMMAND = 1 - SUB_COMMAND_GROUP = 2 - STRING = 3 - INTEGER = 4 - BOOLEAN = 5 - USER = 6 - CHANNEL = 7 - ROLE = 8 - MENTIONABLE = 9 - NUMBER = 10 - ATTACHMENT = 11 diff --git a/main.py b/main.py deleted file mode 100644 index 23f06c7..0000000 --- a/main.py +++ /dev/null @@ -1,598 +0,0 @@ -import asyncio -import json -from typing import Any -from uuid import uuid4 - -import psycopg2 -import requests -import websockets - -from database import (user_exists, insert_user, guild_exists, insert_guild, - insert_poll, insert_vote_options, bet, get_user_vote, change_bet, - is_last_poll_created_opened, get_poll_id_opened, close_poll, get_bet_value, - get_bet_value_multipliers, get_vote_id_by_vote_option_and_poll_id, - get_users_and_bet_value_by_vote_id, add_user_points, minus_user_points, get_user_points, - get_user_by_id) -from enums import OpCode, EventTitle, InteractionType, ApplicationCommand - -with open('configuration.json', 'r') as file: - CONFIG = json.load(file) - -APPLICATION_ID: int = CONFIG["APPLICATION_ID"] -GATEWAY_URL: str = "wss://gateway.discord.gg/?v=10&encoding=json" -API_URL: str = "https://discord.com/api/v10" -TOKEN: str = CONFIG["TOKEN"] - -try: - conn = psycopg2.connect( - database=CONFIG["DB_NAME"], - user=CONFIG["DB_USER"], - password=CONFIG["DB_PASSWORD"], - host=CONFIG["DB_ADDRESS"], - port=CONFIG["DB_PORT"] - ) -except Exception as e: - raise e - - -async def create_command(): - bodies = [ - { - "name": "poll", - "type": ApplicationCommand.SUB_COMMAND, - "description": "Des votes sur lesquels gamble !", - }, - { - "name": "bet", - "type": ApplicationCommand.SUB_COMMAND, - "description": "Pour pouvoir planifier le prochain pari !", - "options": [ - { - "name": "somme", - "required": True, - "min_value": 0, - "description": "La somme du prochain pari", - "type": ApplicationCommand.INTEGER - } - ] - }, - { - "name": "close", - "type": ApplicationCommand.SUB_COMMAND, - "description": "Pour clôturer un sondage", - "options": [ - { - "name": "option", - "description": "L'issue du sondage", - "required": True, - "type": ApplicationCommand.STRING - } - ] - }, - { - "name": "profil", - "type": ApplicationCommand.SUB_COMMAND, - "description": "Afficher des informations sur un utilisateur", - "options": [ - { - "name": "user", - "description": "La personne concernée", - "required": False, - "type": ApplicationCommand.USER - } - ] - } - ] - - for body in bodies: - requests.post(f"{API_URL}/applications/{APPLICATION_ID}/commands", json=body, - headers={"Authorization": f"Bot {TOKEN}"}) - - -async def init_commands(): - res = requests.get(f"{API_URL}/applications/{APPLICATION_ID}/commands", - headers={"Authorization": f"Bot {TOKEN}"}) - commands = json.loads(res.content) - for command in commands: - response = requests.delete(f"{API_URL}/applications/{APPLICATION_ID}/commands/{command['id']}", - headers={"Authorization": f"Bot {TOKEN}"}) - if response.status_code == 204: - print("Suppression des commandes: Supprimé avec succès !") - else: - print("Suppression des commandes: Une erreur est survenue") - await create_command() - - -async def create_poll(guild_id: str, creator_id: str, poll_created_id: str, interaction_id: str, - interaction_token: str, - components: list[dict]): - question = next( - (comp["components"][0]["value"] for comp in components if comp["components"][0]["custom_id"] == "label"), - "Question du poll" - ) - - vote_options = "" - buttons = [] - vote_text_mem = [] # mémoire pour détecter les doublons - for index, comp in enumerate(components): - if "vote" in comp["components"][0]["custom_id"] and comp["components"][0]["value"] != "": - vote_text = comp["components"][0]["value"] - if vote_text not in vote_text_mem: - vote_text_mem.append(vote_text) - vote_options += f"Option {index}: {vote_text}\n" - buttons.append({ - "type": 2, - "label": vote_text, - "style": 1, - "custom_id": f"{poll_created_id}_{vote_text}_{index}" - }) - - action_rows = [{"type": 1, "components": buttons[i:i + 5]} for i in range(0, len(buttons), 5)] - - body = { - "type": 4, - "data": { - "embeds": [ - { - "title": "Les paris sont ouverts !", - "description": "Vous pouvez parier vos points, ils ne passeront pas en dessous de 50.", - "color": 5613215 - }, - { - "title": f"Sondage: {question}", - "description": vote_options, - "color": 16775222 - } - ], - "components": action_rows - } - } - - res = requests.post( - f"{API_URL}/interactions/{interaction_id}/{interaction_token}/callback", - json=body, - headers={"Authorization": f"Bot {TOKEN}"} - ) - - if res.status_code == 400: - print(res.json()) - else: - await insert_poll(conn=conn, creator=creator_id, poll_id=poll_created_id, guild_id=guild_id, - question=question) - for option in buttons: - await insert_vote_options(conn=conn, poll_id=poll_created_id, vote_id=option["custom_id"], - vote_option=option["label"]) - - -async def identify(websocket): - payload: dict = { - "op": OpCode.IDENTIFY, - "d": { - "token": TOKEN, - "properties": { - "os": "linux", - "browser": "gambling", - "device": "gambling" - }, - "presence": { - "activities": [{ - "name": "son argent flamber", - "type": 3, - }], - "status": "dnd", - "since": 91879201, - "afk": False - }, - "intents": 36871, - } - } - await websocket.send(json.dumps(payload)) - - -async def create_poll_form(interaction_id: str, interaction_token: str, guild_id: str, user_id: str, username: str): - poll_id = uuid4() - if not await guild_exists(conn=conn, guild_id=guild_id): - await insert_guild(conn=conn, guild_id=guild_id) - if not await user_exists(conn=conn, user_id=user_id, guild_id=guild_id): - await insert_user(conn=conn, user_id=user_id, guild_id=guild_id, username=username) - if not await is_last_poll_created_opened(conn=conn, creator=user_id): - body = { - "type": 9, - "data": { - "title": "Créer un vote", - "custom_id": str(poll_id), - "components": [ - { - "type": 1, - "components": [ - { - "type": 4, - "custom_id": "label", - "label": "Question du vote", - "placeholder": "Shy aime-t-il les robots?", - "style": 1, - "min_length": 1, - "required": True - } - ] - }, - { - "type": 1, - "components": [ - { - "type": 4, - "custom_id": "vote1", - "label": "Première option", - "placeholder": "Oui", - "style": 1, - "min_length": 1, - "required": True - } - ] - }, - { - "type": 1, - "components": [ - { - "type": 4, - "custom_id": "vote2", - "label": "Deuxième option", - "placeholder": "Non", - "style": 1, - "min_length": 1, - "required": False - } - ] - }, - { - "type": 1, - "components": [ - { - "type": 4, - "custom_id": "vote3", - "label": "Troisième option", - "placeholder": "Peut-être", - "style": 1, - "min_length": 1, - "required": False - } - ] - }, - { - "type": 1, - "components": [ - { - "type": 4, - "custom_id": "vote4", - "label": "Quatrième option", - "placeholder": "Pas du tout", - "style": 1, - "min_length": 1, - "required": False - } - ] - } - ] - } - } - else: - body = { - "type": 4, - "data": { - "content": f"<@{user_id}>, il faut d'abord clôturer ton sondage avant de pouvoir en créer un nouveau. " - f"(commande: **/close**)", - "flags": 64 - } - } - res = requests.post(f"{API_URL}/interactions/{interaction_id}/{interaction_token}/callback", json=body, - headers={"Authorization": f"Bot {TOKEN}"}) - if res.status_code == 400: - print(res.json()) - else: - print(res) - - -async def heartbeat(websocket, interval): - while True: - await asyncio.sleep(interval / 1000) - try: - await websocket.send(json.dumps({"op": OpCode.HEARTBEAT, "d": None})) - except websockets.exceptions.ConnectionClosed: - print("WebSocket fermé") - break - except Exception as e: - print(f"Erreur lors du heartbeat : {e}") - break - - -async def vote_confirmation(interaction_id: str, interaction_token: str, custom_id: str, user_id: str, - guild_id: str, username: str): - user_vote = await get_user_vote(conn=conn, user_id=user_id, poll_id=custom_id.split("_")[0]) - if user_vote is not None: - body = { - "type": 4, - "data": { - "content": f"<@{user_id}>, tu as déjà voté pour **{user_vote[0].split('_')[1]}**", - "flags": 64 - } - } - else: - if not await user_exists(conn=conn, user_id=user_id, guild_id=guild_id): - if not await guild_exists(conn=conn, guild_id=guild_id): - await insert_guild(conn=conn, guild_id=guild_id) - await insert_user(conn=conn, user_id=user_id, guild_id=guild_id, username=username) - bet_value = await get_bet_value(conn=conn, user_id=user_id, guild_id=guild_id) - await bet(conn=conn, guild_id=guild_id, user_id=user_id, vote_id=custom_id, - poll_id=custom_id.split("_")[0], bet_value=bet_value) - await minus_user_points(conn=conn, user_id=user_id, bet_value=bet_value) - body = { - "type": 4, - "data": { - "content": f"<@{user_id}>, tu as misé {bet_value} sur l'option **{custom_id.split('_')[1]}** ! 🎉", - "flags": 64 - } - } - - res = requests.post( - f"{API_URL}/interactions/{interaction_id}/{interaction_token}/callback", - json=body, - headers={"Authorization": f"Bot {TOKEN}", "Content-Type": "application/json"} - ) - if res.status_code == 400: - print(res.json()) - else: - print(res) - - -async def close_poll_cmd(guild_id: str, user_id: str, interaction_id: str, issue: str, interaction_token: str, - username: str): - if not await guild_exists(conn=conn, guild_id=guild_id): - await insert_guild(conn=conn, guild_id=guild_id) - if not await user_exists(conn=conn, user_id=user_id, guild_id=guild_id): - await insert_user(conn=conn, user_id=user_id, guild_id=guild_id, username=username) - poll: tuple = await get_poll_id_opened(conn=conn, creator=user_id) - if poll is not None: - winner_id = await get_vote_id_by_vote_option_and_poll_id(conn=conn, vote_option=issue, poll_id=poll[1]) - if winner_id is not None: - winner_cote = None - values = await get_bet_value_multipliers(conn=conn, poll_id=poll[1]) - for value in values: - vote_id = value[0] - users_with_bet_values = await get_users_and_bet_value_by_vote_id(conn=conn, vote_id=vote_id) - if vote_id == winner_id: - winner_cote = value[1] - for user_with_bet_value in users_with_bet_values: - print(int(user_with_bet_value[1] * winner_cote)) - await add_user_points(conn=conn, - user_id=user_with_bet_value[0], - bet_value=int(user_with_bet_value[1] * winner_cote)) - await close_poll(conn=conn, poll_id=poll[1]) - if winner_cote is not None: - out = (round(winner_cote, 2) - 1) * 100 - else: - out = 0.0 - body = { - "type": 4, - "data": { - "content": f'@everyone le sondage **{poll[2]}** est terminé ! L\'issue gagnante est **{issue}** ' - f'avec une côte de {out}%' - } - } - else: - body = { - "type": 4, - "data": { - "content": f"<@{user_id}>, L'option **{issue}** n'existe pas.", - "flags": 64 - } - } - else: - body = { - "type": 4, - "data": { - "content": f"<@{user_id}>, tu n'as actuellement aucun sondage en cours.", - "flags": 64 - } - } - res = requests.post(f"{API_URL}/interactions/{interaction_id}/{interaction_token}/callback", json=body, - headers={"Authorization": f"Bot {TOKEN}"}) - if res.status_code == 400: - print(res.json()) - else: - print(res) - - -async def get_event(response: Any): - match response["t"]: - # case EventTitle.MESSAGE_CREATE: - case EventTitle.INTERACTION_CREATE: - if response["d"]["type"] == InteractionType.MODAL_SUBMIT: - await create_poll( - guild_id=response["d"]["guild"]["id"], - creator_id=response["d"]["member"]["user"]["id"], - poll_created_id=response["d"]["data"]["custom_id"], - interaction_id=response["d"]["id"], - interaction_token=response["d"]["token"], - components=response["d"]["data"]["components"] - ) - elif response["d"]["type"] == InteractionType.MESSAGE_COMPONENT: - custom_id = response["d"]["data"]["custom_id"] - user_id = response["d"]["member"]["user"]["id"] - await vote_confirmation(interaction_id=response["d"]["id"], - interaction_token=response["d"]["token"], - custom_id=custom_id, - user_id=user_id, - guild_id=response["d"]["guild"]["id"], - username=response["d"]["member"]["user"]["username"]) - elif (response["d"]["type"] == InteractionType.APPLICATION_COMMAND and - response["d"]["data"]["name"] == 'poll'): - await create_poll_form(interaction_id=response["d"]["id"], - user_id=response["d"]["member"]["user"]["id"], - interaction_token=response["d"]["token"], - guild_id=response["d"]["guild_id"], - username=response["d"]["member"]["user"]["username"]) - elif (response["d"]["type"] == InteractionType.APPLICATION_COMMAND and - response["d"]["data"]["name"] == 'close'): - await close_poll_cmd(guild_id=response["d"]["guild_id"], - user_id=response["d"]["member"]["user"]["id"], - interaction_id=response["d"]["id"], - interaction_token=response["d"]["token"], - issue=response["d"]["data"]["options"][0]["value"], - username=response["d"]["member"]["user"]["username"]) - elif (response["d"]["type"] == InteractionType.APPLICATION_COMMAND and - response["d"]["data"]["name"] == 'bet'): - command_option: int = response["d"]["data"]["options"][0]["value"] - await change_bet_cmd(guild_id=response["d"]["guild_id"], - command_option=command_option, - user_id=response["d"]["member"]["user"]["id"], - username=response["d"]["member"]["user"]["username"], - interaction_id=response["d"]["id"], - interaction_token=response["d"]["token"]) - elif (response["d"]["type"] == InteractionType.APPLICATION_COMMAND and - response["d"]["data"]["name"] == 'profil'): - command_option: str = '' - user_avatar: str = '' - username: str = '' - try: - command_option = response["d"]["data"]["options"][0]["value"] - user_avatar = response["d"]["data"]["resolved"]["users"][str(command_option)]["avatar"] - username = response["d"]["data"]["resolved"]["users"][str(command_option)]["username"] - except Exception as e: - pass - if command_option == '': - command_option = response["d"]["member"]["user"]["id"] - if user_avatar == '': - user_avatar = response["d"]["member"]["user"]["avatar"] - username = response["d"]["member"]["user"]["username"] - await show_user_profile(guild_id=response["d"]["guild_id"], - interaction_id=response["d"]["id"], - interaction_token=response["d"]["token"], - avatar=user_avatar, - concerned_user_id=command_option, - username=username) - - case _: - print(response) - - -async def show_user_profile(guild_id: str, interaction_id: str, interaction_token: str, - concerned_user_id: str, avatar: str, username: str): - if not await guild_exists(conn=conn, guild_id=guild_id): - await insert_guild(conn=conn, guild_id=guild_id) - if not await user_exists(conn=conn, user_id=concerned_user_id, guild_id=guild_id): - await insert_user(conn=conn, user_id=concerned_user_id, guild_id=guild_id, username=username) - - concerned_user = await get_user_by_id(conn=conn, user_id=concerned_user_id, guild_id=guild_id) - body = { - "type": 4, - "data": { - "embeds": [ - { - "title": f"Profil de {concerned_user[0]}", - "description": f"Solde : {concerned_user[1]}\n" - f"Valeur du prochain pari: {concerned_user[2]}", - "image": { - "url": f"https://cdn.discordapp.com/avatars/{concerned_user_id}/{avatar}" - }, - "color": 11141375 - } - ], - } - } - - res = requests.post(f"{API_URL}/interactions/{interaction_id}/{interaction_token}/callback", json=body, - headers={"Authorization": f"Bot {TOKEN}"}) - if res.status_code == 400: - print(res.json()) - else: - print(res) - - -async def change_bet_cmd(guild_id: str, command_option: int, user_id: str, username: str, - interaction_id: str, interaction_token: str): - if not await guild_exists(conn=conn, guild_id=guild_id): - await insert_user(conn=conn, guild_id=guild_id, user_id=user_id, username=username) - if not await user_exists(conn=conn, user_id=user_id, guild_id=guild_id): - await insert_guild(conn=conn, guild_id=guild_id) - - if await get_user_points(conn=conn, user_id=user_id, guild_id=guild_id) >= command_option: - await change_bet(conn=conn, guild_id=guild_id, user_id=user_id, new_bet_amount=command_option) - body = { - "type": 4, - "data": { - "content": f"<@{user_id}>, ton prochain pari vaudra **{command_option}**.", - "flags": 64 - } - } - else: - body = { - "type": 4, - "data": { - "content": f"<@{user_id}>, solde insuffisant !", - "flags": 64 - } - } - - res = requests.post(f"{API_URL}/interactions/{interaction_id}/{interaction_token}/callback", json=body, - headers={"Authorization": f"Bot {TOKEN}"}) - if res.status_code == 400: - print(res.json()) - else: - print(res) - - -async def connect(): - while True: - try: - async with websockets.connect(GATEWAY_URL) as websocket: - response = json.loads(await websocket.recv()) - heartbeat_interval = response["d"]["heartbeat_interval"] - - heartbeat_task = asyncio.create_task(heartbeat(websocket, heartbeat_interval)) - await identify(websocket) - - while True: - try: - response = json.loads(await websocket.recv()) - op = response.get("op") - - match op: - case OpCode.DISPATCH: - await get_event(response) - case OpCode.RECONNECT: - print("Reconnexion demandée...") - break - case OpCode.INVALID_SESSION: - print("Session invalide, réidentification...") - await asyncio.sleep(1) - await identify(websocket) - case _: - pass - - except websockets.exceptions.ConnectionClosed as e: - print(f"Connexion fermée : {e}") - break - except Exception as e: - print(f"Erreur dans le loop principal : {e}") - break - - heartbeat_task.cancel() - try: - await heartbeat_task - except asyncio.CancelledError: - pass - - except Exception as e: - print(f"Erreur de connexion WebSocket : {e}") - - print("Tentative de reconnexion dans 5 secondes...") - await asyncio.sleep(5) - - -async def main(): - gateway_connect = asyncio.create_task(connect()) - # await init_commands() - await gateway_connect - - -asyncio.run(main()) diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 36f90d0914f3e7c5fa84f79eded80c4df3bb7810..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 264 zcmYk1+YW*-5JczM#7~h>s}DZ-F@QHBic*Ntk5|tw!I);7b~|%s`*~N|t5dI92Wrb& zDyP=U6sX8P*G!d8Do`f$y%xHv<#wUh;Cng6+!4>pz2>U5Ls>Z2UIJ|E-{3r9ww#Wn zjk%dNf0_>FT?2a}G5s_Vz0GBuff*W~prkWVENRSn;4bmDWSu!roZ$b Date: Sun, 29 Jun 2025 09:38:06 +0200 Subject: [PATCH 8/8] bux fix --- src/main.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main.js b/src/main.js index add7a00..8742534 100644 --- a/src/main.js +++ b/src/main.js @@ -74,7 +74,7 @@ client.on(Events.InteractionCreate, async interaction => { if (!actualOpenedPollCreatedByUser) { await interaction.reply({ flags: MessageFlags.Ephemeral, - content: `<@${interaction.user.id}>, tu n'as actuellement aucun sondage en cours.` + content: `${interaction.user.username}, tu n'as actuellement aucun sondage en cours.` }); break; } @@ -85,7 +85,7 @@ client.on(Events.InteractionCreate, async interaction => { if (!winnerVoteId) { await interaction.reply({ flags: MessageFlags.Ephemeral, - content: `<@${interaction.user.id}>, L'option **${optionSendedByUser}** n'existe pas.` + content: `${interaction.user.username}, L'option **${optionSendedByUser}** n'existe pas.` }); break; } @@ -142,7 +142,7 @@ client.on(Events.InteractionCreate, async interaction => { case BET_COMMAND_NAME: { const amountForNextBet = interaction.options.data[0].value; const senderPoints = await findUserPoints(interaction.user.id, interaction.guildId); - if (senderPoints >= amountForNextBet) { + if (Number.parseInt(senderPoints.points) >= amountForNextBet) { await changeNextBetValueForUser(interaction.user.id, interaction.guildId, amountForNextBet); await interaction.reply({ flags: MessageFlags.Ephemeral, @@ -151,7 +151,7 @@ client.on(Events.InteractionCreate, async interaction => { } else { await interaction.reply({ flags: MessageFlags.Ephemeral, - content: `${interaction.user.id}, solde insuffisant !` + content: `${interaction.user.username}, solde insuffisant !` }); } break;