Compare commits
7 Commits
21e0492a0f
...
3994368f3c
Author | SHA1 | Date |
---|---|---|
Hri7566 | 3994368f3c | |
Hri7566 | 16b4a93e72 | |
Hri7566 | 645cffb4ec | |
Hri7566 | 7d25ea57bf | |
Hri7566 | 6c39c3405b | |
Hri7566 | 943fb1fbcc | |
Hri7566 | 6a1577fc7e |
64
package.json
64
package.json
|
@ -1,34 +1,34 @@
|
||||||
{
|
{
|
||||||
"name": "mpp-server",
|
"name": "mpp-server",
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"description": "Hri7566's MPP Server",
|
"description": "Hri7566's MPP Server",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "bun .",
|
"start": "bun .",
|
||||||
"build": "bun build ./src/index.ts --outdir=out",
|
"build": "bun build ./src/index.ts --outdir=out",
|
||||||
"dev": "bun run src/index.ts"
|
"dev": "bun run src/index.ts"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "Hri7566",
|
"author": "Hri7566",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@prisma/client": "5.2.0",
|
"@prisma/client": "5.2.0",
|
||||||
"@t3-oss/env-core": "^0.6.1",
|
"@t3-oss/env-core": "^0.6.1",
|
||||||
"bun": "^1.0.0",
|
"bun": "^1.0.0",
|
||||||
"bun-types": "^1.0.1",
|
"bun-types": "^1.0.1",
|
||||||
"date-holidays": "^3.21.5",
|
"date-holidays": "^3.21.5",
|
||||||
"dotenv": "^8.6.0",
|
"dotenv": "^8.6.0",
|
||||||
"events": "^3.3.0",
|
"events": "^3.3.0",
|
||||||
"fancy-text-converter": "^1.0.9",
|
"fancy-text-converter": "^1.0.9",
|
||||||
"keccak": "^2.1.0",
|
"keccak": "^2.1.0",
|
||||||
"mppclone-client": "^1.1.3",
|
"mppclone-client": "^1.1.3",
|
||||||
"unique-names-generator": "^4.7.1",
|
"unique-names-generator": "^4.7.1",
|
||||||
"yaml": "^2.3.2",
|
"yaml": "^2.3.2",
|
||||||
"zod": "^3.22.2"
|
"zod": "^3.22.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^20.5.9",
|
"@types/node": "^20.5.9",
|
||||||
"prisma": "^5.2.0",
|
"prisma": "^5.2.0",
|
||||||
"typescript": "^5.2.2"
|
"typescript": "^5.2.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import {
|
||||||
import { Socket } from "../ws/Socket";
|
import { Socket } from "../ws/Socket";
|
||||||
import { validateChannelSettings } from "./settings";
|
import { validateChannelSettings } from "./settings";
|
||||||
import { socketsBySocketID } from "../ws/server";
|
import { socketsBySocketID } from "../ws/server";
|
||||||
|
import Crown from "./Crown";
|
||||||
|
|
||||||
interface ChannelConfig {
|
interface ChannelConfig {
|
||||||
forceLoad: string[];
|
forceLoad: string[];
|
||||||
|
@ -54,8 +55,14 @@ export class Channel extends EventEmitter {
|
||||||
public chatHistory = new Array<ClientEvents["a"]>();
|
public chatHistory = new Array<ClientEvents["a"]>();
|
||||||
|
|
||||||
// TODO Add the crown
|
// TODO Add the crown
|
||||||
|
public crown?: Crown;
|
||||||
|
|
||||||
constructor(private _id: string, set?: Partial<ChannelSettings>) {
|
constructor(
|
||||||
|
private _id: string,
|
||||||
|
set?: Partial<ChannelSettings>,
|
||||||
|
creator?: Socket,
|
||||||
|
owner_id?: string
|
||||||
|
) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.logger = new Logger("Channel - " + _id);
|
this.logger = new Logger("Channel - " + _id);
|
||||||
|
@ -63,13 +70,24 @@ export class Channel extends EventEmitter {
|
||||||
// Validate settings in set
|
// Validate settings in set
|
||||||
// Set the verified settings
|
// Set the verified settings
|
||||||
|
|
||||||
if (set && !this.isLobby()) {
|
if (!this.isLobby()) {
|
||||||
const validatedSet = validateChannelSettings(set);
|
if (set) {
|
||||||
|
const validatedSet = validateChannelSettings(set);
|
||||||
|
|
||||||
for (const key in Object.keys(validatedSet)) {
|
for (const key in Object.keys(validatedSet)) {
|
||||||
if (!(validatedSet as any)[key]) continue;
|
if (!(validatedSet as any)[key]) continue;
|
||||||
|
|
||||||
(this.settings as any)[key] = (set as any)[key];
|
(this.settings as any)[key] = (set as any)[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.crown = new Crown();
|
||||||
|
|
||||||
|
if (creator) {
|
||||||
|
if (this.crown.canBeSetBy(creator)) {
|
||||||
|
const part = creator.getParticipant();
|
||||||
|
if (part) this.giveCrown(part);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,10 +101,79 @@ export class Channel extends EventEmitter {
|
||||||
// TODO channel closing
|
// 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() {
|
public getID() {
|
||||||
return this._id;
|
return this._id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether this channel is a lobby (uses regex from config)
|
||||||
|
* @returns Boolean
|
||||||
|
*/
|
||||||
public isLobby() {
|
public isLobby() {
|
||||||
for (const reg of config.lobbyRegexes) {
|
for (const reg of config.lobbyRegexes) {
|
||||||
let exp = new RegExp(reg, "g");
|
let exp = new RegExp(reg, "g");
|
||||||
|
@ -99,6 +186,12 @@ export class Channel extends EventEmitter {
|
||||||
return false;
|
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(
|
public changeSettings(
|
||||||
set: Partial<ChannelSettings>,
|
set: Partial<ChannelSettings>,
|
||||||
admin: boolean = false
|
admin: boolean = false
|
||||||
|
@ -125,6 +218,20 @@ 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) {
|
public join(socket: Socket) {
|
||||||
if (this.isDestroyed()) return;
|
if (this.isDestroyed()) return;
|
||||||
const part = socket.getParticipant() as Participant;
|
const part = socket.getParticipant() as Participant;
|
||||||
|
@ -200,6 +307,10 @@ export class Channel extends EventEmitter {
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make a socket leave this channel
|
||||||
|
* @param socket Socket that is leaving
|
||||||
|
*/
|
||||||
public leave(socket: Socket) {
|
public leave(socket: Socket) {
|
||||||
// this.logger.debug("Leave called");
|
// this.logger.debug("Leave called");
|
||||||
const part = socket.getParticipant() as Participant;
|
const part = socket.getParticipant() as Participant;
|
||||||
|
@ -234,6 +345,10 @@ export class Channel extends EventEmitter {
|
||||||
this.emit("update");
|
this.emit("update");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether this channel has too many users
|
||||||
|
* @returns Boolean
|
||||||
|
*/
|
||||||
public isFull() {
|
public isFull() {
|
||||||
// TODO Use limit setting
|
// TODO Use limit setting
|
||||||
|
|
||||||
|
@ -244,29 +359,52 @@ export class Channel extends EventEmitter {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get this channel's information
|
||||||
|
* @returns Channel info object (includes ID, number of users, settings, and the crown)
|
||||||
|
*/
|
||||||
public getInfo() {
|
public getInfo() {
|
||||||
return {
|
return {
|
||||||
_id: this.getID(),
|
_id: this.getID(),
|
||||||
id: this.getID(),
|
id: this.getID(),
|
||||||
count: this.ppl.length,
|
count: this.ppl.length,
|
||||||
settings: this.settings
|
settings: this.settings,
|
||||||
|
crown: JSON.parse(JSON.stringify(this.crown))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the people in this channel
|
||||||
|
* @returns List of people
|
||||||
|
*/
|
||||||
public getParticipantList() {
|
public getParticipantList() {
|
||||||
return this.ppl;
|
return this.ppl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether a user is in this channel (by user ID)
|
||||||
|
* @param _id User ID
|
||||||
|
* @returns Boolean
|
||||||
|
*/
|
||||||
public hasUser(_id: string) {
|
public hasUser(_id: string) {
|
||||||
const foundPart = this.ppl.find(p => p._id == _id);
|
const foundPart = this.ppl.find(p => p._id == _id);
|
||||||
return !!foundPart;
|
return !!foundPart;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether a user is in this channel (by participant ID)
|
||||||
|
* @param id Participant ID
|
||||||
|
* @returns Boolean
|
||||||
|
*/
|
||||||
public hasParticipant(id: string) {
|
public hasParticipant(id: string) {
|
||||||
const foundPart = this.ppl.find(p => p.id == id);
|
const foundPart = this.ppl.find(p => p.id == id);
|
||||||
return !!foundPart;
|
return !!foundPart;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send messages to everyone in this channel
|
||||||
|
* @param arr List of events to send to clients
|
||||||
|
*/
|
||||||
public sendArray<EventID extends keyof ClientEvents>(
|
public sendArray<EventID extends keyof ClientEvents>(
|
||||||
arr: ClientEvents[EventID][]
|
arr: ClientEvents[EventID][]
|
||||||
) {
|
) {
|
||||||
|
@ -284,45 +422,12 @@ export class Channel extends EventEmitter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private alreadyBound = false;
|
/**
|
||||||
|
* Play notes (usually from a socket)
|
||||||
private bindEventListeners() {
|
* @param msg Note message
|
||||||
if (this.alreadyBound) return;
|
* @param socket Socket that is sending notes
|
||||||
this.alreadyBound = true;
|
* @returns undefined
|
||||||
|
*/
|
||||||
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) {
|
public playNotes(msg: ServerEvents["n"], socket: Socket) {
|
||||||
if (this.isDestroyed()) return;
|
if (this.isDestroyed()) return;
|
||||||
const part = socket.getParticipant();
|
const part = socket.getParticipant();
|
||||||
|
@ -352,6 +457,10 @@ export class Channel extends EventEmitter {
|
||||||
|
|
||||||
private destroyed = false;
|
private destroyed = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set this channel to the destroyed state
|
||||||
|
* @returns undefined
|
||||||
|
*/
|
||||||
public destroy() {
|
public destroy() {
|
||||||
if (this.destroyed) return;
|
if (this.destroyed) return;
|
||||||
this.destroyed = true;
|
this.destroyed = true;
|
||||||
|
@ -366,12 +475,61 @@ export class Channel extends EventEmitter {
|
||||||
channelList.splice(channelList.indexOf(this), 1);
|
channelList.splice(channelList.indexOf(this), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether the channel is in a destroyed state
|
||||||
|
* @returns Boolean
|
||||||
|
*/
|
||||||
public isDestroyed() {
|
public isDestroyed() {
|
||||||
return this.destroyed == true;
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Forceloader
|
export default Channel;
|
||||||
|
|
||||||
|
// Channel forceloader (cringe)
|
||||||
let hasFullChannel = false;
|
let hasFullChannel = false;
|
||||||
|
|
||||||
for (const id of config.forceLoad) {
|
for (const id of config.forceLoad) {
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
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;
|
|
@ -0,0 +1,7 @@
|
||||||
|
import Channel from "./Channel";
|
||||||
|
import Crown from "./Crown";
|
||||||
|
import validateChannelSettings, {
|
||||||
|
validate as validateChannelSetting
|
||||||
|
} from "./settings";
|
||||||
|
|
||||||
|
export { Channel, Crown, validateChannelSettings, validateChannelSetting };
|
|
@ -52,7 +52,9 @@ export function validateChannelSettings(set: Partial<ChannelSettings>) {
|
||||||
return record;
|
return record;
|
||||||
}
|
}
|
||||||
|
|
||||||
function validate(value: any, validator: Validator) {
|
export default validateChannelSettings;
|
||||||
|
|
||||||
|
export function validate(value: any, validator: Validator) {
|
||||||
// What type of validator?
|
// What type of validator?
|
||||||
if (typeof validator == "function") {
|
if (typeof validator == "function") {
|
||||||
// We are copying Zod's functionality
|
// We are copying Zod's functionality
|
||||||
|
|
|
@ -1,4 +1,12 @@
|
||||||
|
/**
|
||||||
|
* MPP Server 2
|
||||||
|
* for mpp.dev
|
||||||
|
* by Hri7566
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Preload environment variables
|
||||||
import env from "./util/env";
|
import env from "./util/env";
|
||||||
|
|
||||||
// import { app } from "./ws/server";
|
// import { app } from "./ws/server";
|
||||||
import "./ws/server";
|
import "./ws/server";
|
||||||
import { Logger } from "./util/Logger";
|
import { Logger } from "./util/Logger";
|
||||||
|
|
|
@ -1,13 +1,73 @@
|
||||||
import YAML from "yaml";
|
import { existsSync, readFileSync, writeFileSync } from "fs";
|
||||||
import fs from "fs";
|
import { parse, stringify } from "yaml";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
export function loadConfig<T>(filepath: string, def: T) {
|
/**
|
||||||
try {
|
* Load a YAML config file and set default values if config path is nonexistent
|
||||||
const data = fs.readFileSync(filepath).toString();
|
*
|
||||||
const parsed = YAML.parse(data);
|
* Usage:
|
||||||
return parsed as T;
|
* ```ts
|
||||||
} catch (err) {
|
* const config = loadConfig("config/services.yml", {
|
||||||
console.error("Unable to load config:", err);
|
* enableMPP: false
|
||||||
return def;
|
* });
|
||||||
|
* ```
|
||||||
|
* @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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -9,6 +9,8 @@ declare type UserFlags = Partial<{
|
||||||
chat_curse_2: number;
|
chat_curse_2: number;
|
||||||
override_id: string;
|
override_id: string;
|
||||||
volume: number;
|
volume: number;
|
||||||
|
cant_chat: number;
|
||||||
|
cansetcrowns: number;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
declare interface Tag {
|
declare interface Tag {
|
||||||
|
|
|
@ -1,3 +1,9 @@
|
||||||
|
/**
|
||||||
|
* Socket connection module
|
||||||
|
*
|
||||||
|
* Represents user connections
|
||||||
|
*/
|
||||||
|
|
||||||
import { createColor, createID, createUserID } from "../util/id";
|
import { createColor, createID, createUserID } from "../util/id";
|
||||||
import EventEmitter from "events";
|
import EventEmitter from "events";
|
||||||
import {
|
import {
|
||||||
|
@ -303,6 +309,7 @@ export class Socket extends EventEmitter {
|
||||||
public async userset(name?: string, color?: string) {
|
public async userset(name?: string, color?: string) {
|
||||||
let isColor = false;
|
let isColor = false;
|
||||||
|
|
||||||
|
// Color changing
|
||||||
if (color && config.enableColorChanging) {
|
if (color && config.enableColorChanging) {
|
||||||
isColor =
|
isColor =
|
||||||
typeof color === "string" && !!color.match(/^#[0-9a-f]{6}$/i);
|
typeof color === "string" && !!color.match(/^#[0-9a-f]{6}$/i);
|
||||||
|
|
Loading…
Reference in New Issue