diff --git a/messages.json b/messages.json index 659d107..91b3c6f 100644 --- a/messages.json +++ b/messages.json @@ -12,12 +12,27 @@ "COMMAND_CLEARALL_MISSING_CATEGORY_PERM": "The bot does not have the necessary permissions to clone this channel.\nMissing permission: **{PERMISSION}**\nPlease make sure this permission is set on **this category**.\nIf you need help, please visit our [Discord support server](https://discord.gg/HW9tA4Mp3b).", "COMMAND_CLEARALL_MISSING_GUILD_PERM": "The bot does not have the necessary permissions to clone this channel.\nMissing permission: **{PERMISSION}**\nPlease make sure this permission is set on **this server**.\nAlternatively, you can put this channel in a category and give the bot the necessary permissions there.\nIf you need help, please visit our [Discord support server](https://discord.gg/HW9tA4Mp3b).", "COMMAND_CLEARALL_ERROR": "There was an error while clearing this channel.\nPlease check if the bot has all the neccessary permissions!\nIf you need help, please visit our [Discord support server](https://discord.gg/HW9tA4Mp3b).", + "COMMAND_CLEARALL_DELETE_ERROR": "I can not delete this channel.\nPlease note that I can only delete channels not used for community server purposes (rules and public updates channel).\nIf you need help, please visit our [Discord support server](https://discord.gg/HW9tA4Mp3b).", "COMMAND_CLEARALL_SUCCESS": "{USER} deleted all messages in this channel.", "COMMAND_PING": "Pong!\nShard {SHARD} | {PING} ms", + "COMMAND_VOTE_EMBED_TITLE": "ClearChat-Bot Vote Infos ✨", "COMMAND_VOTE_EMBED": "Hey, you can vote via [this link](https://jh220.de/cc/vote).\nThis will give you access to new cool features.\n\nIf you want to invite the bot to your server, click on [this link](https://discord.com/oauth2/authorize?client_id=787789079227006976&permissions=484432&redirect_uri=https%3A%2F%2Fwww.jh220.de&scope=bot%20applications.commands).\nIf you need help with the bot, please visit our [Discord support server](https://discord.gg/HW9tA4Mp3b).", + "COMMAND_SETTINGS_EMBED_TITLE": "ClearChat-Bot Settings 🛠️", + "COMMAND_SETTINGS_EMBED": "This is the settings page of the ClearChat bot.\nYou can select a setting from the drop-down menu below this message and then enable or disable it.\nThe updated setting will immediately appear updated in the message, so you can make further adjustments.\nTo reset all settings to their default values, click \"Reset all settings\" there.", + "COMMAND_SETTINGS_BUTTON_RESET_ALL": "Reset all settings", + "INTERACTION_SETTINGS_RESET": "All settings have been reset to their default values.", + "INTERACTION_SETTINGS_TOGGLE_CONFIRMATION_MESSAGES": "Switched the setting for confirmation messages to {STATE}.", "COMMAND_ADMIN_GET_USER_EMBED": "Information about the user.\n", + "COMMAND_ADMIN_GET_SERVER_EMBED_TITLE": "ClearChat-Bot Admin Server Information 📊", "COMMAND_ADMIN_GET_SERVER_EMBED": "Information about the server.\n", "COMMAND_ADMIN_STATS_STILL_STARTING": "The statistics are still being collected.\nPlease try again later.", + "COMMAND_ADMIN_STATS_EMBED_TITLE": "ClearChat-Bot Admin Stats 📊", + "INTERACTION_ADMIN_GET_USER_BAN_SUCCESS": "The user {BAN_USER} has been banned.", + "INTERACTION_ADMIN_GET_USER_PARDON_SUCCESS": "The User {BAN_USER} has been pardoned.", + "INTERACTION_ADMIN_GET_USER_BAN_CONFIRM_ERROR_ALREADY_BANNED": "The user {BAN_USER} is already banned.", + "INTERACTION_ADMIN_GET_USER_PARDON_CONFIRM_ERROR_ALREADY_PARDONED": "The user {BAN_USER} is already pardoned.", + "INTERACTION_ADMIN_GET_USER_BAN_HISTORY_EMBED_TITLE": "ClearChat-Bot Admin User Ban History 📜", + "INTERACTION_ADMIN_GET_OVERVIEW_EMBED_TITLE": "ClearChat-Bot Admin User Information 📊", "ERROR_EXECUTE": "There was an error while executing this command!\nPlease report it to: https://github.com/JH220/discord-clearchatbot/issues/new?title=Interaction-ID%3A+{INTERACTION_ID}&template=command_error.md\nInteraction-ID: **{INTERACTION_ID}**\nFor more information, visit our [Discord support server](https://discord.gg/HW9tA4Mp3b).", - "SETTINGS_EMBED": "This is the settings page of the ClearChat bot.\nYou can select a setting from the drop-down menu below this message and then enable or disable it.\nThe updated setting will immediately appear updated in the message, so you can make further adjustments.\nTo reset all settings to their default values, click \"Reset all settings\" there." + "SETTINGS_EMBED": "This is the server-side settings page of the ClearChat bot.\nYou can select a setting from the drop-down menu below this message and then enable or disable it.\nTo reset all settings to their default values, click \"Reset all settings\" there." } \ No newline at end of file diff --git a/src/bot.ts b/src/bot.ts index 8688d91..837fb3a 100644 --- a/src/bot.ts +++ b/src/bot.ts @@ -22,7 +22,7 @@ export class CustomClient extends Client implements Logger { } ierror(interaction : Interaction, error : Error = null, message : string = '') { - this.error(`[Interaction ${interaction.id}] ${message ? message : 'Error while executing interaction'}: ${error}${error?.stack ? `\n${error.stack}` : ''}`); + this.error(`[Interaction ${interaction.id}] ${message ?? 'Error while executing interaction'}: ${error}${error?.stack ? `\n${error.stack}` : ''}`); } idebug(interaction : Interaction, message : string) { this.debug(`[Interaction ${interaction.id}] ${message}`); @@ -44,6 +44,7 @@ process.on('message', (message : any) => { if (message?.type == 'started' && !client.startup) { client.debug('Received startup message, now in full operation.'); client.startup = new Date(); + require('./utils/activity-manager').startActivity(client); } }); diff --git a/src/commands/main/clearall.ts b/src/commands/main/clearall.ts index 9f7d8f0..1a82bfe 100644 --- a/src/commands/main/clearall.ts +++ b/src/commands/main/clearall.ts @@ -1,6 +1,7 @@ import { SlashCommandBuilder } from '@discordjs/builders'; import { ChannelType, ChatInputCommandInteraction, PermissionFlagsBits, PermissionsBitField } from 'discord.js'; import { CustomClient } from '../../bot'; +import { Sequelize } from 'sequelize'; module.exports = { data: new SlashCommandBuilder() @@ -26,6 +27,9 @@ module.exports = { else if (!member.permissions.has(PermissionsBitField.Flags.ManageChannels)) return await database.reply(interaction, 'COMMAND_CLEARALL_MISSING_GUILD_PERM', { 'PERMISSION': 'Manage Channels' }); + if (!interaction.channel.deletable) + return await database.reply(interaction, 'COMMAND_CLEARALL_DELETE_ERROR'); + var channel; try { @@ -38,11 +42,14 @@ module.exports = { } await database.reply(interaction, 'COMMAND_CLEARALL_PENDING', {}, false); + await database.reply(interaction, 'COMMAND_CLEARALL_SUCCESS', { 'CHANNEL': `<#${channel.id}>` }, false); + + + const settings : any = await (database.connection as Sequelize).models.ServerSetting.findOne({ where: { serverId: interaction.guildId } }); + if (!(settings?.showreply ?? true)) return; try { - const message = await database.getMessage('COMMAND_CLEARALL_SUCCESS', interaction, { 'USER': `<@${interaction.user.id}>` }); - channel.send(message); - await database.reply(interaction, 'COMMAND_CLEARALL_SUCCESS', { 'CHANNEL': `<#${channel.id}>` }, false); + channel.send(await database.getMessage('COMMAND_CLEARALL_SUCCESS', interaction)); } catch (error) { await (interaction.client as CustomClient).ierror(interaction, error, 'Error while sending message to new channel'); diff --git a/src/commands/main/settings.ts b/src/commands/main/settings.ts index 0a44632..11e6ed8 100644 --- a/src/commands/main/settings.ts +++ b/src/commands/main/settings.ts @@ -1,6 +1,7 @@ import { SlashCommandBuilder } from '@discordjs/builders'; -import { ChatInputCommandInteraction, EmbedBuilder } from 'discord.js'; +import { ActionRowBuilder, ButtonBuilder, ButtonInteraction, ButtonStyle, ChatInputCommandInteraction, EmbedBuilder, StringSelectMenuBuilder, StringSelectMenuInteraction, StringSelectMenuOptionBuilder } from 'discord.js'; import { CustomClient } from '../../bot'; +import { Sequelize } from 'sequelize'; module.exports = { data: new SlashCommandBuilder() @@ -11,13 +12,49 @@ module.exports = { async execute(interaction : ChatInputCommandInteraction) { const database = new (require('../../utils/database'))(); + const select = new StringSelectMenuBuilder() + .setCustomId('settings_select') + .setPlaceholder('Select a setting to view') + .addOptions( + new StringSelectMenuOptionBuilder() + .setLabel('Toggle confirmation messages') + .setDescription('Toggles the confirmation message in the new channel when /clearall is run.') + .setValue('toggle_confirmation_messages'), + ); + const resetButton = new ButtonBuilder() + .setCustomId('settings_reset') + .setLabel(await database.getMessage('COMMAND_SETTINGS_BUTTON_RESET_ALL', interaction)) + .setStyle(ButtonStyle.Danger) + .setEmoji('🔄'); + await interaction.reply({ embeds: [ new EmbedBuilder() .setColor('#00FFFF') - .setTitle('ClearChat-Bot Settings 🛠️') + .setTitle(await database.getMessage('COMMAND_SETTINGS_EMBED_TITLE', interaction)) .setDescription(await database.getMessage('COMMAND_SETTINGS_EMBED', interaction)), - ] }); + ], components: [new ActionRowBuilder().addComponents(select) as any, new ActionRowBuilder().addComponents(resetButton)] }); await database.reply(interaction, 'COMMAND_SETTINGS_SUCCESS', {}, false); await (interaction.client as CustomClient).idebug(interaction, 'Replied to settings command'); }, + async executeButton(interaction : ButtonInteraction, database : any) { + const customId = interaction.customId.split(';')[0]; + if (customId != 'settings_reset') return; + + const models = (database.connection as Sequelize).models; + await models.ServerSetting.destroy({ where: { serverId: interaction.guildId } }); + database.reply(interaction, 'INTERACTION_SETTINGS_RESET'); + }, + async executeStringSelectMenu(interaction : StringSelectMenuInteraction, database : any) { + const customId = interaction.customId.split(';')[0]; + if (customId != 'settings_select') return; + if (!interaction.values.includes('toggle_confirmation_messages')) return; + + const models = (database.connection as Sequelize).models; + const setting : any = await models.ServerSetting.findOne({ where: { serverId: interaction.guildId } }); + const showreply = setting ? !setting.showreply : false; + if (setting) setting.update({ showreply: showreply }); + else models.ServerSetting.create({ serverId: interaction.guildId, showreply: showreply }); + + database.reply(interaction, 'INTERACTION_SETTINGS_TOGGLE_CONFIRMATION_MESSAGES', { 'STATE': showreply ? 'Enabled' : 'Disabled' }); + }, }; \ No newline at end of file diff --git a/src/commands/utility/admin/get/server.ts b/src/commands/utility/admin/get/server.ts index bdece46..de0332f 100644 --- a/src/commands/utility/admin/get/server.ts +++ b/src/commands/utility/admin/get/server.ts @@ -6,16 +6,6 @@ module.exports = { async execute(interaction : ChatInputCommandInteraction, database : any, server : any) { const models = (database.connection as Sequelize).models; - /* - Error [ERR_UNHANDLED_ERROR]: Unhandled error. ('INVALID_KEY') - at new NodeError (node:internal/errors:406:5) - at CustomClient.emit (node:events:503:17) - at emitUnhandledRejectionOrErr (node:events:397:10) - at process.processTicksAndRejections (node:internal/process/task_queues:84:21) { - code: 'ERR_UNHANDLED_ERROR', - context: 'INVALID_KEY' -} - */ const bin = (+server.serverId).toString(2); const diff = 64 - (+server.serverId).toString(2).length; const created = parseInt(bin.substring(0, 42 - diff), 2) + 1420070400000; @@ -25,7 +15,7 @@ module.exports = { await interaction.reply({ embeds: [ new EmbedBuilder() .setColor('#00FFFF') - .setTitle('ClearChat-Bot Admin Server Information 📊') + .setTitle(await database.getMessage('COMMAND_ADMIN_GET_SERVER_EMBED_TITLE', interaction)) .setDescription(await database.getMessage('COMMAND_ADMIN_GET_SERVER_EMBED', interaction)) .setImage(`https://cdn.discordapp.com/icons/${server.serverId}/${server.serverPicture}.webp`) .addFields( diff --git a/src/commands/utility/admin/get/user.ts b/src/commands/utility/admin/get/user.ts index 7cbff2a..5879770 100644 --- a/src/commands/utility/admin/get/user.ts +++ b/src/commands/utility/admin/get/user.ts @@ -1,66 +1,203 @@ -import { ActionRowBuilder, ButtonBuilder, ButtonInteraction, ButtonStyle, ChatInputCommandInteraction, EmbedBuilder } from 'discord.js'; +import { ActionRowBuilder, ButtonBuilder, ButtonInteraction, ButtonStyle, ChatInputCommandInteraction, EmbedBuilder, Interaction, ModalBuilder, ModalSubmitInteraction, TextInputBuilder, TextInputStyle } from 'discord.js'; import { CustomClient } from '../../../../bot'; import { Sequelize } from 'sequelize'; module.exports = { async execute(interaction : ChatInputCommandInteraction, database : any, user : any) { - const models = (database.connection as Sequelize).models; - - const bin = (+user.userId).toString(2); - const diff = 64 - (+user.userId).toString(2).length; - const created = parseInt(bin.substring(0, 42 - diff), 2) + 1420070400000; - - const ban = await models.UserBan.findOne({ where: { userId: user.userId } }); - const currentBan = await models.UserBan.findOne({ where: { userId: user.userId, pardonModId: null } }); - - const embed = new EmbedBuilder() - .setColor('#00FFFF') - .setTitle('ClearChat-Bot Admin User Information 📊') - .setDescription(await database.getMessage('COMMAND_ADMIN_GET_USER_EMBED', interaction)) - .setImage(`https://cdn.discordapp.com/avatars/${user.userId}/${user.userPicture}.webp`) - .addFields( - { name: 'User ID', value: user.userId, inline: false }, - { name: 'Username', value: '@' + user.userName, inline: true }, - { name: 'Mention', value: `<@${user.userId}>`, inline: true }, - { name: 'Account Creation', value: ``, inline: false }, - { name: 'Ban Status', value: currentBan ? 'Banned' : ban ? 'Previously Banned' : 'Not Banned', inline: false }, - ); - - - const viewServersButton = new ButtonBuilder() - .setCustomId('admin_get_user_servers') - .setLabel('View Servers') - .setStyle(ButtonStyle.Secondary) - .setEmoji('🏰'); - - const banButton = new ButtonBuilder() - .setCustomId('admin_get_user_ban') - .setLabel('Ban User') - .setStyle(ButtonStyle.Danger) - .setEmoji('🔨'); - const pardonButton = new ButtonBuilder() - .setCustomId('admin_get_user_pardon') - .setLabel('Pardon User') - .setStyle(ButtonStyle.Success) - .setEmoji('🔓'); - const banHistoryButton = new ButtonBuilder() - .setCustomId('admin_get_user_ban_history') - .setLabel('Ban History') - .setStyle(ButtonStyle.Secondary) - .setEmoji('📜'); - - const row = new ActionRowBuilder().addComponents( - viewServersButton, - currentBan ? pardonButton : banButton, - ban ? banHistoryButton : null, - ); - - // @ts-ignore - await interaction.reply({ embeds: [embed], components: [row] }); + await interaction.reply(await getReply(interaction, user, database) as any); await database.reply(interaction, 'COMMAND_ADMIN_GET_USER_SUCCESS', {}, false); await (interaction.client as CustomClient).idebug(interaction, 'Replied to admin get user command.'); }, async executeButton(interaction : ButtonInteraction, database : any) { - database.reply(interaction, 'INTERACTION_ADMIN_GET_USER'); + switch (interaction.customId.split(';')[0]) { + case 'admin_get_user_servers': { + break; + } + case 'admin_get_user_ban': { + const userId = interaction.customId.split(';')[1]; + if (!userId) return await database.reply(interaction, 'INTERACTION_ADMIN_GET_USER_BAN_ERROR', {}, false); + + const modal = new ModalBuilder().setCustomId(`admin_get_user_ban_confirm;${userId}`).setTitle('User Ban Confirmation 📜'); + const banReason = new TextInputBuilder() + .setCustomId('admin_get_user_ban_confirm_reason') + .setLabel('Please provide a reason for the ban.') + .setStyle(TextInputStyle.Short) + .setRequired(true); + + modal.addComponents(new ActionRowBuilder().addComponents(banReason) as any); + await interaction.showModal(modal); + break; + } + case 'admin_get_user_pardon': { + const banId = interaction.customId.split(';')[1]; + if (!banId) return await database.reply(interaction, 'INTERACTION_ADMIN_GET_USER_PARDON_ERROR', {}, false); + + const modal = new ModalBuilder().setCustomId(`admin_get_user_pardon_confirm;${banId}`).setTitle('User Pardon Confirmation 📜'); + const pardonReason = new TextInputBuilder() + .setCustomId('admin_get_user_pardon_confirm_reason') + .setLabel('Please provide a reason for the pardon.') + .setStyle(TextInputStyle.Short) + .setRequired(false); + + modal.addComponents(new ActionRowBuilder().addComponents(pardonReason) as any); + await interaction.showModal(modal); + break; + } + case 'admin_get_user_ban_history': { + const userId = interaction.customId.split(';')[1]; + if (!userId) return await database.reply(interaction, 'INTERACTION_ADMIN_GET_USER_BAN_HISTORY_ERROR', {}, false); + var description = '**User ID**: ' + userId + '\n\n**Ban History**:\n'; + const models = (database.connection as Sequelize).models; + var bans = await models.UserBan.findAll({ where: { userId: userId } }); + + // only show the last 5 bans + if (bans.length > 5) { + bans = bans.slice(bans.length - 5); + description += 'Only showing the last 5 bans.\n'; + } + else if (bans.length == 0) + description += '\nNo ban history found for this user.'; + + for (let i = 0; i < bans.length; i++) { + const ban = bans[i] as any; + const mod : any = ban.modId ? await models.User.findOne({ where: { userId: ban.modId } }) : null; + const pardonMod : any = ban.pardonModId ? await models.User.findOne({ where: { userId: ban.pardonModId } }) : null; + + description += `\n + **Ban ID**: **__BU${ban.banId}__** + **Ban Reason**: ${ban.reason ?? 'N/A'} + **Ban Date**: + **Ban Mod**: ${mod ? `<@${mod.userId}> (@${mod.userName})` : 'N/A'} + ${ban.pardonModId ? `**Pardon Reason**: ${ban.pardonReason ?? 'N/A'} + **Pardon Date**: + **Pardon Mod**: ${pardonMod ? `<@${pardonMod.userId}> (@${pardonMod.userName})` : 'N/A'}` : ''}`; + } + + var embed = new EmbedBuilder() + .setColor('#00FFFF') + .setTitle(await database.getMessage('INTERACTION_ADMIN_GET_USER_BAN_HISTORY_EMBED_TITLE', interaction)) + .setDescription(description); + const banButton = await getBanButton(interaction, userId, database); + + await interaction.reply({ embeds: [embed], components: [new ActionRowBuilder().addComponents(banButton) as any] }); + await database.reply(interaction, 'INTERACTION_ADMIN_GET_USER_BAN_HISTORY', {}, false); + break; + } + } }, -}; \ No newline at end of file + async executeModal(interaction : ModalSubmitInteraction, database : any) { + switch (interaction.customId.split(';')[0]) { + case 'admin_get_user_ban_confirm': { + const userId = interaction.customId.split(';')[1]; + if (!userId) return await database.reply(interaction, 'INTERACTION_ADMIN_GET_USER_BAN_CONFIRM_ERROR', {}, false); + + const models = (database.connection as Sequelize).models; + const user : any = await models.User.findOne({ where: { userId: userId } }); + if (!user) return await database.reply(interaction, 'INTERACTION_ADMIN_GET_USER_BAN_CONFIRM_ERROR_ID_NOT_FOUND', {}, false); + + const ban = await models.UserBan.findOne({ where: { userId: userId, pardonModId: null } }); + if (ban) return await database.reply(interaction, 'INTERACTION_ADMIN_GET_USER_BAN_CONFIRM_ERROR_ALREADY_BANNED', { 'BAN_USER': `<@${userId}>` }); + + const reason = interaction.fields.getTextInputValue('admin_get_user_ban_confirm_reason'); + const modId = interaction.user.id; + + await models.UserBan.create({ userId: userId, reason: reason, modId: modId }); + const { embeds, components } = await getReply(interaction, user, database); + await interaction.reply({ + content: await database.getMessage('INTERACTION_ADMIN_GET_USER_BAN_SUCCESS', interaction, { 'BAN_USER': `<@${userId}>` }), + embeds: embeds, components: components as any, + }); + await database.reply(interaction, 'INTERACTION_ADMIN_GET_USER_BAN_SUCCESS', { 'USER_ID': userId }, false); + break; + } + case 'admin_get_user_pardon_confirm': { + const banId = interaction.customId.split(';')[1]; + if (!banId) return await database.reply(interaction, 'INTERACTION_ADMIN_GET_USER_PARDON_CONFIRM_ERROR', {}, false); + + const models = (database.connection as Sequelize).models; + const ban : any = await models.UserBan.findOne({ where: { banId: banId } }); + if (!ban) return await database.reply(interaction, 'INTERACTION_ADMIN_GET_USER_PARDON_CONFIRM_ERROR_ID_NOT_FOUND', {}, false); + + if (ban.pardonModId) return await database.reply(interaction, 'INTERACTION_ADMIN_GET_USER_PARDON_CONFIRM_ERROR_ALREADY_PARDONED', { 'BAN_USER': `<@${ban.userId}>` }); + + const user : any = await models.User.findOne({ where: { userId: ban.userId } }); + const pardonReason = interaction.fields.getTextInputValue('admin_get_user_pardon_confirm_reason'); + const pardonModId = interaction.user.id; + + await ban.update({ pardonReason: pardonReason, pardonModId: pardonModId }); + const { embeds, components } = await getReply(interaction, user, database); + await interaction.reply({ + content: await database.getMessage('INTERACTION_ADMIN_GET_USER_PARDON_SUCCESS', interaction, { 'BAN_USER': `<@${ban.userId}>` }), + embeds: embeds, components: components as any, + }); + await database.reply(interaction, 'INTERACTION_ADMIN_GET_USER_PARDON_SUCCESS', { USER_ID: ban.userId }, false); + break; + } + } + }, +}; + +async function getReply(interaction : Interaction, user: any, database : any) : Promise<{ embeds: [EmbedBuilder], components: [ActionRowBuilder] }> { + const bin = (+user.userId).toString(2); + const diff = 64 - (+user.userId).toString(2).length; + const created = parseInt(bin.substring(0, 42 - diff), 2) + 1420070400000; + + const models = (database.connection as Sequelize).models; + const currentBan : any = await models.UserBan.findOne({ where: { userId: user.userId, pardonModId: null } }); + const ban : any = await models.UserBan.findOne({ where: { userId: user.userId } }); + + const embed = new EmbedBuilder() + .setColor('#00FFFF') + .setTitle(await database.getMessage('INTERACTION_ADMIN_GET_OVERVIEW_EMBED_TITLE', interaction)) + .setDescription(await database.getMessage('COMMAND_ADMIN_GET_USER_EMBED', interaction)) + .setImage(`https://cdn.discordapp.com/avatars/${user.userId}/${user.userPicture}.webp`) + .addFields( + { name: 'User ID', value: user.userId, inline: false }, + { name: 'Username', value: '@' + user.userName, inline: true }, + { name: 'Mention', value: `<@${user.userId}>`, inline: true }, + { name: 'Account Creation', value: ``, inline: false }, + { name: 'Ban Status', value: currentBan ? 'Banned' : ban ? 'Previously Banned' : 'Not Banned', inline: false }, + ); + + + const viewServersButton = new ButtonBuilder() + .setCustomId(`admin_get_user_servers;${user.userId}`) + .setLabel('View Servers') + .setStyle(ButtonStyle.Secondary) + .setEmoji('🏰'); + + + const banButton = await getBanButton(interaction, user.userId, database); + const banHistoryButton = new ButtonBuilder() + .setCustomId(`admin_get_user_ban_history;${user.userId}`) + .setLabel('Ban History') + .setStyle(ButtonStyle.Secondary) + .setEmoji('📜'); + + const row = new ActionRowBuilder().addComponents( + viewServersButton, + banButton, + ban ? banHistoryButton : null, + ); + + return { embeds: [embed], components: [row] }; +} + +async function getBanButton(interaction : Interaction, userId : string, database : any) : Promise { + const models = (database.connection as Sequelize).models; + const currentBan : any = await models.UserBan.findOne({ where: { userId: userId, pardonModId: null } }); + + const banButton = new ButtonBuilder() + .setCustomId(`admin_get_user_ban;${userId}`) + .setLabel('Ban User') + .setStyle(ButtonStyle.Danger) + .setEmoji('🔨') + .setDisabled(interaction.user.id == userId); + const pardonButton = new ButtonBuilder() + .setCustomId(`admin_get_user_pardon;${currentBan ? currentBan.banId : ''}`) + .setLabel('Pardon User') + .setStyle(ButtonStyle.Success) + .setEmoji('🔓'); + + return currentBan ? pardonButton : banButton; +} \ No newline at end of file diff --git a/src/commands/utility/admin/stats.ts b/src/commands/utility/admin/stats.ts index 20ff956..6a7f58c 100644 --- a/src/commands/utility/admin/stats.ts +++ b/src/commands/utility/admin/stats.ts @@ -16,7 +16,7 @@ module.exports = { const embed = new EmbedBuilder() .setColor('#00FFFF') - .setTitle('ClearChat-Bot Admin Stats 📊') + .setTitle(await database.getMessage('COMMAND_ADMIN_STATS_EMBED_TITLE', interaction)) .addFields( { name: 'Servers', value: `${serverCountString} servers`, inline: true }, { name: 'Members', value: `${memberCountString} members`, inline: true }, diff --git a/src/commands/utility/vote.ts b/src/commands/utility/vote.ts index e8cacfe..009c524 100644 --- a/src/commands/utility/vote.ts +++ b/src/commands/utility/vote.ts @@ -11,7 +11,7 @@ module.exports = { await interaction.reply({ embeds: [ new EmbedBuilder() .setColor('#00FFFF') - .setTitle('ClearChat-Bot Vote Infos ✨') + .setTitle(await database.getMessage('COMMAND_VOTE_EMBED_TITLE', interaction)) .setDescription(await database.getMessage('COMMAND_VOTE_EMBED', interaction)), ] }); diff --git a/src/events/interactionCreate.ts b/src/events/interactionCreate.ts index df23d22..6808b99 100644 --- a/src/events/interactionCreate.ts +++ b/src/events/interactionCreate.ts @@ -1,4 +1,4 @@ -import { ButtonInteraction, ChatInputCommandInteraction, Events, Interaction } from 'discord.js'; +import { ButtonInteraction, ChatInputCommandInteraction, Events, Interaction, ModalSubmitInteraction, StringSelectMenuInteraction } from 'discord.js'; module.exports = { name: Events.InteractionCreate, @@ -6,5 +6,7 @@ module.exports = { const database = new (require('../utils/database'))(); if (interaction.isChatInputCommand()) require('./interactions/chatInputCommand').execute(interaction as ChatInputCommandInteraction, database); if (interaction.isButton()) require('./interactions/button').execute(interaction as ButtonInteraction, database); + if (interaction.isModalSubmit()) require('./interactions/modalSubmit').execute(interaction as ModalSubmitInteraction, database); + if (interaction.isStringSelectMenu()) require('./interactions/stringSelectMenu').execute(interaction as StringSelectMenuInteraction, database); }, }; \ No newline at end of file diff --git a/src/events/interactions/button.ts b/src/events/interactions/button.ts index 78fd7c5..47dcd73 100644 --- a/src/events/interactions/button.ts +++ b/src/events/interactions/button.ts @@ -38,13 +38,15 @@ module.exports = { if (disabledCommand) return database.reply(interaction, 'INTERACTION_GLOBAL_DISABLED', { 'REASON': disabledCommand.reason, 'MOD_ID': disabledCommand.modId }); try { - const id = interaction.customId; + const id = interaction.customId.split(';')[0]; if (['admin_get_user_servers', 'admin_get_user_ban', 'admin_get_user_pardon', 'admin_get_user_ban_history'].includes(id)) await require('../../commands/utility/admin/get/user').executeButton(interaction, database); + if (id == 'settings_reset') + await require('../../commands/main/settings').executeButton(interaction, database); else - client.warn(`No interaction matching ${interaction.customId} was found.`); + client.warn(`No interaction matching ${id} was found.`); } catch (error) { client.ierror(interaction, error, 'Error while executing button interaction'); diff --git a/src/events/interactions/modalSubmit.ts b/src/events/interactions/modalSubmit.ts new file mode 100644 index 0000000..177b5a6 --- /dev/null +++ b/src/events/interactions/modalSubmit.ts @@ -0,0 +1,54 @@ +import { ModalSubmitInteraction } from 'discord.js'; +import { CustomClient } from '../../bot'; + +module.exports = { + async execute(interaction : ModalSubmitInteraction, database : any) { + const client = interaction.client as CustomClient; + + if (!database.getConnection()) { + client.error(`[Interaction ${interaction.id}] Failed to connect to the database.`); + interaction.reply({ content: 'There was an error while executing this command!', ephemeral: true }); + return; + } + const { models } = database.getConnection(); + + try { + await database.addInteraction(interaction); + } + catch (error) { + client.ierror(interaction, error, 'Error while adding interaction to the database (1)'); + try { + await database.addInteraction(interaction); + client.warn(`[Interaction ${interaction.id}] Interaction was added to the database after a second attempt.`); + } + catch (error2) { + client.ierror(interaction, error2, 'Error while adding interaction to the database (2)'); + interaction.reply({ content: 'There was an error while executing this command!\nPlease try again later.', ephemeral: true }); + return; + } + } + + const bannedUser = await models.UserBan.findOne({ where: { userId: interaction.user.id, pardonModId: null } }); + if (bannedUser) return database.reply(interaction, 'COMMAND_GLOBAL_USER_BANNED', { 'REASON': bannedUser.reason, 'BAN_ID': 'BU' + bannedUser.banId }); + if (interaction.inGuild()) { + const bannedGuild = await models.ServerBan.findOne({ where: { serverId: interaction.guildId, pardonModId: null } }); + if (bannedGuild) return database.reply(interaction, 'COMMAND_GLOBAL_GUILD_BANNED', { 'REASON': bannedGuild.reason, 'BAN_ID': 'BG' + bannedGuild.banId }); + } + const disabledCommand = await models.DisabledCommand.findOne({ where: { commandName: interaction.customId } }); + if (disabledCommand) return database.reply(interaction, 'INTERACTION_GLOBAL_DISABLED', { 'REASON': disabledCommand.reason, 'MOD_ID': disabledCommand.modId }); + + try { + const id = interaction.customId.split(';')[0]; + + if (['admin_get_user_ban_confirm', 'admin_get_user_pardon_confirm'].includes(id)) + await require('../../commands/utility/admin/get/user').executeModal(interaction, database); + + else + client.warn(`No interaction matching ${id} was found.`); + } + catch (error) { + client.ierror(interaction, error, 'Error while executing button interaction'); + database.reply(interaction, 'ERROR_EXECUTE'); + } + }, +}; \ No newline at end of file diff --git a/src/events/interactions/stringSelectMenu.ts b/src/events/interactions/stringSelectMenu.ts new file mode 100644 index 0000000..7429d07 --- /dev/null +++ b/src/events/interactions/stringSelectMenu.ts @@ -0,0 +1,53 @@ +import { StringSelectMenuInteraction } from 'discord.js'; +import { CustomClient } from '../../bot'; + +module.exports = { + async execute(interaction : StringSelectMenuInteraction, database : any) { + const client = interaction.client as CustomClient; + + if (!database.getConnection()) { + client.error(`[Interaction ${interaction.id}] Failed to connect to the database.`); + interaction.reply({ content: 'There was an error while executing this command!', ephemeral: true }); + return; + } + const { models } = database.getConnection(); + + try { + await database.addInteraction(interaction); + } + catch (error) { + client.ierror(interaction, error, 'Error while adding interaction to the database (1)'); + try { + await database.addInteraction(interaction); + client.warn(`[Interaction ${interaction.id}] Interaction was added to the database after a second attempt.`); + } + catch (error2) { + client.ierror(interaction, error2, 'Error while adding interaction to the database (2)'); + interaction.reply({ content: 'There was an error while executing this command!\nPlease try again later.', ephemeral: true }); + return; + } + } + + const bannedUser = await models.UserBan.findOne({ where: { userId: interaction.user.id, pardonModId: null } }); + if (bannedUser) return database.reply(interaction, 'COMMAND_GLOBAL_USER_BANNED', { 'REASON': bannedUser.reason, 'BAN_ID': 'BU' + bannedUser.banId }); + if (interaction.inGuild()) { + const bannedGuild = await models.ServerBan.findOne({ where: { serverId: interaction.guildId, pardonModId: null } }); + if (bannedGuild) return database.reply(interaction, 'COMMAND_GLOBAL_GUILD_BANNED', { 'REASON': bannedGuild.reason, 'BAN_ID': 'BG' + bannedGuild.banId }); + } + const disabledCommand = await models.DisabledCommand.findOne({ where: { commandName: interaction.customId } }); + if (disabledCommand) return database.reply(interaction, 'INTERACTION_GLOBAL_DISABLED', { 'REASON': disabledCommand.reason, 'MOD_ID': disabledCommand.modId }); + + try { + const id = interaction.customId.split(';')[0]; + + if (id == 'settings_select') + await require('../../commands/main/settings').executeStringSelectMenu(interaction, database); + else + client.warn(`No interaction matching ${id} was found.`); + } + catch (error) { + client.ierror(interaction, error, 'Error while executing button interaction'); + database.reply(interaction, 'ERROR_EXECUTE'); + } + }, +}; \ No newline at end of file diff --git a/src/events/ready.ts b/src/events/ready.ts index c5feb72..7a54970 100644 --- a/src/events/ready.ts +++ b/src/events/ready.ts @@ -1,41 +1,11 @@ -import { ActivityType, Events } from 'discord.js'; +import { Events } from 'discord.js'; import { CustomClient } from '../bot'; module.exports = { name: Events.ClientReady, once: true, execute(client : CustomClient) { + // Setting up the database new (require('../utils/database'))().setup(client); - - // Waiting 15 minutes to compensate for the time it takes to start all shards (in addition to the client.startup check) - setTimeout(() => { - setInterval(() => setActivity(client), 30000); - }, 15 * 60 * 1000); }, -}; - - -let count = 1; -async function setActivity(client : CustomClient) { - client.trace(`Setting activity (${count})...`); - let display = 'jh220.de/ccbot'; - if (count == 1 || count == 2) { - const serverCount = (await client.shard.fetchClientValues('guilds.cache.size')).reduce((acc : number, guildCount : number) => acc + guildCount, 0); - const serverCountString = serverCount.toString().replace(/\B(? c.guilds.cache.reduce((acc, guild) => acc + guild.memberCount, 0)); - const memberCountString = memberCount.reduce((acc, memberCountTmp) => acc + memberCountTmp, 0).toString().replace(/\B(? setActivity(client), 30000); +} + +let count = 1; +async function setActivity(client : CustomClient) { + client.trace(`Setting activity (${count})...`); + let display = 'jh220.de/ccbot'; + if (count == 1 || count == 2) { + const serverCount = (await client.shard.fetchClientValues('guilds.cache.size')).reduce((acc : number, guildCount : number) => acc + guildCount, 0); + const serverCountString = serverCount.toString().replace(/\B(? c.guilds.cache.reduce((acc, guild) => acc + guild.memberCount, 0)); + const memberCountString = memberCount.reduce((acc, memberCountTmp) => acc + memberCountTmp, 0).toString().replace(/\B(? { const messages = require('../../messages.json'); - if (!Object.prototype.hasOwnProperty.call(messages, key)) + if (!Object.prototype.hasOwnProperty.call(messages, key)) { + this.logger.debug(`Message ${key} not found in messages.json.`); throw 'INVALID_KEY'; + } let message : string = messages[key]; @@ -176,6 +178,7 @@ module.exports = class database { message = message.replace(/{INTERACTION_ID}/g, interaction.id); if (interaction.channel) message = message.replace(/{CHANNEL_ID}/g, interaction.channel.id).replace(/{CHANNEL_NAME}/g, interaction.channel.name); message = message.replace(/{USER_ID}/g, interaction.user.id).replace(/{USER_NAME}/g, interaction.user.username); + message = message.replace(/{USER}/g, `<@${interaction.user.id}>`); if (interaction.inGuild()) { message = message.replace(/{SHARD_ID}/g, (interaction.guild.shardId + 1).toString());