refactor: re-implement old commands

This commit is contained in:
2024-07-31 02:20:34 +03:00
committed by Chen Asraf
parent cd0dba42ca
commit f2700cc615
24 changed files with 384 additions and 207 deletions

21
LICENSE Normal file
View File

@@ -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.

View File

@@ -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/'],
},

View File

@@ -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';

View File

@@ -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';

View File

@@ -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 {
/**

View File

@@ -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 {
/**

View File

@@ -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';

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -1,3 +0,0 @@
type Environment = 'production' | 'development' | 'test';
export default Environment;

View File

@@ -1,3 +0,0 @@
type LogLevel = 'error' | 'warn' | 'info' | 'verbose' | 'debug' | 'silly';
export default LogLevel;

View File

@@ -1,11 +0,0 @@
export default interface Quote {
author: string;
quote: string;
shortId: string;
meta: {
authorCachedName: string;
createdBy: string;
createdByCachedName: string;
createdAt: Date;
};
}

View File

@@ -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": [

114
pnpm-lock.yaml generated
View File

@@ -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

View File

@@ -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<Discord.Message> {
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!")
},
})

View File

@@ -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<Discord.Message> {
// Just testing db stuff
const matchedChar = await this.dependencies.databaseService.manager.findOne(Character, message.author.id);
const matchedChar = await db
.collection<Character>('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}`)
},
})

View File

@@ -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<Discord.Message> {
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)])
},
})

View File

@@ -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<string, Command>;
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<Discord.Message> {
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?")
}
}
}
},
})

View File

@@ -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<Discord.Message> {
return message.reply('Pong!');
}
}
return message.reply('Pong!')
},
})

View File

@@ -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<Quote>('quotes')
import Command from './Command';
export default class QuotesCommand extends Command {
async execute(message: Discord.Message, args: string[]): Promise<Discord.Message> {
export default command({
command: 'quote',
aliases: ['quotes', 'q'],
description: 'Manage quotes',
examples: [
'`!quote` - Get a random quote',
'`!quote search <query>` - Search for a quote',
'`!quote add <quote>` - Add a new quote',
'`!quote <id>` - 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<void> {
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<void> {
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<void> {
const q = await mongoService.find<Quote>(message.author.id, 'quotes', {
quote: {
$regex: `${args.join(' ')}`,
$options: 'i',
},
});
async function searchQuotes(message: Discord.Message, args: string[]): Promise<void> {
const q = await collection
.find<Quote>({
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<void> {
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<void> {
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<void> {
const id = args[0].slice(1);
const quote = await mongoService.findOne<Quote>(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<void> {
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))
}

View File

@@ -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<Discord.Message> {
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}`,
)
},
})

View File

@@ -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<string, Command> = {}
@@ -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<string, Command> {
return _commands
}

5
src/core/db.ts Normal file
View File

@@ -0,0 +1,5 @@
import { MONGODB_URI } from '@/env'
import { MongoClient } from 'mongodb'
const client = new MongoClient(MONGODB_URI)
export const db = client.db()

View File

@@ -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!