diff --git a/.env.template b/.env.template index d76c3d7..6a865a0 100644 --- a/.env.template +++ b/.env.template @@ -6,4 +6,5 @@ DEV=true/false (this env var is optional) DEVIDS=list of userids that can run commands when in dev mode (this env var is optional) KUMA_PUSH_URL=single push URL/multiple URLs in shard order (this env var is optional) SHARD_COUNT=number of shards, if not provided it will use discords recommendation (this env var is optional) -LIMITS=false, please keep this as false but if set to true it will enable limits and also monetization (this env var is optional) \ No newline at end of file +LIMITS=false, please keep this as false but if set to true it will enable limits and also monetization (this env var is optional) +MODERATORS=list of userids that are considered global moderators (this env var is optional but highly recommended if you want to moderate the bot for others) \ No newline at end of file diff --git a/apps/bot/src/commands/moderation/nick.ts b/apps/bot/src/commands/moderation/nick.ts new file mode 100644 index 0000000..a11fa3e --- /dev/null +++ b/apps/bot/src/commands/moderation/nick.ts @@ -0,0 +1,85 @@ +import { Command } from '@sapphire/framework'; +import { GuildMember, MessageFlags, PermissionsBitField } from 'discord.js'; +import { emojis } from '#utils/emoji.js'; + +export class NickCommand extends Command { + public constructor(context: Command.LoaderContext, options: Command.Options) { + super(context, { ...options }); + } + + public override registerApplicationCommands(registry: Command.Registry) { + registry.registerChatInputCommand((builder: any) => + builder + .setName('nick') + .setDescription("Change a member's nickname.") + .addUserOption((option: any) => + option.setName('member').setDescription('Select a member to change their nickname').setRequired(true) + ) + .addStringOption((option: any) => + option.setName('nickname').setDescription('Nickname (leave empty to reset)') + ) + ); + } + + public override async chatInputRun(interaction: Command.ChatInputCommandInteraction) { + if (!interaction.inCachedGuild()) { + await interaction.reply({ + content: `${emojis.rightArrow2} This command can only be used in a server.`, + flags: MessageFlags.Ephemeral + }); + return; + } + + const member = interaction.member as GuildMember; + + if (!member.permissions.has(PermissionsBitField.Flags.ManageNicknames)) { + await interaction.reply({ + content: `${emojis.rightArrow2} You do not have permission to manage nicknames.`, + flags: MessageFlags.Ephemeral + }); + return; + } + + const targetMember = interaction.options.getMember('member'); + const nickname = interaction.options.getString('nickname') ?? null; + + if (!targetMember) { + await interaction.reply({ + content: `${emojis.rightArrow2} That user is not in this server.`, + flags: MessageFlags.Ephemeral + }); + return; + } + + if (targetMember.id === interaction.guild.ownerId) { + await interaction.reply({ + content: `${emojis.rightArrow2} You cannot change the server owner's nickname.`, + flags: MessageFlags.Ephemeral + }); + return; + } + + if (!targetMember.manageable) { + await interaction.reply({ + content: `${emojis.rightArrow2} I cannot manage this member's nickname.`, + flags: MessageFlags.Ephemeral + }); + return; + } + + try { + await targetMember.setNickname(nickname); + await interaction.reply({ + content: nickname + ? `${emojis.rightArrow2} Set <@${targetMember.user.id}>'s nickname to **${nickname}**.` + : `${emojis.rightArrow2} Reseted <@${targetMember.user.id}>'s nickname.`, + flags: MessageFlags.Ephemeral + }); + } catch { + await interaction.reply({ + content: `${emojis.rightArrow2} Failed to update <@${targetMember.user.id}>'s nickname.`, + flags: MessageFlags.Ephemeral + }); + } + } +} diff --git a/apps/bot/src/commands/promotion/bot.ts b/apps/bot/src/commands/promotion/bot.ts index 613e8cc..b391e4c 100644 --- a/apps/bot/src/commands/promotion/bot.ts +++ b/apps/bot/src/commands/promotion/bot.ts @@ -13,6 +13,6 @@ export class BotCommand extends Command { } public override async chatInputRun(interaction: Command.ChatInputCommandInteraction) { - await interaction.reply(`${emojis.rightArrow1} https://questfoundation.dev/bot/invite`); + await interaction.reply(`${emojis.rightArrow1} https://duckorg.com/bot/invite`); } } \ No newline at end of file diff --git a/apps/bot/src/commands/promotion/invite.ts b/apps/bot/src/commands/promotion/invite.ts index 8620ad3..8996d2a 100644 --- a/apps/bot/src/commands/promotion/invite.ts +++ b/apps/bot/src/commands/promotion/invite.ts @@ -13,6 +13,6 @@ export class InviteCommand extends Command { } public override async chatInputRun(interaction: Command.ChatInputCommandInteraction) { - await interaction.reply(`${emojis.rightArrow1} https://questfoundation.dev/bot/invite`); + await interaction.reply(`${emojis.rightArrow1} https://duckorg.com/bot/invite`); } } \ No newline at end of file diff --git a/apps/bot/src/commands/utility/help.ts b/apps/bot/src/commands/utility/help.ts index 9048566..3a4b1d0 100644 --- a/apps/bot/src/commands/utility/help.ts +++ b/apps/bot/src/commands/utility/help.ts @@ -1,6 +1,6 @@ import { Command } from '@sapphire/framework'; import { emojis } from '#utils/emoji.js'; -import { MessageFlags } from 'discord.js'; +import { EmbedBuilder, MessageFlags } from 'discord.js'; export class HelpCommand extends Command { public constructor(context: Command.LoaderContext, options: Command.Options) { @@ -26,9 +26,14 @@ export class HelpCommand extends Command { }) .join('\n'); + const embed = new EmbedBuilder() + .setTitle('Commands') + .setDescription(commandList) + .addFields({ name: 'Links', value: '**Status:** https://status.questfoundation.dev/\n**Official Discord Server:** https://discord.gg/F4HYE8frK2\n**Documentation:** https://docs.questfoundation.dev/' }); + await interaction.reply({ - content: commandList + `\n\n**Status:** https://status.questfoundation.dev/\n**Official Discord Server:** https://discord.gg/F4HYE8frK2\n**Documentation:** https://docs.questfoundation.dev/`, - flags: MessageFlags.SuppressEmbeds | MessageFlags.Ephemeral + embeds: [embed], + flags: MessageFlags.Ephemeral }); } } \ No newline at end of file diff --git a/apps/bot/src/lib/logging.ts b/apps/bot/src/lib/logging.ts index d5f536e..a940030 100644 --- a/apps/bot/src/lib/logging.ts +++ b/apps/bot/src/lib/logging.ts @@ -28,7 +28,7 @@ export async function isLoggingChannel(guild: Guild, channelId: string | null | export async function getRecentAuditLogEntry(guild: Guild, type: AuditLogEvent, targetId: string) { const auditLogs = await guild.fetchAuditLogs({ type, limit: 5 }).catch((err) => { - console.error(err); + if (err?.code !== 10004) console.error(err); return null; }); diff --git a/apps/bot/src/listeners/guildDelete.ts b/apps/bot/src/listeners/guildDelete.ts new file mode 100644 index 0000000..d81b7bc --- /dev/null +++ b/apps/bot/src/listeners/guildDelete.ts @@ -0,0 +1,13 @@ +import { Listener } from '@sapphire/framework'; +import { Events, type Guild } from 'discord.js'; +import { prisma } from '#lib/prisma.js'; + +export class GuildDeleteListener extends Listener { + public constructor(context: Listener.LoaderContext, options: Listener.Options) { + super(context, { ...options, event: Events.GuildDelete }); + } + + public async run(guild: Guild) { + await prisma.server.delete({ where: { id: guild.id } }).catch(() => null); + } +} diff --git a/apps/bot/src/listeners/messageDelete.ts b/apps/bot/src/listeners/messageDelete.ts index 4ffa91d..af2bf5f 100644 --- a/apps/bot/src/listeners/messageDelete.ts +++ b/apps/bot/src/listeners/messageDelete.ts @@ -19,7 +19,7 @@ export class MessageDeleteListener extends Listener .addFields( { name: 'Channel', value: message.channel?.toString() ?? 'Unknown', inline: true }, { name: 'Author', value: message.author?.tag ?? 'Unknown', inline: true }, - { name: 'Content', value: truncate(message instanceof Object ? (message as Message).content : undefined) || '-' } + { name: 'Content', value: truncate(message instanceof Object ? (message as Message).content : undefined, 1024) || '-' } ) .setFooter({ text: `ID: ${message.id}` }) .setTimestamp(); diff --git a/apps/bot/src/listeners/messageUpdate.ts b/apps/bot/src/listeners/messageUpdate.ts index adcd4ae..dffb5e5 100644 --- a/apps/bot/src/listeners/messageUpdate.ts +++ b/apps/bot/src/listeners/messageUpdate.ts @@ -25,8 +25,8 @@ export class MessageUpdateListener extends Listener { .addFields( { name: 'Channel', value: newMsg.channel?.toString() ?? 'Unknown', inline: true }, { name: 'Author', value: newMsg.author?.tag ?? oldMsg?.author?.tag ?? 'Unknown', inline: true }, - { name: 'Before', value: truncate(oldMsg?.content) || '-' }, - { name: 'After', value: truncate(newMsg.content) || '-' } + { name: 'Before', value: truncate(oldMsg?.content, 1023) || '-' }, + { name: 'After', value: truncate(newMsg.content, 1023) || '-' } ) .setFooter({ text: `ID: ${newMsg.id}` }) .setTimestamp();