forked from Hri7566/mpp-server-dev2
Fix channel settings validation, attempt to implement channel list and admin messages
This commit is contained in:
parent
ffb8044734
commit
8a8ce405fd
|
@ -13,6 +13,7 @@ import { Socket } from "../ws/Socket";
|
|||
import { validateChannelSettings } from "./settings";
|
||||
import { findSocketByPartID, socketsBySocketID } from "../ws/Socket";
|
||||
import Crown from "./Crown";
|
||||
import { ChannelList } from "./ChannelList";
|
||||
|
||||
interface ChannelConfig {
|
||||
forceLoad: string[];
|
||||
|
@ -40,14 +41,11 @@ export const config = loadConfig<ChannelConfig>("config/channels.yml", {
|
|||
color2: "#001014",
|
||||
visible: true
|
||||
},
|
||||
// TODO Test this regex
|
||||
lobbyRegexes: ["^lobby[1-9]?[1-9]?$", "^test/.+$"],
|
||||
lobbyRegexes: ["^lobby[0-9][0-9]$", "^lobby[1-9]$", "^test/.+$"],
|
||||
lobbyBackdoor: "lolwutsecretlobbybackdoor",
|
||||
fullChannel: "test/awkward"
|
||||
});
|
||||
|
||||
export const channelList = new Array<Channel>();
|
||||
|
||||
export class Channel extends EventEmitter {
|
||||
private settings: Partial<IChannelSettings> = config.defaultSettings;
|
||||
private ppl = new Array<Participant>();
|
||||
|
@ -75,9 +73,8 @@ export class Channel extends EventEmitter {
|
|||
if (set) {
|
||||
const validatedSet = validateChannelSettings(set);
|
||||
|
||||
for (const key in Object.keys(validatedSet)) {
|
||||
if (!(validatedSet as any)[key]) continue;
|
||||
|
||||
for (const key of Object.keys(set)) {
|
||||
if ((validatedSet as any)[key] === false) continue;
|
||||
(this.settings as any)[key] = (set as any)[key];
|
||||
}
|
||||
}
|
||||
|
@ -85,10 +82,10 @@ export class Channel extends EventEmitter {
|
|||
this.crown = new Crown();
|
||||
|
||||
if (creator) {
|
||||
if (this.crown.canBeSetBy(creator)) {
|
||||
const part = creator.getParticipant();
|
||||
if (part) this.giveCrown(part);
|
||||
}
|
||||
// if (this.crown.canBeSetBy(creator)) {
|
||||
const part = creator.getParticipant();
|
||||
if (part) this.giveCrown(part);
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -98,8 +95,10 @@ export class Channel extends EventEmitter {
|
|||
|
||||
this.bindEventListeners();
|
||||
|
||||
channelList.push(this);
|
||||
ChannelList.add(this);
|
||||
// TODO channel closing
|
||||
|
||||
this.logger.info("Created");
|
||||
}
|
||||
|
||||
private alreadyBound = false;
|
||||
|
@ -264,7 +263,7 @@ export class Channel extends EventEmitter {
|
|||
|
||||
if (hasChangedChannel) {
|
||||
if (socket.currentChannelID) {
|
||||
const ch = channelList.find(
|
||||
const ch = ChannelList.getList().find(
|
||||
ch => ch._id == socket.currentChannelID
|
||||
);
|
||||
if (ch) {
|
||||
|
@ -475,7 +474,8 @@ export class Channel extends EventEmitter {
|
|||
}
|
||||
}
|
||||
|
||||
channelList.splice(channelList.indexOf(this), 1);
|
||||
ChannelList.remove(this);
|
||||
this.logger.info("Destroyed");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -567,5 +567,5 @@ for (const id of config.forceLoad) {
|
|||
}
|
||||
|
||||
if (!hasFullChannel) {
|
||||
channelList.push(new Channel(config.fullChannel));
|
||||
new Channel(config.fullChannel);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
import { findSocketByPartID } from "../ws/Socket";
|
||||
import type Channel from "./Channel";
|
||||
|
||||
const onChannelUpdate = (channel: Channel) => {
|
||||
const info = channel.getInfo();
|
||||
const ppl = channel.getParticipantList();
|
||||
|
||||
for (const partId of ChannelList.subscribers) {
|
||||
const socket = findSocketByPartID(partId);
|
||||
|
||||
if (typeof socket == "undefined") {
|
||||
ChannelList.subscribers.splice(
|
||||
ChannelList.subscribers.indexOf(partId),
|
||||
1
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
socket.sendChannelUpdate(info, ppl);
|
||||
}
|
||||
};
|
||||
|
||||
export class ChannelList {
|
||||
private static list = new Array<Channel>();
|
||||
public static subscribers = new Array<string>();
|
||||
|
||||
public static add(channel: Channel) {
|
||||
this.list.push(channel);
|
||||
channel.on("update", () => {
|
||||
onChannelUpdate(channel);
|
||||
});
|
||||
}
|
||||
|
||||
public static remove(channel: Channel) {
|
||||
this.list.splice(this.list.indexOf(channel), 1);
|
||||
}
|
||||
|
||||
public static getList() {
|
||||
return this.list;
|
||||
}
|
||||
}
|
|
@ -1,8 +1,9 @@
|
|||
import { ChannelSettings } from "../util/types";
|
||||
import { Logger } from "../util/Logger";
|
||||
import { IChannelSettings } from "../util/types";
|
||||
|
||||
type Validator = "boolean" | "string" | "number" | ((val: any) => boolean);
|
||||
type Validator = "boolean" | "string" | "number" | ((val: unknown) => boolean);
|
||||
|
||||
const validationRecord: Record<keyof ChannelSettings, Validator> = {
|
||||
const validationRecord: Record<keyof IChannelSettings, Validator> = {
|
||||
// Brandon
|
||||
lobby: "boolean",
|
||||
visible: "boolean",
|
||||
|
@ -25,13 +26,12 @@ const validationRecord: Record<keyof ChannelSettings, Validator> = {
|
|||
|
||||
/**
|
||||
* Check the validity of channel settings
|
||||
* @param set Unknown data
|
||||
* @param set Dirty settings
|
||||
* @returns Record of which settings are correct
|
||||
*/
|
||||
export function validateChannelSettings(set: Partial<ChannelSettings>) {
|
||||
export function validateChannelSettings(set: Partial<IChannelSettings>) {
|
||||
// Create record
|
||||
let keys = Object.keys(validationRecord);
|
||||
let record: Partial<Record<keyof ChannelSettings, boolean>> = {};
|
||||
let record: Partial<Record<keyof IChannelSettings, boolean>> = {};
|
||||
|
||||
for (const key of Object.keys(set)) {
|
||||
let val = (set as Record<string, any>)[key];
|
||||
|
@ -46,7 +46,7 @@ export function validateChannelSettings(set: Partial<ChannelSettings>) {
|
|||
}
|
||||
|
||||
// Set valid status
|
||||
record[key as keyof ChannelSettings] = validate(val, validator);
|
||||
record[key as keyof IChannelSettings] = validate(val, validator);
|
||||
}
|
||||
|
||||
return record;
|
||||
|
|
|
@ -12,3 +12,5 @@ import "./ws/server";
|
|||
import { Logger } from "./util/Logger";
|
||||
|
||||
const logger = new Logger("Main");
|
||||
|
||||
import "./util/readline";
|
||||
|
|
|
@ -2,6 +2,10 @@ import { padNum, unimportant } from "./helpers";
|
|||
|
||||
export class Logger {
|
||||
private static log(method: string, ...args: any[]) {
|
||||
// Clear current line
|
||||
process.stdout.write("\x1b[2K\r");
|
||||
|
||||
// Log our stuff
|
||||
(console as unknown as Record<string, (..._args: any[]) => any>)[
|
||||
method
|
||||
](
|
||||
|
@ -9,6 +13,10 @@ export class Logger {
|
|||
unimportant(this.getHHMMSSMS()),
|
||||
...args
|
||||
);
|
||||
|
||||
// Fix the readline prompt (spooky code)
|
||||
if ((globalThis as unknown as any).rl)
|
||||
(globalThis as unknown as any).rl.prompt();
|
||||
}
|
||||
|
||||
public static getHHMMSSMS() {
|
||||
|
|
|
@ -1,19 +1,35 @@
|
|||
import readline from "readline";
|
||||
import { Logger } from "./Logger";
|
||||
|
||||
export const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout
|
||||
});
|
||||
|
||||
rl.setPrompt("mpps> ");
|
||||
const logger = new Logger("CLI");
|
||||
|
||||
rl.setPrompt("mpps> ");
|
||||
rl.prompt();
|
||||
|
||||
rl.on("line", msg => {
|
||||
// TODO readline commands
|
||||
|
||||
if (msg == "mem" || msg == "memory") {
|
||||
const mem = process.memoryUsage();
|
||||
logger.info(
|
||||
`Memory: ${(mem.heapUsed / 1000 / 1000).toFixed(2)} MB used / ${(
|
||||
mem.heapTotal /
|
||||
1000 /
|
||||
1000
|
||||
).toFixed(2)} MB total`
|
||||
);
|
||||
}
|
||||
|
||||
rl.prompt();
|
||||
});
|
||||
|
||||
rl.on("SIGINT", () => {
|
||||
process.exit();
|
||||
});
|
||||
|
||||
(globalThis as unknown as any).rl = rl;
|
||||
|
|
|
@ -91,15 +91,6 @@ declare interface Crown {
|
|||
endPos: Vector2;
|
||||
}
|
||||
|
||||
declare interface ChannelInfo {
|
||||
banned?: boolean;
|
||||
count: number;
|
||||
id: string;
|
||||
_id: string;
|
||||
crown?: Crown;
|
||||
settings: Partial<IChannelSettings>;
|
||||
}
|
||||
|
||||
// Events copied from Hri7566/mppclone-client typedefs
|
||||
declare interface ServerEvents {
|
||||
a: {
|
||||
|
@ -200,6 +191,12 @@ declare interface ServerEvents {
|
|||
set: { name?: string; color?: string };
|
||||
};
|
||||
|
||||
"admin message": {
|
||||
m: "admin message";
|
||||
password: string;
|
||||
msg: ServerEvents<keyof ServerEvents>;
|
||||
};
|
||||
|
||||
// Admin
|
||||
|
||||
color: {
|
||||
|
@ -243,7 +240,7 @@ declare interface ClientEvents {
|
|||
ch: {
|
||||
m: "ch";
|
||||
p: string;
|
||||
ch: ChannelInfo;
|
||||
ch: IChannelInfo;
|
||||
ppl: Participant[];
|
||||
};
|
||||
|
||||
|
@ -342,6 +339,7 @@ declare interface ICrown {
|
|||
}
|
||||
|
||||
declare interface IChannelInfo {
|
||||
banned?: boolean;
|
||||
_id: string;
|
||||
id: string;
|
||||
count: number;
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
import { createColor, createID, createUserID } from "../util/id";
|
||||
import EventEmitter from "events";
|
||||
import {
|
||||
ChannelInfo,
|
||||
IChannelInfo,
|
||||
IChannelSettings,
|
||||
ClientEvents,
|
||||
Participant,
|
||||
|
@ -19,7 +19,8 @@ import { User } from "@prisma/client";
|
|||
import { createUser, readUser, updateUser } from "../data/user";
|
||||
import { eventGroups } from "./events";
|
||||
import { Gateway } from "./Gateway";
|
||||
import { channelList, Channel } from "../channel/Channel";
|
||||
import { Channel } from "../channel/Channel";
|
||||
import { ChannelList } from "../channel/ChannelList";
|
||||
import { ServerWebSocket } from "bun";
|
||||
import { Logger } from "../util/Logger";
|
||||
import { RateLimitConstructorList, RateLimitList } from "./ratelimit/config";
|
||||
|
@ -114,7 +115,7 @@ export class Socket extends EventEmitter {
|
|||
this.desiredChannel.set = set;
|
||||
|
||||
let channel;
|
||||
for (const ch of channelList) {
|
||||
for (const ch of ChannelList.getList()) {
|
||||
if (ch.getID() == _id) {
|
||||
channel = ch;
|
||||
}
|
||||
|
@ -138,18 +139,22 @@ export class Socket extends EventEmitter {
|
|||
);
|
||||
|
||||
channel.join(this);
|
||||
|
||||
// TODO Give the crown upon joining
|
||||
}
|
||||
}
|
||||
|
||||
public admin = new EventEmitter();
|
||||
|
||||
private bindEventListeners() {
|
||||
for (const group of eventGroups) {
|
||||
// TODO Check event group permissions
|
||||
if (group.id == "admin") continue;
|
||||
|
||||
for (const event of group.eventList) {
|
||||
this.on(event.id, event.callback);
|
||||
if (group.id == "admin") {
|
||||
for (const event of group.eventList) {
|
||||
this.admin.on(event.id, event.callback);
|
||||
}
|
||||
} else {
|
||||
// TODO Check event group permissions
|
||||
for (const event of group.eventList) {
|
||||
this.on(event.id, event.callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -198,6 +203,23 @@ export class Socket extends EventEmitter {
|
|||
}
|
||||
}
|
||||
|
||||
public async setUserFlag(key: keyof UserFlags, value: unknown) {
|
||||
if (this.user) {
|
||||
try {
|
||||
const flags = JSON.parse(this.user.flags) as Partial<UserFlags>;
|
||||
if (!flags) return false;
|
||||
(flags as unknown as Record<string, unknown>)[key] = value;
|
||||
this.user.flags = JSON.stringify(flags);
|
||||
await updateUser(this.user.id, this.user);
|
||||
return true;
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public getParticipant() {
|
||||
if (this.user) {
|
||||
const flags = this.getUserFlags();
|
||||
|
@ -226,7 +248,7 @@ export class Socket extends EventEmitter {
|
|||
// Socket was closed or should be closed, clear data
|
||||
// logger.debug("Destroying UID:", this._id);
|
||||
|
||||
const foundCh = channelList.find(
|
||||
const foundCh = ChannelList.getList().find(
|
||||
ch => ch.getID() === this.currentChannelID
|
||||
);
|
||||
|
||||
|
@ -290,10 +312,12 @@ export class Socket extends EventEmitter {
|
|||
}
|
||||
|
||||
public getCurrentChannel() {
|
||||
return channelList.find(ch => ch.getID() == this.currentChannelID);
|
||||
return ChannelList.getList().find(
|
||||
ch => ch.getID() == this.currentChannelID
|
||||
);
|
||||
}
|
||||
|
||||
public sendChannelUpdate(ch: ChannelInfo, ppl: Participant[]) {
|
||||
public sendChannelUpdate(ch: IChannelInfo, ppl: Participant[]) {
|
||||
this.sendArray([
|
||||
{
|
||||
m: "ch",
|
||||
|
@ -370,6 +394,8 @@ export class Socket extends EventEmitter {
|
|||
if (!ch) return;
|
||||
ch.playNotes(msg, this);
|
||||
}
|
||||
|
||||
public subscribeToChannelList() {}
|
||||
}
|
||||
|
||||
export const socketsBySocketID = new Map<string, Socket>();
|
||||
|
@ -380,11 +406,15 @@ export function findSocketByPartID(id: string) {
|
|||
}
|
||||
}
|
||||
|
||||
export function findSocketByUserID(_id: string) {
|
||||
export function findSocketsByUserID(_id: string) {
|
||||
const sockets = [];
|
||||
|
||||
for (const socket of socketsBySocketID.values()) {
|
||||
// logger.debug("User ID:", socket.getUserID());
|
||||
if (socket.getUserID() == _id) return socket;
|
||||
if (socket.getUserID() == _id) sockets.push(socket);
|
||||
}
|
||||
|
||||
return sockets;
|
||||
}
|
||||
|
||||
export function findSocketByIP(ip: string) {
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
import { readUser, updateUser } from "../../../../data/user";
|
||||
import { ServerEventListener } from "../../../../util/types";
|
||||
import { findSocketsByUserID } from "../../../Socket";
|
||||
|
||||
export const user_flag: ServerEventListener<"user_flag"> = {
|
||||
id: "user_flag",
|
||||
callback: async (msg, socket) => {
|
||||
if (typeof msg._id !== "string") return;
|
||||
if (typeof msg.key !== "string") return;
|
||||
if (typeof msg.value == "undefined") return;
|
||||
|
||||
socket.getCurrentChannel()?.logger.debug(msg);
|
||||
|
||||
// Find the user data we're modifying
|
||||
const user = await readUser(msg._id);
|
||||
if (!user) return;
|
||||
|
||||
// Set the flag
|
||||
const flags = JSON.parse(user.flags);
|
||||
flags[msg.key] = msg.value;
|
||||
user.flags = JSON.stringify(flags);
|
||||
|
||||
// Save the user data
|
||||
await updateUser(user.id, user);
|
||||
|
||||
// Update this data for loaded users as well
|
||||
const socks = findSocketsByUserID(user.id);
|
||||
socks.forEach(sock => {
|
||||
sock.setUserFlag(msg.key, msg.value);
|
||||
});
|
||||
|
||||
socket.getCurrentChannel()?.logger.debug("socks:", socks);
|
||||
}
|
||||
};
|
|
@ -1,9 +1,11 @@
|
|||
import { EventGroup, eventGroups } from "../../events";
|
||||
|
||||
export const EVENT_GROUP_ADMIN = new EventGroup("user");
|
||||
export const EVENT_GROUP_ADMIN = new EventGroup("admin");
|
||||
|
||||
import { color } from "./handlers/color";
|
||||
import { user_flag } from "./handlers/user_flag";
|
||||
|
||||
EVENT_GROUP_ADMIN.add(color);
|
||||
EVENT_GROUP_ADMIN.add(user_flag);
|
||||
|
||||
eventGroups.push(EVENT_GROUP_ADMIN);
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
import { ServerEventListener } from "../../../../util/types";
|
||||
|
||||
export const plus_ls: ServerEventListener<"+ls"> = {
|
||||
id: "+ls",
|
||||
callback: (msg, socket) => {
|
||||
socket.subscribeToChannelList();
|
||||
}
|
||||
};
|
|
@ -4,7 +4,11 @@ export const a: ServerEventListener<"a"> = {
|
|||
id: "a",
|
||||
callback: (msg, socket) => {
|
||||
// Chat message
|
||||
if (!socket.rateLimits?.normal.a.attempt()) return;
|
||||
const flags = socket.getUserFlags();
|
||||
if (!flags) return;
|
||||
|
||||
if (!flags["no chat rate limit"] || flags["no chat rate limit"] == 0)
|
||||
if (!socket.rateLimits?.normal.a.attempt()) return;
|
||||
const ch = socket.getCurrentChannel();
|
||||
if (!ch) return;
|
||||
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
import env from "../../../../util/env";
|
||||
import { ServerEventListener } from "../../../../util/types";
|
||||
import { config } from "../../../usersConfig";
|
||||
|
||||
export const admin_message: ServerEventListener<"admin message"> = {
|
||||
id: "admin message",
|
||||
callback: (msg, socket) => {
|
||||
if (typeof msg.password !== "string") return;
|
||||
if (msg.password !== env.ADMIN_PASS) return;
|
||||
socket.admin.emit(msg.msg.m, msg.msg, socket, true);
|
||||
}
|
||||
};
|
Loading…
Reference in New Issue