stuff
This commit is contained in:
parent
5112aa1d01
commit
e447f2a355
40
package.json
40
package.json
|
@ -1,19 +1,25 @@
|
||||||
{
|
{
|
||||||
"name": "fishing-api",
|
"name": "fishing-api",
|
||||||
"module": "src/api/index.ts",
|
"module": "src/api/index.ts",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"devDependencies": {
|
"scripts": {
|
||||||
"@types/bun": "latest"
|
"start": "bun .",
|
||||||
},
|
"start-bot": "bun src/mpp/index.ts"
|
||||||
"peerDependencies": {
|
},
|
||||||
"typescript": "^5.0.0"
|
"devDependencies": {
|
||||||
},
|
"@types/bun": "latest"
|
||||||
"dependencies": {
|
},
|
||||||
"@prisma/client": "^5.9.1",
|
"peerDependencies": {
|
||||||
"@trpc/client": "next",
|
"typescript": "^5.0.0"
|
||||||
"@trpc/server": "next",
|
},
|
||||||
"prisma": "^5.9.1",
|
"dependencies": {
|
||||||
"trpc-bun-adapter": "^1.1.0",
|
"@prisma/client": "^5.9.1",
|
||||||
"zod": "^3.22.4"
|
"@trpc/client": "next",
|
||||||
}
|
"@trpc/server": "next",
|
||||||
|
"mpp-client-net": "^1.1.3",
|
||||||
|
"prisma": "^5.9.1",
|
||||||
|
"trpc-bun-adapter": "^1.1.0",
|
||||||
|
"yaml": "^2.3.4",
|
||||||
|
"zod": "^3.22.4"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,21 @@ datasource db {
|
||||||
}
|
}
|
||||||
|
|
||||||
model User {
|
model User {
|
||||||
id String @id
|
id String @id
|
||||||
name String
|
name String
|
||||||
color String
|
color String
|
||||||
|
inventory Inventory @relation(fields: [inventoryId], references: [id])
|
||||||
|
inventoryId Int @unique
|
||||||
|
}
|
||||||
|
|
||||||
|
model Inventory {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
balance Int
|
||||||
|
items Json @default("[]")
|
||||||
|
User User?
|
||||||
|
}
|
||||||
|
|
||||||
|
model AuthToken {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
token String @unique
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
import { createBunServeHandler } from "trpc-bun-adapter";
|
import { createBunServeHandler } from "trpc-bun-adapter";
|
||||||
import { appRouter } from "./trpc";
|
import { appRouter } from "./trpc";
|
||||||
|
import { Logger } from "@util/Logger";
|
||||||
|
|
||||||
|
const logger = new Logger("Server");
|
||||||
|
|
||||||
export const server = Bun.serve(
|
export const server = Bun.serve(
|
||||||
createBunServeHandler({
|
createBunServeHandler({
|
||||||
|
@ -7,4 +10,6 @@ export const server = Bun.serve(
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
logger.info("Started on port", (process.env.PORT as string) || 3000);
|
||||||
|
|
||||||
export default server;
|
export default server;
|
||||||
|
|
|
@ -1,16 +1,58 @@
|
||||||
import { initTRPC } from "@trpc/server";
|
import { handleCommand } from "@server/commands/handler";
|
||||||
|
import { prefixes } from "@server/commands/prefixes";
|
||||||
|
import { TRPCError, initTRPC } from "@trpc/server";
|
||||||
|
import { Logger } from "@util/Logger";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
const t = initTRPC.create();
|
export interface Context {
|
||||||
|
isAuthed: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const t = initTRPC.context<Context>().create();
|
||||||
|
|
||||||
export const router = t.router;
|
export const router = t.router;
|
||||||
export const publicProcedure = t.procedure;
|
export const publicProcedure = t.procedure;
|
||||||
|
export const privateProcedure = publicProcedure.use(async opts => {
|
||||||
|
const { ctx } = opts;
|
||||||
|
|
||||||
|
if (!ctx.isAuthed) throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||||
|
|
||||||
|
return opts.next({
|
||||||
|
ctx: {
|
||||||
|
isAuthed: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const logger = new Logger("tRPC");
|
||||||
|
|
||||||
export const appRouter = router({
|
export const appRouter = router({
|
||||||
cast: publicProcedure.input(z.string()).query(async opts => {
|
prefixes: publicProcedure.query(async opts => {
|
||||||
const { input } = opts;
|
return prefixes;
|
||||||
const response = `${input} cast their rod`;
|
}),
|
||||||
return { response };
|
|
||||||
|
command: publicProcedure
|
||||||
|
.input(
|
||||||
|
z.object({
|
||||||
|
command: z.string(),
|
||||||
|
prefix: z.string(),
|
||||||
|
args: z.array(z.string()),
|
||||||
|
user: z.object({
|
||||||
|
id: z.string(),
|
||||||
|
name: z.string(),
|
||||||
|
color: z.string()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.query(async opts => {
|
||||||
|
const { command, args, prefix, user } = opts.input;
|
||||||
|
const out = await handleCommand(command, args, prefix, user);
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}),
|
||||||
|
|
||||||
|
auth: publicProcedure.input(z.string()).query(async opts => {
|
||||||
|
const token = opts.input;
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
export class Command {
|
||||||
|
constructor(
|
||||||
|
public id: string,
|
||||||
|
public aliases: string[],
|
||||||
|
public description: string,
|
||||||
|
public usage: string,
|
||||||
|
public permissionNode: string,
|
||||||
|
public callback: TCommandCallback
|
||||||
|
) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Command;
|
|
@ -0,0 +1,12 @@
|
||||||
|
import Command from "@server/commands/Command";
|
||||||
|
|
||||||
|
export const fish = new Command(
|
||||||
|
"fish",
|
||||||
|
["fish", "fosh", "cast"],
|
||||||
|
"Send your LURE into a water for catching fish",
|
||||||
|
"fish",
|
||||||
|
"command.fishing.fish",
|
||||||
|
async () => {
|
||||||
|
return "There is no fishing yet, please come back later when I write the code for it";
|
||||||
|
}
|
||||||
|
);
|
|
@ -0,0 +1,33 @@
|
||||||
|
import Command from "@server/commands/Command";
|
||||||
|
import { commandGroups } from "..";
|
||||||
|
import { logger } from "@server/commands/handler";
|
||||||
|
|
||||||
|
export const help = new Command(
|
||||||
|
"help",
|
||||||
|
[
|
||||||
|
"help",
|
||||||
|
"h",
|
||||||
|
"holp",
|
||||||
|
"halp",
|
||||||
|
"hilp",
|
||||||
|
"hulp",
|
||||||
|
"commands",
|
||||||
|
"commonds",
|
||||||
|
"cmds",
|
||||||
|
"cimminds",
|
||||||
|
"cammands",
|
||||||
|
"cummunds"
|
||||||
|
],
|
||||||
|
"Help command",
|
||||||
|
"help [command]",
|
||||||
|
"command.general.help",
|
||||||
|
async (command, args, prefix, user) => {
|
||||||
|
return `${commandGroups
|
||||||
|
.map(group => {
|
||||||
|
return `${group.displayName}: ${group.commands
|
||||||
|
.map(cmd => cmd.aliases[0])
|
||||||
|
.join(", ")}`;
|
||||||
|
})
|
||||||
|
.join("\n")}`;
|
||||||
|
}
|
||||||
|
);
|
|
@ -0,0 +1,36 @@
|
||||||
|
import type { Command } from "../Command";
|
||||||
|
import { fish } from "./fishing/fish";
|
||||||
|
import { help } from "./general/help";
|
||||||
|
import { data } from "./util/data";
|
||||||
|
|
||||||
|
interface ICommandGroup {
|
||||||
|
id: string;
|
||||||
|
displayName: string;
|
||||||
|
commands: Command[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const commandGroups: ICommandGroup[] = [];
|
||||||
|
|
||||||
|
const general: ICommandGroup = {
|
||||||
|
id: "general",
|
||||||
|
displayName: "General",
|
||||||
|
commands: [help]
|
||||||
|
};
|
||||||
|
|
||||||
|
commandGroups.push(general);
|
||||||
|
|
||||||
|
const fishing: ICommandGroup = {
|
||||||
|
id: "fishing",
|
||||||
|
displayName: "Fishing",
|
||||||
|
commands: [fish]
|
||||||
|
};
|
||||||
|
|
||||||
|
commandGroups.push(fishing);
|
||||||
|
|
||||||
|
const util: ICommandGroup = {
|
||||||
|
id: "util",
|
||||||
|
displayName: "Utility",
|
||||||
|
commands: [data]
|
||||||
|
};
|
||||||
|
|
||||||
|
commandGroups.push(util);
|
|
@ -0,0 +1,10 @@
|
||||||
|
import Command from "@server/commands/Command";
|
||||||
|
|
||||||
|
export const data = new Command(
|
||||||
|
"data",
|
||||||
|
["data"],
|
||||||
|
"Data command",
|
||||||
|
"data",
|
||||||
|
"command.util.data",
|
||||||
|
async () => {}
|
||||||
|
);
|
|
@ -0,0 +1,42 @@
|
||||||
|
import { Logger } from "@util/Logger";
|
||||||
|
import type Command from "./Command";
|
||||||
|
import { commandGroups } from "./groups";
|
||||||
|
|
||||||
|
export const logger = new Logger("Command Handler");
|
||||||
|
|
||||||
|
export async function handleCommand(
|
||||||
|
command: string,
|
||||||
|
args: string[],
|
||||||
|
prefix: string,
|
||||||
|
user: IUser
|
||||||
|
): Promise<ICommandResponse | void> {
|
||||||
|
let foundCommand: Command | undefined;
|
||||||
|
|
||||||
|
commandGroups.forEach(group => {
|
||||||
|
if (!foundCommand) {
|
||||||
|
foundCommand = group.commands.find(cmd => {
|
||||||
|
return cmd.aliases.includes(command);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!foundCommand) return;
|
||||||
|
|
||||||
|
// TODO Check user's (or their groups') permissions against command permission node
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await foundCommand.callback(
|
||||||
|
command,
|
||||||
|
args,
|
||||||
|
prefix,
|
||||||
|
user
|
||||||
|
);
|
||||||
|
if (response) return { response };
|
||||||
|
} catch (err) {
|
||||||
|
logger.error(err);
|
||||||
|
return {
|
||||||
|
response:
|
||||||
|
"An error has occurred, but no fish were lost. If you are the fishing bot owner, check the error logs for details."
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
export const prefixes = ["/"];
|
|
@ -0,0 +1,27 @@
|
||||||
|
import prisma from "./prisma";
|
||||||
|
|
||||||
|
export async function createToken() {
|
||||||
|
const randomToken = crypto.randomUUID();
|
||||||
|
|
||||||
|
await prisma.authToken.create({
|
||||||
|
data: {
|
||||||
|
token: randomToken
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return randomToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteToken(token: string) {
|
||||||
|
await prisma.authToken.delete({
|
||||||
|
where: { token }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function checkToken(token: string) {
|
||||||
|
const existing = await prisma.authToken.findUnique({
|
||||||
|
where: { token }
|
||||||
|
});
|
||||||
|
|
||||||
|
return !!existing;
|
||||||
|
}
|
|
@ -1,2 +1 @@
|
||||||
import "./api/server";
|
import "./api/server";
|
||||||
import prisma from "S:/data/prisma";
|
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { createTRPCClient, httpBatchLink } from "@trpc/client";
|
||||||
|
import type { AppRouter } from "@server/api/trpc";
|
||||||
|
|
||||||
|
export const trpc = createTRPCClient<AppRouter>({
|
||||||
|
links: [
|
||||||
|
httpBatchLink({
|
||||||
|
url: "http://localhost:3000"
|
||||||
|
}),
|
||||||
|
httpBatchLink({
|
||||||
|
url: "https://fishing.hri7566.info/api"
|
||||||
|
})
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
export default trpc;
|
|
@ -0,0 +1,173 @@
|
||||||
|
import Client from "mpp-client-net";
|
||||||
|
import { Logger } from "@util/Logger";
|
||||||
|
import trpc from "@client/api/trpc";
|
||||||
|
|
||||||
|
export interface MPPNetBotConfig {
|
||||||
|
uri: string;
|
||||||
|
|
||||||
|
channel: {
|
||||||
|
id: string;
|
||||||
|
allowColorChanging: boolean;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MPPNetBot {
|
||||||
|
public client: Client;
|
||||||
|
|
||||||
|
public logger: Logger;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public config: MPPNetBotConfig,
|
||||||
|
token: string = process.env[`MPP_TOKEN_NET`] as string
|
||||||
|
) {
|
||||||
|
this.logger = new Logger(config.channel.id);
|
||||||
|
this.client = new Client(config.uri, token);
|
||||||
|
|
||||||
|
this.client.setChannel(config.channel.id);
|
||||||
|
|
||||||
|
this.bindEventListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
public start() {
|
||||||
|
this.client.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public stop() {
|
||||||
|
this.client.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bindEventListeners() {
|
||||||
|
this.client.on("hi", msg => {
|
||||||
|
this.logger.info(`Connected to ${this.client.uri}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.client.on("ch", msg => {
|
||||||
|
this.logger.info(
|
||||||
|
`Received channel update for channel ID "${msg.ch._id}"`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.client.on("a", async msg => {
|
||||||
|
let prefixes: string[];
|
||||||
|
|
||||||
|
try {
|
||||||
|
prefixes = await trpc.prefixes.query();
|
||||||
|
} catch (err) {
|
||||||
|
this.logger.error(err);
|
||||||
|
this.logger.warn("Unable to contact server");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let usedPrefix: string | undefined = prefixes.find(pr =>
|
||||||
|
msg.a.startsWith(pr)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!usedPrefix) return;
|
||||||
|
|
||||||
|
const args = msg.a.split(" ");
|
||||||
|
|
||||||
|
const command = await trpc.command.query({
|
||||||
|
args: args.slice(1, args.length),
|
||||||
|
command: args[0].substring(usedPrefix.length),
|
||||||
|
prefix: usedPrefix,
|
||||||
|
user: {
|
||||||
|
id: msg.p._id,
|
||||||
|
name: msg.p.name,
|
||||||
|
color: msg.p.color
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!command) return;
|
||||||
|
if (command.response) this.sendChat(command.response);
|
||||||
|
});
|
||||||
|
|
||||||
|
(this.client as unknown as any).on(
|
||||||
|
"dm",
|
||||||
|
async (msg: {
|
||||||
|
m: "dm";
|
||||||
|
id: string;
|
||||||
|
t: number;
|
||||||
|
a: string;
|
||||||
|
|
||||||
|
sender: {
|
||||||
|
_id: string;
|
||||||
|
name: string;
|
||||||
|
color: string;
|
||||||
|
afk: boolean;
|
||||||
|
tag?: {
|
||||||
|
text: string;
|
||||||
|
color: string;
|
||||||
|
};
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
recipient: {
|
||||||
|
_id: string;
|
||||||
|
name: string;
|
||||||
|
color: string;
|
||||||
|
afk: boolean;
|
||||||
|
tag?: {
|
||||||
|
text: string;
|
||||||
|
color: string;
|
||||||
|
};
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
}) => {
|
||||||
|
let prefixes: string[];
|
||||||
|
|
||||||
|
try {
|
||||||
|
prefixes = await trpc.prefixes.query();
|
||||||
|
} catch (err) {
|
||||||
|
this.logger.error(err);
|
||||||
|
this.logger.warn("Unable to contact server");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let usedPrefix: string | undefined = prefixes.find(pr =>
|
||||||
|
msg.a.startsWith(pr)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!usedPrefix) return;
|
||||||
|
|
||||||
|
const args = msg.a.split(" ");
|
||||||
|
|
||||||
|
const command = await trpc.command.query({
|
||||||
|
args: args.slice(1, args.length),
|
||||||
|
command: args[0].substring(usedPrefix.length),
|
||||||
|
prefix: usedPrefix,
|
||||||
|
user: {
|
||||||
|
id: msg.sender._id,
|
||||||
|
name: msg.sender.name,
|
||||||
|
color: msg.sender.color
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!command) return;
|
||||||
|
if (command.response) this.sendChat(command.response);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public sendChat(text: string) {
|
||||||
|
let lines = text.split("\n");
|
||||||
|
|
||||||
|
for (const line of lines) {
|
||||||
|
if (line.length <= 510) {
|
||||||
|
this.client.sendArray([
|
||||||
|
{
|
||||||
|
m: "a",
|
||||||
|
message: `\u034f${line
|
||||||
|
.split("\t")
|
||||||
|
.join("")
|
||||||
|
.split("\r")
|
||||||
|
.join("")}`
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
this.sendChat(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MPPNetBot;
|
|
@ -0,0 +1,30 @@
|
||||||
|
import { loadConfig } from "@util/config";
|
||||||
|
import { MPPNetBot, type MPPNetBotConfig } from "./Bot";
|
||||||
|
|
||||||
|
const bots = [];
|
||||||
|
|
||||||
|
const defaults = loadConfig("config/bots.yml", [
|
||||||
|
{
|
||||||
|
uri: "wss://mppclone.com:8443",
|
||||||
|
channel: {
|
||||||
|
id: "✧𝓓𝓔𝓥 𝓡𝓸𝓸𝓶✧",
|
||||||
|
allowColorChanging: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
] as MPPNetBotConfig[]);
|
||||||
|
|
||||||
|
export function connectDefaultBots() {
|
||||||
|
defaults.forEach(conf => {
|
||||||
|
initBot(conf);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function initBot(conf: MPPNetBotConfig) {
|
||||||
|
const bot = new MPPNetBot(conf);
|
||||||
|
bot.start();
|
||||||
|
bots.push(bot);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { MPPNetBot as Bot };
|
||||||
|
|
||||||
|
export default MPPNetBot;
|
|
@ -0,0 +1,3 @@
|
||||||
|
import { connectDefaultBots } from "./bot";
|
||||||
|
|
||||||
|
connectDefaultBots();
|
|
@ -0,0 +1,43 @@
|
||||||
|
import { getHHMMSS } from "./time";
|
||||||
|
|
||||||
|
export class Logger {
|
||||||
|
private static log(...args: any[]) {
|
||||||
|
const time = getHHMMSS();
|
||||||
|
|
||||||
|
console.log(`\x1b[30m${time}\x1b[0m`, ...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(public id: string) {}
|
||||||
|
|
||||||
|
public info(...args: any[]) {
|
||||||
|
Logger.log(
|
||||||
|
`\x1b[34m[${this.id}]\x1b[0m`,
|
||||||
|
`\x1b[34m[INFO]\x1b[0m`,
|
||||||
|
...args
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public error(...args: any[]) {
|
||||||
|
Logger.log(
|
||||||
|
`\x1b[34m[${this.id}]\x1b[0m`,
|
||||||
|
`\x1b[31m[ERROR]\x1b[0m`,
|
||||||
|
...args
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public warn(...args: any[]) {
|
||||||
|
Logger.log(
|
||||||
|
`\x1b[34m[${this.id}]\x1b[0m`,
|
||||||
|
`\x1b[33m[WARNING]\x1b[0m`,
|
||||||
|
...args
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public debug(...args: any[]) {
|
||||||
|
Logger.log(
|
||||||
|
`\x1b[34m[${this.id}]\x1b[0m`,
|
||||||
|
`\x1b[32m[DEBUG]\x1b[0m`,
|
||||||
|
...args
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
||||||
|
import YAML from "yaml";
|
||||||
|
import { parse } from "path/posix";
|
||||||
|
|
||||||
|
export function loadConfig<T>(path: string, defaultConfig: T) {
|
||||||
|
const parsed = parse(path);
|
||||||
|
const dir = parsed.dir;
|
||||||
|
|
||||||
|
if (!existsSync(dir)) {
|
||||||
|
mkdirSync(dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existsSync(path)) {
|
||||||
|
const yaml = readFileSync(path).toString();
|
||||||
|
const data = YAML.parse(yaml);
|
||||||
|
|
||||||
|
return data as T;
|
||||||
|
} else {
|
||||||
|
return defaultConfig;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function saveConfig<T>(path: string, config: T) {
|
||||||
|
writeFileSync(path, YAML.stringify(config));
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
type TickEvent = () => Promise<void> | void;
|
||||||
|
|
||||||
|
const ticks: TickEvent[] = [];
|
||||||
|
|
||||||
|
(globalThis as unknown as Record<string, unknown>).ticker = setInterval(() => {
|
||||||
|
for (const tick of ticks) tick();
|
||||||
|
}, 1000 / 20);
|
||||||
|
|
||||||
|
export function addTickEvent(event: TickEvent) {
|
||||||
|
ticks.push(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeTickEvent(event: TickEvent) {
|
||||||
|
const index = ticks.indexOf(event);
|
||||||
|
|
||||||
|
if (index >= 0) {
|
||||||
|
ticks.splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
export function getHHMMSS() {
|
||||||
|
const now = Date.now();
|
||||||
|
|
||||||
|
const s = now / 1000;
|
||||||
|
const m = s / 60;
|
||||||
|
const h = m / 60;
|
||||||
|
|
||||||
|
const hh = Math.floor(h % 12)
|
||||||
|
.toString()
|
||||||
|
.padStart(2, "0");
|
||||||
|
const mm = Math.floor(m % 60)
|
||||||
|
.toString()
|
||||||
|
.padStart(2, "0");
|
||||||
|
const ss = Math.floor(s % 60)
|
||||||
|
.toString()
|
||||||
|
.padStart(2, "0");
|
||||||
|
const ms = Math.floor(now % 1000)
|
||||||
|
.toString()
|
||||||
|
.padStart(3, "0");
|
||||||
|
|
||||||
|
return `${hh}:${mm}:${ss}.${ms}`;
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
interface IUser {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
color: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ICommandResponse {
|
||||||
|
response: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type TCommandCallback = (
|
||||||
|
command: string,
|
||||||
|
args: string[],
|
||||||
|
prefix: string,
|
||||||
|
user: IUser
|
||||||
|
) => Promise<string | void>;
|
|
@ -0,0 +1,28 @@
|
||||||
|
import { checkToken, createToken, deleteToken } from "@server/data/token";
|
||||||
|
import { test, expect } from "bun:test";
|
||||||
|
|
||||||
|
test("Token can be created and deleted", async () => {
|
||||||
|
const token = await createToken();
|
||||||
|
expect(token).toBeString();
|
||||||
|
await deleteToken(token);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Token can be validated", async () => {
|
||||||
|
const token = await createToken();
|
||||||
|
expect(token).toBeString();
|
||||||
|
|
||||||
|
const checked = await checkToken(token);
|
||||||
|
expect(checked).toBeTruthy();
|
||||||
|
|
||||||
|
await deleteToken(token);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Token can be invalidated", async () => {
|
||||||
|
const token = await createToken();
|
||||||
|
expect(token).toBeString();
|
||||||
|
|
||||||
|
await deleteToken(token);
|
||||||
|
|
||||||
|
const checked = await checkToken(token);
|
||||||
|
expect(checked).toBeFalsy();
|
||||||
|
});
|
|
@ -1,27 +1,28 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"lib": ["ESNext"],
|
"lib": ["ESNext"],
|
||||||
"target": "ESNext",
|
"target": "ESNext",
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"moduleDetection": "force",
|
"moduleDetection": "force",
|
||||||
"jsx": "react-jsx",
|
"jsx": "react-jsx",
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
|
|
||||||
/* Bundler mode */
|
/* Bundler mode */
|
||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
"allowImportingTsExtensions": true,
|
"allowImportingTsExtensions": true,
|
||||||
"verbatimModuleSyntax": true,
|
"verbatimModuleSyntax": true,
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
|
|
||||||
/* Linting */
|
/* Linting */
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"noFallthroughCasesInSwitch": true,
|
"noFallthroughCasesInSwitch": true,
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
|
||||||
"paths": {
|
"paths": {
|
||||||
"S:/*": ["./src/api/*"],
|
"@server/*": ["./src/api/*"],
|
||||||
"C:/*": ["./src/bot/*"],
|
"@client/*": ["./src/mpp/*"],
|
||||||
|
"@util/*": ["./src/util/*"]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue