services
This commit is contained in:
parent
b1d7477889
commit
ba0c944cbb
|
@ -0,0 +1,3 @@
|
||||||
|
DATABASE_URL="postgresql://user:password@localhost/cosmic"
|
||||||
|
MPPNET_TOKEN=""
|
||||||
|
CHATBOX_LICENSE_KEY=""
|
|
@ -0,0 +1,4 @@
|
||||||
|
enableMPP: false
|
||||||
|
enableDiscord: false
|
||||||
|
enableConsole: true
|
||||||
|
enableSwitchChat: true
|
|
@ -0,0 +1 @@
|
||||||
|
ownerOnly: false
|
|
@ -12,9 +12,12 @@
|
||||||
"@t3-oss/env-core": "^0.7.1",
|
"@t3-oss/env-core": "^0.7.1",
|
||||||
"dotenv": "^16.3.1",
|
"dotenv": "^16.3.1",
|
||||||
"hyperimport": "^0.1.0",
|
"hyperimport": "^0.1.0",
|
||||||
|
"mathjs": "^11.11.2",
|
||||||
"mpp-client-net": "^1.1.3",
|
"mpp-client-net": "^1.1.3",
|
||||||
"mpp-client-xt": "^1.3.1",
|
"mpp-client-xt": "^1.3.1",
|
||||||
"prisma": "^5.4.2",
|
"prisma": "^5.4.2",
|
||||||
|
"switchchat": "^3.2.1",
|
||||||
|
"yaml": "^2.3.3",
|
||||||
"zod": "^3.22.4"
|
"zod": "^3.22.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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<unknown>
|
||||||
|
) => Promise<string | void> | string | void,
|
||||||
|
public visible: boolean = true
|
||||||
|
) {}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { Command } from "./Command";
|
||||||
|
|
||||||
|
export class CommandGroup {
|
||||||
|
public commands = new Array<Command>();
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
import { ServiceAgent } from "../services/ServiceAgent";
|
||||||
|
import { Command } from "./Command";
|
||||||
|
import { CommandGroup } from "./CommandGroup";
|
||||||
|
import { Prefix } from "./Prefix";
|
||||||
|
|
||||||
|
export interface CommandMessage<T = unknown> {
|
||||||
|
m: "command";
|
||||||
|
a: string;
|
||||||
|
argv: string[];
|
||||||
|
argc: number;
|
||||||
|
originalMessage: T;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CommandHandler {
|
||||||
|
public static commandGroups = new Array<CommandGroup>();
|
||||||
|
public static prefixes = new Array<Prefix>(
|
||||||
|
{
|
||||||
|
id: "cosmic",
|
||||||
|
spaced: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "*",
|
||||||
|
spaced: false
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
public static addCommandGroup(group: CommandGroup) {
|
||||||
|
this.commandGroups.push(group);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async handleCommand(
|
||||||
|
msg: CommandMessage,
|
||||||
|
agent: ServiceAgent<unknown>
|
||||||
|
) {
|
||||||
|
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.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
export class Prefix {
|
||||||
|
constructor(public id: string, public spaced: boolean) {}
|
||||||
|
}
|
|
@ -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`;
|
||||||
|
}
|
||||||
|
);
|
|
@ -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";
|
||||||
|
}
|
||||||
|
);
|
|
@ -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}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
|
@ -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);
|
||||||
|
}
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { loadCommands } from "./commands";
|
||||||
import { ServiceLoader } from "./services";
|
import { ServiceLoader } from "./services";
|
||||||
|
|
||||||
|
loadCommands();
|
||||||
ServiceLoader.loadServices();
|
ServiceLoader.loadServices();
|
||||||
|
|
|
@ -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<readline.ReadLine> {
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,16 +2,47 @@ import EventEmitter from "events";
|
||||||
import { MPPAgent } from "./mpp";
|
import { MPPAgent } from "./mpp";
|
||||||
import env from "../util/env";
|
import env from "../util/env";
|
||||||
import { ServiceAgent } from "./ServiceAgent";
|
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 {
|
export class ServiceLoader {
|
||||||
public static agents = new Array<ServiceAgent<unknown>>();
|
public static agents = new Array<ServiceAgent<unknown>>();
|
||||||
|
|
||||||
public static loadServices() {
|
public static addAgent(agent: ServiceAgent<unknown>) {
|
||||||
const testAgent = new MPPAgent(
|
this.agents.push(agent);
|
||||||
"wss://mppclone.com:8443",
|
}
|
||||||
env.MPPNET_TOKEN
|
|
||||||
);
|
|
||||||
|
|
||||||
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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<Client> {
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<T>(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<string, any>;
|
||||||
|
let changed = false;
|
||||||
|
|
||||||
|
function mix(
|
||||||
|
obj: Record<string, unknown>,
|
||||||
|
obj2: Record<string, unknown>
|
||||||
|
) {
|
||||||
|
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<string, unknown>,
|
||||||
|
obj2[key] as Record<string, unknown>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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<T>(configPath: string, config: T) {
|
||||||
|
writeFileSync(
|
||||||
|
configPath,
|
||||||
|
stringify(config, {
|
||||||
|
indent: 4
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
|
@ -7,7 +7,8 @@ configDotenv();
|
||||||
export const env = createEnv({
|
export const env = createEnv({
|
||||||
isServer: true,
|
isServer: true,
|
||||||
server: {
|
server: {
|
||||||
MPPNET_TOKEN: z.string()
|
MPPNET_TOKEN: z.string(),
|
||||||
|
CHATBOX_LICENSE_KEY: z.string()
|
||||||
},
|
},
|
||||||
runtimeEnv: process.env
|
runtimeEnv: process.env
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue