Implement basic tag system

This commit is contained in:
Hri7566 2024-08-03 01:00:20 -04:00
parent 828443cb93
commit 9b48d41bd8
9 changed files with 87 additions and 39 deletions

View File

@ -20,10 +20,12 @@ Brandon's server originally used MongoDB for storing user data, but there are to
- Chat - Chat
- Original chat filter by chacha and Brandon Lockaby - Original chat filter by chacha and Brandon Lockaby
- Commands for debugging or administrative purposes
- Piano Notes - Piano Notes
- Uses the same `NoteQuota` implementation from the client - Uses the same `NoteQuota` implementation from the client
- Usernames/colors - Usernames/colors
- Allowing color changing can be toggled in the config - Allowing color changing can be toggled in the config, similar to MPP.com
- Default user parameters can be set
- Channels - Channels
- Channel list - Channel list
- Channel settings - Channel settings
@ -37,20 +39,32 @@ Brandon's server originally used MongoDB for storing user data, but there are to
- Chat muting - Chat muting
- Rate limit bypasses - Rate limit bypasses
- Channel/User-targeted notifications - Channel/User-targeted notifications
- Server-wide/channel-specific/user-specific notifications
- New admin messages - New admin messages
- Restart message - Restart message
- Triggers notification on every connected socket, then shuts down after 20 seconds - Triggers notification on every connected socket, then shuts down after 20 seconds
- Server must be setup as a pm2/docker/systemd process - Server must be setup as a pm2/docker/systemd process for automatic restarting
- Ability to change tags - Ability to change tags
## TODO ## TODO
- Fully implement and test tags - Fully implement and test tags
- Tags are sent to clients now
- Check if tags are sent to everyone
- Permission groups and permissions
- Probable permission groups: owner, admin, mod, trialmod, default
- Setup tags for each permission group
- Full server-wide event bus
- Channel events
- Socket events
- User data events
- Permission-related events
- Redo all of the validations with Zod - Redo all of the validations with Zod
- This probably means making Zod schemas for every single message type - This probably means making Zod schemas for every single message type
- Also user and channel data - Also user and channel data
- Test every frontend - Test every frontend
- Test fishing bot - Test fishing bot
- Remote console
## Backlog/Notes ## Backlog/Notes

View File

@ -7,13 +7,17 @@ defaultFlags:
# Whether or not to allow users to change their color. # Whether or not to allow users to change their color.
# Brandon's server has this set to false, but multiple users have reported it to be on before 2016. # Brandon's server has this set to false, but multiple users have reported it to be on before 2016.
enableColorChanging: false enableColorChanging: true
# Allows custom data inside note messages. # Allows custom data inside note messages.
# This was in the original server, but not in MPP.net's server. # This was in the original server, but not in MPP.net's server.
# This only exists for backwards compatibility with scripts like nagalun's drarwing script. # This only exists for backwards compatibility with scripts like nagalun's drarwing script.
enableCustomNoteData: true enableCustomNoteData: true
# Whether or not to enable tags that are sent publicly.
# This won't prevent admins from changing tags internally, but it will not be sent to clients.
enableTags: true
# This is the user data that the server will use to send admin chat messages with. # This is the user data that the server will use to send admin chat messages with.
adminParticipant: adminParticipant:
_id: "0" _id: "0"
@ -27,11 +31,11 @@ enableAdminEval: true
# The token validation scheme. Valid values are "none", "jwt" and "uuid". # The token validation scheme. Valid values are "none", "jwt" and "uuid".
# This server will still validate existing tokens generated with other schemes if not set to "none". # This server will still validate existing tokens generated with other schemes if not set to "none".
tokenAuth: none tokenAuth: jwt
# The browser challenge scheme. Valid values are "none", "obf" and "basic". # The browser challenge scheme. Valid values are "none", "obf" and "basic".
# This is to change what is sent in the "b" message. # This is to change what is sent in the "b" message.
# "none" will disable the browser challenge, # "none" will disable the browser challenge,
# "obf" will sent an obfuscated function to the client, # "obf" will sent an obfuscated function to the client,
# and "basic" will just send a simple function that expects a boolean. # and "basic" will just send a simple function that expects a boolean.
browserChallenge: none browserChallenge: basic

View File

