commit f455121741c66925ba4875d317403a9c195dec61 Author: guams Date: Sat Jun 28 16:13:36 2025 +0200 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d9b79bd --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +node_modules +package-lock.json +config.json +.idea \ No newline at end of file diff --git a/database.sql b/database.sql new file mode 100644 index 0000000..f5d8435 --- /dev/null +++ b/database.sql @@ -0,0 +1,55 @@ +DROP TABLE IF EXISTS vote_member; +DROP TABLE IF EXISTS member; +DROP TABLE IF EXISTS vote; +DROP TABLE IF EXISTS poll; +DROP TABLE IF EXISTS guild; + +CREATE TABLE guild +( + guild_id VARCHAR(255), + PRIMARY KEY (guild_id) +); + +CREATE TABLE poll +( + poll_id UUID, + creator VARCHAR(255), + guild_id VARCHAR(255), + question VARCHAR(255), + opened BOOLEAN, + PRIMARY KEY (poll_id), + FOREIGN KEY (guild_id) REFERENCES guild (guild_id) +); + +CREATE TABLE vote +( + poll_id UUID, + vote_id VARCHAR(255), + vote_option VARCHAR(255), + PRIMARY KEY (vote_id), + FOREIGN KEY (poll_id) REFERENCES poll (poll_id) +); + +CREATE TABLE member +( + user_id VARCHAR(255), + guild_id VARCHAR(255), + username VARCHAR(255), + 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) +); + +CREATE TABLE vote_member +( + user_id VARCHAR(255), + guild_id VARCHAR(255), + vote_id VARCHAR(255), + poll_id UUID, + bet_value bigint DEFAULT 50, + FOREIGN KEY (guild_id) REFERENCES guild (guild_id), + FOREIGN KEY (user_id) REFERENCES member (user_id), + FOREIGN KEY (vote_id) REFERENCES vote (vote_id), + FOREIGN KEY (poll_id) REFERENCES poll (poll_id) +); \ No newline at end of file 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);