From f2700cc61512fde5185568699ea5addaf7619d3a Mon Sep 17 00:00:00 2001 From: Chen Asraf Date: Wed, 31 Jul 2024 02:20:34 +0300 Subject: [PATCH] refactor: re-implement old commands --- LICENSE | 21 +++ eslint.config.mjs | 9 ++ legacy/src/app.ts | 2 +- legacy/src/bot/Bot.ts | 2 +- legacy/src/bot/commands/Command.ts | 2 +- legacy/src/core/services/config.service.ts | 6 +- legacy/src/core/services/logger.service.ts | 2 +- legacy/src/core/types/Config.ts | 12 -- legacy/src/core/types/Dependencies.ts | 13 -- legacy/src/core/types/Environment.ts | 3 - legacy/src/core/types/LogLevel.ts | 3 - legacy/src/core/types/Quote.ts | 11 -- package.json | 4 +- pnpm-lock.yaml | 114 +++++++++++++++ src/commands/add-greeting.command.ts | 45 +++--- src/commands/character.command.ts | 30 ++-- src/commands/eight-ball.command.ts | 21 ++- src/commands/help.command.ts | 76 +++++----- src/commands/ping.command.ts | 16 +- src/commands/quotes.command.ts | 161 ++++++++++++--------- src/commands/see.command.ts | 18 ++- src/core/commands.ts | 11 +- src/core/db.ts | 5 + src/env.ts | 4 +- 24 files changed, 384 insertions(+), 207 deletions(-) create mode 100644 LICENSE delete mode 100644 legacy/src/core/types/Config.ts delete mode 100644 legacy/src/core/types/Dependencies.ts delete mode 100644 legacy/src/core/types/Environment.ts delete mode 100644 legacy/src/core/types/LogLevel.ts delete mode 100644 legacy/src/core/types/Quote.ts create mode 100644 src/core/db.ts diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..a9e800c --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright © 2020 Chen Asraf, John Deaves, Lance Krasniqi + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/eslint.config.mjs b/eslint.config.mjs index 5700f7f..06298dd 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -3,6 +3,15 @@ import tseslint from 'typescript-eslint' export default [ ...tseslint.config(eslint.configs.recommended, ...tseslint.configs.recommended), + { + rules: { + 'no-unused-vars': ['warn', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }], + '@typescript-eslint/no-unused-vars': [ + 'warn', + { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }, + ], + }, + }, { ignores: ['node_modules/', 'build/', 'dist/', 'gen/'], }, diff --git a/legacy/src/app.ts b/legacy/src/app.ts index 3308045..4ca625b 100644 --- a/legacy/src/app.ts +++ b/legacy/src/app.ts @@ -6,7 +6,7 @@ import HttpService from './core/services/http.service'; import LoggerService from './core/services/logger.service'; import MongoService from './core/services/mongo.service'; -import Dependencies from './core/types/Dependencies'; +import Dependencies from '../../src/types/Dependencies'; import Bot from './bot/Bot'; diff --git a/legacy/src/bot/Bot.ts b/legacy/src/bot/Bot.ts index de0483b..a6fb305 100644 --- a/legacy/src/bot/Bot.ts +++ b/legacy/src/bot/Bot.ts @@ -1,6 +1,6 @@ import Discord from 'discord.js'; -import Dependencies from '../core/types/Dependencies'; +import Dependencies from '../../../src/types/Dependencies'; import Command from './commands/Command'; import AddGreetingCommand from './commands/add-greeting.command'; diff --git a/legacy/src/bot/commands/Command.ts b/legacy/src/bot/commands/Command.ts index 22979b2..a0bab76 100644 --- a/legacy/src/bot/commands/Command.ts +++ b/legacy/src/bot/commands/Command.ts @@ -1,6 +1,6 @@ import Discord from 'discord.js'; -import Dependencies from '../../core/types/Dependencies'; +import Dependencies from '../../../../src/types/Dependencies'; export default abstract class Command { /** diff --git a/legacy/src/core/services/config.service.ts b/legacy/src/core/services/config.service.ts index 8187ffb..d7ff5df 100644 --- a/legacy/src/core/services/config.service.ts +++ b/legacy/src/core/services/config.service.ts @@ -1,6 +1,6 @@ -import Config from '../types/Config'; -import Environment from '../types/Environment'; -import LogLevel from '../types/LogLevel'; +import Config from '../../../../src/types/Config'; +import Environment from '../../../../src/types/Environment'; +import LogLevel from '../../../../src/types/LogLevel'; export default class ConfigService { /** diff --git a/legacy/src/core/services/logger.service.ts b/legacy/src/core/services/logger.service.ts index 0459667..58ab524 100644 --- a/legacy/src/core/services/logger.service.ts +++ b/legacy/src/core/services/logger.service.ts @@ -1,7 +1,7 @@ import path from 'path'; import winston from 'winston'; -import LogLevel from '../types/LogLevel'; +import LogLevel from '../../../../src/types/LogLevel'; import ConfigService from './config.service'; diff --git a/legacy/src/core/types/Config.ts b/legacy/src/core/types/Config.ts deleted file mode 100644 index 4b84192..0000000 --- a/legacy/src/core/types/Config.ts +++ /dev/null @@ -1,12 +0,0 @@ -import Environment from './Environment'; -import LogLevel from './LogLevel'; - -export default interface Config { - BOT_TRIGGER: string; - DISCORD_BOT_TOKEN: string; - MONGODB_URI: string; - MONGODB_DB_NAME: string; - DATABASE_URL: string; - NODE_ENV: Environment; - LOG_LEVEL: LogLevel; -} diff --git a/legacy/src/core/types/Dependencies.ts b/legacy/src/core/types/Dependencies.ts deleted file mode 100644 index 781766c..0000000 --- a/legacy/src/core/types/Dependencies.ts +++ /dev/null @@ -1,13 +0,0 @@ -import ConfigService from '../services/config.service'; -import DatabaseService from '../services/database.service'; -import HttpService from '../services/http.service'; -import LoggerService from '../services/logger.service'; -import MongoService from '../services/mongo.service'; - -export default interface Dependencies { - configService: ConfigService; - databaseService: DatabaseService; - httpService: HttpService; - loggerService: LoggerService; - mongoService: MongoService; -} diff --git a/legacy/src/core/types/Environment.ts b/legacy/src/core/types/Environment.ts deleted file mode 100644 index e83bb1c..0000000 --- a/legacy/src/core/types/Environment.ts +++ /dev/null @@ -1,3 +0,0 @@ -type Environment = 'production' | 'development' | 'test'; - -export default Environment; diff --git a/legacy/src/core/types/LogLevel.ts b/legacy/src/core/types/LogLevel.ts deleted file mode 100644 index a130f8c..0000000 --- a/legacy/src/core/types/LogLevel.ts +++ /dev/null @@ -1,3 +0,0 @@ -type LogLevel = 'error' | 'warn' | 'info' | 'verbose' | 'debug' | 'silly'; - -export default LogLevel; diff --git a/legacy/src/core/types/Quote.ts b/legacy/src/core/types/Quote.ts deleted file mode 100644 index 66038a1..0000000 --- a/legacy/src/core/types/Quote.ts +++ /dev/null @@ -1,11 +0,0 @@ -export default interface Quote { - author: string; - quote: string; - shortId: string; - meta: { - authorCachedName: string; - createdBy: string; - createdByCachedName: string; - createdAt: Date; - }; -} diff --git a/package.json b/package.json index e548852..c9e531a 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,9 @@ }, "dependencies": { "discord.js": "^14.15.3", - "dotenv": "^16.4.5" + "dotenv": "^16.4.5", + "mongodb": "^6.8.0", + "nanoid": "^5.0.7" }, "nodemonConfig": { "ignore": [ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3aa91d4..ab0f266 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,6 +14,12 @@ importers: dotenv: specifier: ^16.4.5 version: 16.4.5 + mongodb: + specifier: ^6.8.0 + version: 6.8.0 + nanoid: + specifier: ^5.0.7 + version: 5.0.7 devDependencies: '@eslint/js': specifier: ^9.8.0 @@ -231,6 +237,9 @@ packages: resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} deprecated: Use @eslint/object-schema instead + '@mongodb-js/saslprep@1.1.8': + resolution: {integrity: sha512-qKwC/M/nNNaKUBMQ0nuzm47b7ZYWQHN3pcXq4IIcoSBc2hOIrflAxJduIvvqmhoz3gR2TacTAs8vlsCVPkiEdQ==} + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -258,6 +267,12 @@ packages: '@types/node@22.0.0': resolution: {integrity: sha512-VT7KSYudcPOzP5Q0wfbowyNLaVR8QWUdw+088uFWwfvpY6uCWaXpqV6ieLAu9WBcnTa7H4Z5RLK8I5t2FuOcqw==} + '@types/webidl-conversions@7.0.3': + resolution: {integrity: sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==} + + '@types/whatwg-url@11.0.5': + resolution: {integrity: sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==} + '@types/ws@8.5.12': resolution: {integrity: sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==} @@ -375,6 +390,10 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} + bson@6.8.0: + resolution: {integrity: sha512-iOJg8pr7wq2tg/zSlCCHMi3hMm5JTOxLTagf3zxhcenHsFp+c6uOs6K7W5UE7A4QIJGtqh/ZovFNMP4mOPJynQ==} + engines: {node: '>=16.20.1'} + callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} @@ -632,6 +651,9 @@ packages: magic-bytes.js@1.10.0: resolution: {integrity: sha512-/k20Lg2q8LE5xiaaSkMXk4sfvI+9EGEykFS4b0CHHGWqDYU0bGUFSwchNOMA56D7TCs9GwVTkqe9als1/ns8UQ==} + memory-pager@1.5.0: + resolution: {integrity: sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==} + merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} @@ -647,9 +669,44 @@ packages: resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} engines: {node: '>=16 || 14 >=14.17'} + mongodb-connection-string-url@3.0.1: + resolution: {integrity: sha512-XqMGwRX0Lgn05TDB4PyG2h2kKO/FfWJyCzYQbIhXUxz7ETt0I/FqHjUeqj37irJ+Dl1ZtU82uYyj14u2XsZKfg==} + + mongodb@6.8.0: + resolution: {integrity: sha512-HGQ9NWDle5WvwMnrvUxsFYPd3JEbqD3RgABHBQRuoCEND0qzhsd0iH5ypHsf1eJ+sXmvmyKpP+FLOKY8Il7jMw==} + engines: {node: '>=16.20.1'} + peerDependencies: + '@aws-sdk/credential-providers': ^3.188.0 + '@mongodb-js/zstd': ^1.1.0 + gcp-metadata: ^5.2.0 + kerberos: ^2.0.1 + mongodb-client-encryption: '>=6.0.0 <7' + snappy: ^7.2.2 + socks: ^2.7.1 + peerDependenciesMeta: + '@aws-sdk/credential-providers': + optional: true + '@mongodb-js/zstd': + optional: true + gcp-metadata: + optional: true + kerberos: + optional: true + mongodb-client-encryption: + optional: true + snappy: + optional: true + socks: + optional: true + ms@2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + nanoid@5.0.7: + resolution: {integrity: sha512-oLxFY2gd2IqnjcYyOXD8XGCftpGtZP2AbHbOkthDkvRywH5ayNtPVy9YlOPcHckXzbLTCHpkb7FB+yuxKV13pQ==} + engines: {node: ^18 || >=20} + hasBin: true + natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} @@ -759,6 +816,9 @@ packages: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} + sparse-bitfield@3.0.3: + resolution: {integrity: sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==} + strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -786,6 +846,10 @@ packages: resolution: {integrity: sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==} hasBin: true + tr46@4.1.1: + resolution: {integrity: sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==} + engines: {node: '>=14'} + ts-api-utils@1.3.0: resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==} engines: {node: '>=16'} @@ -839,6 +903,14 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + webidl-conversions@7.0.0: + resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} + engines: {node: '>=12'} + + whatwg-url@13.0.0: + resolution: {integrity: sha512-9WWbymnqj57+XEuqADHrCJ2eSXzn8WXIW/YSGaZtb2WKAInQ6CHfaUUcTyyver0p8BDg5StLQq8h1vtZuwmOig==} + engines: {node: '>=16'} + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -1022,6 +1094,10 @@ snapshots: '@humanwhocodes/object-schema@2.0.3': {} + '@mongodb-js/saslprep@1.1.8': + dependencies: + sparse-bitfield: 3.0.3 + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -1047,6 +1123,12 @@ snapshots: dependencies: undici-types: 6.11.1 + '@types/webidl-conversions@7.0.3': {} + + '@types/whatwg-url@11.0.5': + dependencies: + '@types/webidl-conversions': 7.0.3 + '@types/ws@8.5.12': dependencies: '@types/node': 22.0.0 @@ -1181,6 +1263,8 @@ snapshots: dependencies: fill-range: 7.1.1 + bson@6.8.0: {} + callsites@3.1.0: {} chalk@4.1.2: @@ -1497,6 +1581,8 @@ snapshots: magic-bytes.js@1.10.0: {} + memory-pager@1.5.0: {} + merge2@1.4.1: {} micromatch@4.0.7: @@ -1512,8 +1598,21 @@ snapshots: dependencies: brace-expansion: 2.0.1 + mongodb-connection-string-url@3.0.1: + dependencies: + '@types/whatwg-url': 11.0.5 + whatwg-url: 13.0.0 + + mongodb@6.8.0: + dependencies: + '@mongodb-js/saslprep': 1.1.8 + bson: 6.8.0 + mongodb-connection-string-url: 3.0.1 + ms@2.1.2: {} + nanoid@5.0.7: {} + natural-compare@1.4.0: {} nodemon@3.1.4: @@ -1606,6 +1705,10 @@ snapshots: slash@3.0.0: {} + sparse-bitfield@3.0.3: + dependencies: + memory-pager: 1.5.0 + strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 @@ -1628,6 +1731,10 @@ snapshots: touch@3.1.1: {} + tr46@4.1.1: + dependencies: + punycode: 2.3.1 + ts-api-utils@1.3.0(typescript@5.5.4): dependencies: typescript: 5.5.4 @@ -1672,6 +1779,13 @@ snapshots: dependencies: punycode: 2.3.1 + webidl-conversions@7.0.0: {} + + whatwg-url@13.0.0: + dependencies: + tr46: 4.1.1 + webidl-conversions: 7.0.0 + which@2.0.2: dependencies: isexe: 2.0.0 diff --git a/src/commands/add-greeting.command.ts b/src/commands/add-greeting.command.ts index b7c9b84..7e7fa34 100644 --- a/src/commands/add-greeting.command.ts +++ b/src/commands/add-greeting.command.ts @@ -1,39 +1,42 @@ -import Discord from 'discord.js'; -import Command from './Command'; +import { command } from '@/core/commands' +import { db } from '@/core/db' +import { BOT_PREFIX } from '@/env' -export default class AddGreetingCommand extends Command { - async execute(message: Discord.Message, args: string[]): Promise { +export default command({ + command: 'addgreeting', + aliases: ['ag', 'add-greeting'], + description: + 'Adds a string to the list greetings used when new users connect to server! Include `{name}` in your message to replace with the new users name.', + examples: [`\`${BOT_PREFIX}addgreeting Welcome to the club {name}\``], + async execute(message, args) { // Only certain users can use this command // TODO: Better handling of permissions for commands in a generic way - const permittedRoles = new Set(['staff', 'mod', 'bot-devs']); - const isPermitted = message.member.roles.cache.some((r) => permittedRoles.has(r.name)); + const permittedRoles = new Set(['staff', 'mod', 'bot-devs']) + const isPermitted = message.member?.roles.cache.some((r) => permittedRoles.has(r.name)) if (!isPermitted) { - return message.author.send("Sorry but I can't let you add greetings!"); + return message.author.send("Sorry but I can't let you add greetings!") } // Can't do much without a message if (args.length === 0) { - return message.author.send('When adding a greeting you need to also provide a message!'); + return message.author.send('When adding a greeting you need to also provide a message!') } // Check for dupes - const greetingStr = args.join(' '); - const matchedMessages = await this.dependencies.mongoService.find(message.author.id, 'greetings', { - message: greetingStr, - }); - if (matchedMessages.length > 0) { - return message.author.send('That greeting has already been added!'); + const greetingStr = args.join(' ') + const collection = db.collection('greetings') + const matchedMessages = await collection.countDocuments({ message: greetingStr }) + if (matchedMessages > 0) { + return message.author.send('That greeting has already been added!') } - const result = await this.dependencies.mongoService.insert(message.author.id, 'greetings', [ - { message: greetingStr }, - ]); + const result = await collection.insertOne({ message: greetingStr }) if (!result) { - return message.author.send("Uh-oh! Couldn't add that greeting!"); + return message.author.send("Uh-oh! Couldn't add that greeting!") } - return message.author.send("I've added the greeting you told me about!"); - } -} + return message.author.send("I've added the greeting you told me about!") + }, +}) diff --git a/src/commands/character.command.ts b/src/commands/character.command.ts index 04898e4..13f247f 100644 --- a/src/commands/character.command.ts +++ b/src/commands/character.command.ts @@ -1,18 +1,30 @@ -import Discord from 'discord.js'; +import Discord from 'discord.js' -import Character from '../../carp/character/character.entity'; +import { db } from '@/core/db' +import { command } from '@/core/commands' -import Command from './Command'; +// TODO move to types file +export interface Character { + uid: string + name: string +} -export default class CharacterCommand extends Command { +export default command({ + command: 'character', + aliases: ['c'], + description: 'Get information about your character', + examples: ['`!character`'], async execute(message: Discord.Message): Promise { // Just testing db stuff - const matchedChar = await this.dependencies.databaseService.manager.findOne(Character, message.author.id); + + const matchedChar = await db + .collection('characters') + .findOne({ userId: message.author.id }) if (!matchedChar) { - return message.reply(`Doesn't look like you have joined this campaign`); + return message.reply(`Doesn't look like you have joined this campaign`) } - return message.reply(`Welcome back ${matchedChar.name}`); - } -} + return message.reply(`Welcome back ${matchedChar.name}`) + }, +}) diff --git a/src/commands/eight-ball.command.ts b/src/commands/eight-ball.command.ts index 91c6f84..9229a38 100644 --- a/src/commands/eight-ball.command.ts +++ b/src/commands/eight-ball.command.ts @@ -1,10 +1,15 @@ -import Discord from 'discord.js'; -import Command from './Command'; +import { command } from '@/core/commands' +import { BOT_PREFIX } from '@/env' +import Discord from 'discord.js' -export default class EightBallCommand extends Command { +export default command({ + command: '8ball', + aliases: ['eightball', 'magicball', 'ball', 'wisdomball'], + description: 'Ask the magic eightball for advice.', + examples: [`\`${BOT_PREFIX} 8ball will I be awesome today?\``], async execute(message: Discord.Message, args: string[]): Promise { if (args.length === 0) { - return message.reply("where's the question?"); + return message.reply("where's the question?") } const responses = [ @@ -32,8 +37,8 @@ export default class EightBallCommand extends Command { 'yes.', 'yes - definitely.', 'yeah, you can rely on it.', - ]; + ] - return message.reply(responses[Math.floor(Math.random() * responses.length - 1)]); - } -} + return message.reply(responses[Math.floor(Math.random() * responses.length - 1)]) + }, +}) diff --git a/src/commands/help.command.ts b/src/commands/help.command.ts index c098374..b39d299 100644 --- a/src/commands/help.command.ts +++ b/src/commands/help.command.ts @@ -1,70 +1,70 @@ -import Discord from 'discord.js'; -import Command from './Command'; +import Discord from 'discord.js' +import { command, commands } from '@/core/commands' +import { BOT_PREFIX } from '@/env' -export default class HelpCommand extends Command { - public commandData: { - commandList: Discord.Collection; - prefix: string; - }; +export default command({ + command: 'help', + aliases: ['h'], + description: 'Lists available commands and their usage.', + examples: ['`!help`', '`!help ping`'], async execute(message: Discord.Message, args: string[]): Promise { - const data = []; + const data = [] + const commandList = Object.values(commands()) if (!args || args.length === 0) { // Get for all commands - data.push("here's a list of all my commands:\n"); + data.push("here's a list of all my commands:\n") - const cmds = this.commandData.commandList.map((c) => c.name); - cmds.forEach((element) => { - const cmd = - this.commandData.commandList.get(element) || - this.commandData.commandList.find((c) => c.aliases && c.aliases.includes(element)); - let response = `\`${this.commandData.prefix}${cmd.name}\` `; + for (const cmd of commandList) { + let response = `\`${BOT_PREFIX}${cmd.command}\` ` if (cmd.description) { - response += `**${cmd.description}** `; + response += `**${cmd.description}** ` } if (cmd.aliases) { - response += `\n\t\t\t*alternatively:* \`${this.commandData.prefix}${cmd.aliases.join( - `\`, \`${this.commandData.prefix}`, - )}\``; + response += `\n\t\t\t*alternatively:* \`${BOT_PREFIX}${cmd.aliases.join( + `\`, \`${BOT_PREFIX}`, + )}\`` } - data.push(response); + data.push(response) cmd.examples.forEach((example) => { - data.push(`\t\t\t*for example:* ${example}`); - }); + data.push(`\t\t\t*for example:* ${example}`) + }) - data.push('\n'); - }); - data.push(`You can send \`${this.commandData.prefix}help [command name]\` to get info on a specific command!`); + data.push('\n') + } + data.push( + `You can send \`${BOT_PREFIX}help [command name]\` to get info on a specific command!`, + ) } else { // Get description of single command - const name = args[0].toLowerCase(); - const cmd = - this.commandData.commandList.get(name) || - this.commandData.commandList.find((c) => c.aliases && c.aliases.includes(name)); + const name = args[0].toLowerCase() + const cmd = commandList.find( + (cmd) => cmd.command === name || (cmd.aliases && cmd.aliases.includes(name)), + ) if (!cmd) { - message.reply("that's not a valid command!"); + message.reply("that's not a valid command!") } else { - data.push(`**Name:** ${cmd.name}`); + data.push(`**Name:** ${cmd.command}`) if (cmd.aliases) { - data.push(`**Aliases:** ${cmd.aliases.join(', ')}`); + data.push(`**Aliases:** ${cmd.aliases.join(', ')}`) } if (cmd.description) { - data.push(`**Description:** ${cmd.description}`); + data.push(`**Description:** ${cmd.description}`) } } } try { - return message.reply(data, { split: true }); + return message.reply(data.join('\n')) } catch (error) { - this.dependencies.loggerService.log('error', `Could not send help DM to ${message.author.tag}.\n`, error); + console.log('error', `Could not send help DM to ${message.author.tag}.\n`, error) - return message.reply("it seems like I can't DM you! Do you have DMs disabled?"); + return message.reply("it seems like I can't DM you! Do you have DMs disabled?") } - } -} + }, +}) diff --git a/src/commands/ping.command.ts b/src/commands/ping.command.ts index 8abcedf..95ba2be 100644 --- a/src/commands/ping.command.ts +++ b/src/commands/ping.command.ts @@ -1,8 +1,12 @@ -import Discord from 'discord.js'; -import Command from './Command'; +import Discord from 'discord.js' +import { command } from '@/core/commands' -export default class PingCommand extends Command { +export default command({ + command: 'ping', + aliases: [], + description: 'ping', + examples: [], async execute(message: Discord.Message): Promise { - return message.reply('Pong!'); - } -} + return message.reply('Pong!') + }, +}) diff --git a/src/commands/quotes.command.ts b/src/commands/quotes.command.ts index 1d9bab0..eed2e34 100644 --- a/src/commands/quotes.command.ts +++ b/src/commands/quotes.command.ts @@ -1,88 +1,117 @@ -import Discord from 'discord.js'; -import shortid from 'shortid'; +import { command } from '@/core/commands' +import { db } from '@/core/db' +import Discord from 'discord.js' +import { nanoid } from 'nanoid' -import MongoService from '../../core/services/mongo.service'; +export interface Quote { + author: string + quote: string + uid: string + meta: { + authorCachedName: string + createdBy: string + createdByCachedName: string + createdAt: Date + } +} -import Quote from '../../core/types/Quote'; +const collection = db.collection('quotes') -import Command from './Command'; - -export default class QuotesCommand extends Command { - async execute(message: Discord.Message, args: string[]): Promise { +export default command({ + command: 'quote', + aliases: ['quotes', 'q'], + description: 'Manage quotes', + examples: [ + '`!quote` - Get a random quote', + '`!quote search ` - Search for a quote', + '`!quote add ` - Add a new quote', + '`!quote ` - Get a specific quote', + ], + async execute(message, args) { // Get random quote if (args.filter((s) => s.trim().length).length === 0) { - getRandomQuote(message, args, this.dependencies.mongoService); - return; + getRandomQuote(message, args) + return } - const first = args[0].trim().toLowerCase(); + const first = args[0].trim().toLowerCase() switch (first) { // Search quotes case 'search': - searchQuotes(message, args.slice(1), this.dependencies.mongoService); - return; + searchQuotes(message, args.slice(1)) + return // Add quote case 'add': default: if (first.startsWith('#')) { - getSingleQuote(message, args, this.dependencies.mongoService); + getSingleQuote(message, args) } else { - addNewQuote(message, args.slice(first === 'add' ? 1 : 0), this.dependencies.mongoService); + addNewQuote(message, args.slice(first === 'add' ? 1 : 0)) } } - } -} + }, +}) -const clean = (str: string): string => str.replace(/[\t\n|]+/g, ' ').replace(/\s+/g, ' '); -const getQuoteStr = ({ author, quote, shortId }: Quote): string => `"${quote}" - ${author} (#${shortId})`; +const clean = (str: string): string => str.replace(/[\t\n|]+/g, ' ').replace(/\s+/g, ' ') +const getQuoteStr = ({ author, quote, uid }: Quote): string => `"${quote}" - ${author} (#${uid})` -async function getRandomQuote(message: Discord.Message, args: string[], mongoService: MongoService): Promise { - const count = await mongoService.count(message.author.id, 'quotes', {}); - const r = Math.floor(Math.random() * count); - const q = await mongoService.dbInstance.collection('quotes').find().skip(r).limit(1).toArray(); +async function getRandomQuote(message: Discord.Message, _args: string[]): Promise { + const count = await collection.countDocuments() + const r = Math.floor(Math.random() * count) + const q = await collection.find().skip(r).limit(1).toArray() if (q.length > 0) { - const quote: Quote = q[0]; - message.reply(getQuoteStr(quote)); + const quote: Quote = q[0] + message.reply(getQuoteStr(quote)) } else { - message.reply("This is where I would usually put a quote. I can't remember any, for some reason..."); + message.reply( + "This is where I would usually put a quote. I can't remember any, for some reason...", + ) } } -async function searchQuotes(message: Discord.Message, args: string[], mongoService: MongoService): Promise { - const q = await mongoService.find(message.author.id, 'quotes', { - quote: { - $regex: `${args.join(' ')}`, - $options: 'i', - }, - }); +async function searchQuotes(message: Discord.Message, args: string[]): Promise { + const q = await collection + .find({ + quote: { + $regex: `${args.join(' ')}`, + $options: 'i', + }, + }) + .toArray() if (q.length > 0) { - let responseTxt = ''; + let responseTxt = '' q.forEach((quote) => { - responseTxt += `\n${getQuoteStr(quote)}`; - }); - message.reply("Found a few, I'll DM you what I got!"); - message.author.send(`Found ${q.length} quote${q.length !== 1 ? 's' : ''}:\n${responseTxt}`); + responseTxt += `\n${getQuoteStr(quote)}` + }) + message.reply("Found a few, I'll DM you what I got!") + message.author.send(`Found ${q.length} quote${q.length !== 1 ? 's' : ''}:\n${responseTxt}`) } else { - message.reply("Sorry, didn't find anything that matches that."); + message.reply("Sorry, didn't find anything that matches that.") } } -async function addNewQuote(message: Discord.Message, args: string[], mongoService: MongoService): Promise { - const [authorRaw, ...restRaw] = args; - const hasAuthor = /<@!\d+>/.test(authorRaw) || authorRaw.startsWith('@'); - const author = hasAuthor ? (authorRaw.startsWith('@') ? authorRaw.slice(1) : authorRaw) : 'Anonymous'; +async function addNewQuote(message: Discord.Message, args: string[]): Promise { + const [authorRaw, ...restRaw] = args + const hasAuthor = /<@!\d+>/.test(authorRaw) || authorRaw.startsWith('@') + const author = hasAuthor + ? authorRaw.startsWith('@') + ? authorRaw.slice(1) + : authorRaw + : 'Anonymous' - const differentAuthor = hasAuthor && author !== `<@!${message.author.id}>`; + const differentAuthor = hasAuthor && author !== `<@!${message.author.id}>` const authorName = differentAuthor ? authorRaw.startsWith('@') ? authorRaw.slice(1) - : message.mentions.guild.members.cache.find((u) => u.user.id === message.mentions.users.first().id)?.displayName - : message.member.displayName; - const quote = (hasAuthor ? restRaw : [authorRaw, ...restRaw]).join(' '); + : message.mentions.guild!.members.cache.find( + (u) => u.user.id === message.mentions.users.first()!.id, + )?.displayName + : message.member!.displayName + const quote = (hasAuthor ? restRaw : [authorRaw, ...restRaw]).join(' ') const replies = [ `wow! How inspiring. I'll forever remember this${differentAuthor ? `, ${author}` : ''}.`, @@ -105,33 +134,33 @@ async function addNewQuote(message: Discord.Message, args: string[], mongoServic } and I will refer to this moment precisely.`), clean(`you're not serious. Are you serious? You can't be serious. It's impossible there's **this** good a quote just floating around out there. It's probably fictional. Yeah.`), - ]; + ] const quoteObj: Quote = { quote, author, - shortId: shortid.generate(), + uid: nanoid(), meta: { - authorCachedName: authorName, + authorCachedName: authorName!, createdAt: new Date(), createdBy: message.author.id, - createdByCachedName: message.member.displayName, + createdByCachedName: message.member!.displayName, }, - }; - - const quoteStr = getQuoteStr(quoteObj); - mongoService.insert(message.author.id, 'quotes', [quoteObj]); - message.reply(`${replies[Math.floor(Math.random() * replies.length)]}\n${quoteStr}`); -} - -async function getSingleQuote(message: Discord.Message, args: string[], mongoService: MongoService): Promise { - const id = args[0].slice(1); - const quote = await mongoService.findOne(message.author.id, 'quotes', { shortId: id }); - - if (!quote) { - message.reply("I'm sorry, I couldn't find a quote with that id!"); - return; } - message.reply(getQuoteStr(quote)); + const quoteStr = getQuoteStr(quoteObj) + collection.insertOne(quoteObj) + message.reply(`${replies[Math.floor(Math.random() * replies.length)]}\n${quoteStr}`) +} + +async function getSingleQuote(message: Discord.Message, args: string[]): Promise { + const id = args[0].slice(1) + const quote = await collection.findOne({ uid: id }) + + if (!quote) { + message.reply("I'm sorry, I couldn't find a quote with that id!") + return + } + + message.reply(getQuoteStr(quote)) } diff --git a/src/commands/see.command.ts b/src/commands/see.command.ts index e909daa..132b89b 100644 --- a/src/commands/see.command.ts +++ b/src/commands/see.command.ts @@ -1,10 +1,14 @@ -import Discord from 'discord.js'; -import Command from './Command'; +import { command } from '@/core/commands' +import Discord from 'discord.js' -export default class SeeCommand extends Command { +export default command({ + command: 'see', + aliases: ['s'], + description: 'See your username and ID', + examples: ['`!s`'], async execute(message: Discord.Message): Promise { return message.author.send( - `Server: ${message.guild.name}\nYour username: ${message.author.username}\nYour ID: ${message.author.id}`, - ); - } -} + `Server: ${message.guild!.name}\nYour username: ${message.author.username}\nYour ID: ${message.author.id}`, + ) + }, +}) diff --git a/src/core/commands.ts b/src/core/commands.ts index 6b6a7a0..b9e7f1a 100644 --- a/src/core/commands.ts +++ b/src/core/commands.ts @@ -7,7 +7,8 @@ export interface Command { command: string aliases: string[] description: string - execute(message: Message, args: string[]): void + examples: string[] + execute(_message: Message, _args: string[]): void } let _commands: Record = {} @@ -50,3 +51,11 @@ export function parseCommand(message: string): Command | null { } return null } + +export function command(conf: Command): Command { + return conf +} + +export function commands(): Record { + return _commands +} diff --git a/src/core/db.ts b/src/core/db.ts new file mode 100644 index 0000000..44d9c22 --- /dev/null +++ b/src/core/db.ts @@ -0,0 +1,5 @@ +import { MONGODB_URI } from '@/env' +import { MongoClient } from 'mongodb' + +const client = new MongoClient(MONGODB_URI) +export const db = client.db() diff --git a/src/env.ts b/src/env.ts index c3e1a2e..799a452 100644 --- a/src/env.ts +++ b/src/env.ts @@ -8,8 +8,10 @@ if (env === 'development') { } dotenv.config({ path: path.resolve(process.cwd(), '.env') }) -export const BOT_TRIGGERS = process.env.BOT_TRIGGERS!.split("|") +export const BOT_TRIGGERS = process.env.BOT_TRIGGERS!.split('|') +export const BOT_PREFIX = BOT_TRIGGERS[0] export const DISCORD_APP_ID = process.env.DISCORD_APP_ID! export const DISCORD_PUBLIC_KEY = process.env.DISCORD_PUBLIC_KEY! export const DISCORD_TOKEN = process.env.DISCORD_TOKEN! export const LOG_LEVEL = process.env.LOG_LEVEL! +export const MONGODB_URI = process.env.MONGODB_URI!