const {Client, Collection, Channel, Permission} = require("@projectdysnomia/dysnomia"); const fs = require("node:fs"); const {resolve} = require("node:path"); const sqlite3 = require("sqlite3"); const {instead, before} = require("spitroast"); const config = require("#root/config.json"); const apikeys = require("#root/apikeys.json"); const logger = require("#lib/logger.js"); const events = require("#lib/events.js"); const timer = require("#lib/timer.js"); const Command = require("#lib/command.js"); const InteractionCommand = require("#lib/interactionCommand.js"); const {APIEndpoints, Intents, ApplicationCommandTypes, GatewayOPCodes} = require("#util/dconstants.js"); const bot = new Client(config.token, { defaultImageFormat: "png", defaultImageSize: 1024, gateway: { intents: Object.values(Intents), }, restMode: true, }); const commands = new Collection(); const interactionCommands = new Collection(); const database = new sqlite3.Database(resolve(__dirname, "..", "database.db")); function registerCommand(cmdObj) { if (cmdObj instanceof Command) { commands.set(cmdObj.name, cmdObj); const aliases = cmdObj.getAliases(); logger.info( "hf:cmd", `Registered command '${cmdObj.name}'${aliases.length > 0 ? ` (aliases: ${aliases.join(", ")})` : ""}` ); } else if (cmdObj instanceof InteractionCommand) { interactionCommands.set(cmdObj.name, cmdObj); logger.info("hf:cmd", `Registered interaction command '${cmdObj.name}'`); } } global.hf = { bot, config, apikeys, commands, interactionCommands, registerCommand, events, timer, database, }; const {formatUsername} = require("#util/misc.js"); const CommandDispatcher = require("#lib/commandDispatcher.js"); const {InteractionDispatcher} = require("#lib/interactionDispatcher.js"); const {hasFlag} = require("#lib/guildSettings.js"); for (const file of fs.readdirSync(resolve(__dirname, "modules"), {withFileTypes: true})) { if (file.isDirectory()) continue; try { require(`#modules/${file.name}`); logger.info("hf:modules", `Loaded module: "${file.name}"`); } catch (err) { logger.error("hf:modules", `Failed to load "${file.name}": ${err}`); } } bot.on("messageCreate", async (msg) => { try { // fix DMs cause of gateway v8 changes if (!(msg.channel instanceof Channel) && msg.author.id != bot.user.id && !msg.guildID) { const newChannel = await bot.getDMChannel(msg.author.id); if (msg.channel.id == newChannel.id) msg.channel = newChannel; } if (!(msg.channel instanceof Channel)) return; if (msg.author.bot && msg.guildID) { const canBot = await hasFlag(msg.guildID, "replyToBots"); if (!canBot) return; } await CommandDispatcher(msg); } catch (err) { const stack = (err?.stack ?? err.message).split("\n"); const error = stack.shift(); logger.error("hf:main", `Failed to dispatch command: ${error}\n\t${stack.join("\n\t")}`); } }); bot.on("messageUpdate", async (msg, oldMsg) => { try { const oneDay = Date.now() - 86400000; if (msg.timestamp > oneDay && !msg.hasRan && oldMsg && oldMsg.content !== msg.content) { await CommandDispatcher(msg); } } catch (err) { const stack = (err?.stack ?? err.message).split("\n"); const error = stack.shift(); logger.error("hf:main", `Failed to dispatch command update: ${error}\n\t${stack.join("\n\t")}`); } }); bot.on("messageReactionAdd", async (msg, reaction, reactor) => { if (msg?.author?.id !== bot.user.id) return; if (reaction.name !== "\u274c") return; try { let channel = msg.channel; if (!(channel instanceof Channel)) { const newChannel = hf.bot.getChannel(channel.id); if (newChannel) { channel = newChannel; } else { channel = await hf.bot.getRESTChannel(channel.id); } } if (!msg.messageReference) { msg = await channel.getMessage(msg.id); } if (!msg.messageReference) return; const ref = await channel.getMessage(msg.messageReference.messageID); if (!ref) return; if (ref.author.id !== reactor.id) return; await msg.delete("Command sender requested output deletion."); } catch (err) { const stack = (err?.stack ?? err.message).split("\n"); const error = stack.shift(); logger.error("hf:main", `Failed to self-delete message: ${error}\n\t${stack.join("\n\t")}`); } }); bot.on("interactionCreate", async (interaction) => { try { await InteractionDispatcher(interaction); } catch (err) { const stack = (err?.stack ?? err.message).split("\n"); const error = stack.shift(); logger.error("hf:main", `Failed to dispatch interaction command: ${error}\n\t${stack.join("\n\t")}`); } }); bot.once("ready", async () => { logger.info("hf:main", "Connected to Discord."); logger.info("hf:main", `Logged in as: ${formatUsername(bot.user)} (${bot.user.id})`); try { const channel = await bot.getDMChannel(config.owner_id); if (channel) { await channel.createMessage({ content: "<:ms_tick:503341995348066313> Loaded HiddenPhox.", }); } } catch (err) { logger.error("hf:main", `Failed to send startup message, API probably broken currently.\n${err}`); } bot.on("ready", () => { logger.info("hf:main", "Reconnected to Discord."); }); if (!config.no_commands) { const commands = []; for (const command of interactionCommands.values()) { const options = []; for (const option of Object.values(command.options)) { const newOption = Object.assign({}, option); delete newOption.default; options.push(newOption); } // stupid hack // FIXME: make send optional, check if it is send const send = options.shift(); options.push(send); const formattedCommand = { name: command.name, type: command.type, description: command.helpText, options: options, integration_types: [0], contexts: [0], }; if (!command.guildOnly) { formattedCommand.integration_types.push(1); formattedCommand.contexts.push(1, 2); } if (command.type === ApplicationCommandTypes.CHAT_INPUT) formattedCommand.name = formattedCommand.name.toLowerCase(); if (command.permissions !== undefined) { formattedCommand.default_member_permissions = command.permissions instanceof Permission ? String(command.permissions.allow) : String(command.permissions); } commands.push(formattedCommand); } try { await bot.requestHandler.request("PUT", APIEndpoints.COMMANDS(bot.application.id), true, commands); } catch (err) { logger.error("hf:main", `Failed to update interaction commands, API probably broken currently.\n${err}`); } } }); bot.on("error", (err) => { logger.error("hf:main", "Catching error: " + err); }); bot.on("warn", (err) => { logger.warn("hf:main", "Catching warn: " + err); }); bot.on("shardDisconnect", (err, id) => { logger.verbose("hf:shard", `Disconnecting from shard ${id}: ${err}`); }); bot.on("shardResume", (id) => { logger.verbose("hf:shard", "Resuming on shard " + id); }); bot.on("shardPreReady", (id) => { logger.verbose("hf:shard", `Shard ${id} getting ready`); }); bot.on("shardReady", (id) => { logger.verbose("hf:shard", `Shard ${id} ready`); }); bot.on("unknown", (packet, id) => { logger.verbose("hf:main", `Shard ${id} caught unknown packet: ${JSON.stringify(packet)}`); }); instead("spawn", bot.shards, function (args, orig) { const ret = orig.apply(this, args); const shard = this.get(args[0]); if (shard) { before("sendWS", shard.__proto__, function ([op, _data]) { if (op === GatewayOPCodes.IDENTIFY) { _data.properties.browser = "Discord Embedded"; delete _data.properties.device; } }); } return ret; }); bot.connect();