From ba0c944cbb5bd4f6414e621ac7a4a2690bd5059e Mon Sep 17 00:00:00 2001 From: Hri7566 Date: Wed, 18 Oct 2023 01:05:14 -0400 Subject: [PATCH] services --- .env.template | 3 + bun.lockb | Bin 9269 -> 15072 bytes config/services.yml | 4 ++ config/switchchat.yml | 1 + package.json | 3 + src/commands/Command.ts | 20 ++++++ src/commands/CommandGroup.ts | 17 ++++++ src/commands/CommandHandler.ts | 78 ++++++++++++++++++++++++ src/commands/Prefix.ts | 3 + src/commands/commands/general/about.ts | 11 ++++ src/commands/commands/general/help.ts | 11 ++++ src/commands/commands/utility/math.ts | 21 +++++++ src/commands/commands/utility/memory.ts | 0 src/commands/index.ts | 14 +++++ src/index.ts | 2 + src/services/console/index.ts | 65 ++++++++++++++++++++ src/services/index.ts | 43 +++++++++++-- src/services/switchchat/index.ts | 61 ++++++++++++++++++ src/util/config.ts | 73 ++++++++++++++++++++++ src/util/env.ts | 3 +- 20 files changed, 426 insertions(+), 7 deletions(-) create mode 100644 .env.template create mode 100644 config/services.yml create mode 100644 config/switchchat.yml create mode 100644 src/commands/Command.ts create mode 100644 src/commands/CommandGroup.ts create mode 100644 src/commands/CommandHandler.ts create mode 100644 src/commands/Prefix.ts create mode 100644 src/commands/commands/general/about.ts create mode 100644 src/commands/commands/general/help.ts create mode 100644 src/commands/commands/utility/math.ts create mode 100644 src/commands/commands/utility/memory.ts create mode 100644 src/commands/index.ts create mode 100644 src/services/console/index.ts create mode 100644 src/services/switchchat/index.ts create mode 100644 src/util/config.ts diff --git a/.env.template b/.env.template new file mode 100644 index 0000000..175a02a --- /dev/null +++ b/.env.template @@ -0,0 +1,3 @@ +DATABASE_URL="postgresql://user:password@localhost/cosmic" +MPPNET_TOKEN="" +CHATBOX_LICENSE_KEY="" diff --git a/bun.lockb b/bun.lockb index e0f28ce9a57bd29e2ab88968d0f368d232833a89..3101961871b131d34d1c5eb357fb31bea39f9658 100755 GIT binary patch delta 5019 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 delta 1512 zcmcgsZ)jUp6uvP!neA;tUALtUhS8@WmDz@6j@oXu zEe>W1K^O(4-A_Rr%A6Y$LCWX`1^wWh{jf~LiGK4#ar;miOvebI-XelUMf6r8d7e-2N4Y?q7cO!S#hdhQFE9*WXrW)WQd?%~J0Oei^>7Q8*Nv zw>Olw#j2qCT_eQIiLns}j2}NTF?vXdWkL-kA0TcLpPs1do|9EvAl^vwiLv3Myfv+a zs3&}6ysDQaY!FU(s`@90bDTWeCFp0Q@Y3XA(jBcib`7?ZY%T4(0dZEQPpeZtueR^x z(px)jU_}*W9{W7I)VzS9Tl)88VZqe1Dkw3lTWU(8LTo|8sI$~PiT!n!{v;)o!JD3} zDl1e-eoevfTK->1zJuavxasLr+dP={TIvlCD#WgNVE8O$YS>R~mxftlM>SM@mj9wA zL_gU{%=`M(9Sw$N`O`Y3LPCL?{yzUJgn9@CFt7LN9}(J(r;Mz+rK3{+c*#$B^I~F7 zmEn>(#GER|rFZ015r>#l!Xi-PK}RaYzd8E}(PAF1?K5Y)CR!|yx+e7F2)H36M$HW+ zR%@Z<(*K-h`@?xUdHg3$-ANO#HGGX{f;s8Ihd~>w0TX&CCv_YQ*?70n#1%qb$gqvQ zK@){=PWo|)&;vq|NKUT7p@@x-Lnf99nP_aX@m$!%WK&Ky;3^?s#Kfb~oNUCgsEs8; zKM)Gx=YWl3lZoG(b25S_;x=MY6Bpt+8O6Q$G*(*bRaq?FZP_CcXnlKeuC1sUGqt(< zJci(=1HBPC8{bAoWZw!bX@0jqNY`Tzg` diff --git a/config/services.yml b/config/services.yml new file mode 100644 index 0000000..42ce890 --- /dev/null +++ b/config/services.yml @@ -0,0 +1,4 @@ +enableMPP: false +enableDiscord: false +enableConsole: true +enableSwitchChat: true diff --git a/config/switchchat.yml b/config/switchchat.yml new file mode 100644 index 0000000..914d4a9 --- /dev/null +++ b/config/switchchat.yml @@ -0,0 +1 @@ +ownerOnly: false diff --git a/package.json b/package.json index 090c180..8c76226 100644 --- a/package.json +++ b/package.json @@ -12,9 +12,12 @@ "@t3-oss/env-core": "^0.7.1", "dotenv": "^16.3.1", "hyperimport": "^0.1.0", + "mathjs": "^11.11.2", "mpp-client-net": "^1.1.3", "mpp-client-xt": "^1.3.1", "prisma": "^5.4.2", + "switchchat": "^3.2.1", + "yaml": "^2.3.3", "zod": "^3.22.4" } } \ No newline at end of file diff --git a/src/commands/Command.ts b/src/commands/Command.ts new file mode 100644 index 0000000..251a4dd --- /dev/null +++ b/src/commands/Command.ts @@ -0,0 +1,20 @@ +import { ServiceAgent } from "../services/ServiceAgent"; +import { CommandMessage } from "./CommandHandler"; + +export class Command { + public static getUsage(usage: string, prefix: string) { + return usage.split("{prefix}").join(prefix); + } + + constructor( + public id: string, + public aliases: string[], + public description: string, + public usage: string, + public callback: ( + msg: CommandMessage, + agent: ServiceAgent + ) => Promise | string | void, + public visible: boolean = true + ) {} +} diff --git a/src/commands/CommandGroup.ts b/src/commands/CommandGroup.ts new file mode 100644 index 0000000..0464712 --- /dev/null +++ b/src/commands/CommandGroup.ts @@ -0,0 +1,17 @@ +import { Command } from "./Command"; + +export class CommandGroup { + public commands = new Array(); + + constructor(public id: string, public displayName: string) {} + + public addCommand(command: Command) { + this.commands.push(command); + } + + public addCommands(commands: Command[]) { + for (const command of commands) { + this.addCommand(command); + } + } +} diff --git a/src/commands/CommandHandler.ts b/src/commands/CommandHandler.ts new file mode 100644 index 0000000..5d05ced --- /dev/null +++ b/src/commands/CommandHandler.ts @@ -0,0 +1,78 @@ +import { ServiceAgent } from "../services/ServiceAgent"; +import { Command } from "./Command"; +import { CommandGroup } from "./CommandGroup"; +import { Prefix } from "./Prefix"; + +export interface CommandMessage { + m: "command"; + a: string; + argv: string[]; + argc: number; + originalMessage: T; +} + +export class CommandHandler { + public static commandGroups = new Array(); + public static prefixes = new Array( + { + id: "cosmic", + spaced: true + }, + { + id: "*", + spaced: false + } + ); + + public static addCommandGroup(group: CommandGroup) { + this.commandGroups.push(group); + } + + public static async handleCommand( + msg: CommandMessage, + agent: ServiceAgent + ) { + let usedPrefix: Prefix | undefined; + + for (const prefix of this.prefixes) { + if (msg.argv[0].startsWith(prefix.id)) { + usedPrefix = prefix; + + if (prefix.spaced) { + msg.argv.splice(0, 1); + } + + break; + } + } + + if (!usedPrefix) return; + + let usedAlias = msg.argv[0]; + if (!usedPrefix.spaced) + usedAlias = msg.argv[0].substring(usedPrefix.id.length); + + if (!usedAlias) return; + + let usedCommand: Command | undefined; + + for (const group of this.commandGroups) { + for (const command of group.commands) { + if (command.aliases.includes(usedAlias)) { + usedCommand = command; + break; + } + } + } + + if (!usedCommand) return; + + try { + const out = usedCommand.callback(msg, agent); + if (out) return out; + } catch (err) { + console.error(err); + return "An error has occurred."; + } + } +} diff --git a/src/commands/Prefix.ts b/src/commands/Prefix.ts new file mode 100644 index 0000000..3737d3c --- /dev/null +++ b/src/commands/Prefix.ts @@ -0,0 +1,3 @@ +export class Prefix { + constructor(public id: string, public spaced: boolean) {} +} diff --git a/src/commands/commands/general/about.ts b/src/commands/commands/general/about.ts new file mode 100644 index 0000000..6e9a93f --- /dev/null +++ b/src/commands/commands/general/about.ts @@ -0,0 +1,11 @@ +import { Command } from "../../Command"; + +export const help = new Command( + "about", + ["about", "info"], + "get about bozo", + "{prefix}about", + (msg, agent) => { + return `This is a dumb chat bot`; + } +); diff --git a/src/commands/commands/general/help.ts b/src/commands/commands/general/help.ts new file mode 100644 index 0000000..0202d82 --- /dev/null +++ b/src/commands/commands/general/help.ts @@ -0,0 +1,11 @@ +import { Command } from "../../Command"; + +export const help = new Command( + "help", + ["help", "h", "commands", "cmds"], + "get help bozo", + "{prefix}help", + (msg, agent) => { + return "test"; + } +); diff --git a/src/commands/commands/utility/math.ts b/src/commands/commands/utility/math.ts new file mode 100644 index 0000000..4f44ab8 --- /dev/null +++ b/src/commands/commands/utility/math.ts @@ -0,0 +1,21 @@ +import { Command } from "../../Command"; + +import { evaluate } from "mathjs"; + +export const math = new Command( + "math", + ["math"], + "math bozo", + "{prefix}math", + (msg, agent) => { + try { + const argcat = msg.argv.slice(1, msg.argv.length).join(" "); + console.log(argcat); + const answer = evaluate(argcat); + return `Answer: ${answer}`; + } catch (err) { + console.error(err); + return `Invalid expression: ${err}`; + } + } +); diff --git a/src/commands/commands/utility/memory.ts b/src/commands/commands/utility/memory.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/commands/index.ts b/src/commands/index.ts new file mode 100644 index 0000000..902d0fc --- /dev/null +++ b/src/commands/index.ts @@ -0,0 +1,14 @@ +import { CommandGroup } from "./CommandGroup"; +import { CommandHandler } from "./CommandHandler"; +import { help } from "./commands/general/help"; +import { math } from "./commands/utility/math"; + +export function loadCommands() { + const general = new CommandGroup("general", "General"); + general.addCommands([help]); + CommandHandler.addCommandGroup(general); + + const utility = new CommandGroup("utility", "Utility"); + utility.addCommands([math]); + CommandHandler.addCommandGroup(utility); +} diff --git a/src/index.ts b/src/index.ts index d2cc960..0a8dcc3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,5 @@ +import { loadCommands } from "./commands"; import { ServiceLoader } from "./services"; +loadCommands(); ServiceLoader.loadServices(); diff --git a/src/services/console/index.ts b/src/services/console/index.ts new file mode 100644 index 0000000..16b405c --- /dev/null +++ b/src/services/console/index.ts @@ -0,0 +1,65 @@ +import { CommandHandler, CommandMessage } from "../../commands/CommandHandler"; +import { loadConfig } from "../../util/config"; +import { ServiceAgent } from "../ServiceAgent"; +import readline from "readline"; + +const config = loadConfig("config/switchchat.yml", { + ownerOnly: false +}); + +export class SwitchChatAgent extends ServiceAgent { + public desiredUser = { + name: "🟇 𝙎𝙪𝙥𝙚𝙧 Cosmic", + color: "#1d0054" + }; + + constructor(token: string) { + const cl = readline.createInterface({ + input: process.stdin, + output: process.stdout + }); + + super(cl); + } + + public started = false; + + public start() { + if (this.started) return; + this.started = true; + this.client.setPrompt(">"); + this.client.prompt(true); + } + + public stop() { + if (!this.started) return; + this.started = false; + this.client.setPrompt(""); + } + + protected bindEventListeners(): void { + super.bindEventListeners(); + + this.client.on("command", async cmd => { + if (config.ownerOnly && !cmd.ownerOnly) return; + + console.log( + `${cmd.user.displayName}: ${cmd.ownerOnly ? "^" : "\\"}${ + cmd.command + } ${cmd.args.join(" ")}` + ); + + const message: CommandMessage = { + m: "command", + a: `${cmd.command} ${cmd.args.join(" ")}`, + argc: cmd.args.length + 1, + argv: [cmd.command, ...cmd.args], + originalMessage: cmd + }; + + const out = await CommandHandler.handleCommand(message, this); + console.log(out); + if (out) this.client.tell(cmd.user.name, out); + }); + } +} diff --git a/src/services/index.ts b/src/services/index.ts index 55fd069..04a5430 100644 --- a/src/services/index.ts +++ b/src/services/index.ts @@ -2,16 +2,47 @@ import EventEmitter from "events"; import { MPPAgent } from "./mpp"; import env from "../util/env"; import { ServiceAgent } from "./ServiceAgent"; +import { loadConfig } from "../util/config"; +import { z } from "zod"; +import { SwitchChatAgent } from "./switchchat"; + +/** + * Services are anything (any platforms or environments) that the bot will directly communicate to users with + */ + +const config = loadConfig("config/services.yml", { + enableConsole: true, + enableMPP: false, + enableDiscord: false, + enableSwitchChat: false +}); export class ServiceLoader { public static agents = new Array>(); - public static loadServices() { - const testAgent = new MPPAgent( - "wss://mppclone.com:8443", - env.MPPNET_TOKEN - ); + public static addAgent(agent: ServiceAgent) { + this.agents.push(agent); + } - testAgent.start(); + public static loadServices() { + if (config.enableMPP) { + // TODO Implement URI and channel configuration + const testAgent = new MPPAgent( + "wss://mppclone.com:8443", + env.MPPNET_TOKEN + ); + + testAgent.start(); + this.addAgent(testAgent); + } + + if (config.enableSwitchChat) { + const switchChatAgent = new SwitchChatAgent( + env.CHATBOX_LICENSE_KEY + ); + + switchChatAgent.start(); + this.addAgent(switchChatAgent); + } } } diff --git a/src/services/switchchat/index.ts b/src/services/switchchat/index.ts new file mode 100644 index 0000000..4340625 --- /dev/null +++ b/src/services/switchchat/index.ts @@ -0,0 +1,61 @@ +import { CommandHandler, CommandMessage } from "../../commands/CommandHandler"; +import { loadConfig } from "../../util/config"; +import { ServiceAgent } from "../ServiceAgent"; +import { Client } from "switchchat"; + +const config = loadConfig("config/switchchat.yml", { + ownerOnly: false +}); + +export class SwitchChatAgent extends ServiceAgent { + public desiredUser = { + name: "🟇 𝙎𝙪𝙥𝙚𝙧 Cosmic", + color: "#1d0054" + }; + + constructor(token: string) { + const cl = new Client(token); + super(cl); + + this.client.defaultName = this.desiredUser.name; + this.client.defaultFormattingMode = "markdown"; + } + + public start() { + this.client.connect(); + } + + public stop() { + this.client.close(); + } + + protected bindEventListeners(): void { + super.bindEventListeners(); + + this.client.on("command", async cmd => { + if (config.ownerOnly && !cmd.ownerOnly) return; + + console.log( + `${cmd.user.displayName}: ${cmd.ownerOnly ? "^" : "\\"}${ + cmd.command + } ${cmd.args.join(" ")}` + ); + + const message: CommandMessage = { + m: "command", + a: `${cmd.command} ${cmd.args.join(" ")}`, + argc: cmd.args.length + 1, + argv: [cmd.command, ...cmd.args], + originalMessage: cmd + }; + + const out = await CommandHandler.handleCommand(message, this); + console.log(out); + if (out) await this.client.tell(cmd.user.name, out); + }); + + this.client.on("rawraw", data => { + console.log(data); + }); + } +} diff --git a/src/util/config.ts b/src/util/config.ts new file mode 100644 index 0000000..f42a0bf --- /dev/null +++ b/src/util/config.ts @@ -0,0 +1,73 @@ +import { existsSync, readFileSync, writeFileSync } from "fs"; +import { parse, stringify } from "yaml"; +import { z } from "zod"; + +/** + * Load a YAML config file and set default values if config path is nonexistent + * + * Usage: + * ```ts + * const config = loadConfig("config/services.yml", { + * enableMPP: false + * }); + * ``` + * @param configPath Path to load config from + * @param defaultConfig Config to use if none is present (will save to path if used) + * @returns Parsed YAML config + */ +export function loadConfig(configPath: string, defaultConfig: T): T { + // Config exists? + if (existsSync(configPath)) { + // Load config + const data = readFileSync(configPath); + const config = parse(data.toString()); + + const defRecord = defaultConfig as Record; + let changed = false; + + function mix( + obj: Record, + obj2: Record + ) { + for (const key of Object.keys(obj2)) { + if (typeof obj[key] == "undefined") { + obj[key] = obj2[key]; + changed = true; + } + + if (typeof obj[key] == "object" && !Array.isArray(obj[key])) { + mix( + obj[key] as Record, + obj2[key] as Record + ); + } + } + } + + // Apply any missing default values + mix(config, defRecord); + + // Save config if modified + if (changed) writeConfig(configPath, config); + + return config as T; + } else { + // Write default config to disk and use that + writeConfig(configPath, defaultConfig); + return defaultConfig as T; + } +} + +/** + * Write a YAML config to disk + * @param configPath + * @param config + */ +export function writeConfig(configPath: string, config: T) { + writeFileSync( + configPath, + stringify(config, { + indent: 4 + }) + ); +} diff --git a/src/util/env.ts b/src/util/env.ts index c6799e7..f4be196 100644 --- a/src/util/env.ts +++ b/src/util/env.ts @@ -7,7 +7,8 @@ configDotenv(); export const env = createEnv({ isServer: true, server: { - MPPNET_TOKEN: z.string() + MPPNET_TOKEN: z.string(), + CHATBOX_LICENSE_KEY: z.string() }, runtimeEnv: process.env });