From 85800e3c8f1c231f9b62556f1cfc4722330a4dc2 Mon Sep 17 00:00:00 2001 From: UltraLionFr Date: Sat, 19 Jul 2025 05:01:14 +0200 Subject: [PATCH] Initial commit of karuta-starboard bot --- .gitignore | 4 +++ SlashCommands/setemoji.js | 30 ++++++++++++++++ SlashCommands/setstarboard.js | 30 ++++++++++++++++ config.yml | 12 +++++++ deploy-commands.js | 23 +++++++++++++ events/interactionCreate.js | 19 ++++++++++ events/messageReactionAdd.js | 46 +++++++++++++++++++++++++ events/ready.js | 20 +++++++++++ index.js | 38 ++++++++++++++++++++ package.json | 22 ++++++++++++ update-avatar.js | 26 ++++++++++++++ utils/configManager.js | 65 +++++++++++++++++++++++++++++++++++ utils/dbManager.js | 36 +++++++++++++++++++ 13 files changed, 371 insertions(+) create mode 100644 .gitignore create mode 100644 SlashCommands/setemoji.js create mode 100644 SlashCommands/setstarboard.js create mode 100644 config.yml create mode 100644 deploy-commands.js create mode 100644 events/interactionCreate.js create mode 100644 events/messageReactionAdd.js create mode 100644 events/ready.js create mode 100644 index.js create mode 100644 package.json create mode 100644 update-avatar.js create mode 100644 utils/configManager.js create mode 100644 utils/dbManager.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..186e6c4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +package-lock.json +.env +db.json \ No newline at end of file diff --git a/SlashCommands/setemoji.js b/SlashCommands/setemoji.js new file mode 100644 index 0000000..a474691 --- /dev/null +++ b/SlashCommands/setemoji.js @@ -0,0 +1,30 @@ +const { SlashCommandBuilder, PermissionFlagsBits } = require('discord.js'); +const { getMessages } = require('../utils/configManager'); +const { setGuildData } = require('../utils/dbManager'); + +module.exports = { + data: new SlashCommandBuilder() + .setName('setemoji') + .setDescription('Set the reaction emoji') + .addStringOption(option => + option.setName('emoji') + .setDescription('Emoji (ex: ⭐ or <:name:id> or )') + .setRequired(true)) + .setDefaultMemberPermissions(PermissionFlagsBits.Administrator), + + async execute(interaction) { + const emoji = interaction.options.getString('emoji'); + setGuildData(interaction.guildId, { emoji }); + + const messages = getMessages(); + if (!messages.emoji_set_success) { + return interaction.reply({ + content: '❌ Missing `emoji_set_success` message in config.yml.', + ephemeral: true + }); + } + + const reply = messages.emoji_set_success.replace('{emoji}', emoji); + await interaction.reply({ content: reply, ephemeral: true }); + } +}; diff --git a/SlashCommands/setstarboard.js b/SlashCommands/setstarboard.js new file mode 100644 index 0000000..fd08aaa --- /dev/null +++ b/SlashCommands/setstarboard.js @@ -0,0 +1,30 @@ +const { SlashCommandBuilder, PermissionFlagsBits } = require('discord.js'); +const { getMessages } = require('../utils/configManager'); +const { setGuildData } = require('../utils/dbManager'); + +module.exports = { + data: new SlashCommandBuilder() + .setName('setstarboard') + .setDescription('Set up the highlight channel') + .addChannelOption(option => + option.setName('channel') + .setDescription('Select the channel for highlighted messages') + .setRequired(true)) + .setDefaultMemberPermissions(PermissionFlagsBits.ManageGuild), + + async execute(interaction) { + const channel = interaction.options.getChannel('channel'); + setGuildData(interaction.guildId, { starboard: channel.id }); + + const messages = getMessages(); + if (!messages.starboard_set_success) { + return interaction.reply({ + content: '❌ Missing `starboard_set_success` message in config.yml.', + ephemeral: true + }); + } + + const reply = messages.starboard_set_success.replace('{channel}', `<#${channel.id}>`); + await interaction.reply({ content: reply, ephemeral: true }); + } +}; diff --git a/config.yml b/config.yml new file mode 100644 index 0000000..613d43b --- /dev/null +++ b/config.yml @@ -0,0 +1,12 @@ +messages: + error: "❌ An error occurred while executing the command." + emoji_set_success: "Emoji set: {emoji}" + starboard_set_success: "Channel set: {channel}" + embed_jump_text: "Click to jump to message!" + +appearance: + embed_color: "#ff5733" + +activity: + name: "Made by UltraLion" + type: "WATCHING" # PLAYING, LISTENING, COMPETING, WATCHING \ No newline at end of file diff --git a/deploy-commands.js b/deploy-commands.js new file mode 100644 index 0000000..ca8550d --- /dev/null +++ b/deploy-commands.js @@ -0,0 +1,23 @@ +const { REST, Routes } = require('discord.js'); +const fs = require('fs'); +require('dotenv').config(); + +const commands = []; +const commandFiles = fs.readdirSync('./SlashCommands').filter(file => file.endsWith('.js')); + +for (const file of commandFiles) { + const command = require(`./SlashCommands/${file}`); + commands.push(command.data.toJSON()); +} + +const rest = new REST({ version: '10' }).setToken(process.env.TOKEN); + +(async () => { + try { + console.log('⏳ Deploying slash commands...'); + await rest.put(Routes.applicationCommands(process.env.CLIENT_ID), { body: commands }); + console.log('✅ Commands deployed successfully.'); + } catch (error) { + console.error(error); + } +})(); diff --git a/events/interactionCreate.js b/events/interactionCreate.js new file mode 100644 index 0000000..3e91aad --- /dev/null +++ b/events/interactionCreate.js @@ -0,0 +1,19 @@ +module.exports = { + name: 'interactionCreate', + async execute(interaction) { + if (!interaction.isChatInputCommand()) return; + + const command = interaction.client.commands.get(interaction.commandName); + if (!command) return; + + try { + await command.execute(interaction); + } catch (error) { + console.error(error); + await interaction.reply({ + content: '❌ Error executing command.', + ephemeral: true + }); + } + } +}; diff --git a/events/messageReactionAdd.js b/events/messageReactionAdd.js new file mode 100644 index 0000000..8646955 --- /dev/null +++ b/events/messageReactionAdd.js @@ -0,0 +1,46 @@ +const { EmbedBuilder } = require('discord.js'); +const { getGuildData } = require('../utils/dbManager'); +const { getMessages, getAppearance } = require('../utils/configManager'); + +module.exports = { + name: 'messageReactionAdd', + async execute(reaction, user) { + if (user.bot) return; + const message = reaction.message.partial ? await reaction.message.fetch() : reaction.message; + + const guildConfig = getGuildData(message.guildId); + if (!guildConfig || !guildConfig.emoji || !guildConfig.starboard) return; + + const usedEmoji = reaction.emoji.toString(); + if (usedEmoji !== guildConfig.emoji) return; + + if (!message.author?.bot || !message.author.username.includes('Karuta')) return; + + const channel = await reaction.client.channels.fetch(guildConfig.starboard); + + const messages = getMessages(); + const appearance = getAppearance(); + + if (!messages.embed_jump_text || !appearance.embed_color) { + console.warn('⚠️ Missing config: embed_jump_text or embed_color'); + return; + } + + const jumpLink = `https://discord.com/channels/${message.guildId}/${message.channelId}/${message.id}`; + const jumpText = messages.embed_jump_text; + const embedColor = parseInt(appearance.embed_color.replace('#', ''), 16); + + const embed = new EmbedBuilder() + .setDescription(`${message.content}\n\n[${jumpText}](${jumpLink})`) + .setColor(embedColor) + .setTimestamp(); + + const attachment = message.attachments.find(att => + att.contentType?.startsWith('image/') || att.contentType?.startsWith('video/') + ); + + if (attachment) embed.setImage(attachment.url); + + await channel.send({ embeds: [embed] }); + } +}; diff --git a/events/ready.js b/events/ready.js new file mode 100644 index 0000000..abf98de --- /dev/null +++ b/events/ready.js @@ -0,0 +1,20 @@ +const { ActivityType } = require('discord.js'); +const { getActivity } = require('../utils/configManager'); + +module.exports = { + name: 'ready', + once: true, + execute(client) { + console.log(`✅ Logged in as ${client.user.tag}`); + + const activity = getActivity(); + if (!activity.name || !activity.type) { + console.warn('⚠️ Missing configuration for bot status in config.yml.'); + return; + } + + client.user.setActivity(activity.name, { + type: ActivityType[activity.type.toUpperCase()] + }); + } +}; diff --git a/index.js b/index.js new file mode 100644 index 0000000..4ff2651 --- /dev/null +++ b/index.js @@ -0,0 +1,38 @@ +const { Client, GatewayIntentBits, Collection, Partials } = require('discord.js'); +const fs = require('fs'); +const path = require('path'); +require('dotenv').config(); + +const client = new Client({ + intents: [ + GatewayIntentBits.Guilds, + GatewayIntentBits.GuildMessages, + GatewayIntentBits.MessageContent, + GatewayIntentBits.GuildMessageReactions + ], + partials: [Partials.Message, Partials.Channel, Partials.Reaction] +}); + +client.commands = new Collection(); + +// Load commands +const commandsPath = path.join(__dirname, 'SlashCommands'); +const commandFiles = fs.readdirSync(commandsPath).filter(file => file.endsWith('.js')); +for (const file of commandFiles) { + const command = require(path.join(commandsPath, file)); + client.commands.set(command.data.name, command); +} + +// Load events +const eventsPath = path.join(__dirname, 'events'); +const eventFiles = fs.readdirSync(eventsPath).filter(file => file.endsWith('.js')); +for (const file of eventFiles) { + const event = require(path.join(eventsPath, file)); + if (event.once) { + client.once(event.name, (...args) => event.execute(...args, client)); + } else { + client.on(event.name, (...args) => event.execute(...args, client)); + } +} + +client.login(process.env.TOKEN); \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..475ec35 --- /dev/null +++ b/package.json @@ -0,0 +1,22 @@ +{ + "dependencies": { + "@discordjs/rest": "^2.5.1", + "csv-parser": "^3.2.0", + "discord-api-types": "^0.38.13", + "discord.js": "^14.20.0", + "dotenv": "^16.5.0", + "fs": "^0.0.1-security", + "node-fetch": "^3.3.2", + "yaml": "^2.8.0" + }, + "name": "dabi", + "version": "1.0.0", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "description": "" +} diff --git a/update-avatar.js b/update-avatar.js new file mode 100644 index 0000000..9a94034 --- /dev/null +++ b/update-avatar.js @@ -0,0 +1,26 @@ +require('dotenv').config(); +const { Client, GatewayIntentBits } = require('discord.js'); +const fs = require('fs'); +const path = require('path'); +const client = new Client({ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages, GatewayIntentBits.MessageContent] }); + + +const TOKEN = process.env.TOKEN; +const AVATAR_PATH = path.join(__dirname, 'assets', 'avatar.gif'); + +client.once('ready', async () => { + + try { + // Read GIF file + const avatar = fs.readFileSync(AVATAR_PATH); + + // Set the avatar + await client.user.setAvatar(avatar); + console.log('Bot avatar successfully updated!'); + } catch (error) { + console.error('Erreur de Discord :', error.response?.data || error); + console.error('Error updating bot avatar:', error); + } +}); + +client.login(TOKEN); \ No newline at end of file diff --git a/utils/configManager.js b/utils/configManager.js new file mode 100644 index 0000000..b4fa314 --- /dev/null +++ b/utils/configManager.js @@ -0,0 +1,65 @@ +const fs = require('fs'); +const YAML = require('yaml'); + +const configPath = './config.yml'; + +function readConfig() { + try { + const file = fs.readFileSync(configPath, 'utf8'); + return YAML.parse(file) || {}; + } catch (error) { + console.error('Error reading config.yml file:', error); + return {}; + } +} + +function writeConfig(config) { + try { + const yamlString = YAML.stringify(config); + fs.writeFileSync(configPath, yamlString, 'utf8'); + } catch (error) { + console.error('Error writing to config.yml:', error); + } +} + +function getMessages() { + const config = readConfig(); + return config.messages || {}; +} + +function getGuildConfig(guildId) { + const config = readConfig(); + if (!config.guilds) config.guilds = {}; + if (!config.guilds[guildId]) config.guilds[guildId] = {}; + return config.guilds[guildId]; +} + +function setGuildConfig(guildId, newData) { + const config = readConfig(); + if (!config.guilds) config.guilds = {}; + config.guilds[guildId] = { + ...config.guilds[guildId], + ...newData + }; + writeConfig(config); +} + +function getAppearance() { + const config = readConfig(); + return config.appearance || {}; +} + +function getActivity() { + const config = readConfig(); + return config.activity || {}; +} + +module.exports = { + readConfig, + writeConfig, + getMessages, + getGuildConfig, + setGuildConfig, + getAppearance, + getActivity +}; diff --git a/utils/dbManager.js b/utils/dbManager.js new file mode 100644 index 0000000..85e9660 --- /dev/null +++ b/utils/dbManager.js @@ -0,0 +1,36 @@ +const fs = require('fs'); + +const dbPath = './db.json'; + +function readDB() { + try { + const data = fs.readFileSync(dbPath, 'utf8'); + return JSON.parse(data) || {}; + } catch { + return {}; + } +} + +function writeDB(data) { + fs.writeFileSync(dbPath, JSON.stringify(data, null, 2)); +} + +function getGuildData(guildId) { + const db = readDB(); + if (!db[guildId]) db[guildId] = {}; + return db[guildId]; +} + +function setGuildData(guildId, newData) { + const db = readDB(); + db[guildId] = { + ...db[guildId], + ...newData + }; + writeDB(db); +} + +module.exports = { + getGuildData, + setGuildData +};