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:
wss://mppclone.com:
- id: "✧𝓓𝓔𝓥 𝓡𝓸𝓸𝓶✧"
- id: "test/awkward"

View File

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

View File

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

View File

@ -1,27 +1,27 @@
{
"name": "supercosmic",
"version": "0.1.0",
"module": "src/index.ts",
"type": "module",
"devDependencies": {
"bun-types": "^1.0.16"
},
"peerDependencies": {
"typescript": "^5.0.0"
},
"dependencies": {
"@prisma/client": "^5.5.2",
"@t3-oss/env-core": "^0.7.1",
"discord.js": "^14.14.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",
"typescript": "^5.3.2",
"yaml": "^2.3.3",
"zod": "^3.22.4"
}
"name": "supercosmic",
"version": "0.1.0",
"module": "src/index.ts",
"type": "module",
"devDependencies": {
"bun-types": "latest"
},
"peerDependencies": {
"typescript": "^5.0.0"
},
"dependencies": {
"@prisma/client": "^5.5.2",
"@t3-oss/env-core": "^0.7.1",
"discord.js": "^14.14.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",
"typescript": "^5.3.2",
"yaml": "^2.3.3",
"zod": "^3.22.4"
}
}

View File

@ -34,8 +34,3 @@ enum Role {
ADMINISTRATOR
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 {
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");
@ -172,12 +172,5 @@ export class CommandHandler {
// Add 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",
"inventory",
msg => {
const items = msg.inventory.items as string;
console.log(items);
const list = JSON.parse(items)
const items = msg.inventory.items as unknown as Item[];
const list = items
.map(
i =>
`${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",
"id",
(msg, agent) => {
if (agent.platform === "mpp") {
if (agent.platform == "mpp") {
return `ID: \`${
(msg.originalMessage as any).p._id
}\` Cosmic ID: \`${msg.p._id}\``;

View File

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

View File

@ -1,7 +1,5 @@
import { Inventory } from "@prisma/client";
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">) {
await prisma.inventory.create({
@ -16,51 +14,13 @@ export async function readInventory(userId: Inventory["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">) {
collapseInventory(data.items as unknown as Item[]);
return await prisma.inventory.update({
where: { userId: data.userId },
data: {
balance: data.balance,
items: JSON.stringify(data.items)
}
data: {}
});
}
export async function deleteInventory(userId: Inventory["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 { JsonObject } from "@prisma/client/runtime/library";
declare global {
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;
}
export const prisma = new PrismaClient();

View File

@ -6,22 +6,3 @@ export interface Item {
export interface StackableItem extends Item {
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 { CommandHandler } from "./commands/CommandHandler";
import { loadCommands } from "./commands";
import { loadRoleConfig } from "./permissions";
import { ServiceLoader } from "./services";
import { ConsoleAgent } from "./services/console";
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();
loadRoleConfig();
loadCommands();
globalThis.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();
}
printStartupASCII();
loadRoleConfig();
loadCommands();
ServiceLoader.loadServices();
export function scopedEval(code: string) {
return eval(code);

View File

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

View File

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

View File

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

View File

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

View File

@ -50,10 +50,6 @@ export class ServiceLoader {
this.agents.push(agent);
}
public static getAgent(index: number) {
return this.agents[index];
}
public static loadServices() {
if (config.enableMPP) {
for (const uri of Object.keys(mppConfig.agents)) {
@ -100,11 +96,4 @@ export class ServiceLoader {
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(" ");
// Run command and get output
const str = await globalThis.commandHandler.handleCommand(
const str = await CommandHandler.handleCommand(
{
m: "command",
a: msg.a,
@ -80,7 +80,7 @@ export class MPPAgent extends ServiceAgent<Client> {
// Send message in chat
if (str) {
if (typeof str === "string") {
if (typeof str == "string") {
if (str.includes("\n")) {
let sp = str.split("\n");

View File

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

View File

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