diff --git a/src/client.ts b/src/client.ts index 2a8feeb..9036c0d 100644 --- a/src/client.ts +++ b/src/client.ts @@ -27,7 +27,7 @@ interface CommandInfoOpt { import Discord, { Client, GatewayIntentBits, Partials } from 'discord.js'; import fs from 'node:fs'; import { Database } from './database'; -import timeNames from './timeNames.js'; +import timeNames from './timeNames'; export class TClient extends Client { invites: any; commands: any; diff --git a/src/commands/ban.ts b/src/commands/ban.ts new file mode 100644 index 0000000..6b3c338 --- /dev/null +++ b/src/commands/ban.ts @@ -0,0 +1,12 @@ +import Discord from 'discord.js'; +import { TClient } from 'src/client'; +export default { + async run(client: TClient, message: Discord.Message, args: any){ + if (!message.inGuild() || !message.channel) return; + client.punish(client, message, args, 'ban'); + }, + name: 'ban', + description: 'Ban a member from server.', + usage: ['user mention or id', '?time', '?reason'], + category: 'moderation' +} \ No newline at end of file diff --git a/src/commands/botlog.ts b/src/commands/botlog.ts new file mode 100644 index 0000000..d3a6cee --- /dev/null +++ b/src/commands/botlog.ts @@ -0,0 +1,12 @@ +import Discord from 'discord.js'; +import { TClient } from 'src/client'; +export default { + async run(client: TClient, message: Discord.Message){ + if (!client.config.eval.whitelist.includes(message.author.id)) return message.reply('You\'re not allowed to use this command'); + (client.channels.resolve(client.config.mainServer.channels.console) as Discord.TextChannel).send({content: `Uploaded the current console dump as of `, files: ['../.pm2/logs/Daggerbot-out.log']}) + await message.reply({content: 'It has been uploaded to dev server.'}) + }, + name: 'botlog', + description: 'Retrieves the bot\'s log from host and sends it to appropriate channel.', + category: 'bot' +} \ No newline at end of file diff --git a/src/commands/help.ts b/src/commands/help.ts new file mode 100644 index 0000000..c10d761 --- /dev/null +++ b/src/commands/help.ts @@ -0,0 +1,78 @@ +import Discord,{ActionRowBuilder,ButtonBuilder} from 'discord.js'; +import { TClient } from 'src/client'; +let msg; +function helpPage(pageNumber: number, client: TClient, message: Discord.Message, args: any, toEdit = false){ + async function onEnd(msg){ + await msg.edit({content: '_Removed to save space._', embeds: [], components: []}); + }; + let pageIndex = pageNumber || 0; + const pageInfo = client.commandPages[pageIndex]; + let text = ''; + client.commands.filter(command=>!command.hidden && command.category === pageInfo.category && command.page === pageInfo.page).forEach(command=>{ + text += client.commandInfo(client, command, client.helpDefaultOptions); + }); + const embed = new client.embed().setColor(client.config.embedColor).setTitle(`__Commands: ${pageInfo.name}__`).setDescription(text); + if (toEdit){ + return embed; + } else { + message.reply({embeds: [embed], fetchReply: true, components: [new ActionRowBuilder().addComponents(new ButtonBuilder().setStyle('Secondary').setCustomId('back').setEmoji('◀'), new ButtonBuilder().setStyle('Secondary').setCustomId('forward').setEmoji('▶'))]}) + // add buttons to go forward or backwards + .then(async botMessage=>{ + let endTimestamp = Date.now()+90000; + const filter = (interaction: Discord.ChatInputCommandInteraction)=>{ + return message.author.id === interaction.user.id; + }; + const collector = botMessage.createMessageComponentCollector({filter, time: 90000});; + collector.on('collect', async(button: Discord.ButtonInteraction)=>{ + endTimestamp = Date.now()+60000; + if (button.customId === 'back'){ + if (pageIndex - 1<0) pageIndex = client.commandPages.length; + pageIndex--; + button.update({embeds: [helpPage(pageIndex, client, message, args, true)]}) + } else if (button.customId === 'forward'){ + if (pageIndex + 1>=client.commandPages.length) pageIndex = -1; + pageIndex++; + button.update({embeds: [helpPage(pageIndex, client, message, args, true)]}) + } + }); + async function onEnd(){ + await botMessage.edit({content: '_Removed to save space._', embeds: [], components: []}) + } + const interval = setInterval(()=>{ + if (Date.now()>endTimestamp){ + collector.stop(); + onEnd(); + } + },5000); + collector.on('end', async()=>{ + onEnd(); + clearInterval(interval) + }) + }) + } +} +export default { + async run(client: TClient, message: Discord.Message, args: any){ + // if they ask for specific page # + if (parseInt(args[1])){ + if (!client.commandPages[parseInt(args[1]) - 1]) return message.reply('That page number doesn\'t exist.'); + return helpPage(parseInt(args[1]) - 1, client, message, args); + } + // category (name) + if (client.commandPages.some(x=>x.category.toLowerCase() === args.slice(1).join(' ').toLowerCase())){ + return helpPage(client.commandPages.map(x=>x.category.toLowerCase()).indexOf(args.slice(1).join(' ').toLowerCase()), client, message, args) + } + // or command (name) + const command = client.commands.find(x=>x.name === args[1] || x.alias?.includes(args[1])); + if (command){ + const embed = new client.embed().setColor(client.config.embedColor).setTitle(`__Commands: ${command.name}__`).setDescription(client.commandInfo(client, command, {insertNewline: true, parts: ['name', 'usage', 'description', 'shortDescription', 'alias', 'category', 'autores'], titles: ['name', 'usage', 'shortDescription', 'alias', 'category', 'autores']})); + return message.reply({embeds: [embed]}); + } + // if run() still hasnt been returned, send category 0 page 1 + return helpPage(undefined, client, message, args); + }, + name: 'help', + description: 'Command information and their usage.', + usage: ['?command / ?category / ?page'], + category: 'bot' +} \ No newline at end of file diff --git a/src/commands/kick.ts b/src/commands/kick.ts new file mode 100644 index 0000000..281d5b2 --- /dev/null +++ b/src/commands/kick.ts @@ -0,0 +1,12 @@ +import Discord from 'discord.js'; +import { TClient } from 'src/client'; +export default { + async run(client: TClient, message: Discord.Message, args: any){ + if (!message.inGuild() || !message.channel) return; + client.punish(client, message, args, 'kick'); + }, + name: 'kick', + description: 'Kick a member from server.', + usage: ['user mention or id', '?reason'], + category: 'moderation' +} \ No newline at end of file diff --git a/src/commands/ping.ts b/src/commands/ping.ts index 716f795..27afb7b 100644 --- a/src/commands/ping.ts +++ b/src/commands/ping.ts @@ -1,7 +1,7 @@ import { TClient } from "src/client"; import Discord from 'discord.js'; export default { - async run(client: TClient, message: Discord.Message, args: any){ + async run(client: TClient, message: Discord.Message){ const msg = await message.reply(`Pinging...`) const time = msg.createdTimestamp - message.createdTimestamp; msg.edit(`Websocket: \`${client.ws.ping}\`ms\nBot: \`${time}\``) diff --git a/src/commands/unpunish.ts b/src/commands/unpunish.ts new file mode 100644 index 0000000..82a5c75 --- /dev/null +++ b/src/commands/unpunish.ts @@ -0,0 +1,12 @@ +import Discord from 'discord.js'; +import { TClient } from 'src/client'; +export default { + async run(client: TClient, message: Discord.Message, args: any){ + client.unPunish(client, message, args); + }, + name: 'unpunish', + description: 'Remove an active punishment from a user or an entry from their punishment history.', + usage: ['case id', '?reason'], + alias: ['unban', 'unmute', 'unwarn'], + category: 'moderation' +} \ No newline at end of file diff --git a/src/commands/update.ts b/src/commands/update.ts new file mode 100644 index 0000000..d079903 --- /dev/null +++ b/src/commands/update.ts @@ -0,0 +1,23 @@ +import Discord from 'discord.js'; +import { TClient } from 'src/client'; +import { exec } from 'node:child_process'; +export default { + async run(client: TClient, message: Discord.Message, args: any){ + if (!client.config.eval.whitelist.includes(message.author.id)) return message.reply('You\'re not allowed to use this command') + const msg = await message.reply({content: 'Pulling...', fetchReply: true}) + exec( + 'git pull',(err:Error,stdout)=>{ + if (err){ + msg.edit(`Pull failed:\n\`\`\`${err.message}\`\`\``) + } else if (stdout.includes('Already up to date')){ + msg.edit(`Pull aborted:\nUp to date with the repository`) + } else { + setTimeout(()=>{msg.edit('Restarting...').then(()=>eval(process.exit(-1)))},1000) + } + } + ) + }, + name: 'update', + description: 'Pull from repository and restart.', + category: 'bot' +} \ No newline at end of file diff --git a/src/commands/warn.ts b/src/commands/warn.ts new file mode 100644 index 0000000..dde1d62 --- /dev/null +++ b/src/commands/warn.ts @@ -0,0 +1,12 @@ +import Discord from 'discord.js'; +import { TClient } from 'src/client'; +export default { + async run(client: TClient, message: Discord.Message, args: any){ + if (!message.inGuild() || !message.channel) return; + client.punish(client, message, args, 'warn'); + }, + name: 'kick', + description: 'Warn a member.', + usage: ['user mention or id', '?reason'], + category: 'moderation' +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 9bcb25d..0d2fc9d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,5 @@ import Discord = require('discord.js'); -const TClient = require('./client'); +import { TClient } from './client'; const client = new TClient; client.init(); import fs = require('node:fs'); @@ -12,15 +12,16 @@ client.on('ready', async()=>{ // Playing: 0, Streaming (Requires YT/Twitch URL to work): 1, Listening to: 2, Watching: 3, Competing in: 5 }, 60000); setInterval(()=>{ - client.guilds.cache.get(client.config.mainServer.id).invites.fetch().then((invs: any[])=>{ - invs.forEach(async(inv: { code: any; uses: any; inviter: { id: any; }; })=>{ - client.invites.set(inv.code, {uses: inv.uses, creator: inv.inviter.id}) + const guild = client.guilds.cache.get(client.config.mainServer.id) as Discord.Guild; + guild.invites.fetch().then((invs)=>{ + invs.forEach(async(inv)=>{ + client.invites.set(inv.code, {uses: inv.uses, creator: inv.inviterId}) }) }) }, 500000); - console.log(`${client.user.tag} has logged into Discord API and now ready for operation`) - console.log(client.config.botSwitches) - client.channels.resolve(client.config.mainServer.channels.bot_status).send(`${client.user.username} is active`); + console.log(`${client.user.tag} has logged into Discord API and now ready for operation`); + console.log(client.config.botSwitches); + (client.channels.resolve(client.config.mainServer.channels.bot_status) as Discord.TextChannel).send(`${client.user.username} is active`); // Event handler const eventFiles = fs.readdirSync('./events').filter(file=>file.endsWith('.js')); @@ -32,16 +33,16 @@ client.on('ready', async()=>{ // Handle errors process.on('unhandledRejection', async(error: Error)=>{ - console.log(error) - client.channels.resolve(client.config.mainServer.channels.errors).send({embeds: [new client.embed().setColor('#420420').setTitle('Error caught!').setDescription(`**Error:** \`${error.message}\`\n\n**Stack:** \`${`${error.stack}`.slice(0, 2500)}\``)]}) + console.log(error); + (client.channels.resolve(client.config.mainServer.channels.errors) as Discord.TextChannel).send({embeds: [new client.embed().setColor('#420420').setTitle('Error caught!').setDescription(`**Error:** \`${error.message}\`\n\n**Stack:** \`${`${error.stack}`.slice(0, 2500)}\``)]}) }); process.on('uncaughtException', async(error: Error)=>{ - console.log(error) - client.channels.resolve(client.config.mainServer.channels.errors).send({embeds: [new client.embed().setColor('#420420').setTitle('Error caught!').setDescription(`**Error:** \`${error.message}\`\n\n**Stack:** \`${`${error.stack}`.slice(0, 2500)}\``)]}) + console.log(error); + (client.channels.resolve(client.config.mainServer.channels.errors) as Discord.TextChannel).send({embeds: [new client.embed().setColor('#420420').setTitle('Error caught!').setDescription(`**Error:** \`${error.message}\`\n\n**Stack:** \`${`${error.stack}`.slice(0, 2500)}\``)]}) }); process.on('error', async(error: Error)=>{ - console.log(error) - client.channels.resolve(client.config.mainServer.channels.errors).send({embeds: [new client.embed().setColor('#420420').setTitle('Error caught!').setDescription(`**Error:** \`${error.message}\`\n\n**Stack:** \`${`${error.stack}`.slice(0, 2500)}\``)]}) + console.log(error); + (client.channels.resolve(client.config.mainServer.channels.errors) as Discord.TextChannel).send({embeds: [new client.embed().setColor('#420420').setTitle('Error caught!').setDescription(`**Error:** \`${error.message}\`\n\n**Stack:** \`${`${error.stack}`.slice(0, 2500)}\``)]}) }); // Command handler