Compare commits

..

No commits in common. "economy" and "main" have entirely different histories.

29 changed files with 71 additions and 423 deletions

BIN
bun.lockb

Binary file not shown.

View File

@ -4,3 +4,4 @@ desiredUser:
agents: agents:
wss://mppclone.com: wss://mppclone.com:
- id: "✧𝓓𝓔𝓥 𝓡𝓸𝓸𝓶✧" - id: "✧𝓓𝓔𝓥 𝓡𝓸𝓸𝓶✧"
- id: "test/awkward"

View File

@ -1,7 +1,5 @@
prefixes: prefixes:
- id: "**" - id: "*"
spaced: false spaced: false
- id: cdebug - id: cosmic
spaced: true spaced: true
- id: d
spaced: false

View File

@ -1,5 +1,5 @@
debug: true debug: false
enableConsole: true enableConsole: false
enableMPP: true enableMPP: true
enableDiscord: false enableDiscord: true
enableSwitchChat: false enableSwitchChat: false

View File

@ -4,7 +4,7 @@
"module": "src/index.ts", "module": "src/index.ts",
"type": "module", "type": "module",
"devDependencies": { "devDependencies": {
"bun-types": "^1.0.16" "bun-types": "latest"
}, },
"peerDependencies": { "peerDependencies": {
"typescript": "^5.0.0" "typescript": "^5.0.0"

View File

@ -34,8 +34,3 @@ enum Role {
ADMINISTRATOR ADMINISTRATOR
OWNER OWNER
} }
model KeyValueStore {
id Int @id @unique @default(1)
data Json @default("{}")
}

View File

@ -46,7 +46,7 @@ export type BaseCommandMessage<T = unknown> = Omit<
export class CommandHandler { export class CommandHandler {
public static commandGroups = new Array<CommandGroup>(); public static commandGroups = new Array<CommandGroup>();
public static prefixes = new Set<Prefix>(); public static prefixes = new Array<Prefix>();
public static logger = new Logger("Command Handler"); public static logger = new Logger("Command Handler");
@ -172,12 +172,5 @@ export class CommandHandler {
// Add prefixes // Add prefixes
for (const prefix of prefixConfig.prefixes) { for (const prefix of prefixConfig.prefixes) {
CommandHandler.prefixes.add(prefix); CommandHandler.prefixes.push(prefix);
} }
// Store commands for hot reload
declare global {
var commandHandler: any;
}
globalThis.commandHandler ??= CommandHandler;

View File

@ -1,23 +0,0 @@
import { KekklefruitTree } from "../../../economy/kekkle";
import { Command } from "../../Command";
export const grow = new Command(
"grow",
["grow"],
"grow bozo's kekklefruit (forcefully)",
"grow [number]",
async msg => {
let num: number;
if (msg.argv[1]) {
num = parseInt(msg.argv[1]);
if (isNaN(num)) return `Need number bozo`;
} else {
num = 1;
}
await KekklefruitTree.growFruit(num);
return `You grew ${num} fruit.`;
}
);

View File

@ -7,9 +7,8 @@ export const inventory = new Command(
"get bozo's inventory", "get bozo's inventory",
"inventory", "inventory",
msg => { msg => {
const items = msg.inventory.items as string; const items = msg.inventory.items as unknown as Item[];
console.log(items); const list = items
const list = JSON.parse(items)
.map( .map(
i => i =>
`${i.name}${ `${i.name}${

View File

@ -1,20 +0,0 @@
import { JsonArray, JsonValue } from "@prisma/client/runtime/library";
import { KekklefruitTree } from "../../../economy/kekkle";
import { Command } from "../../Command";
import { addItem, updateInventory } from "../../../data/inventory";
export const pick = new Command(
"pick",
["pick"],
"bozo will pick fruit off the kekklefruit tree",
"pick",
async msg => {
const fruit = await KekklefruitTree.pickFruit();
if (!fruit)
return `There are not enough fruit on the kekklefruit tree.`;
addItem(msg.p._id, fruit);
return `(insert random boring message about ${fruit.name} here)`;
}
);

View File

@ -1,12 +0,0 @@
import { KekklefruitTree } from "../../../economy/kekkle";
import { Command } from "../../Command";
export const tree = new Command(
"tree",
["tree"],
"bozo will get the amount of fruit on the kekklefruit tree",
"tree",
async msg => {
return `There are ${KekklefruitTree.getFruitCount()} kekklefruit on the tree.`;
}
);

View File

@ -1,14 +0,0 @@
import { deleteInventory } from "../../../data/inventory";
import { Command } from "../../Command";
export const delinv = new Command(
"delinv",
["delinv"],
"delete a bozo's inventory",
"delinv [id]",
async (msg) => {
let userId = msg.argv[1] ? msg.argv[1] : msg.p._id;
await deleteInventory(userId);
return `Inventory of \`${userId}\` deleted.`
}
);

View File

@ -6,7 +6,7 @@ export const id = new Command(
"get your id bozo", "get your id bozo",
"id", "id",
(msg, agent) => { (msg, agent) => {
if (agent.platform === "mpp") { if (agent.platform == "mpp") {
return `ID: \`${ return `ID: \`${
(msg.originalMessage as any).p._id (msg.originalMessage as any).p._id
}\` Cosmic ID: \`${msg.p._id}\``; }\` Cosmic ID: \`${msg.p._id}\``;

View File

@ -1,4 +1,5 @@
import { CommandGroup } from "./CommandGroup"; import { CommandGroup } from "./CommandGroup";
import { CommandHandler } from "./CommandHandler";
import { about } from "./commands/general/about"; import { about } from "./commands/general/about";
import { help } from "./commands/general/help"; import { help } from "./commands/general/help";
import { id } from "./commands/utility/id"; import { id } from "./commands/utility/id";
@ -15,24 +16,20 @@ import { uptime } from "./commands/utility/uptime";
import { balance } from "./commands/economy/balance"; import { balance } from "./commands/economy/balance";
import { permissions } from "./commands/utility/permissions"; import { permissions } from "./commands/utility/permissions";
import { branch } from "./commands/utility/branch"; import { branch } from "./commands/utility/branch";
import { tree } from "./commands/economy/tree";
import { pick } from "./commands/economy/pick";
import { grow } from "./commands/economy/grow";
import { delinv } from "./commands/utility/delinv";
export function loadCommands() { export function loadCommands() {
// cringe // cringe
const general = new CommandGroup("general", "⭐ General"); const general = new CommandGroup("general", "⭐ General");
general.addCommands([help, about]); general.addCommands([help, about]);
globalThis.commandHandler.addCommandGroup(general); CommandHandler.addCommandGroup(general);
const economy = new CommandGroup("economy", "💸 Economy"); const economy = new CommandGroup("economy", "💸 Economy");
economy.addCommands([inventory, balance, tree, pick, grow]); economy.addCommands([inventory, balance]);
globalThis.commandHandler.addCommandGroup(economy); CommandHandler.addCommandGroup(economy);
const fun = new CommandGroup("fun", "✨ Fun"); const fun = new CommandGroup("fun", "✨ Fun");
fun.addCommands([magic8ball]); fun.addCommands([magic8ball]);
globalThis.commandHandler.addCommandGroup(fun); CommandHandler.addCommandGroup(fun);
const utility = new CommandGroup("utility", "🔨 Utility"); const utility = new CommandGroup("utility", "🔨 Utility");
utility.addCommands([ utility.addCommands([
@ -46,10 +43,7 @@ export function loadCommands() {
ic, ic,
uptime, uptime,
permissions, permissions,
branch, branch
delinv
]); ]);
globalThis.commandHandler.addCommandGroup(utility); CommandHandler.addCommandGroup(utility);
} }
export { CommandGroup };

View File

@ -1,7 +1,5 @@
import { Inventory } from "@prisma/client"; import { Inventory } from "@prisma/client";
import { prisma } from "./prisma"; import { prisma } from "./prisma";
import { JsonArray } from "@prisma/client/runtime/library";
import { Item, StackableItem } from "../economy/Item";
export async function createInventory(data: Omit<Inventory, "id">) { export async function createInventory(data: Omit<Inventory, "id">) {
await prisma.inventory.create({ await prisma.inventory.create({
@ -16,51 +14,13 @@ export async function readInventory(userId: Inventory["userId"]) {
return await prisma.inventory.findUnique({ where: { userId: userId } }); return await prisma.inventory.findUnique({ where: { userId: userId } });
} }
export function collapseInventory(inventoryData: Item[]) {
for (let i = 0; i < inventoryData.length; i++) {
if (i <= 0) continue;
if (inventoryData[i].id === inventoryData[i - 1].id) {
if (
typeof (inventoryData[i - 1] as StackableItem).count ===
"number" &&
typeof (inventoryData[i] as StackableItem).count === "number"
) {
(inventoryData[i - 1] as StackableItem).count += (
inventoryData[i] as StackableItem
).count;
inventoryData.splice(i, 1);
i--;
}
}
}
}
export async function updateInventory(data: Omit<Inventory, "id">) { export async function updateInventory(data: Omit<Inventory, "id">) {
collapseInventory(data.items as unknown as Item[]);
return await prisma.inventory.update({ return await prisma.inventory.update({
where: { userId: data.userId }, where: { userId: data.userId },
data: { data: {}
balance: data.balance,
items: JSON.stringify(data.items)
}
}); });
} }
export async function deleteInventory(userId: Inventory["userId"]) { export async function deleteInventory(userId: Inventory["userId"]) {
return await prisma.inventory.delete({ where: { userId } }); return await prisma.inventory.delete({ where: { userId } });
} }
export async function addItem<T extends Item>(userId: Inventory["userId"], item: T) {
let inventory = await readInventory(userId);
if (!inventory) return false;
console.log(inventory.items);
inventory.items = JSON.stringify(JSON.parse(inventory.items as string).push(item));
collapseInventory(inventory.items as unknown as Item[]);
await updateInventory(inventory);
return true;
}

View File

@ -1,51 +1,3 @@
import { PrismaClient } from "@prisma/client"; import { PrismaClient } from "@prisma/client";
import { JsonObject } from "@prisma/client/runtime/library";
declare global { export const prisma = new PrismaClient();
var prisma: PrismaClient;
}
globalThis.prisma ??= new PrismaClient();
export const prisma = globalThis.prisma;
export async function set(key: string, value: any) {
const store = await globalThis.prisma.keyValueStore.findUnique({
where: { id: 1 }
});
if (!store) {
// throw new Error("Unable to access key-value store.");
await prisma.keyValueStore.create({
data: {}
});
return set(key, value);
}
const data = store.data as JsonObject;
data[key] = value;
await globalThis.prisma.keyValueStore.update({
where: { id: 1 },
data: { data: data }
});
return;
}
export async function get<T = unknown>(key: string) {
const store = await globalThis.prisma.keyValueStore.findUnique({
where: { id: 1 }
});
if (!store) {
// throw new Error("Unable to access key-value store.");
await globalThis.prisma.keyValueStore.create({
data: {}
});
return get(key);
}
const data = store.data as JsonObject;
return data[key] as T;
}

View File

@ -6,22 +6,3 @@ export interface Item {
export interface StackableItem extends Item { export interface StackableItem extends Item {
count: number; count: number;
} }
export interface ConsumableItem extends Item {
consumable: true;
}
export interface FoodItem extends ConsumableItem {
edible: true;
}
export interface CakeItem extends FoodItem {
emoji: string;
icing: string;
filling: string;
}
export interface ShopItem extends Item {
buyValue: number;
sellValue: number;
}

View File

@ -1,15 +0,0 @@
import { Item } from "./Item";
const items = new Map<string, Item>();
export function getItem(key: string) {
return items.get(key);
}
export function setItem(key: string, item: Item) {
return items.set(key, item);
}
export function deleteItem(key: string) {
return items.delete(key);
}

View File

@ -1,55 +0,0 @@
import { get, set } from "../data/prisma";
import { Logger } from "../util/Logger";
import { FoodItem } from "./Item";
export class KekklefruitTree {
protected static fruit: number = 0;
public static logger = new Logger("Kekklefruit Tree");
public static async saveFruit() {
return set("kekklefruit-tree", this.fruit);
}
public static async loadFruit() {
let fruit = await get<number>("kekklefruit-tree");
let save = false;
if (!fruit) {
fruit = 0;
save = true;
}
this.fruit = fruit;
if (save) this.saveFruit();
}
public static async growFruit(amount: number = 1) {
this.fruit += amount;
await this.saveFruit();
}
public static getFruitCount() {
return this.fruit;
}
public static async pickFruit() {
if (this.fruit > 0) {
this.fruit--;
await this.saveFruit();
return this.randomFruit();
} else {
return undefined;
}
}
public static randomFruit() {
return {
id: "kekklefruit",
name: "Kekklefruit",
consumable: true,
edible: true
} as FoodItem;
}
}
await KekklefruitTree.loadFruit();

View File

@ -1,54 +1,12 @@
import { loadCommands, type CommandGroup } from "./commands"; import { loadCommands } from "./commands";
import { CommandHandler } from "./commands/CommandHandler";
import { loadRoleConfig } from "./permissions"; import { loadRoleConfig } from "./permissions";
import { ServiceLoader } from "./services"; import { ServiceLoader } from "./services";
import { ConsoleAgent } from "./services/console";
import { printStartupASCII } from "./util/ascii"; import { printStartupASCII } from "./util/ascii";
// Hot reload persistence
declare global {
var loaded: boolean;
var serviceLoader: any;
}
globalThis.loaded ??= false;
globalThis.serviceLoader ??= ServiceLoader;
function load() {
printStartupASCII(); printStartupASCII();
loadRoleConfig(); loadRoleConfig();
loadCommands(); loadCommands();
globalThis.serviceLoader.loadServices(); ServiceLoader.loadServices();
globalThis.loaded = true;
}
function reload() {
console.log("Reloading...");
// Reload commands
globalThis.commandHandler.commandGroups = new Array<CommandGroup>();
loadCommands();
// Reload services
globalThis.serviceLoader.unloadServices();
globalThis.serviceLoader.loadServices();
// Set console prompt
globalThis.serviceLoader.agents.forEach(agent => {
if (agent.platform === "console")
(agent as ConsoleAgent).client.prompt();
});
}
// Check for hot reload
if (!globalThis.loaded) {
load();
} else {
console.clear();
console.log("Hot reload triggered");
reload();
}
export function scopedEval(code: string) { export function scopedEval(code: string) {
return eval(code); return eval(code);

View File

@ -23,9 +23,9 @@ export function handlePermission(node1: string, node2: string) {
// Check nodes in order // Check nodes in order
for (let i = 0; i < hierarchy1.length; i++) { for (let i = 0; i < hierarchy1.length; i++) {
if (hierarchy1[i] === hierarchy2[i]) { if (hierarchy1[i] == hierarchy2[i]) {
// Last node? // Last node?
if (i === hierarchy1.length - 1 || i === hierarchy2.length - 1) { if (i == hierarchy1.length - 1 || i == hierarchy2.length - 1) {
return true; return true;
} else { } else {
continue; continue;
@ -33,8 +33,8 @@ export function handlePermission(node1: string, node2: string) {
} }
// Wildcard? // Wildcard?
if (hierarchy1[i] === "*") return true; if (hierarchy1[i] == "*") return true;
if (hierarchy2[i] === "*") return true; if (hierarchy2[i] == "*") return true;
return false; return false;
} }
@ -66,12 +66,7 @@ export function hasPermission(role: Role, permission: string) {
return false; return false;
} }
declare global { export const roles = new Map<Role, TRole>();
var roles: Map<Role, TRole>;
}
globalThis.roles ??= new Map<Role, TRole>();
export const roles = globalThis.roles;
export type TRole = { export type TRole = {
displayName: string; displayName: string;

View File

@ -22,7 +22,7 @@ export interface ChatMessage<T = unknown> {
function onChildMessage(msg: ChatMessage) { function onChildMessage(msg: ChatMessage) {
const consoleAgent = ServiceLoader.agents.find( const consoleAgent = ServiceLoader.agents.find(
ag => ag.platform === "console" ag => ag.platform == "console"
) as ConsoleAgent | undefined; ) as ConsoleAgent | undefined;
if (!consoleAgent) return; if (!consoleAgent) return;
@ -34,7 +34,7 @@ function onChildMessage(msg: ChatMessage) {
function onConsoleMessage(text: string) { function onConsoleMessage(text: string) {
const consoleAgent = ServiceLoader.agents.find( const consoleAgent = ServiceLoader.agents.find(
ag => ag.platform === "console" ag => ag.platform == "console"
) as ConsoleAgent | undefined; ) as ConsoleAgent | undefined;
if (!consoleAgent) return; if (!consoleAgent) return;
@ -77,7 +77,7 @@ export class MicroHandler {
for (let i in ServiceLoader.agents) { for (let i in ServiceLoader.agents) {
const agent2 = ServiceLoader.agents[i]; const agent2 = ServiceLoader.agents[i];
if (agent2.platform === "mpp") { if (agent2.platform == "mpp") {
agent.emit( agent.emit(
"log", "log",
`${i} - ${agent2.platform} - ${ `${i} - ${agent2.platform} - ${
@ -101,7 +101,7 @@ export class MicroHandler {
let walkie = agent as ConsoleAgent; let walkie = agent as ConsoleAgent;
let talky = ServiceLoader.agents[index]; let talky = ServiceLoader.agents[index];
if (index === ServiceLoader.agents.indexOf(walkie)) if (index == ServiceLoader.agents.indexOf(walkie))
return "Why would you want to chat with yourself?"; return "Why would you want to chat with yourself?";
// Remove old listeners // Remove old listeners

View File

@ -37,7 +37,7 @@ export class ConsoleAgent extends ServiceAgent<readline.ReadLine> {
public stop() { public stop() {
if (!this.started) return; if (!this.started) return;
this.started = false; this.started = false;
this.client.close(); this.client.setPrompt("");
} }
protected bindEventListeners(): void { protected bindEventListeners(): void {
@ -65,10 +65,7 @@ export class ConsoleAgent extends ServiceAgent<readline.ReadLine> {
if (text.startsWith("/")) { if (text.startsWith("/")) {
out = await MicroHandler.handleMicroCommand(message, this); out = await MicroHandler.handleMicroCommand(message, this);
} else { } else {
out = await globalThis.commandHandler.handleCommand( out = await CommandHandler.handleCommand(message, this);
message,
this
);
} }
if (out) { if (out) {

View File

@ -56,7 +56,7 @@ export class DiscordAgent extends ServiceAgent<Discord.Client> {
let args = msg.content.split(" "); let args = msg.content.split(" ");
const str = await globalThis.commandHandler.handleCommand( const str = await CommandHandler.handleCommand(
{ {
m: "command", m: "command",
a: msg.content, a: msg.content,
@ -74,7 +74,7 @@ export class DiscordAgent extends ServiceAgent<Discord.Client> {
); );
if (str) { if (str) {
if (typeof str === "string") { if (typeof str == "string") {
const channel = await this.client.channels.fetch( const channel = await this.client.channels.fetch(
msg.channelId msg.channelId
); );

View File

@ -50,10 +50,6 @@ export class ServiceLoader {
this.agents.push(agent); this.agents.push(agent);
} }
public static getAgent(index: number) {
return this.agents[index];
}
public static loadServices() { public static loadServices() {
if (config.enableMPP) { if (config.enableMPP) {
for (const uri of Object.keys(mppConfig.agents)) { for (const uri of Object.keys(mppConfig.agents)) {
@ -100,11 +96,4 @@ export class ServiceLoader {
this.addAgent(consoleAgent); this.addAgent(consoleAgent);
} }
} }
public static unloadServices() {
for (const agent of this.agents) {
agent.stop();
this.agents.splice(this.agents.indexOf(agent), 1);
}
}
} }

View File

@ -61,7 +61,7 @@ export class MPPAgent extends ServiceAgent<Client> {
let args = msg.a.split(" "); let args = msg.a.split(" ");
// Run command and get output // Run command and get output
const str = await globalThis.commandHandler.handleCommand( const str = await CommandHandler.handleCommand(
{ {
m: "command", m: "command",
a: msg.a, a: msg.a,
@ -80,7 +80,7 @@ export class MPPAgent extends ServiceAgent<Client> {
// Send message in chat // Send message in chat
if (str) { if (str) {
if (typeof str === "string") { if (typeof str == "string") {
if (str.includes("\n")) { if (str.includes("\n")) {
let sp = str.split("\n"); let sp = str.split("\n");

View File

@ -80,9 +80,9 @@ export class CosmicColor {
let g = (~~this.g || 0).toString(16); let g = (~~this.g || 0).toString(16);
let b = (~~this.b || 0).toString(16); let b = (~~this.b || 0).toString(16);
if (r.length === 1) r = "0" + r; if (r.length == 1) r = "0" + r;
if (g.length === 1) g = "0" + g; if (g.length == 1) g = "0" + g;
if (b.length === 1) b = "0" + b; if (b.length == 1) b = "0" + b;
return "#" + r + g + b; return "#" + r + g + b;
} }

View File

@ -16,7 +16,6 @@ import { parse as parsePath } from "path/posix";
* @returns Parsed YAML config * @returns Parsed YAML config
*/ */
export function loadConfig<T>(configPath: string, defaultConfig: T): T { export function loadConfig<T>(configPath: string, defaultConfig: T): T {
console.time(`Loading config ${configPath}`);
// Config exists? // Config exists?
if (existsSync(configPath)) { if (existsSync(configPath)) {
// Load config // Load config
@ -31,12 +30,12 @@ export function loadConfig<T>(configPath: string, defaultConfig: T): T {
obj2: Record<string, unknown> obj2: Record<string, unknown>
) { ) {
for (const key of Object.keys(obj2)) { for (const key of Object.keys(obj2)) {
if (typeof obj[key] === "undefined") { if (typeof obj[key] == "undefined") {
obj[key] = obj2[key]; obj[key] = obj2[key];
changed = true; changed = true;
} }
if (typeof obj[key] === "object" && !Array.isArray(obj[key])) { if (typeof obj[key] == "object" && !Array.isArray(obj[key])) {
mix( mix(
obj[key] as Record<string, unknown>, obj[key] as Record<string, unknown>,
obj2[key] as Record<string, unknown> obj2[key] as Record<string, unknown>
@ -46,7 +45,7 @@ export function loadConfig<T>(configPath: string, defaultConfig: T): T {
} }
// Apply any missing default values // Apply any missing default values
// mix(config, defRecord); mix(config, defRecord);
// Save config if modified // Save config if modified
if (changed) writeConfig(configPath, config); if (changed) writeConfig(configPath, config);
@ -55,7 +54,6 @@ export function loadConfig<T>(configPath: string, defaultConfig: T): T {
} else { } else {
// Write default config to disk and use that // Write default config to disk and use that
writeConfig(configPath, defaultConfig); writeConfig(configPath, defaultConfig);
return defaultConfig as T; return defaultConfig as T;
} }
} }

View File

@ -1,23 +0,0 @@
import { expect, test } from "bun:test";
import { collapseInventory } from "../../src/data/inventory";
import { StackableItem } from "../../src/economy/Item";
test("Collapse inventory", () => {
let sampleData: StackableItem[] = [
{
id: "test_item",
name: "Test Item",
count: 10
},
{
id: "test_item",
name: "Test Item",
count: 15
}
];
collapseInventory(sampleData);
expect(sampleData[0].count).toBe(25);
expect(sampleData[1]).toBe(undefined);
console.log(sampleData);
});