HiddenPhox/src/index.js

252 lines
7.7 KiB
JavaScript

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();