diff --git a/README.md b/README.md index 7cb1b45..a37f3da 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,3 @@ ## Bot invite -link [here](https://discord.com/api/oauth2/authorize?client_id=1203887026282438686&permissions=8&scope=applications.commands+bot). +link [here](https://discord.com/api/oauth2/authorize?client_id=1203887026282438686&permissions=8&scope=applications.commands+bot). diff --git a/commands/admin/ctx_add_self_roles.js b/commands/admin/ctx_add_self_roles.js index 5b6c512..bce7e65 100644 --- a/commands/admin/ctx_add_self_roles.js +++ b/commands/admin/ctx_add_self_roles.js @@ -24,7 +24,7 @@ export async function modalSubmit(interaction) { if (role === null) { await interaction.reply({ content: 'Could not fetch role! Please contact server staff.', - ephemeral: true, + ephemeral: true }); return; } @@ -43,21 +43,24 @@ export async function execute(interaction) { .setStyle(TextInputStyle.Short) .setCustomId('message') .setRequired(true) - .setValue(id)); + .setValue(id) + ); const role = new ActionRowBuilder().addComponents( new TextInputBuilder() .setLabel('Enter exactly one role ID.') .setStyle(TextInputStyle.Short) .setCustomId('role') - .setRequired(true)); + .setRequired(true) + ); const emoji = new ActionRowBuilder().addComponents( new TextInputBuilder() .setLabel('Enter exactly one emoji.') .setStyle(TextInputStyle.Short) .setCustomId('emoji') - .setRequired(true)); + .setRequired(true) + ); modal.addComponents(message, role, emoji); diff --git a/commands/admin/ctx_register_self_roles.js b/commands/admin/ctx_register_self_roles.js index 041ebc1..485216b 100644 --- a/commands/admin/ctx_register_self_roles.js +++ b/commands/admin/ctx_register_self_roles.js @@ -15,7 +15,7 @@ export async function execute(interaction) { // Reply successfully to acknowledge command await interaction.reply({ content: `Successfully saved data from message! Add roles to it with reference ID '${id}'.`, - ephemeral: true, + ephemeral: true }); console.info(`[INFO] New self roles on message with ID: '${id}'.`); @@ -24,7 +24,7 @@ export async function execute(interaction) { // Reply failed to acknowledge command await interaction.reply({ content: 'Failed to save data from message!', - ephemeral: true, + ephemeral: true }); } } diff --git a/commands/admin/slash_self_roles.js b/commands/admin/slash_self_roles.js index f9b2751..1abf70d 100644 --- a/commands/admin/slash_self_roles.js +++ b/commands/admin/slash_self_roles.js @@ -1,6 +1,5 @@ -import { Op } from 'sequelize'; import { SlashCommandBuilder } from 'discord.js'; -import { Message, RoleEmojiPair } from './../../database.js'; +import { Message } from './../../database.js'; import { addSelfRoles } from '../../shared.js'; const createSelfRoles = async (interaction) => { @@ -22,7 +21,7 @@ const registerSelfRoles = async (interaction) => { const id = options.getString('id'); const response = { success: false, - msgID: null, + msgID: null }; try { @@ -32,7 +31,7 @@ const registerSelfRoles = async (interaction) => { // Reply successfully to acknowledge command await interaction.reply({ content: 'Successfully fetched message!', - ephemeral: true, + ephemeral: true }); response.success = true; @@ -45,7 +44,7 @@ const registerSelfRoles = async (interaction) => { // Reply failed to acknowledge command await interaction.reply({ content: 'Failed to fetch message!', - ephemeral: true, + ephemeral: true }); } return response; @@ -55,60 +54,64 @@ export const data = new SlashCommandBuilder() .setName('self_roles') .setDMPermission(false) .setDescription('Manages reactions for self roles.') - .addSubcommand(subcommand => + .addSubcommand((subcommand) => subcommand .setName('create') .setDescription('Creates new message in channel.') - .addStringOption(option => + .addStringOption((option) => option .setName('text') .setRequired(true) - .setDescription('The text to be displayed in the message.'))) - .addSubcommand(subcommand => + .setDescription('The text to be displayed in the message.') + ) + ) + .addSubcommand((subcommand) => subcommand .setName('register') .setDescription('Registers an existing message.') - .addStringOption(option => + .addStringOption((option) => option .setName('id') .setRequired(true) - .setDescription('The ID to reference the message to be used.'))) - .addSubcommand(subcommand => + .setDescription('The ID to reference the message to be used.') + ) + ) + .addSubcommand((subcommand) => subcommand .setName('add') .setDescription('Add a role-emoji-pair to a message.') - .addStringOption(option => + .addStringOption((option) => option .setName('id') .setRequired(true) - .setDescription('The ID to reference the message to be used.')) - .addRoleOption(option => - option - .setName('role') - .setRequired(true) - .setDescription('The role be assigned to.')) - .addStringOption(option => - option - .setName('emoji') - .setRequired(true) - .setDescription('The emoji to be reacted with.'))); + .setDescription('The ID to reference the message to be used.') + ) + .addRoleOption((option) => + option.setName('role').setRequired(true).setDescription('The role be assigned to.') + ) + .addStringOption((option) => + option.setName('emoji').setRequired(true).setDescription('The emoji to be reacted with.') + ) + ); export async function execute(interaction) { const { options } = interaction; - let createNew = false, id; + let createNew = false, + id; switch (options.getSubcommand()) { case 'create': id = await createSelfRoles(interaction); // Flag to create new database entry createNew = true; break; - case 'register': + case 'register': { const response = await registerSelfRoles(interaction); id = response.msgID ?? id; // Flag to create new database entry createNew = response.success; break; - case 'add': + } + case 'add': { // Get command options const msgID = options.getString('id'); const role = options.getRole('role'); @@ -116,6 +119,7 @@ export async function execute(interaction) { // Try adding self role pair await addSelfRoles(interaction, msgID, role, emoji); break; + } } if (createNew) { @@ -128,7 +132,7 @@ export async function execute(interaction) { // Reply failed to acknowledge command await interaction.followUp({ content: 'Failed to save data from message!', - ephemeral: true, + ephemeral: true }); } diff --git a/database.js b/database.js index 0c39ee5..0bcbc0e 100644 --- a/database.js +++ b/database.js @@ -1,20 +1,20 @@ -import defineRoleEmojiPair from './models/roleEmojiPairs.js'; -import defineVoiceChannel from './models/voiceChannels.js'; -import defineMessage from './models/messages.js'; -import { Sequelize } from 'sequelize'; -import { config } from 'dotenv'; -config(); - -const { DB_NAME } = process.env; -const sequelize = new Sequelize({ - storage: `${DB_NAME}.sqlite`, - dialect: 'sqlite', - logging: false, -}); -const RoleEmojiPair = defineRoleEmojiPair(sequelize); -const VoiceChannel = defineVoiceChannel(sequelize); -const Message = defineMessage(sequelize); - -sequelize.sync(); - -export { sequelize, RoleEmojiPair, VoiceChannel, Message }; +import defineRoleEmojiPair from './models/roleEmojiPairs.js'; +import defineVoiceChannel from './models/voiceChannels.js'; +import defineMessage from './models/messages.js'; +import { Sequelize } from 'sequelize'; +import { config } from 'dotenv'; +config(); + +const { DB_NAME } = process.env; +const sequelize = new Sequelize({ + storage: `${DB_NAME}.sqlite`, + dialect: 'sqlite', + logging: false +}); +const RoleEmojiPair = defineRoleEmojiPair(sequelize); +const VoiceChannel = defineVoiceChannel(sequelize); +const Message = defineMessage(sequelize); + +sequelize.sync(); + +export { sequelize, RoleEmojiPair, VoiceChannel, Message }; diff --git a/deploy.js b/deploy.js index 9319cee..458e210 100644 --- a/deploy.js +++ b/deploy.js @@ -34,4 +34,5 @@ getFiles(cmdPath) (await Promise.all(files.map(importAndCheck))) .filter((module) => module !== 0) .map((module) => module.data.toJSON()) - ).then(putCommands); + ) + .then(putCommands); diff --git a/events/interactionCreate.js b/events/interactionCreate.js index 991a368..4771ecb 100644 --- a/events/interactionCreate.js +++ b/events/interactionCreate.js @@ -23,7 +23,11 @@ const executeCommand = async (interaction, command) => { const genericExecute = async (interaction, command, name, description, cmdName) => { try { - console.info(`[INFO] Command ${(cmdName ?? interaction.commandName) ?? 'anonymous'} ${description ?? `used "${name}"`}.`); + console.info( + `[INFO] Command ${cmdName ?? interaction.commandName ?? 'anonymous'} ${ + description ?? `used "${name}"` + }.` + ); await command[name](interaction); } catch (error) { console.error(error); diff --git a/events/reactionAdd.js b/events/reactionAdd.js index 29cce2a..8e6d4e5 100644 --- a/events/reactionAdd.js +++ b/events/reactionAdd.js @@ -11,7 +11,7 @@ export async function execute(reaction, user) { const msgID = reaction.message.id; const message = await Message.findOne({ where: { - id: msgID, + id: msgID } }); // Ignore if unregistered @@ -22,7 +22,7 @@ export async function execute(reaction, user) { const rep = await RoleEmojiPair.findOne({ where: { message: msgID, - emoji, + emoji } }); // Deny if unregistered diff --git a/events/reactionRemove.js b/events/reactionRemove.js index f77ef5a..bd3d56e 100644 --- a/events/reactionRemove.js +++ b/events/reactionRemove.js @@ -11,7 +11,7 @@ export async function execute(reaction, user) { const msgID = reaction.message.id; const message = await Message.findOne({ where: { - id: msgID, + id: msgID } }); // Ignore if unregistered @@ -22,7 +22,7 @@ export async function execute(reaction, user) { const rep = await RoleEmojiPair.findOne({ where: { message: msgID, - emoji, + emoji } }); // Deny if unregistered diff --git a/events/voiceStateUpdate.js b/events/voiceStateUpdate.js index 3473ee0..d698ca5 100644 --- a/events/voiceStateUpdate.js +++ b/events/voiceStateUpdate.js @@ -4,7 +4,7 @@ export const name = Events.VoiceStateUpdate; export async function execute(oldState, newState) { console.debug('[DEBUG] Voice State Update'); - const change = (!!oldState.channel) ^ (!!newState.channel); + const change = !!oldState.channel ^ !!newState.channel; if (!change) return; const guild = newState.guild.name; diff --git a/index.js b/index.js index 65b0944..d39384f 100644 --- a/index.js +++ b/index.js @@ -37,13 +37,10 @@ const evtPath = join(__dirname, 'events'); getFiles(cmdPath) // For each command file .then(async (files) => - (await Promise.all(files.map(importAndCheck))) - .filter((module) => module !== 0) - ).then(async (commands) => { + (await Promise.all(files.map(importAndCheck))).filter((module) => module !== 0) + ) + .then(async (commands) => { const files = await getFiles(evtPath); - const events = await Promise.all( - files.map(async (filePath) => - await import(filePath) - )); + const events = await Promise.all(files.map(async (filePath) => await import(filePath))); runClient(commands, events); }); diff --git a/models/messages.js b/models/messages.js index 45f1340..dae0097 100644 --- a/models/messages.js +++ b/models/messages.js @@ -1,10 +1,10 @@ -import { DataTypes } from "sequelize"; - -export default function(sequelize) { - return sequelize.define('Messages', { - id: { - type: DataTypes.STRING, - primaryKey: true, - }, - }); -} +import { DataTypes } from 'sequelize'; + +export default function (sequelize) { + return sequelize.define('Messages', { + id: { + type: DataTypes.STRING, + primaryKey: true + } + }); +} diff --git a/models/roleEmojiPairs.js b/models/roleEmojiPairs.js index 7814483..9b962c6 100644 --- a/models/roleEmojiPairs.js +++ b/models/roleEmojiPairs.js @@ -1,25 +1,25 @@ -import { DataTypes, Deferrable } from "sequelize"; - -export default function(sequelize) { - return sequelize.define('RoleEmojiPairs', { - id: { - defaultValue: DataTypes.UUIDV4, - type: DataTypes.UUID, - primaryKey: true, - }, - message: { - type: DataTypes.STRING, - references: { - deferrable: Deferrable.INITIALLY_IMMEDIATE, - model: 'Messages', - key: 'id', - }, - }, - role: { - type: DataTypes.STRING, - }, - emoji: { - type: DataTypes.STRING, - } - }); -} +import { DataTypes, Deferrable } from 'sequelize'; + +export default function (sequelize) { + return sequelize.define('RoleEmojiPairs', { + id: { + defaultValue: DataTypes.UUIDV4, + type: DataTypes.UUID, + primaryKey: true + }, + message: { + type: DataTypes.STRING, + references: { + deferrable: Deferrable.INITIALLY_IMMEDIATE, + model: 'Messages', + key: 'id' + } + }, + role: { + type: DataTypes.STRING + }, + emoji: { + type: DataTypes.STRING + } + }); +} diff --git a/models/voiceChannels.js b/models/voiceChannels.js index 6fb9f41..f175404 100644 --- a/models/voiceChannels.js +++ b/models/voiceChannels.js @@ -1,10 +1,10 @@ -import { DataTypes } from "sequelize"; - -export default function(sequelize) { - return sequelize.define('VoiceChannel', { - id: { - type: DataTypes.STRING, - primaryKey: true, - }, - }); -} +import { DataTypes } from 'sequelize'; + +export default function (sequelize) { + return sequelize.define('VoiceChannel', { + id: { + type: DataTypes.STRING, + primaryKey: true + } + }); +} diff --git a/shared.js b/shared.js index 3740d7d..4126019 100644 --- a/shared.js +++ b/shared.js @@ -1,98 +1,104 @@ -import { Op } from "sequelize"; -import { Message, RoleEmojiPair } from "./database.js"; - -const saveMessageData = async (id, role, emoji) => { - // Try finding message - const msg = await Message.findOne({ where: { id } }); - if (msg === null) throw new Error(`No message with ID '${id}' could be found!`); - - // Try finding existing entry - const rep = await RoleEmojiPair.findOne({ - where: { - [Op.or]: [ - { - message: id, - role: role.id - }, { - message: id, - emoji - } - ] - } - }); - if (rep !== null) throw new Error(`Existing RoleEmojiPair entry with (partial) data {message:${id},role:${role.id},emoji:${emoji}}!`); - - // Create database entry for pair - await RoleEmojiPair.create({ message: id, role: role.id, emoji }); -}; - -export const addSelfRoles = async (interaction, msgID, role, emoji) => { - const { channel } = interaction; - - let step = 'fetch'; - try { - // Get message by id - const message = await channel.messages.fetch(msgID); - - step = 'save data from'; - await saveMessageData(msgID, role, emoji); - - step = 'react to'; - // React with emoji to message - await message.react(emoji); - - // Reply successfully to acknowledge command - await interaction.reply({ - content: 'Added new entry for self roles!', - ephemeral: true, - }); - - console.info(`[INFO] Added new entry to get role with ID '${role.id}' using '${emoji}'.`); - } catch (error) { - console.error(error); - - // Reply failed to acknowledge command - await interaction.reply({ - content: `Failed to ${step} message!`, - ephemeral: true, - }); - } -} -import { join } from 'path'; -import { readdir } from 'fs/promises'; - -const required = ['data', 'execute']; -const optional = ['autocomplete', 'modalSubmit']; - -export const getFiles = async (dir) => { - const dirents = await readdir(dir, { withFileTypes: true }); - const files = await Promise.all(dirents.map((dirent) => { - const res = join(dir, dirent.name); - return dirent.isDirectory() ? getFiles(res) : res; - })); - return Array.prototype.concat(...files); -}; - -export const importAndCheck = async (filePath) => { - if (!filePath.endsWith('.js') || filePath.endsWith('.example.js')) { - // Skip this file - return 0; - } - const command = await import(filePath); - // Warn incomplete commands - if (!required.every((name) => name in command)) { - console.error( - `[ERROR] The command at ${filePath} is missing a required "data" or "execute" property.` - ); - return 0; - } - const properties = optional.filter((name) => !(name in command)); - if (properties.length > 0) - properties.forEach((name) => - console.warn( - `[WARNING] The command at ${filePath} is missing an optional "${name}" property.` - ) - ); - // Add command to collection - return command; -}; +import { Op } from 'sequelize'; +import { Message, RoleEmojiPair } from './database.js'; + +const saveMessageData = async (id, role, emoji) => { + // Try finding message + const msg = await Message.findOne({ where: { id } }); + if (msg === null) throw new Error(`No message with ID '${id}' could be found!`); + + // Try finding existing entry + const rep = await RoleEmojiPair.findOne({ + where: { + [Op.or]: [ + { + message: id, + role: role.id + }, + { + message: id, + emoji + } + ] + } + }); + if (rep !== null) + throw new Error( + `Existing RoleEmojiPair entry with (partial) data {message:${id},role:${role.id},emoji:${emoji}}!` + ); + + // Create database entry for pair + await RoleEmojiPair.create({ message: id, role: role.id, emoji }); +}; + +export const addSelfRoles = async (interaction, msgID, role, emoji) => { + const { channel } = interaction; + + let step = 'fetch'; + try { + // Get message by id + const message = await channel.messages.fetch(msgID); + + step = 'save data from'; + await saveMessageData(msgID, role, emoji); + + step = 'react to'; + // React with emoji to message + await message.react(emoji); + + // Reply successfully to acknowledge command + await interaction.reply({ + content: 'Added new entry for self roles!', + ephemeral: true + }); + + console.info(`[INFO] Added new entry to get role with ID '${role.id}' using '${emoji}'.`); + } catch (error) { + console.error(error); + + // Reply failed to acknowledge command + await interaction.reply({ + content: `Failed to ${step} message!`, + ephemeral: true + }); + } +}; +import { join } from 'path'; +import { readdir } from 'fs/promises'; + +const required = ['data', 'execute']; +const optional = ['autocomplete', 'modalSubmit']; + +export const getFiles = async (dir) => { + const dirents = await readdir(dir, { withFileTypes: true }); + const files = await Promise.all( + dirents.map((dirent) => { + const res = join(dir, dirent.name); + return dirent.isDirectory() ? getFiles(res) : res; + }) + ); + return Array.prototype.concat(...files); +}; + +export const importAndCheck = async (filePath) => { + if (!filePath.endsWith('.js') || filePath.endsWith('.example.js')) { + // Skip this file + return 0; + } + const command = await import(filePath); + // Warn incomplete commands + if (!required.every((name) => name in command)) { + console.error( + `[ERROR] The command at ${filePath} is missing a required "data" or "execute" property.` + ); + return 0; + } + const properties = optional.filter((name) => !(name in command)); + if (properties.length > 0) + properties.forEach((name) => + console.warn( + `[WARNING] The command at ${filePath} is missing an optional "${name}" property.` + ) + ); + // Add command to collection + return command; +};