Compare commits

..

No commits in common. "3994368f3c5488baf1fd930bb569f7e2514eee23" and "21e0492a0fdd2e91d3acc6c5eb7aa49f722e6777" have entirely different histories.

10 changed files with 90 additions and 375 deletions

BIN
bun.lockb

Binary file not shown.

View File

@ -11,7 +11,6 @@ import {
import { Socket } from "../ws/Socket";
import { validateChannelSettings } from "./settings";
import { socketsBySocketID } from "../ws/server";
import Crown from "./Crown";
interface ChannelConfig {
forceLoad: string[];
@ -55,14 +54,8 @@ export class Channel extends EventEmitter {
public chatHistory = new Array<ClientEvents["a"]>();
// TODO Add the crown
public crown?: Crown;
constructor(
private _id: string,
set?: Partial<ChannelSettings>,
creator?: Socket,
owner_id?: string
) {
constructor(private _id: string, set?: Partial<ChannelSettings>) {
super();
this.logger = new Logger("Channel - " + _id);
@ -70,8 +63,7 @@ export class Channel extends EventEmitter {
// Validate settings in set
// Set the verified settings
if (!this.isLobby()) {
if (set) {
if (set && !this.isLobby()) {
const validatedSet = validateChannelSettings(set);
for (const key in Object.keys(validatedSet)) {
@ -81,16 +73,6 @@ 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.isLobby()) {
this.settings = config.lobbySettings;
}
@ -101,79 +83,10 @@ export class Channel extends EventEmitter {
// TODO channel closing
}
private alreadyBound = false;
private bindEventListeners() {
if (this.alreadyBound) return;
this.alreadyBound = true;
this.on("update", () => {
// Send updated info
for (const socket of socketsBySocketID.values()) {
for (const p of this.ppl) {
if (socket.getParticipantID() == p.id) {
socket.sendChannelUpdate(
this.getInfo(),
this.getParticipantList()
);
}
}
}
if (this.ppl.length == 0) {
this.destroy();
}
});
this.on("message", (msg: ServerEvents["a"], socket: Socket) => {
if (!msg.message) return;
const userFlags = socket.getUserFlags();
this.logger.debug(userFlags);
if (userFlags) {
if (userFlags.cant_chat) return;
}
// Sanitize
msg.message = msg.message
.replace(/\p{C}+/gu, "")
.replace(/(\p{Mc}{5})\p{Mc}+/gu, "$1")
.trim();
let outgoing: ClientEvents["a"] = {
m: "a",
a: msg.message,
t: Date.now(),
p: socket.getParticipant() as Participant
};
this.sendArray([outgoing]);
this.chatHistory.push(outgoing);
try {
if (msg.message.startsWith("/")) {
this.emit("command", msg, socket);
}
} catch (err) {
this.logger.debug(err);
}
});
}
/**
* Get this channel's ID (channel name)
* @returns Channel ID
*/
public getID() {
return this._id;
}
/**
* Determine whether this channel is a lobby (uses regex from config)
* @returns Boolean
*/
public isLobby() {
for (const reg of config.lobbyRegexes) {
let exp = new RegExp(reg, "g");
@ -186,12 +99,6 @@ export class Channel extends EventEmitter {
return false;
}
/**
* Change this channel's settings
* @param set Channel settings
* @param admin Whether a user is changing the settings (set to true to force the changes)
* @returns undefined
*/
public changeSettings(
set: Partial<ChannelSettings>,
admin: boolean = false
@ -218,20 +125,6 @@ export class Channel extends EventEmitter {
}
}
/**
* Get a channel setting's value
* @param setting Channel setting to get
* @returns Value of setting
*/
public getSetting(setting: keyof ChannelSettings) {
return this.settings[setting];
}
/**
* Make a socket join this channel
* @param socket Socket that is joining
* @returns undefined
*/
public join(socket: Socket) {
if (this.isDestroyed()) return;
const part = socket.getParticipant() as Participant;
@ -307,10 +200,6 @@ export class Channel extends EventEmitter {
]);
}
/**
* Make a socket leave this channel
* @param socket Socket that is leaving
*/
public leave(socket: Socket) {
// this.logger.debug("Leave called");
const part = socket.getParticipant() as Participant;
@ -345,10 +234,6 @@ export class Channel extends EventEmitter {
this.emit("update");
}
/**
* Determine whether this channel has too many users
* @returns Boolean
*/
public isFull() {
// TODO Use limit setting
@ -359,52 +244,29 @@ export class Channel extends EventEmitter {
return false;
}
/**
* Get this channel's information
* @returns Channel info object (includes ID, number of users, settings, and the crown)
*/
public getInfo() {
return {
_id: this.getID(),
id: this.getID(),
count: this.ppl.length,
settings: this.settings,
crown: JSON.parse(JSON.stringify(this.crown))
settings: this.settings
};
}
/**
* Get the people in this channel
* @returns List of people
*/
public getParticipantList() {
return this.ppl;
}
/**
* Determine whether a user is in this channel (by user ID)
* @param _id User ID
* @returns Boolean
*/
public hasUser(_id: string) {
const foundPart = this.ppl.find(p => p._id == _id);
return !!foundPart;
}
/**
* Determine whether a user is in this channel (by participant ID)
* @param id Participant ID
* @returns Boolean
*/
public hasParticipant(id: string) {
const foundPart = this.ppl.find(p => p.id == id);
return !!foundPart;
}
/**
* Send messages to everyone in this channel
* @param arr List of events to send to clients
*/
public sendArray<EventID extends keyof ClientEvents>(
arr: ClientEvents[EventID][]
) {
@ -422,12 +284,45 @@ export class Channel extends EventEmitter {
}
}
/**
* Play notes (usually from a socket)
* @param msg Note message
* @param socket Socket that is sending notes
* @returns undefined
*/
private alreadyBound = false;
private bindEventListeners() {
if (this.alreadyBound) return;
this.alreadyBound = true;
this.on("update", () => {
// Send updated info
for (const socket of socketsBySocketID.values()) {
for (const p of this.ppl) {
if (socket.getParticipantID() == p.id) {
socket.sendChannelUpdate(
this.getInfo(),
this.getParticipantList()
);
}
}
}
if (this.ppl.length == 0) {
this.destroy();
}
});
this.on("message", (msg: ServerEvents["a"], socket: Socket) => {
if (!msg.message) return;
let outgoing: ClientEvents["a"] = {
m: "a",
a: msg.message,
t: Date.now(),
p: socket.getParticipant() as Participant
};
this.sendArray([outgoing]);
this.chatHistory.push(outgoing);
});
}
public playNotes(msg: ServerEvents["n"], socket: Socket) {
if (this.isDestroyed()) return;
const part = socket.getParticipant();
@ -457,10 +352,6 @@ export class Channel extends EventEmitter {
private destroyed = false;
/**
* Set this channel to the destroyed state
* @returns undefined
*/
public destroy() {
if (this.destroyed) return;
this.destroyed = true;
@ -475,61 +366,12 @@ export class Channel extends EventEmitter {
channelList.splice(channelList.indexOf(this), 1);
}
/**
* Determine whether the channel is in a destroyed state
* @returns Boolean
*/
public isDestroyed() {
return this.destroyed == true;
}
/**
* Change ownership (don't forget to use crown.canBeSetBy if you're letting a user call this)
* @param part Participant to give crown to (or undefined to drop crown)
*/
public chown(part?: Participant) {
if (this.crown) {
if (part) {
this.giveCrown(part);
} else {
this.dropCrown();
}
}
}
/**
* Give the crown to a user (no matter what)
* @param part Participant to give crown to
* @param force Whether or not to force-create a crown (useful for lobbies)
*/
public giveCrown(part: Participant, force?: boolean) {
if (force) {
if (!this.crown) this.crown = new Crown();
}
if (this.crown) {
this.crown.userId = part._id;
this.crown.participantId = part.id;
this.crown.time = Date.now();
this.emit("update");
}
}
/**
* Drop the crown (remove from user)
*/
public dropCrown() {
if (this.crown) {
delete this.crown.participantId;
this.crown.time = Date.now();
this.emit("update");
}
}
}
export default Channel;
// Channel forceloader (cringe)
// Forceloader
let hasFullChannel = false;
for (const id of config.forceLoad) {

View File

@ -1,41 +0,0 @@
import { Participant } from "../util/types";
import { Socket } from "../ws/Socket";
export class Crown {
public userId: string | undefined;
public participantId: string | undefined;
public time: number = Date.now();
public canBeSetBy(socket: Socket) {
// can claim, drop, or give if...
const flags = socket.getUserFlags();
if (!flags) return false;
if (flags.cansetcrowns) return true;
const channel = socket.getCurrentChannel();
if (!channel) return false;
const part = socket.getParticipant();
if (!part) return false;
if (!channel.getSetting("lobby")) {
// if there is no user (never been owned)
if (!this.userId) return true;
// if you're the user (you dropped it or left the room, nobody has claimed it)
if (this.userId === part._id) return true;
// if there is no participant and 15 seconds have passed
if (!this.participantId && this.time + 15000 < Date.now())
return true;
// you're the specially designated channel owner
if (channel.getSetting("owner_id") === part._id) return true;
}
return false;
}
}
export default Crown;

View File

@ -1,7 +0,0 @@
import Channel from "./Channel";
import Crown from "./Crown";
import validateChannelSettings, {
validate as validateChannelSetting
} from "./settings";
export { Channel, Crown, validateChannelSettings, validateChannelSetting };

View File

@ -52,9 +52,7 @@ export function validateChannelSettings(set: Partial<ChannelSettings>) {
return record;
}
export default validateChannelSettings;
export function validate(value: any, validator: Validator) {
function validate(value: any, validator: Validator) {
// What type of validator?
if (typeof validator == "function") {
// We are copying Zod's functionality

View File

@ -1,12 +1,4 @@
/**
* MPP Server 2
* for mpp.dev
* by Hri7566
*/
// Preload environment variables
import env from "./util/env";
// import { app } from "./ws/server";
import "./ws/server";
import { Logger } from "./util/Logger";

View File

@ -1,73 +1,13 @@
import { existsSync, readFileSync, writeFileSync } from "fs";
import { parse, stringify } from "yaml";
import { z } from "zod";
import YAML from "yaml";
import fs from "fs";
/**
* Load a YAML config file and set default values if config path is nonexistent
*
* Usage:
* ```ts
* const config = loadConfig("config/services.yml", {
* enableMPP: false
* });
* ```
* @param configPath Path to load config from
* @param defaultConfig Config to use if none is present (will save to path if used)
* @returns Parsed YAML config
*/
export function loadConfig<T>(configPath: string, defaultConfig: T): T {
// Config exists?
if (existsSync(configPath)) {
// Load config
const data = readFileSync(configPath);
const config = parse(data.toString());
const defRecord = defaultConfig as Record<string, any>;
let changed = false;
function mix(
obj: Record<string, unknown>,
obj2: Record<string, unknown>
) {
for (const key of Object.keys(obj2)) {
if (typeof obj[key] == "undefined") {
obj[key] = obj2[key];
changed = true;
}
if (typeof obj[key] == "object" && !Array.isArray(obj[key])) {
mix(
obj[key] as Record<string, unknown>,
obj2[key] as Record<string, unknown>
);
export function loadConfig<T>(filepath: string, def: T) {
try {
const data = fs.readFileSync(filepath).toString();
const parsed = YAML.parse(data);
return parsed as T;
} catch (err) {
console.error("Unable to load config:", err);
return def;
}
}
}
// Apply any missing default values
mix(config, defRecord);
// Save config if modified
if (changed) writeConfig(configPath, config);
return config as T;
} else {
// Write default config to disk and use that
writeConfig(configPath, defaultConfig);
return defaultConfig as T;
}
}
/**
* Write a YAML config to disk
* @param configPath
* @param config
*/
export function writeConfig<T>(configPath: string, config: T) {
writeFileSync(
configPath,
stringify(config, {
indent: 4
})
);
}

2
src/util/types.d.ts vendored
View File

@ -9,8 +9,6 @@ declare type UserFlags = Partial<{
chat_curse_2: number;
override_id: string;
volume: number;
cant_chat: number;
cansetcrowns: number;
}>;
declare interface Tag {

View File

@ -1,9 +1,3 @@
/**
* Socket connection module
*
* Represents user connections
*/
import { createColor, createID, createUserID } from "../util/id";
import EventEmitter from "events";
import {
@ -309,7 +303,6 @@ export class Socket extends EventEmitter {
public async userset(name?: string, color?: string) {
let isColor = false;
// Color changing
if (color && config.enableColorChanging) {
isColor =
typeof color === "string" && !!color.match(/^#[0-9a-f]{6}$/i);