diff --git a/secret/env b/secret/env index 7e6fd8c1..7f824f9c 100644 --- a/secret/env +++ b/secret/env @@ -1,5 +1,8 @@ BOT_TOKEN=BOT_TOKEN_GOES_HERE +CLIENT_ID=OAuth2 Client ID (Used for registerAppCommands) +CLIENT_SECRET=OAuth2 Client Secret (Used for registerAppCommands) + MYSQL_HOST=localhost MYSQL_USER=user MYSQL_PASS=password diff --git a/src/commands/commandList/battle/ab.js b/src/commands/commandList/battle/ab.js index 1a2f76bf..42a445bc 100644 --- a/src/commands/commandList/battle/ab.js +++ b/src/commands/commandList/battle/ab.js @@ -65,17 +65,25 @@ module.exports = new CommandInterface({ /* Get opponent name */ let sender = result[0][0].sender; - sender = await p.fetch.getMember(p.msg.channel.guild.id, sender); + if (p.msg.channel.guild) { + sender = await p.fetch.getMember(p.msg.channel.guild.id, sender); + } else { + sender = await p.fetch.getUser(sender); + } if (!sender) { p.errorMsg(', I could not find your opponent!', 3000); return; } + if (!p.msg.channel.guild) { + // Can't seem to edit message after interaction in DMs + flags.instant = true; + } const settingOverride = { friendlyBattle: true, display: flags.display ? flags.display : 'image', - speed: flags.log ? 'instant' : 'short', - instant: flags.log ? true : false, + speed: flags.instant || flags.log ? 'instant' : 'short', + instant: flags.instant || flags.log ? true : false, title: this.getName(author) + ' vs ' + this.getName(sender), showLogs: flags.link ? 'link' : flags.log ? true : false, }; diff --git a/src/commands/commandList/battle/battle.js b/src/commands/commandList/battle/battle.js index dfb089d5..1534cde4 100644 --- a/src/commands/commandList/battle/battle.js +++ b/src/commands/commandList/battle/battle.js @@ -25,6 +25,23 @@ module.exports = new CommandInterface({ group: ['animals'], + appCommands: [ + { + 'name': 'battle', + 'type': 1, + 'description': 'Fight with your team of animals!', + 'options': [ + { + 'type': 6, + 'name': 'user', + 'description': 'Fight a friend.', + }, + ], + 'integration_types': [0, 1], + 'contexts': [0, 1, 2], + }, + ], + cooldown: 15000, half: 80, six: 500, diff --git a/src/commands/commandList/battle/crate.js b/src/commands/commandList/battle/crate.js index b094af44..e3eff083 100644 --- a/src/commands/commandList/battle/crate.js +++ b/src/commands/commandList/battle/crate.js @@ -27,6 +27,23 @@ module.exports = new CommandInterface({ group: ['animals'], + appCommands: [ + { + 'name': 'crate', + 'type': 1, + 'description': 'Open a weapon crate', + 'options': [ + { + 'type': 4, + 'name': 'count', + 'description': 'Number of weapon crates', + }, + ], + 'integration_types': [0, 1], + 'contexts': [0, 1, 2], + }, + ], + cooldown: 30000, half: 100, six: 500, diff --git a/src/commands/commandList/memegen/headpat.js b/src/commands/commandList/memegen/headpat.js index 9bf7a48b..14913a14 100644 --- a/src/commands/commandList/memegen/headpat.js +++ b/src/commands/commandList/memegen/headpat.js @@ -7,6 +7,7 @@ const CommandInterface = require('../../CommandInterface.js'); +const commandGroups = require('../../../utils/commandGroups.js'); const request = require('request'); module.exports = new CommandInterface({ @@ -24,26 +25,65 @@ module.exports = new CommandInterface({ group: ['memegeneration'], + appCommands: [ + commandGroups.addOption('headpat', ['gen'], { + 'name': 'headpat', + 'description': 'Generate a headpat emoji', + 'type': 2, + 'options': [ + { + 'name': 'user', + 'description': "Generate a headpat emoji with a user's avatar", + 'type': 1, + 'required': false, + 'options': [ + { + 'name': 'user', + 'description': 'The user to use', + 'type': 6, + 'required': false, + }, + ], + }, + { + 'name': 'emoji', + 'description': 'Generate a headpat emoji with an emoji', + 'type': 1, + 'required': false, + 'options': [ + { + 'name': 'emoji', + 'description': 'The emoji to use', + 'type': 3, + 'required': true, + }, + ], + }, + ], + }), + ], + cooldown: 30000, half: 100, six: 500, bot: true, execute: async function (p) { - let user = p.getMention(p.args[0]); + let user = p.getMention(p.args[0]) || p.options.user; let link; let name; if (user) { link = user.dynamicAvatarURL('png', 128); name = user.username; - } else if (!user && !p.args.length) { - link = p.msg.author.dynamicAvatarURL('png', 128); - name = p.getName(); - } else if (!user && p.global.isEmoji(p.args[0])) { - link = p.args[0].match(/:[0-9]+>/gi)[0]; + } else if (p.global.isEmoji(p.args[0] || p.options.emoji)) { + const emoji = p.args[0] || p.options.emoji; + link = emoji.match(/:[0-9]+>/gi)[0]; link = `https://cdn.discordapp.com/emojis/${link.slice(1, link.length - 1)}.png`; - name = p.args[0].match(/:[\w]+:/gi)[0]; + name = emoji.match(/:[\w]+:/gi)[0]; name = name.slice(1, name.length - 1); + } else if (!p.args.length) { + link = p.msg.author.dynamicAvatarURL('png', 128); + name = p.getName(); } else { p.errorMsg(', invalid arguments! Please tag a user or add an emoji!', 3000); p.setCooldown(5); @@ -65,41 +105,36 @@ module.exports = new CommandInterface({ async function display(p, url, name) { const emojiName = `${name.replace(/[^\w]/gi, '')}_pat`; let embed = createEmbed(p, url, name, emojiName); - let msg = await p.send({ embed }); - - // Check if user set stealing - let sql = `SELECT emoji_steal.guild FROM emoji_steal INNER JOIN user ON emoji_steal.uid = user.uid WHERE id = ${p.msg.author.id};`; - await p.query(sql); - let canSteal = (await p.query(sql))[0]?.guild; - - // Add reactions - if (canSteal) await msg.addReaction(p.config.emoji.steal); + const components = await p.global.getStealButton(p, true); + const content = { + embed, + components, + }; + let msg = await p.send(content); - // Create reaction collector - let filter = (emoji, userId) => emoji.name == p.config.emoji.steal && userId != p.client.user.id; - const collector = p.reactionCollector.create(msg, filter, { idle: 120000 }); + // Create interaction collector + let filter = (componentName) => componentName === 'steal'; + let collector = p.interactionCollector.create(msg, filter, { idle: 120000 }); const emojiAdder = new p.EmojiAdder(p, { name: emojiName, url }); - collector.on('collect', async function (emoji, userId) { + collector.on('collect', async (component, user, ack) => { try { - if (await emojiAdder.addEmoji(userId)) { - await msg.edit({ - embed: createEmbed(p, url, name, emojiName, emojiAdder), - }); + if (await emojiAdder.addEmoji(user.id)) { + (content.embed = createEmbed(p, url, name, emojiName, emojiAdder)), ack(content); } } catch (err) { if (!emojiAdder.successCount) { - await msg.edit({ - embed: createEmbed(p, url, name, emojiName, emojiAdder), - }); + (content.embed = createEmbed(p, url, name, emojiName, emojiAdder)), ack(content); } } }); collector.on('end', async function (_collected) { - const embed = createEmbed(p, url, name, emojiName, emojiAdder); - embed.color = 6381923; - await msg.edit({ content: 'This message is now inactive', embed }); + content.embed = createEmbed(p, url, name, emojiName, emojiAdder); + content.embed.color = 6381923; + content.content = 'This message is now inactive'; + content.components[0].components[0].disabled = true; + await msg.edit(content); }); } diff --git a/src/commands/commandList/memegen/waddle.js b/src/commands/commandList/memegen/waddle.js index 497d4a65..7c105d90 100644 --- a/src/commands/commandList/memegen/waddle.js +++ b/src/commands/commandList/memegen/waddle.js @@ -1,12 +1,13 @@ /* * OwO Bot for Discord - * Copyright (C) 2021 Christopher Thai + * Copyright (C) 2024 Christopher Thai * This software is licensed under Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International * For more information, see README.md and LICENSE */ const CommandInterface = require('../../CommandInterface.js'); +const commandGroups = require('../../../utils/commandGroups.js'); const request = require('request'); module.exports = new CommandInterface({ @@ -24,26 +25,65 @@ module.exports = new CommandInterface({ group: ['memegeneration'], + appCommands: [ + commandGroups.addOption('waddle', ['gen'], { + 'name': 'waddle', + 'description': 'Generate a waddle emoji', + 'type': 2, + 'options': [ + { + 'name': 'user', + 'description': "Generate a waddle emoji with a user's avatar", + 'type': 1, + 'required': false, + 'options': [ + { + 'name': 'user', + 'description': 'The user to use', + 'type': 6, + 'required': false, + }, + ], + }, + { + 'name': 'emoji', + 'description': 'Generate a waddle emoji with an emoji', + 'type': 1, + 'required': false, + 'options': [ + { + 'name': 'emoji', + 'description': 'The emoji to use', + 'type': 3, + 'required': true, + }, + ], + }, + ], + }), + ], + cooldown: 30000, half: 100, six: 500, bot: true, execute: async function (p) { - let user = p.getMention(p.args[0]); + let user = p.getMention(p.args[0]) || p.options.user; let link; let name; if (user) { link = user.dynamicAvatarURL('png', 128); name = user.username; - } else if (!user && !p.args.length) { - link = p.msg.author.dynamicAvatarURL('png', 128); - name = p.getName(); - } else if (!user && p.global.isEmoji(p.args[0])) { - link = p.args[0].match(/:[0-9]+>/gi)[0]; + } else if (p.global.isEmoji(p.args[0] || p.options.emoji)) { + const emoji = p.args[0] || p.options.emoji; + link = emoji.match(/:[0-9]+>/gi)[0]; link = `https://cdn.discordapp.com/emojis/${link.slice(1, link.length - 1)}.png`; - name = p.args[0].match(/:[\w]+:/gi)[0]; + name = emoji.match(/:[\w]+:/gi)[0]; name = name.slice(1, name.length - 1); + } else if (!p.args.length) { + link = p.msg.author.dynamicAvatarURL('png', 128); + name = p.getName(); } else { p.errorMsg(', invalid arguments! Please tag a user or add an emoji!', 3000); p.setCooldown(5); @@ -65,41 +105,36 @@ module.exports = new CommandInterface({ async function display(p, url, name) { const emojiName = `${name.replace(/[^\w]/gi, '')}_waddle`; let embed = createEmbed(p, url, name, emojiName); - let msg = await p.send({ embed }); - - // Check if user set stealing - let sql = `SELECT emoji_steal.guild FROM emoji_steal INNER JOIN user ON emoji_steal.uid = user.uid WHERE id = ${p.msg.author.id};`; - await p.query(sql); - let canSteal = (await p.query(sql))[0]?.guild; - - // Add reactions - if (canSteal) await msg.addReaction(p.config.emoji.steal); + const components = await p.global.getStealButton(p, true); + const content = { + embed, + components, + }; + let msg = await p.send(content); - // Create reaction collector - let filter = (emoji, userId) => emoji.name == p.config.emoji.steal && userId != p.client.user.id; - const collector = p.reactionCollector.create(msg, filter, { idle: 120000 }); + // Create interaction collector + let filter = (componentName) => componentName === 'steal'; + let collector = p.interactionCollector.create(msg, filter, { idle: 120000 }); const emojiAdder = new p.EmojiAdder(p, { name: emojiName, url }); - collector.on('collect', async function (emoji, userId) { + collector.on('collect', async (component, user, ack) => { try { - if (await emojiAdder.addEmoji(userId)) { - await msg.edit({ - embed: createEmbed(p, url, name, emojiName, emojiAdder), - }); + if (await emojiAdder.addEmoji(user.id)) { + (content.embed = createEmbed(p, url, name, emojiName, emojiAdder)), ack(content); } } catch (err) { if (!emojiAdder.successCount) { - await msg.edit({ - embed: createEmbed(p, url, name, emojiName, emojiAdder), - }); + (content.embed = createEmbed(p, url, name, emojiName, emojiAdder)), ack(content); } } }); collector.on('end', async function (_collected) { - const embed = createEmbed(p, url, name, emojiName, emojiAdder); - embed.color = 6381923; - await msg.edit({ content: 'This message is now inactive', embed }); + content.embed = createEmbed(p, url, name, emojiName, emojiAdder); + content.embed.color = 6381923; + content.content = 'This message is now inactive'; + content.components[0].components[0].disabled = true; + await msg.edit(content); }); } diff --git a/src/commands/commandList/social/emoji.js b/src/commands/commandList/social/emoji.js index 83c98acb..001a1a3e 100644 --- a/src/commands/commandList/social/emoji.js +++ b/src/commands/commandList/social/emoji.js @@ -9,7 +9,6 @@ const CommandInterface = require('../../CommandInterface.js'); const baseURL = 'https://cdn.discordapp.com/emojis/'; const stickerUrl = 'https://media.discordapp.net/stickers/'; -const stealEmoji = '🕵️'; module.exports = new CommandInterface({ alias: ['emoji', 'enlarge', 'jumbo'], @@ -26,6 +25,16 @@ module.exports = new CommandInterface({ group: ['social'], + appCommands: [ + { + 'type': 3, + 'name': 'Grab Emojis', + 'dm_permission': true, + 'integration_types': [0, 1], + 'contexts': [0, 1, 2], + }, + ], + cooldown: 7000, half: 100, six: 500, @@ -158,7 +167,7 @@ async function display(p, emojis) { return embed; }; - const additionalButtons = await getStealButton(p); + const additionalButtons = await p.global.getStealButton(p); const additionalFilter = (componentName, _user) => componentName === 'steal'; const pagedMsg = new p.PagedMessage(p, createEmbed, emojis.length - 1, { idle: 120000, @@ -183,25 +192,6 @@ async function display(p, emojis) { }); } -async function getStealButton(p) { - const sql = `SELECT emoji_steal.guild FROM emoji_steal INNER JOIN user ON emoji_steal.uid = user.uid WHERE id = ${p.msg.author.id};`; - const canSteal = (await p.query(sql))[0]?.guild; - if (canSteal) { - return [ - { - type: 2, - label: 'Steal', - style: 1, - custom_id: 'steal', - emoji: { - id: null, - name: stealEmoji, - }, - }, - ]; - } -} - async function setServer(p) { // Check if the user has emoji permissions if (!p.msg.member.permissions.has('manageEmojis')) { @@ -228,11 +218,11 @@ async function setServer(p) { } } - p.replyMsg(stealEmoji, ', stolen emojis will now be sent to this server!'); + p.replyMsg(p.config.emoji.steal, ', stolen emojis will now be sent to this server!'); } async function unsetServer(p) { let sql = `DELETE FROM emoji_steal WHERE uid = (SELECT uid FROM user WHERE id = ${p.msg.author.id});`; await p.query(sql); - p.replyMsg(stealEmoji, ', your server has been unset for stealing!'); + p.replyMsg(p.config.emoji.steal, ', your server has been unset for stealing!'); } diff --git a/src/commands/commandList/utils/help.js b/src/commands/commandList/utils/help.js index 4f2c6cca..195badeb 100644 --- a/src/commands/commandList/utils/help.js +++ b/src/commands/commandList/utils/help.js @@ -33,6 +33,16 @@ module.exports = new CommandInterface({ related: [], + appCommands: [ + { + 'name': 'help', + 'type': 1, + 'description': 'OwO, do you need some help~?', + 'integration_types': [0, 1], + 'contexts': [0, 1, 2], + }, + ], + cooldown: 1000, half: 100, six: 500, diff --git a/src/commands/commandList/zoo/catch.js b/src/commands/commandList/zoo/catch.js index ef4d25cb..5faab51b 100644 --- a/src/commands/commandList/zoo/catch.js +++ b/src/commands/commandList/zoo/catch.js @@ -32,6 +32,16 @@ module.exports = new CommandInterface({ group: ['animals'], + appCommands: [ + { + 'name': 'hunt', + 'type': 1, + 'description': 'Hunt for some animals!', + 'integration_types': [0, 1], + 'contexts': [0, 1, 2], + }, + ], + cooldown: 15000, half: 80, six: 500, diff --git a/src/commands/commandList/zoo/lootbox.js b/src/commands/commandList/zoo/lootbox.js index c1fab0d1..f933c51b 100644 --- a/src/commands/commandList/zoo/lootbox.js +++ b/src/commands/commandList/zoo/lootbox.js @@ -31,25 +31,51 @@ module.exports = new CommandInterface({ group: ['animals'], + appCommands: [ + { + 'name': 'lootbox', + 'type': 1, + 'description': 'Open a lootbox', + 'options': [ + { + 'type': 3, + 'name': 'count', + 'description': 'Number of lootboxes: [number, "all", "fabled"]', + }, + ], + 'integration_types': [0, 1], + 'contexts': [0, 1, 2], + }, + ], + cooldown: 5000, half: 100, six: 500, - execute: async function (p) { - if (p.args.length > 0 && p.global.isInt(p.args[0])) await openMultiple(p, parseInt(p.args[0])); - else if (p.args.length > 0 && p.args[0].toLowerCase() == 'all') { - let sql = `SELECT boxcount FROM lootbox WHERE id = ${p.msg.author.id};`; - let result = await p.query(sql); + execute: async function () { + if (this.args.length > 0 && this.global.isInt(this.args[0])) { + await openMultiple(this, parseInt(this.args[0])); + } else if (this.options.count && this.global.isInt(this.options.count)) { + await openMultiple(this, parseInt(this.options.count)); + } else if ( + (this.args.length > 0 && this.args[0].toLowerCase() == 'all') || + (this.options.count && this.options.count.toLowerCase() == 'all') + ) { + let sql = `SELECT boxcount FROM lootbox WHERE id = ${this.msg.author.id};`; + let result = await this.query(sql); if (!result || result[0].boxcount <= 0) { - p.errorMsg(", you don't have any more lootboxes!"); + this.errorMsg(", you don't have any more lootboxes!"); return; } let boxcount = result[0].boxcount; if (boxcount > maxBoxes) boxcount = maxBoxes; - await openMultiple(p, boxcount); - } else if (p.args.length && ['f', 'fabled'].includes(p.args[0].toLowerCase())) { - await openFabledBox(p); - } else await openBox(p); + await openMultiple(this, boxcount); + } else if ( + (this.args.length && ['f', 'fabled'].includes(this.args[0].toLowerCase())) || + (this.options.count && ['f', 'fabled'].includes(this.options.count.toLowerCase())) + ) { + await openFabledBox(this); + } else await openBox(this); }, }); diff --git a/src/data/slash.txt b/src/data/slash.txt index b5459872..8e1bdd48 100644 --- a/src/data/slash.txt +++ b/src/data/slash.txt @@ -1,30 +1,4 @@ -// Battle -{ - "name": "battle", - "description": "Fight with your team of animals!", - "options": [ - { - "type": 6, - "name": "user", - "description": "Fight a friend." - } - ] -} - -// Weapon Crate -{ - "name": "crate", - "description": "Open a weapon crate", - "options": [ - { - "type": 4, - "name": "count", - "description": "Number of weapon crates" - } - ] -} - -// Team commands +// Team commands (unused) { "name": "team", "description": "Create a strong team of pets to fight!", diff --git a/src/eventHandlers/interactionCreate.js b/src/eventHandlers/interactionCreate.js index f92ae119..91e3eb64 100644 --- a/src/eventHandlers/interactionCreate.js +++ b/src/eventHandlers/interactionCreate.js @@ -5,6 +5,8 @@ * For more information, see README.md and LICENSE */ +const commandGroups = require('../utils/commandGroups.js'); + exports.handle = function (interaction) { switch (interaction.type) { case 2: @@ -27,30 +29,38 @@ function handleCommand(interaction) { async function handleSlash(interaction) { ackTimer(interaction); - interaction.command = interaction.data.name; - interaction.author = interaction.member.user || interaction.user; + interaction.command = getSlashCommand(interaction); + interaction.author = interaction.member?.user || interaction.user; interaction.interaction = true; interaction.acked = false; - interaction.options = getInteractionArgs(interaction); + interaction.options = getInteractionArgs(interaction.data, interaction.data.resolved); interaction.args = []; this.command.executeInteraction(interaction); } -function getInteractionArgs(interaction) { - // console.log(interaction.data.options); - const result = {}; - interaction.data.options?.forEach((option) => { +function getSlashCommand(interaction) { + let command = commandGroups.interactionToCommand(interaction.data); + return command || interaction.data.name; +} + +function getInteractionArgs(interaction, resolved, result = {}) { + interaction.options?.forEach((option) => { + /* eslint-disable no-fallthrough */ switch (option.type) { // User case 6: - result[option.name] = interaction.data.resolved.members.get(option.value); + result[option.name] = resolved.users.get(option.value); break; // Sub command case 2: - // console.log(option); + // Command + case 1: + getInteractionArgs(option, resolved, result); break; // Number case 4: + // String + case 3: result[option.name] = option.value; break; } diff --git a/src/eventHandlers/rawWS.js b/src/eventHandlers/rawWS.js index ceedd23f..ccfc3da8 100644 --- a/src/eventHandlers/rawWS.js +++ b/src/eventHandlers/rawWS.js @@ -59,7 +59,7 @@ class Interaction { id: data.channel_id, }; - const author = data.member.user; + const author = data.member?.user || data.user; if (author.discriminator !== '0000') { this.author = bot.users.update(author, bot); } else { diff --git a/src/utils/animalInfoUtil.js b/src/utils/animalInfoUtil.js index a0cb12e9..06c0b724 100644 --- a/src/utils/animalInfoUtil.js +++ b/src/utils/animalInfoUtil.js @@ -27,6 +27,13 @@ class AnimalJson { } async initialize() { + if (!bot) { + return new Promise((res) => { + setTimeout(() => { + res(this.initialize()); + }, 5000); + }); + } this.animalNameToKey = {}; this.animals = {}; this.order = []; diff --git a/src/utils/ban.js b/src/utils/ban.js index e0a88c0b..235e5acc 100644 --- a/src/utils/ban.js +++ b/src/utils/ban.js @@ -13,7 +13,7 @@ const timerEmoji = '⏱'; exports.check = async function (p, command) { let channel = p.msg.channel.id; - let guild = p.msg.channel.guild.id; + let guild = p.msg.channel.guild?.id || 0; let author = p.msg.author.id; if (cooldown[author + command]) return; diff --git a/src/utils/commandGroups.js b/src/utils/commandGroups.js new file mode 100644 index 00000000..97235bc4 --- /dev/null +++ b/src/utils/commandGroups.js @@ -0,0 +1,54 @@ +/* + * OwO Bot for Discord + * Copyright (C) 2024 Christopher Thai + * This software is licensed under Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International + * For more information, see README.md and LICENSE + */ + +const commandGroups = [ + { + 'name': 'gen', + 'type': 1, + 'description': 'Generate some images', + 'options': [], + 'integration_types': [0, 1], + 'contexts': [0, 1, 2], + }, +]; + +const commandMap = {}; + +exports.addOption = function (commandName, names, options) { + const command = JSON.parse(JSON.stringify(findName(names[0], commandGroups))); + let innerCommand = command; + + let innerMap = (commandMap[names[0]] = commandMap[names[0]] || {}); + + for (let i = 1; i < names.length; i++) { + innerCommand = findName(names[i], command.options); + innerMap = innerMap[names[i]] = innerMap[names[i]] || {}; + } + innerCommand.options.push(options); + innerMap[options.name] = commandName; + return command; +}; + +exports.interactionToCommand = function (interaction) { + return interactionInMap(interaction, commandMap); +}; + +function interactionInMap(interaction, map) { + if (typeof map === 'string') { + return map; + } + const name = interaction.name; + if (map[name]) { + return interactionInMap(interaction.options[0], map[name]); + } else { + return false; + } +} + +function findName(name, list) { + return list.find((ele) => ele.name === name); +} diff --git a/src/utils/global.js b/src/utils/global.js index 3c7e9998..298ad90c 100644 --- a/src/utils/global.js +++ b/src/utils/global.js @@ -221,7 +221,7 @@ exports.cleanString = function (string) { }; exports.isEmoji = function (string) { - return /^$/gi.test(string.trim()); + return /^$/gi.test(string?.trim()); }; exports.parseTime = function (diff) { @@ -392,3 +392,29 @@ exports.selectRandom = function (array, total) { } } }; + +exports.getStealButton = async function (p, withComponent) { + const sql = `SELECT emoji_steal.guild FROM emoji_steal INNER JOIN user ON emoji_steal.uid = user.uid WHERE id = ${p.msg.author.id};`; + const canSteal = (await p.query(sql))[0]?.guild; + if (!canSteal) { + return; + } + const components = [ + { + type: 1, + components: [ + { + type: 2, + label: 'Steal', + style: 1, + custom_id: 'steal', + emoji: { + id: null, + name: p.config.emoji.steal, + }, + }, + ], + }, + ]; + return withComponent ? components : components[0].components; +}; diff --git a/src/utils/interactionCollector.js b/src/utils/interactionCollector.js index a504e6bd..6ad87cf8 100644 --- a/src/utils/interactionCollector.js +++ b/src/utils/interactionCollector.js @@ -23,11 +23,13 @@ class InteractionCollector { return iee; } - interact({ member, message, data, id, token, entitlements }) { - member.id = member.user.id; + interact({ user, member, message, data, id, token, entitlements }) { + if (member) { + member.id = member.user.id; + } const listener = this.listeners[message.id] || this.listeners[message.interaction?.id]; if (listener) { - listener.interact(data, member, id, token, entitlements); + listener.interact(data, member || user, id, token, entitlements); } else { const url = `https://discord.com/api/v8/interactions/${id}/${token}/callback`; const content = { diff --git a/src/utils/logger.js b/src/utils/logger.js index 31b7536a..63c7ad4f 100644 --- a/src/utils/logger.js +++ b/src/utils/logger.js @@ -118,7 +118,7 @@ exports.logstash = function (command, p) { user: p.msg.author.id, command: command, text: p.msg.content, - guild: p.msg.channel.guild.id, + guild: p.msg.channel.guild?.id | 'dm', }; request( diff --git a/utils/registerAppCommands.js b/utils/registerAppCommands.js new file mode 100644 index 00000000..3de269fb --- /dev/null +++ b/utils/registerAppCommands.js @@ -0,0 +1,170 @@ +/* + * OwO Bot for Discord + * Copyright (C) 2024 Christopher Thai + * This software is licensed under Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International + * For more information, see README.md and LICENSE + */ + +// Run this file to update all application commands +require('dotenv').config(); +const axios = require('axios'); +const requireDir = require('require-dir'); +const dir = requireDir('../src/commands/commandList', { recurse: true }); +const CommandInterface = require('../src/commands/CommandInterface.js'); + +const url = `https://discord.com/api/v8/applications/${process.env.CLIENT_ID}/commands`; +const headers = { + 'Authorization': `Bot ${process.env.CLIENT_SECRET}`, +}; + +const newCommands = {}; +const currCommands = {}; +const uniqueCommands = {}; + +async function run() { + let result = await axios({ + method: 'GET', + headers, + url, + }); + parseAppCommands(currCommands, result.data); + + for (let key in uniqueCommands) { + if (isDiff(newCommands[key], currCommands[key])) { + if (!newCommands[key]) { + console.log(`Deleting command: ${key}`); + await deleteCommand(currCommands[key].id); + } else if (!currCommands[key]) { + console.log(`Adding command: ${key}`); + await addCommand(newCommands[key]); + } else { + console.log(`Editing command: ${key}`); + await editCommand(newCommands[key], currCommands[key].id); + } + } + } + + process.exit(); +} + +function init() { + for (let key in dir) { + if (dir[key] instanceof CommandInterface) { + parseAppCommands(newCommands, dir[key].appCommands); + } else if (Array.isArray(dir[key])) { + dir[key].forEach((val) => { + if (val instanceof CommandInterface) { + parseAppCommands(newCommands, val.appCommands); + } + }); + } else { + for (let key2 in dir[key]) { + if (dir[key][key2] instanceof CommandInterface) { + parseAppCommands(newCommands, dir[key][key2].appCommands); + } else if (Array.isArray(dir[key][key2])) { + dir[key][key2].forEach((val) => { + if (val instanceof CommandInterface) { + parseAppCommands(newCommands, val.appCommands); + } + }); + } + } + } + } +} + +function parseAppCommands(dict, commands) { + commands?.forEach((appCommand) => { + if (!appCommand.type) { + // Default to slash command + appCommand.type = 1; + } + const key = getKey(appCommand); + if (dict[key]) { + appendOptions(dict[key], appCommand); + } else { + dict[key] = appCommand; + uniqueCommands[key] = true; + } + }); +} + +function appendOptions(baseCommand, newCommand) { + const depth = getDepth(baseCommand, newCommand); + let options = baseCommand.options; + let append = newCommand.options; + for (let i = 1; i < depth; i++) { + options = options[0].options; + append = append[0].options; + } + options.push(...append); +} + +function getDepth(baseCommand, newCommand, depth = 0) { + if (baseCommand.name === newCommand.name) { + depth++; + return getDepth(baseCommand.options[0], newCommand.options[0], depth); + } else { + return depth; + } +} + +function getKey(command) { + return `${command.name}-${command.type}`; +} + +function isDiff(newCommand, currCommand) { + if (typeof newCommand !== typeof currCommand) { + return true; + } else if (typeof newCommand !== 'object') { + if (newCommand !== currCommand) { + return true; + } + } else if (Array.isArray(newCommand)) { + if (!Array.isArray(currCommand) || newCommand.length !== currCommand.length) { + return true; + } else { + for (let i = 0; i < newCommand.length; i++) { + if (isDiff(newCommand[i], currCommand[i])) { + return true; + } + } + } + } else { + for (const key in newCommand) { + if (isDiff(newCommand[key], currCommand[key])) { + return true; + } + } + } + return false; +} + +async function deleteCommand(commandId) { + return axios({ + method: 'DELETE', + headers, + url: `${url}/${commandId}`, + }); +} + +async function addCommand(command) { + return axios({ + method: 'POST', + headers, + url, + data: command, + }); +} + +async function editCommand(command, commandId) { + return axios({ + method: 'PATCH', + headers, + url: `${url}/${commandId}`, + data: command, + }); +} + +init(); +run();