@ -88,7 +88,7 @@ export class Channel extends EventEmitter {
(typeof set.color2 == "undefined" || (typeof set.color2 == "undefined" ||
set.color2 === this.settings.color2) set.color2 === this.settings.color2)
) { ) {
this.logger.debug("color 2 darken triggered"); //this.logger.debug("color 2 darken triggered");
set.color2 = darken(set.color); set.color2 = darken(set.color);
} }
@ -97,7 +97,7 @@ export class Channel extends EventEmitter {
// Set the verified settings // Set the verified settings
for (const key of Object.keys(validatedSet)) { for (const key of Object.keys(validatedSet)) {
this.logger.debug(`${key}: ${(validatedSet as any)[key]}`); //this.logger.debug(`${key}: ${(validatedSet as any)[key]}`);
if ((validatedSet as any)[key] === false) continue; if ((validatedSet as any)[key] === false) continue;
(this.settings as any)[key] = (set as any)[key]; (this.settings as any)[key] = (set as any)[key];
} }
@ -480,7 +480,7 @@ export class Channel extends EventEmitter {
if (this.isTrueLobby()) { if (this.isTrueLobby()) {
// Get the next lobby number // Get the next lobby number
const nextID = this.getNextLobbyID(); const nextID = this.getNextLobbyID();
this.logger.debug("New ID:", nextID); //this.logger.debug("New ID:", nextID);
// Move them to the next lobby // Move them to the next lobby
return socket.setChannel(nextID); return socket.setChannel(nextID);
} }
@ -691,7 +691,7 @@ export class Channel extends EventEmitter {
name: p.name, name: p.name,
color: p.color, color: p.color,
id: p.id, id: p.id,
tag: config.sendTags ? p.tag : undefined tag: usersConfig.enableTags ? p.tag : undefined
}); });
} }

View File

@ -20,31 +20,6 @@ loadForcedStartupChannels();
// This literally breaks editors and they stick all the imports here instead of at the top // This literally breaks editors and they stick all the imports here instead of at the top
import "./util/readline"; import "./util/readline";
import { Socket, socketsBySocketID } from "./ws/Socket";
import { createSocketID } from "./util/id";
// Nevermind we use it twice // Nevermind we use it twice
logger.info("Ready"); logger.info("Ready");
// Connect 5 sockets
const sockets = [];
for (let i = 0; i < 5; i++) {
setTimeout(() => {
const socket = new Socket(undefined, createSocketID());
socket.on("ready", () => {
logger.info(`Socket ${i} is ready`);
});
socket.setChannel("lobby");
sockets.push(socket);
if (socket.socketID == undefined) {
socket.socketID = createSocketID();
}
socketsBySocketID.set(socket.socketID, socket);
}, i * 5000);
}

View File

