From 3e6168a396663d12ad44320756013dfa19f10119 Mon Sep 17 00:00:00 2001 From: "Ean Milligan (Bastion)" Date: Wed, 11 Jan 2023 18:06:20 -0500 Subject: [PATCH] Initial setup for standard events --- .gitignore | 1 + config.example.ts | 2 +- deps.ts | 46 ++++++--------------------- mod.ts | 18 +++++++++++ src/commandUtils.ts | 30 ++++++++++++++++++ src/db.ts | 15 +++++++++ src/events.ts | 17 ++++++++++ src/events/_index.ts | 13 ++++++++ src/events/debug.ts | 8 +++++ src/events/guildCreate.ts | 40 ++++++++++++++++++++++++ src/events/guildDelete.ts | 39 +++++++++++++++++++++++ src/events/messageCreate.ts | 17 ++++++++++ src/events/ready.ts | 62 +++++++++++++++++++++++++++++++++++++ src/utils.ts | 39 +++++++++++++++++++++++ 14 files changed, 310 insertions(+), 37 deletions(-) create mode 100644 src/commandUtils.ts create mode 100644 src/db.ts create mode 100644 src/events.ts create mode 100644 src/events/_index.ts create mode 100644 src/events/debug.ts create mode 100644 src/events/guildCreate.ts create mode 100644 src/events/guildDelete.ts create mode 100644 src/events/messageCreate.ts create mode 100644 src/events/ready.ts create mode 100644 src/utils.ts diff --git a/.gitignore b/.gitignore index 3ffa028..ca9e9d4 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ config.ts logs db/update.ts +deno.lock diff --git a/config.example.ts b/config.example.ts index 04c30f8..e3f5ee6 100644 --- a/config.example.ts +++ b/config.example.ts @@ -2,7 +2,7 @@ export const config = { 'name': 'Group Up', // Name of the bot 'version': '1.0.0', // Version of the bot 'token': 'the_bot_token', // Discord API Token for this bot - 'localtoken': 'local_testing_token', // Discord API Token for a secondary OPTIONAL testing bot, THIS MUST BE DIFFERENT FROM "token" + 'localToken': 'local_testing_token', // Discord API Token for a secondary OPTIONAL testing bot, THIS MUST BE DIFFERENT FROM "token" 'prefix': 'gu!', // Prefix for all commands 'db': { // Settings for the MySQL database, this is required for use with the API, if you do not want to set this up, you will need to rip all code relating to the DB out of the bot 'host': '', // IP address for the db, usually localhost diff --git a/deps.ts b/deps.ts index 0e16319..5c937fa 100644 --- a/deps.ts +++ b/deps.ts @@ -1,40 +1,14 @@ -// All external dependancies are to be loaded here to make updating dependancy versions much easier -export { - botId, - cache, - cacheHandlers, - deleteMessage, - DiscordActivityTypes, - DiscordButtonStyles, - DiscordInteractionResponseTypes, - DiscordInteractionTypes, - editBotNickname, - editBotStatus, - getGuild, - getMessage, - getUser, - hasGuildPermissions, - Intents, - sendDirectMessage, - sendInteractionResponse, - sendMessage, - startBot, - structures, -} from 'https://deno.land/x/discordeno@17.0.1/mod.ts'; +// All external dependencies are to be loaded here to make updating dependency versions much easier +import { getBotIdFromToken } from 'https://deno.land/x/discordeno@16.0.1/mod.ts'; +import config from './config.ts'; +import { LOCALMODE } from './flags.ts'; +export const botId = getBotIdFromToken(LOCALMODE ? config.localToken : config.token); -export type { - ActionRow, - ButtonComponent, - ButtonData, - CreateMessage, - DebugArg, - DiscordenoGuild, - DiscordenoMember, - DiscordenoMessage, - Embed, - EmbedField, - Interaction, -} from 'https://deno.land/x/discordeno@17.0.1/mod.ts'; +export { enableCachePlugin, enableCacheSweepers } from 'https://deno.land/x/discordeno@17.0.1/plugins/cache/mod.ts'; +export type { BotWithCache } from 'https://deno.land/x/discordeno@17.0.1/plugins/cache/mod.ts'; + +export { ActivityTypes, createBot, editBotMember, editBotStatus, getBotIdFromToken, Intents, sendInteractionResponse, sendMessage, startBot } from 'https://deno.land/x/discordeno@17.0.1/mod.ts'; +export type { ActionRow, Bot, ButtonComponent, CreateMessage, Embed, EventHandlers, Guild, Interaction, Message } from 'https://deno.land/x/discordeno@17.0.1/mod.ts'; export { Client } from 'https://deno.land/x/mysql@v2.11.0/mod.ts'; diff --git a/mod.ts b/mod.ts index e69de29..478c0ff 100644 --- a/mod.ts +++ b/mod.ts @@ -0,0 +1,18 @@ +import config from './config.ts'; +import { DEBUG, LOCALMODE } from './flags.ts'; +import { createBot, enableCachePlugin, enableCacheSweepers, initLog, Intents, startBot } from './deps.ts'; +import { events } from './src/events.ts'; + +// Initialize logging client with folder to use for logs, needs --allow-write set on Deno startup +initLog('logs', DEBUG); + +// Set up the Discord Bot +const bot = enableCachePlugin(createBot({ + token: LOCALMODE ? config.localToken : config.token, + intents: Intents.MessageContent | Intents.GuildMessages | Intents.DirectMessages | Intents.Guilds | Intents.GuildMessageReactions, + events, +})); +enableCacheSweepers(bot); + +// Start the bot +startBot(bot); diff --git a/src/commandUtils.ts b/src/commandUtils.ts new file mode 100644 index 0000000..bde9ce3 --- /dev/null +++ b/src/commandUtils.ts @@ -0,0 +1,30 @@ +import config from '../config.ts'; + +export const failColor = 0xe71212; +export const warnColor = 0xe38f28; +export const successColor = 0x0f8108; +export const infoColor1 = 0x313bf9; +export const infoColor2 = 0x6805e9; + +export const getRandomStatus = (guildCount: number): string => { + let status = ''; + switch (Math.floor((Math.random() * 5) + 1)) { + case 1: + status = `${config.prefix}help for commands`; + break; + case 2: + status = `Running V${config.version}`; + break; + case 3: + status = `${config.prefix}info to learn more`; + break; + case 4: + status = 'Mention me to check my prefix!'; + break; + default: + status = `Running LFGs in ${guildCount} servers`; + break; + } + + return status; +}; diff --git a/src/db.ts b/src/db.ts new file mode 100644 index 0000000..099cc2a --- /dev/null +++ b/src/db.ts @@ -0,0 +1,15 @@ +import config from '../config.ts'; +import { Client } from '../deps.ts'; +import { LOCALMODE } from '../flags.ts'; + +export const dbClient = await new Client().connect({ + hostname: LOCALMODE ? config.db.localhost : config.db.host, + port: config.db.port, + db: config.db.name, + username: config.db.username, + password: config.db.password, +}); + +export const queries = { + callIncCnt: (cmdName: string) => `CALL INC_CNT("${cmdName}");`, +}; diff --git a/src/events.ts b/src/events.ts new file mode 100644 index 0000000..9361475 --- /dev/null +++ b/src/events.ts @@ -0,0 +1,17 @@ +import { DEVMODE } from '../flags.ts'; +import { + // Discordeno deps + EventHandlers, +} from '../deps.ts'; +import eventHandlers from './events/_index.ts'; + +export const events: Partial = {}; + +events.ready = eventHandlers.ready; +events.guildCreate = eventHandlers.guildCreate; +events.guildDelete = eventHandlers.guildDelete; +events.messageCreate = eventHandlers.messageCreate; + +if (DEVMODE) { + events.debug = eventHandlers.debug; +} diff --git a/src/events/_index.ts b/src/events/_index.ts new file mode 100644 index 0000000..2bdf3db --- /dev/null +++ b/src/events/_index.ts @@ -0,0 +1,13 @@ +import { ready } from './ready.ts'; +import { guildCreate } from './guildCreate.ts'; +import { guildDelete } from './guildDelete.ts'; +import { debug } from './debug.ts'; +import { messageCreate } from './messageCreate.ts'; + +export default { + ready, + guildCreate, + guildDelete, + debug, + messageCreate, +}; diff --git a/src/events/debug.ts b/src/events/debug.ts new file mode 100644 index 0000000..e997a1a --- /dev/null +++ b/src/events/debug.ts @@ -0,0 +1,8 @@ +import { + // Log4Deno deps + log, + LT, +} from '../../deps.ts'; +import utils from '../utils.ts'; + +export const debug = (dmsg: string) => log(LT.LOG, `Debug Message | ${utils.jsonStringifyBig(dmsg)}`); diff --git a/src/events/guildCreate.ts b/src/events/guildCreate.ts new file mode 100644 index 0000000..38d3b02 --- /dev/null +++ b/src/events/guildCreate.ts @@ -0,0 +1,40 @@ +import config from '../../config.ts'; +import { + // Discordeno deps + Bot, + Guild, + // Log4Deno deps + log, + LT, + // Discordeno deps + sendMessage, +} from '../../deps.ts'; +import { infoColor1 } from '../commandUtils.ts'; +import utils from '../utils.ts'; + +export const guildCreate = (bot: Bot, guild: Guild) => { + log(LT.LOG, `Handling joining guild ${utils.jsonStringifyBig(guild)}`); + sendMessage(bot, config.logChannel, { + embeds: [{ + title: 'Guild Joined!', + color: infoColor1, + fields: [ + { + name: 'Name:', + value: `${guild.name}`, + inline: true, + }, + { + name: 'Id:', + value: `${guild.id}`, + inline: true, + }, + { + name: 'Member Count:', + value: `${guild.memberCount}`, + inline: true, + }, + ], + }], + }).catch((e: Error) => utils.commonLoggers.messageSendError('mod.ts:95', 'Join Guild', e)); +}; diff --git a/src/events/guildDelete.ts b/src/events/guildDelete.ts new file mode 100644 index 0000000..333eb23 --- /dev/null +++ b/src/events/guildDelete.ts @@ -0,0 +1,39 @@ +import config from '../../config.ts'; +import { + // Discordeno deps + Bot, + // Log4Deno deps + log, + LT, + // Discordeno deps + sendMessage, +} from '../../deps.ts'; +import { warnColor } from '../commandUtils.ts'; +import { dbClient } from '../db.ts'; +import utils from '../utils.ts'; + +export const guildDelete = async (bot: Bot, guildId: bigint) => { + log(LT.LOG, `Handling leaving guild ${utils.jsonStringifyBig(guildId)}`); + + try { + await dbClient.execute('DELETE FROM guild_prefix WHERE guildId = ?', [guildId]); + await dbClient.execute('DELETE FROM guild_mod_role WHERE guildId = ?', [guildId]); + await dbClient.execute('DELETE FROM guild_clean_channel WHERE guildId = ?', [guildId]); + } catch (e) { + log(LT.WARN, `Failed to remove guild from DB: ${utils.jsonStringifyBig(e)}`); + } + + sendMessage(bot, config.logChannel, { + embeds: [{ + title: 'Removed from Guild', + color: warnColor, + fields: [ + { + name: 'Id:', + value: `${guildId}`, + inline: true, + }, + ], + }], + }).catch((e: Error) => utils.commonLoggers.messageSendError('guildDelete.ts:28', 'Leave Guild', e)); +}; diff --git a/src/events/messageCreate.ts b/src/events/messageCreate.ts new file mode 100644 index 0000000..ebbaf7c --- /dev/null +++ b/src/events/messageCreate.ts @@ -0,0 +1,17 @@ +import config from '../../config.ts'; +import { Bot, botId, Message } from '../../deps.ts'; + +export const messageCreate = async (bot: Bot, message: Message) => { + // Ignore all messages that are not commands + if (message.content.indexOf(config.prefix) !== 0) { + // Handle @bot messages + if (message.mentionedUserIds[0] === botId && (message.content.trim().startsWith(`<@${botId}>`) || message.content.trim().startsWith(`<@!${botId}>`))) { + } + + // return as we are done handling this command + return; + } + + // Ignore all other bots + if (message.isFromBot) return; +}; diff --git a/src/events/ready.ts b/src/events/ready.ts new file mode 100644 index 0000000..eac7fe9 --- /dev/null +++ b/src/events/ready.ts @@ -0,0 +1,62 @@ +import config from '../../config.ts'; +import { LOCALMODE } from '../../flags.ts'; +import { ActivityTypes, Bot, BotWithCache, editBotMember, editBotStatus, log, LT, sendMessage } from '../../deps.ts'; +import { getRandomStatus, successColor } from '../commandUtils.ts'; +import utils from '../utils.ts'; + +export const ready = (rawBot: Bot) => { + const bot = rawBot as BotWithCache; + log(LT.INFO, `${config.name} Logged in!`); + editBotStatus(bot, { + activities: [{ + name: 'Booting up . . .', + type: ActivityTypes.Game, + createdAt: new Date().getTime(), + }], + status: 'online', + }); + + // Interval to rotate the status text every 30 seconds to show off more commands + setInterval(async () => { + log(LT.LOG, 'Changing bot status'); + try { + // Wrapped in try-catch due to hard crash possible + editBotStatus(bot, { + activities: [{ + name: getRandomStatus(bot.guilds.size + bot.dispatchedGuildIds.size), + type: ActivityTypes.Game, + createdAt: new Date().getTime(), + }], + status: 'online', + }); + } catch (e) { + log(LT.ERROR, `Failed to update status: ${utils.jsonStringifyBig(e)}`); + } + }, 30000); + + // setTimeout added to make sure the startup message does not error out + setTimeout(() => { + LOCALMODE && editBotMember(bot, config.devServer, { nick: `LOCAL - ${config.name}` }); + editBotStatus(bot, { + activities: [{ + name: 'Booting Complete', + type: ActivityTypes.Game, + createdAt: new Date().getTime(), + }], + status: 'online', + }); + sendMessage(bot, config.logChannel, { + embeds: [{ + title: `${config.name} is now Online`, + color: successColor, + fields: [ + { + name: 'Version:', + value: `${config.version}`, + inline: true, + }, + ], + }], + }).catch((e: Error) => utils.commonLoggers.messageSendError('ready.ts:71', 'Startup', e)); + }, 1000); +}; diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..aaa8964 --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,39 @@ +import { + // Log4Deno deps + log, + LT, + // Discordeno deps + Message, +} from '../deps.ts'; + +const jsonStringifyBig = (input: any) => { + return JSON.stringify(input, (_key, value) => typeof value === 'bigint' ? value.toString() + 'n' : value); +}; + +const genericLogger = (level: LT, message: string) => log(level, message); +const messageEditError = (location: string, message: Message | string, err: Error) => + genericLogger(LT.ERROR, `${location} | Failed to edit message: ${jsonStringifyBig(message)} | Error: ${err.name} - ${err.message}`); +const messageGetError = (location: string, message: Message | string, err: Error) => + genericLogger(LT.ERROR, `${location} | Failed to get message: ${jsonStringifyBig(message)} | Error: ${err.name} - ${err.message}`); +const messageSendError = (location: string, message: Message | string, err: Error) => + genericLogger(LT.ERROR, `${location} | Failed to send message: ${jsonStringifyBig(message)} | Error: ${err.name} - ${err.message}`); +const messageDeleteError = (location: string, message: Message | string, err: Error) => + genericLogger(LT.ERROR, `${location} | Failed to delete message: ${jsonStringifyBig(message)} | Error: ${err.name} - ${err.message}`); +const reactionAddError = (location: string, message: Message | string, err: Error, emoji: string) => + genericLogger(LT.ERROR, `${location} | Failed to add emoji (${emoji}) to message: ${jsonStringifyBig(message)} | Error: ${err.name} - ${err.message}`); +const reactionDeleteError = (location: string, message: Message | string, err: Error, emoji: string) => + genericLogger(LT.ERROR, `${location} | Failed to delete emoji (${emoji}) from message: ${jsonStringifyBig(message)} | Error: ${err.name} - ${err.message}`); +const dbError = (location: string, type: string, err: Error) => genericLogger(LT.ERROR, `${location} | Failed to ${type} database | Error: ${err.name} - ${err.message}`); + +export default { + commonLoggers: { + dbError, + messageGetError, + messageEditError, + messageSendError, + messageDeleteError, + reactionAddError, + reactionDeleteError, + }, + jsonStringifyBig, +};