diff --git a/src/client.ts b/src/client.ts index c1bfd3e..45e1e12 100644 --- a/src/client.ts +++ b/src/client.ts @@ -52,9 +52,11 @@ export default class TClient extends Discord.Client { Discord.GatewayIntentBits.GuildModeration, Discord.GatewayIntentBits.GuildInvites, Discord.GatewayIntentBits.GuildPresences, Discord.GatewayIntentBits.MessageContent, Discord.GatewayIntentBits.GuildMessages, Discord.GatewayIntentBits.DirectMessages - ], partials: [ - Discord.Partials.Channel, Discord.Partials.Message - ], allowedMentions: {users:[], roles:[]} + ], + partials: [ + Discord.Partials.Message + ], + allowedMentions: {users:[], roles:[]} }) this.config = ConfigHelper.loadConfig() as Config; this.setMaxListeners(50); diff --git a/src/commands/inviteinfo.ts b/src/commands/inviteinfo.ts index 013de03..77f4b2c 100644 --- a/src/commands/inviteinfo.ts +++ b/src/commands/inviteinfo.ts @@ -3,7 +3,7 @@ import TClient from '../client.js'; import MessageTool from '../helpers/MessageTool.js'; export default class InviteInfo { static async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){ - await client.fetchInvite(interaction.options.getString('code',true).replace(/(https:\/\/|discord.gg\/)/g,'')).then(async inviteData=> + await client.fetchInvite(interaction.options.getString('code',true).replace(/(https:\/\/)/g,'')).then(async inviteData=> await interaction.reply({embeds:[new client.embed() .setColor(client.config.embedColor).setURL(`https://discord.gg/${inviteData.code}`).setTitle(inviteData.guild.name).setDescription(MessageTool.concatMessage( `ID: \`${inviteData.guild.id}\``, diff --git a/src/commands/mp.ts b/src/commands/mp.ts index ccad14d..973899e 100644 --- a/src/commands/mp.ts +++ b/src/commands/mp.ts @@ -120,16 +120,12 @@ export default class MP { const reason = interaction.options.getString('reason'); const channel = interaction.guild.channels.cache.get(channels.activePlayers) as Discord.TextChannel; const embed = new client.embed().setColor(client.config.embedColor).setAuthor({name: interaction.member.displayName, iconURL: interaction.member.displayAvatarURL({size:1024})}).setTimestamp(); + const isLocked = channel.permissionsFor(interaction.guildId).has('SendMessages'); + const titleAction = isLocked ? '🔒 Locked' : '🔓 Unlocked'; - if (channel.permissionsFor(interaction.guildId).has('SendMessages')) { - channel.permissionOverwrites.edit(interaction.guildId, {SendMessages: false}, {type: 0, reason: `Locked by ${interaction.member.displayName}`}); - channel.send({embeds: [embed.setTitle('🔒 Locked').setDescription(`**Reason:**\n${reason}`)]}); - interaction.reply({content: `${MessageTool.formatMention(channels.activePlayers, 'channel')} locked successfully`, ephemeral: true}); - } else { - channel.permissionOverwrites.edit(interaction.guildId, {SendMessages: true}, {type: 0, reason: `Unlocked by ${interaction.member.displayName}`}); - channel.send({embeds: [embed.setTitle('🔓 Unlocked').setDescription(`**Reason:**\n${reason}`)]}); - interaction.reply({content: `${MessageTool.formatMention(channels.activePlayers, 'channel')} unlocked successfully`, ephemeral: true}); - } + channel.permissionOverwrites.edit(interaction.guildId, {SendMessages: !isLocked}, {type: 0, reason: `${isLocked ? 'Locked' : 'Unlocked'} by ${interaction.member.displayName}`}); + channel.send({embeds: [embed.setTitle(titleAction).setDescription(`**Reason:**\n${reason}`)]}); + interaction.reply({content: `${MessageTool.formatMention(channels.activePlayers, 'channel')} ${isLocked ? 'locked' : 'unlocked'} successfully`, ephemeral: true}); }, start: async()=>{ if (client.config.dcServer.id === interaction.guildId) { diff --git a/src/commands/statistics.ts b/src/commands/statistics.ts index 74740ef..574a8fa 100644 --- a/src/commands/statistics.ts +++ b/src/commands/statistics.ts @@ -28,22 +28,23 @@ export default class Statistics { const nameLen = Math.max(...cmdUses.map(x=>x.command.data.name.length), col[0].length) + 2; const usesLen = Math.max(...cmdUses.map(x=>x.uses.toString().length), col[1].length) + 1; - const rows = [`${col[0] + ' '.repeat(nameLen-col[0].length)}|${' '.repeat(usesLen-col[1].length) + col[1]}\n`, '-'.repeat(nameLen) + '-'.repeat(usesLen) + '\n']; + const rows = [`${col[0].padEnd(nameLen)}${col[1].padStart(usesLen)}\n`, '־'.repeat(nameLen + usesLen) + '\n']; cmdUses.forEach(cmd=>{ const name = cmd.command.data.name; const uses = cmd.uses.toString(); - rows.push(`${name+' '.repeat(nameLen-name.length)}${' '.repeat(usesLen-uses.length)+uses}\n`); + rows.push(`${name.padEnd(nameLen)}${uses.padStart(usesLen)}\n`); }); - if (rows.join('').length > 1024) { - let field = ''; - rows.forEach(r=>{ - if (field.length+r.length > 1024) { - embed.addFields({name: '\u200b', value: `\`\`\`\n${field}\`\`\``}); - field = r; - } - }); - embed.addFields({name: '\u200b', value: `\`\`\`\n${field}\`\`\``}); - } else embed.addFields({name: '\u200b', value: `\`\`\`\n${rows.join('')}\`\`\``}); + + const fieldChunks = []; + let field = ''; + rows.forEach(r=>{ + if (field.length+r.length > 1024) { + fieldChunks.push(field); + field = r; + } else field += r; + }); + fieldChunks.push(field); + fieldChunks.forEach(field=>embed.addFields({name: '\u200b', value: `\`\`\`\n${field}\`\`\``})); const pkg = JSON.parse(readFileSync('package.json', 'utf8')); embed.addFields( diff --git a/src/events/messageDelete.ts b/src/events/messageDelete.ts index 14ed391..dcaad2f 100644 --- a/src/events/messageDelete.ts +++ b/src/events/messageDelete.ts @@ -3,7 +3,7 @@ import TClient from '../client.js'; import Logger from '../helpers/Logger.js'; import {disabledChannels} from '../index.js'; export default class MessageDelete { - static run(client:TClient, msg:Discord.Message){ + static run(client:TClient, msg:Discord.Message|Discord.PartialMessage){ if (!client.config.botSwitches.logs) return; if (msg.guild?.id != client.config.dcServer.id || msg.partial || msg.author.bot || disabledChannels.includes(msg.channelId)) return; if (Discord.DiscordAPIError.name === '10008') return Logger.console('log', 'MsgDelete', 'Caught an unexpected error returned by Discord API. (Unknown Message)'); diff --git a/src/events/messageDeleteBulk.ts b/src/events/messageDeleteBulk.ts index 3ea8858..66a50b6 100644 --- a/src/events/messageDeleteBulk.ts +++ b/src/events/messageDeleteBulk.ts @@ -1,7 +1,7 @@ import Discord from 'discord.js'; import TClient from '../client.js'; export default class MessageDeleteBulk { - static run(client:TClient, messages:Discord.Collection>, channel:Discord.GuildTextBasedChannel){ + static run(client:TClient, messages:Discord.Collection|Discord.PartialMessage>, channel:Discord.GuildTextBasedChannel){ if (!client.config.botSwitches.logs || channel.guildId != client.config.dcServer.id) return; if (messages.some(msg=>{ msg.author?.username === undefined ?? null; diff --git a/src/events/messageUpdate.ts b/src/events/messageUpdate.ts index f50bd48..debe913 100644 --- a/src/events/messageUpdate.ts +++ b/src/events/messageUpdate.ts @@ -3,9 +3,9 @@ import TClient from '../client.js'; import MessageTool from '../helpers/MessageTool.js'; import {disabledChannels, rawSwitches} from '../index.js'; export default class MessageUpdate { - static async run(client:TClient, oldMsg:Discord.Message, newMsg:Discord.Message){ + static async run(client:TClient, oldMsg:Discord.Message|Discord.PartialMessage, newMsg:Discord.Message){ if (!client.config.botSwitches.logs) return; - if (oldMsg.guild?.id != client.config.dcServer.id || oldMsg.author === null || oldMsg?.author.bot || oldMsg.partial || newMsg.partial || !newMsg.member || disabledChannels.includes(newMsg.channelId)) return; + if (oldMsg.guild?.id != client.config.dcServer.id || oldMsg.author === null || oldMsg?.author.bot || newMsg.partial || !newMsg.member || disabledChannels.includes(newMsg.channelId)) return; if (await client.prohibitedWords.findWord(newMsg.content.toLowerCase().replaceAll(/[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?\n?0-9]|[]|ing\b/g, '').split(' ').join('')) && (!MessageTool.isStaff(newMsg.member))) newMsg.delete(); if (!rawSwitches.MESSAGE_UPDATE || (rawSwitches.MESSAGE_UPDATE && newMsg.content !== oldMsg.content)) { rawSwitches.MESSAGE_UPDATE = true; diff --git a/src/helpers/MessageTool.ts b/src/helpers/MessageTool.ts index 1993442..f46f428 100644 --- a/src/helpers/MessageTool.ts +++ b/src/helpers/MessageTool.ts @@ -1,18 +1,17 @@ import Discord from 'discord.js'; import ConfigHelper from './ConfigHelper.js'; const config = ConfigHelper.readConfig(); -type RoleKeys = keyof typeof config.dcServer.roles; export default class MessageTool { - static embedStruct(color:Discord.ColorResolvable, title:string, description?:string|null, image?:string|null) { + public static embedStruct(color:Discord.ColorResolvable, title:string, description?:string|null, image?:string|null) { const embed = new Discord.EmbedBuilder().setColor(color).setTitle(title); if (description) embed.setDescription(description); if (image) embed.setImage(image); return embed } - static concatMessage =(...messages:string[])=>messages.join('\n'); - static formatMention =(mention:string, type:'user'|'channel'|'role')=>`<${type === 'role' ? '@&' : type === 'channel' ? '#' : '@'}${mention}>`; - static isStaff =(guildMember:Discord.GuildMember)=>config.dcServer.staffRoles.map((x:string)=>config.dcServer.roles[x]).some((x:string)=>guildMember.roles.cache.has(x)); - static youNeedRole =(interaction:Discord.CommandInteraction, role:RoleKeys)=>interaction.reply(`You do not have ${this.formatMention(config.dcServer.roles[role], 'role')} role to use this command.`); - static isModerator =(guildMember:Discord.GuildMember)=>config.dcServer.staffRoles.filter((x:string)=>/^admin|^dcmod/.test(x)).map((x:string)=>config.dcServer.roles[x]).some((x:string)=>guildMember.roles.cache.has(x)); + public static concatMessage =(...messages:string[])=>messages.join('\n'); + public static formatMention =(mention:string, type:'user'|'channel'|'role')=>`<${type === 'role' ? '@&' : type === 'channel' ? '#' : '@'}${mention}>`; + public static isStaff =(guildMember:Discord.GuildMember)=>config.dcServer.staffRoles.map((x:string)=>config.dcServer.roles[x]).some((x:string)=>guildMember.roles.cache.has(x)); + public static youNeedRole =(interaction:Discord.CommandInteraction, role:keyof typeof config.dcServer.roles)=>interaction.reply(`You do not have ${this.formatMention(config.dcServer.roles[role], 'role')} role to use this command.`); + public static isModerator =(guildMember:Discord.GuildMember)=>config.dcServer.staffRoles.filter((x:string)=>/^admin|^dcmod/.test(x)).map((x:string)=>config.dcServer.roles[x]).some((x:string)=>guildMember.roles.cache.has(x)); } diff --git a/src/index.ts b/src/index.ts index cc09e95..6ec8e78 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,27 +9,13 @@ import MPModule, {refreshTimerSecs} from './modules/MPModule.js'; import UsernameHelper from './helpers/UsernameHelper.js'; import {Punishment, RawGatewayPacket, RawMessageDelete, RawMessageUpdate} from 'src/interfaces'; import {readFileSync} from 'node:fs'; -import {execSync} from 'node:child_process'; export const disabledChannels = ['548032776830582794', '541677709487505408', '949380187668242483']; // Error handler function _(error:Error, type:string) { if (JSON.parse(readFileSync('src/errorBlocklist.json', 'utf8')).includes(error.message)) return; console.error(error); - (client.channels.resolve(client.config.dcServer.channels.errors) as Discord.TextChannel | null)?.send({embeds: [new client.embed().setColor('#560000').setTitle('Error caught!').setFooter({text: `Error type: ${type}`}).setDescription(`**Error:**\n\`\`\`${error.message}\`\`\`**Stack:**\n\`\`\`${`${UsernameHelper(error.stack)}`.slice(0, 2500)}\`\`\``)]}) - - if (error.message.includes('could not fdatasync file')) { - client.users.createDM(client.config.whitelist[1]).then(u=>u.send({ - embeds: [new client.embed() - .setColor(client.config.embedColorYellow) - .setTitle('Database error') - .setDescription('I couldn\'t write to the database due to filesystem issues, shutting down...') - .setFields({name: 'Error message', value: `\`\`\`${error.message}\`\`\``}) - .setFooter({text: `Error type: ${type}`}) - ] - })); - setTimeout(()=>execSync('pm2 stop 0', {shell: 'bash'}), 5500); - } + (client.channels.resolve(client.config.dcServer.channels.errors) as Discord.TextChannel | null)?.send({embeds: [new client.embed().setColor('#560000').setTitle('Error caught!').setFooter({text: `Error type: ${type}`}).setDescription(`**Error:**\n\`\`\`${error.message}\`\`\`**Stack:**\n\`\`\`${`${UsernameHelper(error.stack)}`.slice(0, 2500)}\`\`\``)]}); } process.on('unhandledRejection', (error: Error)=>_(error, 'unhandledRejection')); process.on('uncaughtException', (error: Error)=>_(error, 'uncaughtException')); @@ -106,24 +92,15 @@ client.on('raw', async (packet:RawGatewayPacket)=>{ if (typeof packet.d.content === 'undefined') return; const channel = client.channels.cache.get(packet.d.channel_id) as Discord.TextBasedChannel; - const old_message = await channel.messages.fetch(packet.d.id); - const new_message = await channel.messages.fetch(packet.d.id); + const message = await channel.messages.fetch(packet.d.id); - client.emit('messageUpdate', old_message, new_message); + client.emit('messageUpdate', message, message); }); client.on('raw', async (packet:RawGatewayPacket)=>{ if (rawSwitches[packet.t]) return; if (packet.t !== 'MESSAGE_DELETE' || packet.d.guild_id != client.config.dcServer.id || disabledChannels.includes(packet.d.channel_id)) return; - (client.channels.resolve(client.config.dcServer.channels.logs) as Discord.TextChannel).send({ - embeds: [new client.embed() - .setColor(client.config.embedColorRed) - .setTitle('Message deleted') - .addFields( - {name: 'This was received over raw API event', value: '\u200b'}, - {name: 'Channel', value: `<#${packet.d.channel_id}>`}, - ).setTimestamp() - ] - }); + + Logger.console('log', 'RawEvent:Del', `Message was deleted in #${(client.channels.resolve(packet.d.channel_id) as Discord.TextChannel).name}`); rawSwitches[packet.t] = true; });