@ -1,10 +1,19 @@
import EventEmitter from "events";
import { padNum, unimportant } from "./helpers"; import { padNum, unimportant } from "./helpers";
export const logEvents = new EventEmitter();
/** /**
* A logger that doesn't fuck with the readline prompt * A logger that doesn't fuck with the readline prompt
* timestamps are likely wrong because of js timezones * timestamps are likely wrong because of js timezones
**/ **/
export class Logger { export class Logger {
/**
* Log a message
* This does weird prompt things
* @param method The method from `console` to use
* @param args The data to print
**/
private static log(method: string, ...args: any[]) { private static log(method: string, ...args: any[]) {
// Un-fuck the readline prompt // Un-fuck the readline prompt
process.stdout.write("\x1b[2K\r"); process.stdout.write("\x1b[2K\r");
@ -21,8 +30,15 @@ export class Logger {
// Re-print the readline prompt (spooky cringe global variable) // Re-print the readline prompt (spooky cringe global variable)
if ((globalThis as unknown as any).rl) if ((globalThis as unknown as any).rl)
(globalThis as unknown as any).rl.prompt(); (globalThis as unknown as any).rl.prompt();
// Emit the log event for remote consoles
logEvents.emit("log", method, unimportant(this.getDate()), unimportant(this.getHHMMSSMS()), args);
} }
/**
* Get the current time in HH:MM:SS.MS format
* @returns The current time in HH:MM:SS.MS format
**/
public static getHHMMSSMS() { public static getHHMMSSMS() {
const ms = Date.now(); const ms = Date.now();
@ -38,24 +54,44 @@ export class Logger {
return `${hh}:${mm}:${ss}.${ll}`; return `${hh}:${mm}:${ss}.${ll}`;
} }
/**
* Get the current date in ISO format
* @returns The current date in ISO format
**/
public static getDate() { public static getDate() {
return new Date().toISOString().split("T")[0]; return new Date().toISOString().split("T")[0];
} }
constructor(public id: string) { } constructor(public id: string) { }
/**
* Print an info message
* @param args The data to print
**/
public info(...args: any[]) { public info(...args: any[]) {
Logger.log("log", `[${this.id}]`, `\x1b[34m[info]\x1b[0m`, ...args); Logger.log("log", `[${this.id}]`, `\x1b[34m[info]\x1b[0m`, ...args);
} }
/**
* Print an error message
* @param args The data to print
**/
public error(...args: any[]) { public error(...args: any[]) {
Logger.log("error", `[${this.id}]`, `\x1b[31m[error]\x1b[0m`, ...args); Logger.log("error", `[${this.id}]`, `\x1b[31m[error]\x1b[0m`, ...args);
} }
/**
* Print a warning message
* @param args The data to print
**/
public warn(...args: any[]) { public warn(...args: any[]) {
Logger.log("warn", `[${this.id}]`, `\x1b[33m[warn]\x1b[0m`, ...args); Logger.log("warn", `[${this.id}]`, `\x1b[33m[warn]\x1b[0m`, ...args);
} }
/**
* Print a debug message
* @param args The data to print
**/
public debug(...args: any[]) { public debug(...args: any[]) {
Logger.log("debug", `[${this.id}]`, `\x1b[32m[debug]\x1b[0m`, ...args); Logger.log("debug", `[${this.id}]`, `\x1b[32m[debug]\x1b[0m`, ...args);
} }

View File

@ -14,7 +14,8 @@ import {
ServerEvents, ServerEvents,
UserFlags, UserFlags,
Vector2, Vector2,
Notification Notification,
Tag
} from "../util/types"; } from "../util/types";
import type { User } from "@prisma/client"; import type { User } from "@prisma/client";
import { createUser, readUser, updateUser } from "../data/user"; import { createUser, readUser, updateUser } from "../data/user";
@ -355,11 +356,20 @@ export class Socket extends EventEmitter {
} }
} }
let tag: Tag | undefined;
try {
tag = JSON.parse(this.user.tag) as Tag;
} catch (err) {
logger.warn("Unable to parse tag:", err);
}
return { return {
_id: facadeID, _id: facadeID,
name: this.user.name, name: this.user.name,
color: this.user.color, color: this.user.color,
id: this.getParticipantID() id: this.getParticipantID(),
tag: config.enableTags ? tag : undefined
}; };
} else { } else {
return null; return null;
@ -744,6 +754,7 @@ export class Socket extends EventEmitter {
* @param color Color of the tag * @param color Color of the tag
**/ **/
public setTag(text: string, color: string) { public setTag(text: string, color: string) {
//logger.debug("Setting tag:", text, color);
const user = this.getUser(); const user = this.getUser();
if (!user) return; if (!user) return;
user.tag = JSON.stringify({ text, color }); user.tag = JSON.stringify({ text, color });

View File

@ -11,6 +11,7 @@ import { name } from "./handlers/name";
import { notification } from "./handlers/notification"; import { notification } from "./handlers/notification";
import { rename_channel } from "./handlers/rename_channel"; import { rename_channel } from "./handlers/rename_channel";
import { restart } from "./handlers/restart"; import { restart } from "./handlers/restart";
import { tag } from "./handlers/tag";
import { user_flag } from "./handlers/user_flag"; import { user_flag } from "./handlers/user_flag";
// EVENT_GROUP_ADMIN.add(color); // EVENT_GROUP_ADMIN.add(color);
@ -27,7 +28,8 @@ EVENT_GROUP_ADMIN.addMany(
move, move,
rename_channel, rename_channel,
admin_chat, admin_chat,
eval_msg eval_msg,
tag
); );
eventGroups.push(EVENT_GROUP_ADMIN); eventGroups.push(EVENT_GROUP_ADMIN);

View File

@ -65,10 +65,13 @@ export const hi: ServerEventListener<"hi"> = {
_id: socket.getUserID(), _id: socket.getUserID(),
name: "Anonymous", name: "Anonymous",
color: "#777", color: "#777",
id: "" id: "",
tag: undefined
}; };
} }
logger.debug("Tag:", part.tag);
socket.sendArray([{ socket.sendArray([{
m: "hi", m: "hi",
accountInfo: undefined, accountInfo: undefined,
@ -77,7 +80,8 @@ export const hi: ServerEventListener<"hi"> = {
u: { u: {
_id: part._id, _id: part._id,
color: part.color, color: part.color,
name: part.name name: part.name,
tag: part.tag
}, },
motd: getMOTD(), motd: getMOTD(),
token token

View File

@ -6,6 +6,7 @@ export interface UsersConfig {
defaultFlags: UserFlags; defaultFlags: UserFlags;
enableColorChanging: boolean; enableColorChanging: boolean;
enableCustomNoteData: boolean; enableCustomNoteData: boolean;
enableTags: boolean;
adminParticipant: Participant; adminParticipant: Participant;
enableAdminEval: boolean; enableAdminEval: boolean;
tokenAuth: "jwt" | "uuid" | "none"; tokenAuth: "jwt" | "uuid" | "none";
@ -21,6 +22,7 @@ export const defaultUsersConfig: UsersConfig = {
}, },
enableColorChanging: false, enableColorChanging: false,
enableCustomNoteData: true, enableCustomNoteData: true,
enableTags: false,
adminParticipant: { adminParticipant: {
_id: "0", _id: "0",
name: "mpp", name: "mpp",