[READ-ONLY] Mirror of https://github.com/maybeanerd/MagiBot. MagiBot - the community building discord bot that adds joinsounds to your voice channels! discord.gg/2Evcf4T
bot bots community discord gaming hacktoberfest sound
1

Configure Feed

Select the types of activity you want to include in your feed.

feat: add /salt (#295)


* fix: pin discord types version

- default to pin versions on new installs
- slightly adjust message when using old commands

* fix: clean up slash command type

* feat: migrate k.salt to slash commands

only the non-admin version tho

Sebastian Di Luzio (Apr 7, 2022, 8:27 PM +0200) b75691c1 82e5f62f

+348 -362
+1
.npmrc
··· 1 + save-exact=true
+42 -30
package-lock.json
··· 16 16 "axios": "^0.26.1", 17 17 "blapi": "^2.2.2", 18 18 "bufferutil": "^4.0.6", 19 - "discord-api-types": "^0.26.1", 19 + "discord-api-types": "0.26.1", 20 20 "discord.js": "^13.6.0", 21 21 "dotenv": "^16.0.0", 22 22 "ffmpeg-static": "^5.0.0", ··· 1138 1138 "version": "0.4.14", 1139 1139 "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz", 1140 1140 "integrity": "sha1-j2G3XN4BKy6esoTUVFWDtWQ7Yas=" 1141 - }, 1142 - "node_modules/broadway/node_modules/nconf": { 1143 - "version": "0.6.9", 1144 - "resolved": "https://registry.npmjs.org/nconf/-/nconf-0.6.9.tgz", 1145 - "integrity": "sha1-lXDvFe1vmuays8jV5xtm0xk81mE=", 1146 - "dependencies": { 1147 - "async": "0.2.9", 1148 - "ini": "1.x.x", 1149 - "optimist": "0.6.0" 1150 - }, 1151 - "engines": { 1152 - "node": ">= 0.4.0" 1153 - } 1154 1141 }, 1155 1142 "node_modules/broadway/node_modules/pkginfo": { 1156 1143 "version": "0.3.1", ··· 3935 3922 } 3936 3923 }, 3937 3924 "node_modules/minimist": { 3938 - "version": "1.2.5", 3939 - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", 3940 - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" 3925 + "version": "1.2.6", 3926 + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", 3927 + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" 3941 3928 }, 3942 3929 "node_modules/minipass": { 3943 3930 "version": "3.1.5", ··· 4179 4166 "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", 4180 4167 "dev": true 4181 4168 }, 4169 + "node_modules/nconf": { 4170 + "version": "0.6.9", 4171 + "resolved": "https://registry.npmjs.org/nconf/-/nconf-0.6.9.tgz", 4172 + "integrity": "sha1-lXDvFe1vmuays8jV5xtm0xk81mE=", 4173 + "dependencies": { 4174 + "async": "0.2.9", 4175 + "ini": "1.x.x", 4176 + "optimist": "0.6.0" 4177 + }, 4178 + "engines": { 4179 + "node": ">= 0.4.0" 4180 + } 4181 + }, 4182 + "node_modules/nconf/node_modules/async": { 4183 + "version": "0.2.9", 4184 + "resolved": "https://registry.npmjs.org/async/-/async-0.2.9.tgz", 4185 + "integrity": "sha1-32MGD789Myhqdqr21Vophtn/hhk=" 4186 + }, 4182 4187 "node_modules/ncp": { 4183 4188 "version": "0.4.2", 4184 4189 "resolved": "https://registry.npmjs.org/ncp/-/ncp-0.4.2.tgz", ··· 7135 7140 "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz", 7136 7141 "integrity": "sha1-j2G3XN4BKy6esoTUVFWDtWQ7Yas=" 7137 7142 }, 7138 - "nconf": { 7139 - "version": "0.6.9", 7140 - "resolved": "https://registry.npmjs.org/nconf/-/nconf-0.6.9.tgz", 7141 - "integrity": "sha1-lXDvFe1vmuays8jV5xtm0xk81mE=", 7142 - "requires": { 7143 - "async": "0.2.9", 7144 - "ini": "1.x.x", 7145 - "optimist": "0.6.0" 7146 - } 7147 - }, 7148 7143 "pkginfo": { 7149 7144 "version": "0.3.1", 7150 7145 "resolved": "https://registry.npmjs.org/pkginfo/-/pkginfo-0.3.1.tgz", ··· 9289 9284 } 9290 9285 }, 9291 9286 "minimist": { 9292 - "version": "1.2.5", 9293 - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", 9294 - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" 9287 + "version": "1.2.6", 9288 + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", 9289 + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" 9295 9290 }, 9296 9291 "minipass": { 9297 9292 "version": "3.1.5", ··· 9486 9481 "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", 9487 9482 "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", 9488 9483 "dev": true 9484 + }, 9485 + "nconf": { 9486 + "version": "0.6.9", 9487 + "resolved": "https://registry.npmjs.org/nconf/-/nconf-0.6.9.tgz", 9488 + "integrity": "sha1-lXDvFe1vmuays8jV5xtm0xk81mE=", 9489 + "requires": { 9490 + "async": "0.2.9", 9491 + "ini": "1.x.x", 9492 + "optimist": "0.6.0" 9493 + }, 9494 + "dependencies": { 9495 + "async": { 9496 + "version": "0.2.9", 9497 + "resolved": "https://registry.npmjs.org/async/-/async-0.2.9.tgz", 9498 + "integrity": "sha1-32MGD789Myhqdqr21Vophtn/hhk=" 9499 + } 9500 + } 9489 9501 }, 9490 9502 "ncp": { 9491 9503 "version": "0.4.2",
+1 -1
package.json
··· 18 18 "axios": "^0.26.1", 19 19 "blapi": "^2.2.2", 20 20 "bufferutil": "^4.0.6", 21 - "discord-api-types": "^0.26.1", 21 + "discord-api-types": "0.26.1", 22 22 "discord.js": "^13.6.0", 23 23 "dotenv": "^16.0.0", 24 24 "ffmpeg-static": "^5.0.0",
+6 -6
src/applicationCommandHandler.ts
··· 54 54 bot, 55 55 ); 56 56 const command = applicationCommands[interaction.commandName]; 57 - if (command && command.slashCommand) { 58 - const { slashCommand, perm } = command; 57 + if (command) { 58 + const { permissions } = command; 59 59 if ( 60 60 !(await commandAllowed(interaction.guild.id, interaction.channel?.id)) 61 61 ) { ··· 75 75 const botPermissions = ( 76 76 interaction.channel as Discord.TextChannel 77 77 ).permissionsFor(interaction.guild.me); 78 - if (!botPermissions.has(perm)) { 78 + if (!botPermissions.has(permissions)) { 79 79 await interaction.reply( 80 - `I am missing permissions for this command. I require all of the following:\n${perm}`, 80 + `I am missing permissions for this command. I require all of the following:\n${permissions}`, 81 81 ); 82 82 } 83 - if (slashCommand.isSlow) { 83 + if (command.isSlow) { 84 84 // allow slow commands to have more time to respond 85 85 await interaction.deferReply(); 86 86 } 87 87 // actually use the command 88 - await slashCommand.main(interaction); 88 + await command.run(interaction); 89 89 await usageUp(interaction.member.user.id, interaction.guild.id); 90 90 } 91 91 } catch (err) {
+26 -18
src/commandHandler.ts
··· 2 2 import Statcord from 'statcord.js'; 3 3 import { vote } from './commands/old/vote'; 4 4 import { sound } from './commands/old/sound'; 5 - import { randomfact } from './commands/rfact'; 6 5 // eslint-disable-next-line import/no-cycle 7 6 import { stats } from './commands/old/stats'; 8 - import { salt } from './commands/old/salt'; 9 7 // eslint-disable-next-line import/no-cycle 10 - import { bugreport } from './commands/bug'; 11 8 import { inf as info } from './commands/old/info'; 12 - import { invite } from './commands/invite'; 13 - import { ping } from './commands/ping'; 14 9 import { profile } from './commands/old/profile'; 15 - import { roll } from './commands/roll'; 16 10 // eslint-disable-next-line import/no-cycle 17 11 import { queue as _queue } from './commands/old/@queue'; 18 12 import { salt as _salt } from './commands/old/@salt'; ··· 37 31 } from './dbHelpers'; 38 32 // eslint-disable-next-line import/no-cycle 39 33 import { bot } from './bot'; 40 - import { asyncWait } from './helperFunctions'; 34 + import { asyncWait, notifyAboutSlashCommand } from './helperFunctions'; 41 35 42 36 export const commands: { [k: string]: magibotCommand } = { 43 37 _queue, ··· 46 40 _update, 47 41 _salt, 48 42 help, 49 - salt, 50 43 stats, 51 - rfact: randomfact, 52 44 sound, 53 45 vote, 54 - bug: bugreport, 55 46 info, 56 - invite, 57 - ping, 58 47 profile, 59 - roll, 60 48 }; 49 + 50 + const migratedCommands = new Map([ 51 + ['rfact', 'randomfact'], 52 + ['bug', 'bugreport'], 53 + ['invite', 'invite'], 54 + ['ping', 'ping'], 55 + ['roll', 'roll'], 56 + ['salt', 'salt'], 57 + ]); 58 + 59 + async function sendMigrationMessageIfComandHasBeenMigrated( 60 + message: Discord.Message, 61 + commandName: string, 62 + ) { 63 + const migratedCommand = migratedCommands.get(commandName); 64 + if (migratedCommand) { 65 + await notifyAboutSlashCommand(message, migratedCommand); 66 + } 67 + } 61 68 62 69 async function catchError(error: Error, msg: Discord.Message, command: string) { 63 70 console.error( ··· 165 172 } 166 173 // Check if the command exists, to not just spam k: msgs 167 174 if (!commands[command]) { 175 + await sendMigrationMessageIfComandHasBeenMigrated(message, command); 168 176 return; 169 177 } 170 178 if ( ··· 187 195 } else { 188 196 return; 189 197 } 190 - 191 - if ( 192 - commands[command] 193 - && (!commands[command].dev || message.author.id === OWNERID) 194 - ) { 198 + if (!commands[command]) { 199 + await sendMigrationMessageIfComandHasBeenMigrated(message, command); 200 + return; 201 + } 202 + if (!commands[command].dev || message.author.id === OWNERID) { 195 203 if ( 196 204 pre === ':' 197 205 || (await commandAllowed(message.guild.id, message.channel.id))
+1 -1
src/commandSync.ts
··· 7 7 import { APP_ID, TOKEN } from './shared_assets'; 8 8 9 9 const commands = Object.values(applicationCommands).map( 10 - (command) => command.slashCommand!.definition, 10 + (command) => command.definition, 11 11 ); 12 12 const testCommands: Array<RESTPostAPIApplicationCommandsJSONBody> = [ 13 13 /* invite.slashCommand!.definition,
+4 -2
src/commands/applicationCommands.ts
··· 3 3 import { invite } from './invite'; 4 4 import { bugreport } from './bug'; 5 5 import { randomfact } from './rfact'; 6 - import { magibotCommand } from '../types/command'; 6 + import { salt } from './salt'; 7 + import { MagibotSlashCommand } from '../types/command'; 7 8 8 - export const applicationCommands: { [k: string]: magibotCommand } = { 9 + export const applicationCommands: { [k: string]: MagibotSlashCommand } = { 9 10 ping, 10 11 roll, 11 12 invite, 12 13 bugreport, 13 14 randomfact, 15 + salt, 14 16 };
+9 -21
src/commands/bug.ts
··· 1 1 import { SlashCommandBuilder } from '@discordjs/builders'; 2 2 import { CommandInteraction } from 'discord.js'; 3 - import { 4 - interactionConfirmation, 5 - notifyAboutSlashCommand, 6 - } from '../helperFunctions'; 3 + import { interactionConfirmation } from '../helperFunctions'; 7 4 import { commandCategories } from '../types/enums'; 8 - import { magibotCommand } from '../types/command'; 5 + import { MagibotSlashCommand } from '../types/command'; 9 6 import { sendBugreport } from '../webhooks'; 10 7 11 8 const slashCommand = new SlashCommandBuilder() ··· 38 35 } 39 36 } 40 37 41 - export const bugreport: magibotCommand = { 42 - name: 'bug', 43 - async main({ message }) { 44 - return notifyAboutSlashCommand(message, 'bugreport'); 45 - }, 46 - admin: false, 47 - ehelp() { 38 + export const bugreport: MagibotSlashCommand = { 39 + help() { 48 40 return [ 49 41 { 50 42 name: '<bugreport with information about what you did, what was expected, and what went wrong>', ··· 52 44 }, 53 45 ]; 54 46 }, 55 - perm: 'SEND_MESSAGES', 56 - hide: false, 57 - dev: false, 47 + permissions: 'SEND_MESSAGES', 58 48 category: commandCategories.misc, 59 - slashCommand: { 60 - async main(interaction: CommandInteraction) { 61 - const input = interaction.options.getString('description', true); 62 - return main(interaction, input); 63 - }, 64 - definition: slashCommand.toJSON(), 49 + async run(interaction: CommandInteraction) { 50 + const input = interaction.options.getString('description', true); 51 + return main(interaction, input); 65 52 }, 53 + definition: slashCommand.toJSON(), 66 54 };
+6 -13
src/commands/invite.ts
··· 1 1 import { CommandInteraction, TextChannel } from 'discord.js'; 2 2 import { SlashCommandBuilder } from '@discordjs/builders'; 3 3 import { commandCategories } from '../types/enums'; 4 - import { magibotCommand } from '../types/command'; 5 - import { notifyAboutSlashCommand } from '../helperFunctions'; 4 + import { MagibotSlashCommand } from '../types/command'; 6 5 7 6 const slashCommand = new SlashCommandBuilder() 8 7 .setName('invite') ··· 15 14 interaction.reply(`Here's an invite link to this channel: ${invite}`); 16 15 } 17 16 18 - export const invite: magibotCommand = { 19 - name: 'invite', 20 - hide: false, 21 - dev: false, 22 - async main({ message }) { 23 - return notifyAboutSlashCommand(message, 'invite'); 24 - }, 25 - ehelp() { 17 + export const invite: MagibotSlashCommand = { 18 + help() { 26 19 return [ 27 20 { 28 21 name: '', ··· 30 23 }, 31 24 ]; 32 25 }, 33 - perm: ['SEND_MESSAGES', 'CREATE_INSTANT_INVITE'], 34 - admin: false, 26 + permissions: ['SEND_MESSAGES', 'CREATE_INSTANT_INVITE'], 35 27 category: commandCategories.util, 36 - slashCommand: { main, definition: slashCommand.toJSON() }, 28 + run: main, 29 + definition: slashCommand.toJSON(), 37 30 };
+1 -1
src/commands/old/@salt.ts
··· 5 5 import { magibotCommand } from '../../types/command'; 6 6 import { SaltModel, SaltrankModel } from '../../db'; 7 7 import { updateSaltKing } from '../../dbHelpers'; 8 - import { saltUp, saltGuild } from './salt'; 8 + import { saltUp, saltGuild } from '../salt'; 9 9 10 10 async function resetSalt(G: Guild) { 11 11 const guildID = G.id;
-208
src/commands/old/salt.ts
··· 1 - import { Guild, Message, MessageEmbedOptions } from 'discord.js'; 2 - import * as cmds from '../../helperFunctions'; 3 - import { commandCategories } from '../../types/enums'; 4 - import { PREFIXES } from '../../shared_assets'; 5 - import { magibotCommand } from '../../types/command'; 6 - import { SaltModel, SaltrankModel } from '../../db'; 7 - import { updateSaltKing, topSalt } from '../../dbHelpers'; 8 - 9 - async function saltDowntimeDone(userid1: string, userid2: string) { 10 - // get newest entry in salt 11 - const d2 = await SaltModel.find({ 12 - salter: userid1, 13 - reporter: userid2, 14 - }) 15 - .sort({ date: -1 }) 16 - .limit(1); 17 - if (d2[0]) { 18 - const d1 = new Date(); 19 - const ret = (d1.getTime() - d2[0].date.getTime()) / 1000 / 60 / 60; 20 - return ret; 21 - } 22 - return 2; 23 - } 24 - 25 - export async function saltGuild( 26 - salter: string, 27 - guildID: string, 28 - add = 1, 29 - reset = false, 30 - ) { 31 - const user = await SaltrankModel.findOne({ 32 - salter, 33 - guild: guildID, 34 - }); 35 - if (!user) { 36 - const myobj = new SaltrankModel({ 37 - salter, 38 - salt: 1, 39 - guild: guildID, 40 - }); 41 - await myobj.save(); 42 - } else { 43 - const slt = user.salt + add; 44 - if (slt <= 0 || reset) { 45 - await SaltrankModel.deleteOne({ 46 - salter, 47 - guild: guildID, 48 - }); 49 - } else { 50 - const update = { $set: { salt: slt } }; 51 - await SaltrankModel.updateOne( 52 - { 53 - salter, 54 - guild: guildID, 55 - }, 56 - update, 57 - ); 58 - } 59 - } 60 - } 61 - 62 - export async function saltUp( 63 - salter: string, 64 - reporter: string, 65 - guild: Guild, 66 - admin = false, 67 - ) { 68 - const time = await saltDowntimeDone(salter, reporter); 69 - if (time > 1 || admin) { 70 - const date = new Date(); 71 - const myobj = new SaltModel({ 72 - salter, 73 - reporter, 74 - date, 75 - guild: guild.id, 76 - }); 77 - await myobj.save(); 78 - await saltGuild(salter, guild.id, 1); 79 - await updateSaltKing(guild); 80 - return 0; 81 - } 82 - return time; 83 - } 84 - 85 - // TODO we might want to add even more of the DB logic into here 86 - 87 - function printHelp(message: Message) { 88 - const info: Array<{ name: string; value: string }> = []; 89 - info.push({ 90 - name: 'add <@user|userid|nickname>', 91 - value: 92 - 'Report a user being salty. If you use nickname it has to be at least three characters long and unique.\nThis has a 1h cooldown for reporting the same user.', 93 - }); 94 - info.push({ 95 - name: 'top', 96 - value: `Displays the top 5 salter in ${message.guild!.name}`, 97 - }); 98 - return info; 99 - } 100 - 101 - export const salt: magibotCommand = { 102 - main: async function main({ content, message }) { 103 - const args = content.split(/ +/); 104 - const command = args[0].toLowerCase(); 105 - if (message.guild) { 106 - switch (command) { 107 - case 'add': 108 - /* eslint-disable no-case-declarations */ 109 - const mention = args[1]; 110 - const { user, fuzzy } = await cmds.findMember(message.guild, mention); 111 - /* eslint-enable no-case-declarations */ 112 - if (mention && user) { 113 - if (user.id === message.author.id) { 114 - message.reply("you can't report yourself!"); 115 - return; 116 - } 117 - if (user.user.bot) { 118 - message.reply("you can't report bots!"); 119 - return; 120 - } 121 - if (fuzzy) { 122 - const confirm = await cmds.yesOrNo( 123 - message, 124 - `Do you want to report ${user} for being a salty bitch?`, 125 - ); 126 - if (!confirm) { 127 - return; 128 - } 129 - } 130 - const time = await saltUp(user.id, message.author.id, message.guild); 131 - if (time === 0) { 132 - message.channel.send( 133 - `Successfully reported ${user} for being a salty bitch!`, 134 - ); 135 - } else { 136 - message.reply( 137 - `you can report ${user} again in ${ 138 - 59 - Math.floor((time * 60) % 60) 139 - } min and ${60 - Math.floor((time * 60 * 60) % 60)} sec!`, 140 - ); 141 - } 142 - } else { 143 - message.reply('you need to mention a user you want to report!'); 144 - } 145 - break; 146 - case 'top': 147 - /* eslint-disable no-case-declarations */ 148 - const salters = await topSalt(message.guild.id); 149 - const info: Array<{ 150 - name: string; 151 - value: string; 152 - inline: boolean; 153 - }> = []; 154 - /* eslint-enable no-case-declarations */ 155 - for (let i = 0; i < 5; i++) { 156 - let mname = 'User left guild'; 157 - if (salters[i]) { 158 - // eslint-disable-next-line no-await-in-loop 159 - const member = await message.guild.members 160 - .fetch(salters[i].salter) 161 - .catch(() => {}); 162 - if (member) { 163 - mname = member.displayName; 164 - } 165 - info.push({ 166 - name: `${i + 1}. place: ${mname}`, 167 - value: `${salters[i].salt} salt`, 168 - inline: false, 169 - }); 170 - } else { 171 - break; 172 - } 173 - } 174 - /* eslint-disable no-case-declarations */ 175 - const embed: MessageEmbedOptions = { 176 - color: 0xffffff, 177 - description: `Top 5 salter in ${message.guild.name}:`, 178 - fields: info, 179 - footer: { 180 - iconURL: message.guild.iconURL() || '', 181 - text: message.guild.name, 182 - }, 183 - }; 184 - /* eslint-enable no-case-declarations */ 185 - message.channel.send({ embeds: [embed] }); 186 - break; 187 - default: 188 - message.reply( 189 - `this command doesn't exist. Use \`${PREFIXES.get( 190 - message.guild.id, 191 - )}.help salt\` for more info.`, 192 - ); 193 - break; 194 - } 195 - } else { 196 - message.reply('commands are only functional when used in a guild.'); 197 - } 198 - }, 199 - name: 'salt', 200 - ehelp(message: Message) { 201 - return printHelp(message); 202 - }, 203 - perm: ['SEND_MESSAGES', 'EMBED_LINKS'], 204 - admin: false, 205 - hide: false, 206 - category: commandCategories.fun, 207 - dev: false, 208 - };
+10 -19
src/commands/ping.ts
··· 1 1 import { SlashCommandBuilder } from '@discordjs/builders'; 2 2 import { CommandInteraction } from 'discord.js'; 3 - import { notifyAboutSlashCommand } from '../helperFunctions'; 4 3 import { commandCategories } from '../types/enums'; 5 - import { magibotCommand } from '../types/command'; 4 + import { MagibotSlashCommand } from '../types/command'; 6 5 7 6 const slashCommand = new SlashCommandBuilder() 8 7 .setName('ping') 9 8 .setDescription('Returns the round trip time between you and MagiBot!'); 10 - export const ping: magibotCommand = { 11 - name: 'ping', 12 - dev: false, 13 - hide: false, 14 - async main({ message }) { 15 - return notifyAboutSlashCommand(message, 'ping'); 16 - }, 17 - ehelp() { 9 + 10 + export const ping: MagibotSlashCommand = { 11 + help() { 18 12 return [ 19 13 { 20 14 name: '', ··· 22 16 }, 23 17 ]; 24 18 }, 25 - perm: 'SEND_MESSAGES', 26 - admin: false, 19 + permissions: 'SEND_MESSAGES', 27 20 category: commandCategories.misc, 28 - slashCommand: { 29 - async main(interaction: CommandInteraction) { 30 - const stop = new Date(); 31 - const diff = stop.getTime() - interaction.createdAt.getTime(); 32 - await interaction.reply(`Pong! \`(${diff}ms)\``); 33 - }, 34 - definition: slashCommand.toJSON(), 21 + async run(interaction: CommandInteraction) { 22 + const stop = new Date(); 23 + const diff = stop.getTime() - interaction.createdAt.getTime(); 24 + await interaction.reply(`Pong! \`(${diff}ms)\``); 35 25 }, 26 + definition: slashCommand.toJSON(), 36 27 };
+8 -16
src/commands/rfact.ts
··· 3 3 import { CommandInteraction } from 'discord.js'; 4 4 import { commandCategories } from '../types/enums'; 5 5 import { COLOR, user } from '../shared_assets'; 6 - import { magibotCommand } from '../types/command'; 7 - import { notifyAboutSlashCommand } from '../helperFunctions'; 6 + import { MagibotSlashCommand } from '../types/command'; 8 7 9 8 // we needed to manually type this because the inferred type collided with date type later on 10 9 const options: { weekday: 'long'; month: 'long'; day: 'numeric' } = { ··· 41 40 .setName('randomfact') 42 41 .setDescription('Get a random fact of the day.'); 43 42 44 - export const randomfact: magibotCommand = { 45 - main: async ({ message }) => notifyAboutSlashCommand(message, 'randomfact'), 46 - ehelp() { 43 + export const randomfact: MagibotSlashCommand = { 44 + help() { 47 45 return [ 48 46 { 49 47 name: '', ··· 51 49 }, 52 50 ]; 53 51 }, 54 - name: 'rfact', 55 - dev: false, 56 - perm: 'SEND_MESSAGES', 57 - admin: false, 58 - hide: false, 52 + permissions: 'SEND_MESSAGES', 59 53 category: commandCategories.fun, 60 - slashCommand: { 61 - async main(interaction: CommandInteraction) { 62 - return main(interaction); 63 - }, 64 - definition: slashCommand.toJSON(), 65 - isSlow: true, 54 + async run(interaction: CommandInteraction) { 55 + return main(interaction); 66 56 }, 57 + definition: slashCommand.toJSON(), 58 + isSlow: true, 67 59 };
+9 -19
src/commands/roll.ts
··· 1 1 import { SlashCommandBuilder } from '@discordjs/builders'; 2 2 import { CommandInteraction } from 'discord.js'; 3 - import { notifyAboutSlashCommand } from '../helperFunctions'; 4 3 import { COLOR, PREFIXES } from '../shared_assets'; 5 4 import { commandCategories } from '../types/enums'; 6 - import { magibotCommand } from '../types/command'; 5 + import { MagibotSlashCommand } from '../types/command'; 7 6 8 7 /** definition of calculation of dice, use parse(input) 9 8 returns array of throws with last index being sum, ··· 65 64 ) 66 65 .setRequired(true)); 67 66 68 - async function main(interaction: CommandInteraction, input: string) { 67 + async function runCommand(interaction: CommandInteraction, input: string) { 69 68 const diceRollCalculation = parse(input); 70 69 if (!diceRollCalculation) { 71 70 interaction.reply( ··· 132 131 interaction.reply({ embeds: [embed] }); 133 132 } 134 133 135 - export const roll: magibotCommand = { 136 - name: 'roll', 137 - hide: false, 138 - dev: false, 139 - async main({ message }) { 140 - return notifyAboutSlashCommand(message, 'roll'); 141 - }, 142 - ehelp() { 134 + export const roll: MagibotSlashCommand = { 135 + help() { 143 136 const ret: Array<{ name: string; value: string }> = []; 144 137 ret.push({ 145 138 name: '[multiplier]*[number of rolls]d<die number>[+ <modifier>]', ··· 148 141 }); 149 142 return ret; 150 143 }, 151 - perm: 'SEND_MESSAGES', 152 - admin: false, 144 + permissions: 'SEND_MESSAGES', 153 145 category: commandCategories.fun, 154 - slashCommand: { 155 - async main(interaction: CommandInteraction) { 156 - const input = interaction.options.getString('dice', true); 157 - return main(interaction, input); 158 - }, 159 - definition: slashCommand.toJSON(), 146 + async run(interaction: CommandInteraction) { 147 + const input = interaction.options.getString('dice', true); 148 + return runCommand(interaction, input); 160 149 }, 150 + definition: slashCommand.toJSON(), 161 151 };
+211
src/commands/salt.ts
··· 1 + import { 2 + CommandInteraction, 3 + Guild, 4 + MessageEmbedOptions, 5 + User, 6 + } from 'discord.js'; 7 + import { SlashCommandBuilder } from '@discordjs/builders'; 8 + import { commandCategories } from '../types/enums'; 9 + import { MagibotSlashCommand } from '../types/command'; 10 + import { SaltModel, SaltrankModel } from '../db'; 11 + import { updateSaltKing, topSalt } from '../dbHelpers'; 12 + 13 + async function saltDowntimeDone(salterUserId: string, reporterUserId: string) { 14 + // get newest entry in salt 15 + const saltEntry = await SaltModel.find({ 16 + salter: salterUserId, 17 + reporter: reporterUserId, 18 + }) 19 + .sort({ date: -1 }) 20 + .limit(1); 21 + if (saltEntry[0]) { 22 + const now = new Date(); 23 + return (now.getTime() - saltEntry[0].date.getTime()) / 1000 / 60 / 60; 24 + } 25 + return 2; 26 + } 27 + 28 + export async function saltGuild( 29 + salter: string, 30 + guildID: string, 31 + add = 1, 32 + reset = false, 33 + ) { 34 + const user = await SaltrankModel.findOne({ 35 + salter, 36 + guild: guildID, 37 + }); 38 + if (!user) { 39 + const myobj = new SaltrankModel({ 40 + salter, 41 + salt: 1, 42 + guild: guildID, 43 + }); 44 + await myobj.save(); 45 + } else { 46 + const slt = user.salt + add; 47 + if (slt <= 0 || reset) { 48 + await SaltrankModel.deleteOne({ 49 + salter, 50 + guild: guildID, 51 + }); 52 + } else { 53 + const update = { $set: { salt: slt } }; 54 + await SaltrankModel.updateOne( 55 + { 56 + salter, 57 + guild: guildID, 58 + }, 59 + update, 60 + ); 61 + } 62 + } 63 + } 64 + 65 + export async function saltUp( 66 + salter: string, 67 + reporter: string, 68 + guild: Guild, 69 + admin = false, 70 + ) { 71 + const time = await saltDowntimeDone(salter, reporter); 72 + if (time > 1 || admin) { 73 + const date = new Date(); 74 + const myobj = new SaltModel({ 75 + salter, 76 + reporter, 77 + date, 78 + guild: guild.id, 79 + }); 80 + await myobj.save(); 81 + await saltGuild(salter, guild.id, 1); 82 + await updateSaltKing(guild); 83 + return 0; 84 + } 85 + return time; 86 + } 87 + 88 + function printHelp() { 89 + const info: Array<{ name: string; value: string }> = []; 90 + info.push({ 91 + name: 'report @User', 92 + value: 93 + 'Report a user being salty. If you use nickname it has to be at least three characters long and unique.\nThis has a 1h cooldown for reporting the same user.', 94 + }); 95 + info.push({ 96 + name: 'ranking', 97 + value: 'Displays the top 5 salter of this server.', 98 + }); 99 + return info; 100 + } 101 + 102 + async function addSalt(interaction: CommandInteraction, reportedUser: User) { 103 + if (reportedUser.id === interaction.user.id) { 104 + return interaction.reply("You can't report yourself!"); 105 + } 106 + if (reportedUser.bot) { 107 + return interaction.reply("You can't report bots!"); 108 + } 109 + const time = await saltUp( 110 + reportedUser.id, 111 + interaction.user.id, 112 + interaction.guild!, 113 + ); 114 + if (time === 0) { 115 + return interaction.reply( 116 + `Successfully reported ${reportedUser} for being a salty bitch!`, 117 + ); 118 + } 119 + return interaction.reply( 120 + `You can report ${reportedUser} again in ${ 121 + 59 - Math.floor((time * 60) % 60) 122 + } min and ${60 - Math.floor((time * 60 * 60) % 60)} sec!`, 123 + ); 124 + } 125 + 126 + async function getMemberSaltInfo( 127 + guild: Guild, 128 + salter: string, 129 + index: number, 130 + salt: number, 131 + ) { 132 + let memberName = 'User left guild'; 133 + const member = await guild.members.fetch(salter).catch(() => {}); 134 + if (member) { 135 + memberName = member.displayName; 136 + } 137 + return { 138 + name: `${index + 1}. place: ${memberName}`, 139 + value: `${salt} salt`, 140 + inline: false, 141 + }; 142 + } 143 + 144 + async function getTopSalters(interaction: CommandInteraction) { 145 + const guild = interaction.guild!; 146 + const salters = await topSalt(guild.id); 147 + const info: Array< 148 + Promise<{ 149 + name: string; 150 + value: string; 151 + inline: boolean; 152 + }> 153 + > = []; 154 + for (let i = 0; i < 5; i++) { 155 + if (salters[i]) { 156 + info.push( 157 + getMemberSaltInfo(guild, salters[i].salter, i, salters[i].salt), 158 + ); 159 + } else { 160 + break; 161 + } 162 + } 163 + const embed: MessageEmbedOptions = { 164 + color: 0xffffff, 165 + description: `Top 5 salter in ${guild.name}:`, 166 + fields: await Promise.all(info), 167 + footer: { 168 + iconURL: guild.iconURL() || '', 169 + text: guild.name, 170 + }, 171 + }; 172 + return interaction.reply({ embeds: [embed] }); 173 + } 174 + 175 + const slashCommand = new SlashCommandBuilder() 176 + .setName('salt') 177 + .setDescription('Interact with the salt ranking of this server!') 178 + .addSubcommand((subcommand) => subcommand 179 + .setName('report') 180 + .setDescription('Report a user being salty.') 181 + .addUserOption((option) => option 182 + .setName('user') 183 + .setDescription('The user you want to report.') 184 + .setRequired(true))) 185 + .addSubcommand((subcommand) => subcommand 186 + .setName('ranking') 187 + .setDescription('Get the current ranking of saltyness on this server.')); 188 + 189 + async function runCommand(interaction: CommandInteraction) { 190 + const subcommand = interaction.options.getSubcommand(true) as 191 + | 'report' 192 + | 'ranking'; 193 + if (subcommand === 'report') { 194 + const user = interaction.options.getUser('user', true); 195 + return addSalt(interaction, user); 196 + } 197 + if (subcommand === 'ranking') { 198 + return getTopSalters(interaction); 199 + } 200 + return null; 201 + } 202 + 203 + export const salt: MagibotSlashCommand = { 204 + help() { 205 + return printHelp(); 206 + }, 207 + permissions: ['SEND_MESSAGES', 'EMBED_LINKS'], 208 + category: commandCategories.fun, 209 + run: runCommand, 210 + definition: slashCommand.toJSON(), 211 + };
+2 -2
src/helperFunctions.ts
··· 137 137 message: Message, 138 138 command: string, 139 139 ) { 140 - await message.reply(`This command has been moved to application commands! You can simply use it by typing \`/${command}\`! 140 + await message.reply(`This command has been moved to /slashcommands! You can simply use it by typing \`/${command}\`! 141 141 If you can't find it, either you are missing permissions, or the admins of this server have not given MagiBot permission to create application commands yet. 142 - To do the latter, re-invite the bot by clicking the big blue button in the bot's profile!`); 142 + To do the latter, re-invite the bot by clicking the big blue "Add to Server" button in the bot's profile!`); 143 143 } 144 144 145 145 // this is an idea to implement rather reusable confirmation processes.
+11 -5
src/types/command.ts
··· 14 14 content: string; 15 15 message: Discord.Message; 16 16 }) => Promise<void> | void; 17 - slashCommand?: { 18 - main: (interaction: Discord.CommandInteraction) => Promise<void> | void; 19 - definition: RESTPostAPIApplicationCommandsJSONBody; 20 - isSlow?: boolean; 21 - }; 17 + }; 18 + 19 + export type MagibotSlashCommand = { 20 + help: (commandName: string) => Array<{ name: string; value: string }>; 21 + permissions: Discord.PermissionResolvable | Discord.PermissionResolvable[]; 22 + category: commandCategories; 23 + run: ( 24 + interaction: Discord.CommandInteraction 25 + ) => Promise<void | null> | void | null; // allow null to allow for "empty" returns 26 + definition: RESTPostAPIApplicationCommandsJSONBody; 27 + isSlow?: boolean; 22 28 };