Add permissions
This commit is contained in:
parent
8eef0cd925
commit
85643184ea
|
@ -7,8 +7,8 @@ node_modules
|
||||||
# SQLite databases
|
# SQLite databases
|
||||||
prisma/*.sqlite
|
prisma/*.sqlite
|
||||||
|
|
||||||
# TS build
|
# Build script output
|
||||||
/out
|
/dist
|
||||||
|
|
||||||
# JWT token keypair
|
# JWT token keypair
|
||||||
mppkey
|
mppkey
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
admin:
|
||||||
|
- clearChat
|
||||||
|
- vanish
|
||||||
|
- chsetAnywhere
|
||||||
|
- chownAnywhere
|
||||||
|
- usersetOthers
|
||||||
|
- siteBan
|
||||||
|
- siteBanAnyReason
|
||||||
|
- siteBanAnyDuration
|
|
@ -1,12 +0,0 @@
|
||||||
global:
|
|
||||||
scrape_interval: 15s
|
|
||||||
|
|
||||||
scrape_configs:
|
|
||||||
- job_name: prometheus
|
|
||||||
scrape_interval: "5s"
|
|
||||||
static_configs:
|
|
||||||
- targets: ["localhost:9090"]
|
|
||||||
|
|
||||||
- job_name: mpp
|
|
||||||
static_configs:
|
|
||||||
- targets: ["192.168.1.24:9100"]
|
|
|
@ -32,3 +32,14 @@ model Channel {
|
||||||
forceload Boolean @default(false) // Whether the channel is forceloaded
|
forceload Boolean @default(false) // Whether the channel is forceloaded
|
||||||
flags String @default("{}") // JSON flags object
|
flags String @default("{}") // JSON flags object
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model Role {
|
||||||
|
userId String @unique
|
||||||
|
roleId String
|
||||||
|
}
|
||||||
|
|
||||||
|
model RolePermission {
|
||||||
|
id Int @id @unique @default(autoincrement())
|
||||||
|
roleId String
|
||||||
|
permission String
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
console.log("Building...");
|
||||||
|
|
||||||
|
await Bun.build({
|
||||||
|
entrypoints: ["./src/index.ts"],
|
||||||
|
outdir: "./dist",
|
||||||
|
target: "bun",
|
||||||
|
format: "esm",
|
||||||
|
minify: false,
|
||||||
|
splitting: true
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("Done");
|
|
@ -1,6 +1,6 @@
|
||||||
import EventEmitter from "events";
|
import EventEmitter from "events";
|
||||||
import { Logger } from "../util/Logger";
|
import { Logger } from "../util/Logger";
|
||||||
import {
|
import type {
|
||||||
ChannelSettingValue,
|
ChannelSettingValue,
|
||||||
IChannelSettings,
|
IChannelSettings,
|
||||||
ClientEvents,
|
ClientEvents,
|
||||||
|
@ -10,19 +10,28 @@ import {
|
||||||
Notification,
|
Notification,
|
||||||
UserFlags,
|
UserFlags,
|
||||||
Tag,
|
Tag,
|
||||||
|
ChannelFlags as TChannelFlags
|
||||||
} from "../util/types";
|
} from "../util/types";
|
||||||
import type { Socket } from "../ws/Socket";
|
import type { Socket } from "../ws/Socket";
|
||||||
import { validateChannelSettings } from "./settings";
|
import { validateChannelSettings } from "./settings";
|
||||||
import { findSocketByPartID, socketsBySocketID } from "../ws/Socket";
|
import { findSocketByPartID, socketsByUUID } from "../ws/Socket";
|
||||||
import Crown from "./Crown";
|
import Crown from "./Crown";
|
||||||
import { ChannelList } from "./ChannelList";
|
import { ChannelList } from "./ChannelList";
|
||||||
import { config } from "./config";
|
import { config } from "./config";
|
||||||
import { config as usersConfig } from "../ws/usersConfig";
|
import { config as usersConfig } from "../ws/usersConfig";
|
||||||
import { saveChatHistory, getChatHistory, deleteChatHistory } from "../data/history";
|
import {
|
||||||
import { mixin, darken } from "../util/helpers";
|
saveChatHistory,
|
||||||
import { User } from "@prisma/client";
|
getChatHistory,
|
||||||
|
deleteChatHistory
|
||||||
|
} from "../data/history";
|
||||||
|
import { mixin, darken, spoop_text } from "../util/helpers";
|
||||||
|
import type { User } from "@prisma/client";
|
||||||
import { heapStats } from "bun:jsc";
|
import { heapStats } from "bun:jsc";
|
||||||
import { deleteSavedChannel, getSavedChannel, saveChannel } from "../data/channel";
|
import {
|
||||||
|
deleteSavedChannel,
|
||||||
|
getSavedChannel,
|
||||||
|
saveChannel
|
||||||
|
} from "../data/channel";
|
||||||
import { forceloadChannel } from "./forceLoad";
|
import { forceloadChannel } from "./forceLoad";
|
||||||
|
|
||||||
interface CachedKickban {
|
interface CachedKickban {
|
||||||
|
@ -31,9 +40,15 @@ interface CachedKickban {
|
||||||
endTime: number;
|
endTime: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface CachedCursor {
|
||||||
|
x: string | number;
|
||||||
|
y: string | number;
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
interface ExtraPartData {
|
interface ExtraPartData {
|
||||||
uuids: string[];
|
uuids: string[];
|
||||||
flags: Partial<UserFlags>;
|
flags: UserFlags;
|
||||||
}
|
}
|
||||||
|
|
||||||
type ExtraPart = Participant & ExtraPartData;
|
type ExtraPart = Participant & ExtraPartData;
|
||||||
|
@ -48,10 +63,12 @@ export class Channel extends EventEmitter {
|
||||||
try {
|
try {
|
||||||
this.chatHistory = await getChatHistory(this.getID());
|
this.chatHistory = await getChatHistory(this.getID());
|
||||||
|
|
||||||
this.sendArray([{
|
this.sendArray([
|
||||||
|
{
|
||||||
m: "c",
|
m: "c",
|
||||||
c: this.chatHistory
|
c: this.chatHistory
|
||||||
}]);
|
}
|
||||||
|
]);
|
||||||
} catch (err) {}
|
} catch (err) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,22 +129,22 @@ export class Channel extends EventEmitter {
|
||||||
|
|
||||||
public logger: Logger;
|
public logger: Logger;
|
||||||
public bans = new Array<CachedKickban>();
|
public bans = new Array<CachedKickban>();
|
||||||
public cursorCache = new Array<{ x: string | number; y: string | number; id: string }>();
|
public cursorCache = new Array<CachedCursor>();
|
||||||
|
|
||||||
public crown?: Crown;
|
public crown?: Crown;
|
||||||
|
|
||||||
private flags: Record<string, any> = {};
|
private flags: TChannelFlags = {};
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private _id: string,
|
private _id: string,
|
||||||
set?: Partial<IChannelSettings>,
|
set?: Partial<IChannelSettings>,
|
||||||
creator?: Socket,
|
creator?: Socket,
|
||||||
owner_id?: string,
|
owner_id?: string,
|
||||||
public stays: boolean = false
|
public stays = false
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.logger = new Logger("Channel - " + _id, "logs/channel");
|
this.logger = new Logger(`Channel - ${_id}`, "logs/channel");
|
||||||
this.settings = {};
|
this.settings = {};
|
||||||
|
|
||||||
// Copy default settings
|
// Copy default settings
|
||||||
|
@ -138,8 +155,8 @@ export class Channel extends EventEmitter {
|
||||||
// Copied from changeSettings below
|
// Copied from changeSettings below
|
||||||
// TODO do these cases need to be here? can this be determined another way?
|
// TODO do these cases need to be here? can this be determined another way?
|
||||||
if (
|
if (
|
||||||
typeof set.color == "string" &&
|
typeof set.color === "string" &&
|
||||||
(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");
|
||||||
|
@ -152,8 +169,8 @@ 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[key] === false) continue;
|
||||||
(this.settings as any)[key] = (set as any)[key];
|
this.settings[key] = set[key];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -172,11 +189,13 @@ export class Channel extends EventEmitter {
|
||||||
this.bindEventListeners();
|
this.bindEventListeners();
|
||||||
|
|
||||||
ChannelList.add(this);
|
ChannelList.add(this);
|
||||||
this.settings.owner_id = this.flags["owner_id"];
|
if (this.flags.owner_id) {
|
||||||
|
this.settings.owner_id = this.flags.owner_id;
|
||||||
|
}
|
||||||
|
|
||||||
this.logger.info("Created");
|
this.logger.info("Created");
|
||||||
|
|
||||||
if (this.getID() == "test/mem") {
|
if (this.getID() === "test/mem") {
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
this.printMemoryInChat();
|
this.printMemoryInChat();
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
@ -194,7 +213,7 @@ export class Channel extends EventEmitter {
|
||||||
|
|
||||||
this.on("update", (self, uuid) => {
|
this.on("update", (self, uuid) => {
|
||||||
// Send updated info
|
// Send updated info
|
||||||
for (const socket of socketsBySocketID.values()) {
|
for (const socket of socketsByUUID.values()) {
|
||||||
for (const p of this.ppl) {
|
for (const p of this.ppl) {
|
||||||
const socketUUID = socket.getUUID();
|
const socketUUID = socket.getUUID();
|
||||||
|
|
||||||
|
@ -210,7 +229,7 @@ export class Channel extends EventEmitter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.ppl.length == 0 && !this.stays) {
|
if (this.ppl.length === 0 && !this.stays) {
|
||||||
if (config.channelDestroyTimeout) {
|
if (config.channelDestroyTimeout) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.destroy();
|
this.destroy();
|
||||||
|
@ -221,10 +240,7 @@ export class Channel extends EventEmitter {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const BANNED_WORDS = [
|
const BANNED_WORDS = ["AMIGHTYWIND", "CHECKLYHQ"];
|
||||||
"AMIGHTYWIND",
|
|
||||||
"CHECKLYHQ"
|
|
||||||
];
|
|
||||||
|
|
||||||
this.on("a", async (msg: ServerEvents["a"], socket: Socket) => {
|
this.on("a", async (msg: ServerEvents["a"], socket: Socket) => {
|
||||||
try {
|
try {
|
||||||
|
@ -233,7 +249,13 @@ export class Channel extends EventEmitter {
|
||||||
const userFlags = socket.getUserFlags();
|
const userFlags = socket.getUserFlags();
|
||||||
|
|
||||||
if (userFlags) {
|
if (userFlags) {
|
||||||
if (userFlags.cant_chat) return;
|
if (userFlags.cant_chat == 1) return;
|
||||||
|
if (userFlags.chat_curse_1 == 1)
|
||||||
|
msg.message = msg.message
|
||||||
|
.replace(/[aeiu]/g, "o")
|
||||||
|
.replace(/[AEIU]/g, "O");
|
||||||
|
if (userFlags.chat_curse_2 == 1)
|
||||||
|
msg.message = spoop_text(msg.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.settings.chat) return;
|
if (!this.settings.chat) return;
|
||||||
|
@ -241,7 +263,13 @@ export class Channel extends EventEmitter {
|
||||||
if (msg.message.length > 512) return;
|
if (msg.message.length > 512) return;
|
||||||
|
|
||||||
for (const word of BANNED_WORDS) {
|
for (const word of BANNED_WORDS) {
|
||||||
if (msg.message.toLowerCase().split(" ").join("").includes(word.toLowerCase())) {
|
if (
|
||||||
|
msg.message
|
||||||
|
.toLowerCase()
|
||||||
|
.split(" ")
|
||||||
|
.join("")
|
||||||
|
.includes(word.toLowerCase())
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -256,7 +284,7 @@ export class Channel extends EventEmitter {
|
||||||
|
|
||||||
const part = socket.getParticipant() as Participant;
|
const part = socket.getParticipant() as Participant;
|
||||||
|
|
||||||
let outgoing: ClientEvents["a"] = {
|
const outgoing: ClientEvents["a"] = {
|
||||||
m: "a",
|
m: "a",
|
||||||
a: msg.message,
|
a: msg.message,
|
||||||
t: Date.now(),
|
t: Date.now(),
|
||||||
|
@ -274,7 +302,9 @@ export class Channel extends EventEmitter {
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.logger.error(err);
|
this.logger.error(err);
|
||||||
this.logger.warn("Error whilst processing a chat message from user " + socket.getUserID());
|
this.logger.warn(
|
||||||
|
`Error whilst processing a chat message from user ${socket.getUserID()}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -283,8 +313,8 @@ export class Channel extends EventEmitter {
|
||||||
const cmd = args[0].substring(1);
|
const cmd = args[0].substring(1);
|
||||||
const ownsChannel = this.hasUser(socket.getUserID());
|
const ownsChannel = this.hasUser(socket.getUserID());
|
||||||
|
|
||||||
if (cmd == "help") {
|
if (cmd === "help") {
|
||||||
} else if (cmd == "mem") {
|
} else if (cmd === "mem") {
|
||||||
this.printMemoryInChat();
|
this.printMemoryInChat();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -294,18 +324,26 @@ export class Channel extends EventEmitter {
|
||||||
if (typeof user.name !== "string") return;
|
if (typeof user.name !== "string") return;
|
||||||
if (typeof user.color !== "string") return;
|
if (typeof user.color !== "string") return;
|
||||||
if (typeof user.id !== "string") return;
|
if (typeof user.id !== "string") return;
|
||||||
if (typeof user.tag !== "undefined" && typeof user.tag !== "string") return;
|
if (
|
||||||
if (typeof user.flags !== "undefined" && typeof user.flags !== "string") return;
|
typeof user.tag !== "undefined" &&
|
||||||
|
typeof user.tag !== "string"
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
if (
|
||||||
|
typeof user.flags !== "undefined" &&
|
||||||
|
typeof user.flags !== "string"
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
|
||||||
let tag;
|
let tag: Tag | undefined;
|
||||||
let flags;
|
let flags: UserFlags | undefined;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
tag = JSON.parse(user.tag);
|
tag = JSON.parse(user.tag);
|
||||||
} catch (err) {}
|
} catch (err) {}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
flags = JSON.parse(user.flags);
|
flags = JSON.parse(user.flags) as UserFlags;
|
||||||
} catch (err) {}
|
} catch (err) {}
|
||||||
|
|
||||||
for (const p of this.ppl) {
|
for (const p of this.ppl) {
|
||||||
|
@ -315,13 +353,15 @@ export class Channel extends EventEmitter {
|
||||||
p.name = user.name;
|
p.name = user.name;
|
||||||
p.color = user.color;
|
p.color = user.color;
|
||||||
p.tag = tag;
|
p.tag = tag;
|
||||||
p.flags = flags;
|
if (flags) p.flags = flags;
|
||||||
|
|
||||||
let found;
|
let found:
|
||||||
|
| { x: string | number; y: string | number; id: string }
|
||||||
|
| undefined;
|
||||||
|
|
||||||
for (const cursor of this.cursorCache) {
|
for (const cursor of this.cursorCache) {
|
||||||
if (cursor.id == p.id) {
|
if (cursor.id === p.id) {
|
||||||
found = cursor
|
found = cursor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -333,8 +373,7 @@ export class Channel extends EventEmitter {
|
||||||
y = found.y;
|
y = found.y;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.sendArray(
|
this.sendArray([
|
||||||
[
|
|
||||||
{
|
{
|
||||||
m: "p",
|
m: "p",
|
||||||
_id: p._id,
|
_id: p._id,
|
||||||
|
@ -345,8 +384,7 @@ export class Channel extends EventEmitter {
|
||||||
y: y,
|
y: y,
|
||||||
tag: usersConfig.enableTags ? p.tag : undefined
|
tag: usersConfig.enableTags ? p.tag : undefined
|
||||||
}
|
}
|
||||||
]
|
]);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//this.logger.debug("Update from user data update handler");
|
//this.logger.debug("Update from user data update handler");
|
||||||
|
@ -357,11 +395,11 @@ export class Channel extends EventEmitter {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.on("cursor", (pos: { x: string | number; y: string | number; id: string }) => {
|
this.on("cursor", (pos: CachedCursor) => {
|
||||||
let found;
|
let found: CachedCursor | undefined;
|
||||||
|
|
||||||
for (const cursor of this.cursorCache) {
|
for (const cursor of this.cursorCache) {
|
||||||
if (cursor.id == pos.id) {
|
if (cursor.id === pos.id) {
|
||||||
found = cursor;
|
found = cursor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -379,9 +417,8 @@ export class Channel extends EventEmitter {
|
||||||
{
|
{
|
||||||
m: "m",
|
m: "m",
|
||||||
id: pos.id,
|
id: pos.id,
|
||||||
// not type safe
|
x: pos.x,
|
||||||
x: pos.x as string,
|
y: pos.y
|
||||||
y: pos.y as string
|
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
@ -416,7 +453,7 @@ export class Channel extends EventEmitter {
|
||||||
*/
|
*/
|
||||||
public isLobby() {
|
public isLobby() {
|
||||||
for (const reg of config.lobbyRegexes) {
|
for (const reg of config.lobbyRegexes) {
|
||||||
let exp = new RegExp(reg, "g");
|
const exp = new RegExp(reg, "g");
|
||||||
|
|
||||||
if (this.getID().match(exp)) {
|
if (this.getID().match(exp)) {
|
||||||
return true;
|
return true;
|
||||||
|
@ -430,9 +467,13 @@ export class Channel extends EventEmitter {
|
||||||
* Determine whether this channel is a lobby with the name "lobby" in it
|
* Determine whether this channel is a lobby with the name "lobby" in it
|
||||||
*/
|
*/
|
||||||
public isTrueLobby() {
|
public isTrueLobby() {
|
||||||
if (this.getID().match("^lobby[0-9][0-9]$") && this.getID().match("^lobby[0-9]$") && this.getID().match("^lobby$"), "^lobbyNaN$") return true;
|
const _id = this.getID();
|
||||||
|
return (
|
||||||
return false;
|
_id.match("^lobby[0-9][0-9]$") &&
|
||||||
|
_id.match("^lobby[0-9]$") &&
|
||||||
|
_id.match("^lobby$") &&
|
||||||
|
_id.match("^lobbyNaN$")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -441,10 +482,7 @@ export class Channel extends EventEmitter {
|
||||||
* @param admin Whether a user is changing the settings (set to true to force the changes)
|
* @param admin Whether a user is changing the settings (set to true to force the changes)
|
||||||
* @returns undefined
|
* @returns undefined
|
||||||
*/
|
*/
|
||||||
public changeSettings(
|
public changeSettings(set: Partial<IChannelSettings>, admin = false) {
|
||||||
set: Partial<IChannelSettings>,
|
|
||||||
admin: boolean = false
|
|
||||||
) {
|
|
||||||
if (this.isDestroyed()) return;
|
if (this.isDestroyed()) return;
|
||||||
if (!admin) {
|
if (!admin) {
|
||||||
if (set.lobby) set.lobby = undefined;
|
if (set.lobby) set.lobby = undefined;
|
||||||
|
@ -452,8 +490,8 @@ export class Channel extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
typeof set.color == "string" &&
|
typeof set.color === "string" &&
|
||||||
(typeof set.color2 == "undefined" ||
|
(typeof set.color2 === "undefined" ||
|
||||||
set.color2 === this.settings.color2)
|
set.color2 === this.settings.color2)
|
||||||
) {
|
) {
|
||||||
set.color2 = darken(set.color);
|
set.color2 = darken(set.color);
|
||||||
|
@ -467,8 +505,8 @@ 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[key] === false) continue;
|
||||||
(this.settings as any)[key] = (set as any)[key];
|
this.settings[key] = set[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -524,13 +562,17 @@ export class Channel extends EventEmitter {
|
||||||
for (const ch of chs) {
|
for (const ch of chs) {
|
||||||
const chid = ch.getID();
|
const chid = ch.getID();
|
||||||
|
|
||||||
if (chid == config.fullChannel) {
|
if (chid === config.fullChannel) {
|
||||||
const banTime = this.getBanTime(socket.getUserID());
|
const banTime = this.getBanTime(socket.getUserID());
|
||||||
|
|
||||||
//this.logger.debug("Ban time:", banTime);
|
//this.logger.debug("Ban time:", banTime);
|
||||||
|
|
||||||
if (banTime) {
|
if (banTime) {
|
||||||
const minutes = Math.floor((banTime.endTime - banTime.startTime) / 1000 / 60);
|
const minutes = Math.floor(
|
||||||
|
(banTime.endTime - banTime.startTime) /
|
||||||
|
1000 /
|
||||||
|
60
|
||||||
|
);
|
||||||
|
|
||||||
socket.sendNotification({
|
socket.sendNotification({
|
||||||
class: "short",
|
class: "short",
|
||||||
|
@ -540,7 +582,8 @@ export class Channel extends EventEmitter {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return socket.setChannel(chid)
|
socket.setChannel(chid);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -554,7 +597,8 @@ export class Channel extends EventEmitter {
|
||||||
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);
|
socket.setChannel(nextID);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -566,7 +610,7 @@ export class Channel extends EventEmitter {
|
||||||
|
|
||||||
for (const p of this.ppl) {
|
for (const p of this.ppl) {
|
||||||
if (p.id !== part.id) continue;
|
if (p.id !== part.id) continue;
|
||||||
p.uuids.push(socket.getUUID())
|
p.uuids.push(socket.getUUID());
|
||||||
}
|
}
|
||||||
|
|
||||||
//socket.sendChannelUpdate(this.getInfo(), this.getParticipantList());
|
//socket.sendChannelUpdate(this.getInfo(), this.getParticipantList());
|
||||||
|
@ -578,9 +622,9 @@ export class Channel extends EventEmitter {
|
||||||
name: part.name,
|
name: part.name,
|
||||||
color: part.color,
|
color: part.color,
|
||||||
id: part.id,
|
id: part.id,
|
||||||
tag: part.tag,
|
|
||||||
uuids: [socket.getUUID()],
|
uuids: [socket.getUUID()],
|
||||||
flags: socket.getUserFlags() || {}
|
flags: socket.getUserFlags() || {},
|
||||||
|
tag: part.tag
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -590,7 +634,7 @@ export class Channel extends EventEmitter {
|
||||||
if (socket.currentChannelID) {
|
if (socket.currentChannelID) {
|
||||||
// Find the other channel they were in
|
// Find the other channel they were in
|
||||||
const ch = ChannelList.getList().find(
|
const ch = ChannelList.getList().find(
|
||||||
ch => ch._id == socket.currentChannelID
|
ch => ch._id === socket.currentChannelID
|
||||||
);
|
);
|
||||||
|
|
||||||
// Tell the channel they left
|
// Tell the channel they left
|
||||||
|
@ -611,7 +655,7 @@ export class Channel extends EventEmitter {
|
||||||
if (this.crown && config.chownOnRejoin) {
|
if (this.crown && config.chownOnRejoin) {
|
||||||
// TODO Should we check participant ID as well?
|
// TODO Should we check participant ID as well?
|
||||||
if (typeof this.crown.userId !== "undefined") {
|
if (typeof this.crown.userId !== "undefined") {
|
||||||
if (socket.getUserID() == this.crown.userId) {
|
if (socket.getUserID() === this.crown.userId) {
|
||||||
// Check if they exist
|
// Check if they exist
|
||||||
const p = socket.getParticipant();
|
const p = socket.getParticipant();
|
||||||
|
|
||||||
|
@ -653,7 +697,8 @@ export class Channel extends EventEmitter {
|
||||||
color: part.color,
|
color: part.color,
|
||||||
id: part.id,
|
id: part.id,
|
||||||
x: cursorPos.x,
|
x: cursorPos.x,
|
||||||
y: cursorPos.y
|
y: cursorPos.y,
|
||||||
|
tag: usersConfig.enableTags ? part.tag : undefined
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
part.id
|
part.id
|
||||||
|
@ -676,9 +721,9 @@ export class Channel extends EventEmitter {
|
||||||
const part = socket.getParticipant() as Participant;
|
const part = socket.getParticipant() as Participant;
|
||||||
|
|
||||||
let dupeCount = 0;
|
let dupeCount = 0;
|
||||||
for (const s of socketsBySocketID.values()) {
|
for (const s of socketsByUUID.values()) {
|
||||||
if (s.getParticipantID() == part.id) {
|
if (s.getParticipantID() === part.id) {
|
||||||
if (s.currentChannelID == this.getID()) {
|
if (s.currentChannelID === this.getID()) {
|
||||||
dupeCount++;
|
dupeCount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -686,14 +731,14 @@ export class Channel extends EventEmitter {
|
||||||
|
|
||||||
// this.logger.debug("Dupes:", dupeCount);
|
// this.logger.debug("Dupes:", dupeCount);
|
||||||
|
|
||||||
if (dupeCount == 1) {
|
if (dupeCount === 1) {
|
||||||
const p = this.ppl.find(p => p.id == socket.getParticipantID());
|
const p = this.ppl.find(p => p.id === socket.getParticipantID());
|
||||||
|
|
||||||
if (p) {
|
if (p) {
|
||||||
this.ppl.splice(this.ppl.indexOf(p), 1);
|
this.ppl.splice(this.ppl.indexOf(p), 1);
|
||||||
|
|
||||||
if (this.crown) {
|
if (this.crown) {
|
||||||
if (this.crown.participantId == p.id) {
|
if (this.crown.participantId === p.id) {
|
||||||
// Channel owner left, reset crown timeout
|
// Channel owner left, reset crown timeout
|
||||||
this.chown();
|
this.chown();
|
||||||
}
|
}
|
||||||
|
@ -751,19 +796,22 @@ export class Channel extends EventEmitter {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the people in this channel
|
* Get the people in this channel
|
||||||
|
* @param showVanished Whether to include vanished users
|
||||||
* @returns List of people
|
* @returns List of people
|
||||||
*/
|
*/
|
||||||
public getParticipantList() {
|
public getParticipantList(showVanished = false) {
|
||||||
const ppl = [];
|
const ppl = [];
|
||||||
|
|
||||||
for (const p of this.ppl) {
|
for (const p of this.ppl) {
|
||||||
if (p.flags.vanish) continue;
|
if (p.flags.vanish && !showVanished) continue;
|
||||||
|
|
||||||
ppl.push({
|
ppl.push({
|
||||||
_id: p._id,
|
_id: p._id,
|
||||||
name: p.name,
|
name: p.name,
|
||||||
color: p.color,
|
color: p.color,
|
||||||
id: p.id,
|
id: p.id,
|
||||||
tag: usersConfig.enableTags ? p.tag : undefined
|
tag: usersConfig.enableTags ? p.tag : undefined,
|
||||||
|
vanished: p.flags.vanish
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -780,7 +828,7 @@ export class Channel extends EventEmitter {
|
||||||
* @returns Boolean
|
* @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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -790,7 +838,7 @@ export class Channel extends EventEmitter {
|
||||||
* @returns Boolean
|
* @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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -802,19 +850,18 @@ export class Channel extends EventEmitter {
|
||||||
arr: ClientEvents[EventID][],
|
arr: ClientEvents[EventID][],
|
||||||
blockPartID?: string
|
blockPartID?: string
|
||||||
) {
|
) {
|
||||||
let sentSocketIDs = new Array<string>();
|
const sentSocketIDs = new Array<string>();
|
||||||
|
|
||||||
for (const p of this.ppl) {
|
for (const p of this.ppl) {
|
||||||
if (blockPartID) {
|
if (blockPartID) {
|
||||||
if (p.id == blockPartID) continue;
|
if (p.id === blockPartID) continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
socketLoop: for (const socket of socketsBySocketID.values()) {
|
for (const socket of socketsByUUID.values()) {
|
||||||
if (socket.isDestroyed()) continue socketLoop;
|
if (socket.isDestroyed()) continue;
|
||||||
if (!socket.socketID) continue socketLoop;
|
if (!socket.socketID) continue;
|
||||||
if (socket.getParticipantID() != p.id) continue socketLoop;
|
if (socket.getParticipantID() !== p.id) continue;
|
||||||
if (sentSocketIDs.includes(socket.socketID))
|
if (sentSocketIDs.includes(socket.socketID)) continue;
|
||||||
continue socketLoop;
|
|
||||||
socket.sendArray(arr);
|
socket.sendArray(arr);
|
||||||
sentSocketIDs.push(socket.socketID);
|
sentSocketIDs.push(socket.socketID);
|
||||||
}
|
}
|
||||||
|
@ -837,27 +884,27 @@ export class Channel extends EventEmitter {
|
||||||
pianoPartID = part.id;
|
pianoPartID = part.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
let clientMsg: ClientEvents["n"] = {
|
const clientMsg: ClientEvents["n"] = {
|
||||||
m: "n",
|
m: "n",
|
||||||
n: msg.n,
|
n: msg.n,
|
||||||
t: msg.t,
|
t: msg.t,
|
||||||
p: pianoPartID
|
p: pianoPartID
|
||||||
};
|
};
|
||||||
|
|
||||||
let sentSocketIDs = new Array<string>();
|
const sentSocketIDs = new Array<string>();
|
||||||
|
|
||||||
for (const p of this.ppl) {
|
for (const p of this.ppl) {
|
||||||
socketLoop: for (const sock of socketsBySocketID.values()) {
|
for (const sock of socketsByUUID.values()) {
|
||||||
if (sock.isDestroyed()) continue socketLoop;
|
if (sock.isDestroyed()) continue;
|
||||||
if (!sock.socketID) continue socketLoop;
|
if (!sock.socketID) continue;
|
||||||
|
|
||||||
if (socket) {
|
if (socket) {
|
||||||
if (sock.getUUID() == socket.getUUID()) continue socketLoop;
|
if (sock.getUUID() === socket.getUUID()) continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sock.getParticipantID() != p.id) continue socketLoop;
|
if (sock.getParticipantID() !== p.id) continue;
|
||||||
//if (socket.getParticipantID() == part.id) continue socketLoop;
|
//if (socket.getParticipantID() == part.id) continue socketLoop;
|
||||||
if (sentSocketIDs.includes(sock.socketID)) continue socketLoop;
|
if (sentSocketIDs.includes(sock.socketID)) continue;
|
||||||
|
|
||||||
sock.sendArray([clientMsg]);
|
sock.sendArray([clientMsg]);
|
||||||
sentSocketIDs.push(sock.socketID);
|
sentSocketIDs.push(sock.socketID);
|
||||||
|
@ -876,7 +923,7 @@ export class Channel extends EventEmitter {
|
||||||
this.destroyed = true;
|
this.destroyed = true;
|
||||||
|
|
||||||
if (this.ppl.length > 0) {
|
if (this.ppl.length > 0) {
|
||||||
for (const socket of socketsBySocketID.values()) {
|
for (const socket of socketsByUUID.values()) {
|
||||||
if (socket.currentChannelID !== this.getID()) continue;
|
if (socket.currentChannelID !== this.getID()) continue;
|
||||||
socket.setChannel(config.fullChannel);
|
socket.setChannel(config.fullChannel);
|
||||||
}
|
}
|
||||||
|
@ -939,31 +986,31 @@ export class Channel extends EventEmitter {
|
||||||
if (this.crown) {
|
if (this.crown) {
|
||||||
this.crown.time = Date.now();
|
this.crown.time = Date.now();
|
||||||
|
|
||||||
let socket;
|
let socket: Socket | undefined;
|
||||||
if (this.crown.participantId)
|
if (this.crown.participantId)
|
||||||
socket = findSocketByPartID(this.crown.participantId);
|
socket = findSocketByPartID(this.crown.participantId);
|
||||||
|
|
||||||
let x = Math.random() * 100;
|
const x = Math.random() * 100;
|
||||||
let y1 = Math.random() * 100;
|
const y1 = Math.random() * 100;
|
||||||
let y2 = y1 + Math.random() * (100 - y1);
|
const y2 = y1 + Math.random() * (100 - y1);
|
||||||
|
|
||||||
if (socket) {
|
if (socket) {
|
||||||
const cursorPos = socket.getCursorPos();
|
const cursorPos = socket.getCursorPos();
|
||||||
|
|
||||||
let cursorX = cursorPos.x;
|
let cursorX = cursorPos.x;
|
||||||
if (typeof cursorPos.x == "string")
|
if (typeof cursorPos.x === "string")
|
||||||
cursorX = parseInt(cursorPos.x);
|
cursorX = Number.parseInt(cursorPos.x);
|
||||||
|
|
||||||
let cursorY = cursorPos.y;
|
let cursorY = cursorPos.y;
|
||||||
if (typeof cursorPos.y == "string")
|
if (typeof cursorPos.y === "string")
|
||||||
cursorY = parseInt(cursorPos.y);
|
cursorY = Number.parseInt(cursorPos.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Screen positions
|
// Screen positions
|
||||||
this.crown.startPos = { x, y: y1 };
|
this.crown.startPos = { x, y: y1 };
|
||||||
this.crown.endPos = { x, y: y2 };
|
this.crown.endPos = { x, y: y2 };
|
||||||
|
|
||||||
delete this.crown.participantId;
|
this.crown.participantId = undefined;
|
||||||
|
|
||||||
//this.logger.debug("Update from dropCrown");
|
//this.logger.debug("Update from dropCrown");
|
||||||
this.emit("update", this);
|
this.emit("update", this);
|
||||||
|
@ -975,14 +1022,18 @@ export class Channel extends EventEmitter {
|
||||||
* @param _id User ID to ban
|
* @param _id User ID to ban
|
||||||
* @param t Time in millseconds to ban for
|
* @param t Time in millseconds to ban for
|
||||||
**/
|
**/
|
||||||
public async kickban(_id: string, t: number = 1000 * 60 * 30, banner?: string) {
|
public async kickban(
|
||||||
|
_id: string,
|
||||||
|
t: number = 1000 * 60 * 30,
|
||||||
|
banner?: string
|
||||||
|
) {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
if (t < 0 || t > 300 * 60 * 1000) return;
|
if (t < 0 || t > 300 * 60 * 1000) return;
|
||||||
|
|
||||||
let shouldUpdate = false;
|
let shouldUpdate = false;
|
||||||
|
|
||||||
const banChannel = ChannelList.getList().find(
|
const banChannel = ChannelList.getList().find(
|
||||||
ch => ch.getID() == config.fullChannel
|
ch => ch.getID() === config.fullChannel
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!banChannel) return;
|
if (!banChannel) return;
|
||||||
|
@ -990,8 +1041,8 @@ export class Channel extends EventEmitter {
|
||||||
// Check if they are on the server at all
|
// Check if they are on the server at all
|
||||||
let bannedPart: Participant | undefined;
|
let bannedPart: Participant | undefined;
|
||||||
const bannedUUIDs: string[] = [];
|
const bannedUUIDs: string[] = [];
|
||||||
for (const sock of socketsBySocketID.values()) {
|
for (const sock of socketsByUUID.values()) {
|
||||||
if (sock.getUserID() == _id) {
|
if (sock.getUserID() === _id) {
|
||||||
bannedUUIDs.push(sock.getUUID());
|
bannedUUIDs.push(sock.getUUID());
|
||||||
const part = sock.getParticipant();
|
const part = sock.getParticipant();
|
||||||
|
|
||||||
|
@ -1001,7 +1052,7 @@ export class Channel extends EventEmitter {
|
||||||
|
|
||||||
if (!bannedPart) return;
|
if (!bannedPart) return;
|
||||||
|
|
||||||
let isBanned = this.bans.map(b => b.userId).includes(_id);
|
const isBanned = this.bans.map(b => b.userId).includes(_id);
|
||||||
let overwrite = false;
|
let overwrite = false;
|
||||||
|
|
||||||
if (isBanned) {
|
if (isBanned) {
|
||||||
|
@ -1019,24 +1070,24 @@ export class Channel extends EventEmitter {
|
||||||
|
|
||||||
shouldUpdate = true;
|
shouldUpdate = true;
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
for (const ban of this.bans) {
|
for (const ban of this.bans) {
|
||||||
if (ban.userId !== _id) continue;
|
if (ban.userId !== _id) continue;
|
||||||
ban.startTime = now;
|
ban.startTime = now;
|
||||||
ban.endTime = now + t;
|
ban.endTime = now + t;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
shouldUpdate = true;
|
shouldUpdate = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
uuidsToKick = [...uuidsToKick, ...bannedUUIDs];
|
uuidsToKick = [...uuidsToKick, ...bannedUUIDs];
|
||||||
|
|
||||||
for (const socket of socketsBySocketID.values()) {
|
for (const socket of socketsByUUID.values()) {
|
||||||
if (uuidsToKick.indexOf(socket.getUUID()) !== -1) {
|
if (uuidsToKick.indexOf(socket.getUUID()) !== -1) {
|
||||||
socket.sendNotification({
|
socket.sendNotification({
|
||||||
title: "Notice",
|
title: "Notice",
|
||||||
text: `Banned from "${this.getID()}" for ${Math.floor(t / 1000 / 60)} minutes.`,
|
text: `Banned from "${this.getID()}" for ${Math.floor(
|
||||||
|
t / 1000 / 60
|
||||||
|
)} minutes.`,
|
||||||
duration: 7000,
|
duration: 7000,
|
||||||
target: "#room",
|
target: "#room",
|
||||||
class: "short"
|
class: "short"
|
||||||
|
@ -1045,7 +1096,7 @@ export class Channel extends EventEmitter {
|
||||||
// If they are here, move them to the ban channel
|
// If they are here, move them to the ban channel
|
||||||
const ch = socket.getCurrentChannel();
|
const ch = socket.getCurrentChannel();
|
||||||
if (ch) {
|
if (ch) {
|
||||||
if (ch.getID() == this.getID())
|
if (ch.getID() === this.getID())
|
||||||
socket.setChannel(banChannel.getID());
|
socket.setChannel(banChannel.getID());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1056,14 +1107,19 @@ export class Channel extends EventEmitter {
|
||||||
this.emit("update", this);
|
this.emit("update", this);
|
||||||
|
|
||||||
if (typeof banner !== "undefined") {
|
if (typeof banner !== "undefined") {
|
||||||
const p = this.getParticipantListUnsanitized().find(p => p._id == banner);
|
const p = this.getParticipantListUnsanitized().find(
|
||||||
|
p => p._id === banner
|
||||||
|
);
|
||||||
const minutes = Math.floor(t / 1000 / 60);
|
const minutes = Math.floor(t / 1000 / 60);
|
||||||
|
|
||||||
if (p && bannedPart) {
|
if (p && bannedPart) {
|
||||||
await this.sendChat({
|
await this.sendChat(
|
||||||
|
{
|
||||||
m: "a",
|
m: "a",
|
||||||
message: `Banned ${bannedPart.name} from the channel for ${minutes} minutes.`
|
message: `Banned ${bannedPart.name} from the channel for ${minutes} minutes.`
|
||||||
}, p);
|
},
|
||||||
|
p
|
||||||
|
);
|
||||||
this.sendNotification({
|
this.sendNotification({
|
||||||
title: "Notice",
|
title: "Notice",
|
||||||
text: `${p.name} banned ${bannedPart.name} from the channel for ${minutes} minutes.`,
|
text: `${p.name} banned ${bannedPart.name} from the channel for ${minutes} minutes.`,
|
||||||
|
@ -1072,7 +1128,7 @@ export class Channel extends EventEmitter {
|
||||||
class: "short"
|
class: "short"
|
||||||
});
|
});
|
||||||
|
|
||||||
if (banner == _id) {
|
if (banner === _id) {
|
||||||
const certificate = {
|
const certificate = {
|
||||||
title: "Certificate of Award",
|
title: "Certificate of Award",
|
||||||
text: `Let it be known that ${p.name} kickbanned him/her self.`,
|
text: `Let it be known that ${p.name} kickbanned him/her self.`,
|
||||||
|
@ -1082,7 +1138,7 @@ export class Channel extends EventEmitter {
|
||||||
|
|
||||||
this.sendNotification(certificate);
|
this.sendNotification(certificate);
|
||||||
|
|
||||||
for (const s of socketsBySocketID.values()) {
|
for (const s of socketsByUUID.values()) {
|
||||||
const userID = s.getUserID();
|
const userID = s.getUserID();
|
||||||
if (!userID) continue;
|
if (!userID) continue;
|
||||||
if (userID !== banner) continue;
|
if (userID !== banner) continue;
|
||||||
|
@ -1110,7 +1166,7 @@ export class Channel extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if they are banned
|
// Check if they are banned
|
||||||
if (ban.userId == _id) {
|
if (ban.userId === _id) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1128,7 +1184,7 @@ export class Channel extends EventEmitter {
|
||||||
if (!isBanned) return;
|
if (!isBanned) return;
|
||||||
|
|
||||||
for (const ban of this.bans) {
|
for (const ban of this.bans) {
|
||||||
if (ban.userId == _id) {
|
if (ban.userId === _id) {
|
||||||
this.bans.splice(this.bans.indexOf(ban), 1);
|
this.bans.splice(this.bans.indexOf(ban), 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1141,10 +1197,12 @@ export class Channel extends EventEmitter {
|
||||||
this.chatHistory = [];
|
this.chatHistory = [];
|
||||||
await saveChatHistory(this.getID(), this.chatHistory);
|
await saveChatHistory(this.getID(), this.chatHistory);
|
||||||
|
|
||||||
this.sendArray([{
|
this.sendArray([
|
||||||
|
{
|
||||||
m: "c",
|
m: "c",
|
||||||
c: this.chatHistory
|
c: this.chatHistory
|
||||||
}]);
|
}
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1152,7 +1210,8 @@ export class Channel extends EventEmitter {
|
||||||
* @param notif Notification to send
|
* @param notif Notification to send
|
||||||
**/
|
**/
|
||||||
public sendNotification(notif: Notification) {
|
public sendNotification(notif: Notification) {
|
||||||
this.sendArray([{
|
this.sendArray([
|
||||||
|
{
|
||||||
m: "notification",
|
m: "notification",
|
||||||
id: notif.id,
|
id: notif.id,
|
||||||
target: notif.target,
|
target: notif.target,
|
||||||
|
@ -1161,7 +1220,8 @@ export class Channel extends EventEmitter {
|
||||||
title: notif.title,
|
title: notif.title,
|
||||||
text: notif.text,
|
text: notif.text,
|
||||||
html: notif.html
|
html: notif.html
|
||||||
}]);
|
}
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1180,7 +1240,7 @@ export class Channel extends EventEmitter {
|
||||||
.replace(/(\p{Mc}{5})\p{Mc}+/gu, "$1")
|
.replace(/(\p{Mc}{5})\p{Mc}+/gu, "$1")
|
||||||
.trim();
|
.trim();
|
||||||
|
|
||||||
let outgoing: ClientEvents["a"] = {
|
const outgoing: ClientEvents["a"] = {
|
||||||
m: "a",
|
m: "a",
|
||||||
a: msg.message,
|
a: msg.message,
|
||||||
t: Date.now(),
|
t: Date.now(),
|
||||||
|
@ -1203,10 +1263,13 @@ export class Channel extends EventEmitter {
|
||||||
* @param message Message to send in chat
|
* @param message Message to send in chat
|
||||||
**/
|
**/
|
||||||
public async sendChatAdmin(message: string) {
|
public async sendChatAdmin(message: string) {
|
||||||
this.sendChat({
|
this.sendChat(
|
||||||
|
{
|
||||||
m: "a",
|
m: "a",
|
||||||
message
|
message
|
||||||
}, usersConfig.adminParticipant);
|
},
|
||||||
|
usersConfig.adminParticipant
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1214,7 +1277,10 @@ export class Channel extends EventEmitter {
|
||||||
* @param key Flag ID
|
* @param key Flag ID
|
||||||
* @param val Value of which the flag will be set to
|
* @param val Value of which the flag will be set to
|
||||||
**/
|
**/
|
||||||
public setFlag(key: string, val: any) {
|
public setFlag<K extends keyof TChannelFlags>(
|
||||||
|
key: K,
|
||||||
|
val: TChannelFlags[K]
|
||||||
|
) {
|
||||||
this.flags[key] = val;
|
this.flags[key] = val;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1223,7 +1289,7 @@ export class Channel extends EventEmitter {
|
||||||
* @param key Flag ID
|
* @param key Flag ID
|
||||||
* @returns Value of flag
|
* @returns Value of flag
|
||||||
**/
|
**/
|
||||||
public getFlag(key: string) {
|
public getFlag<K extends keyof TChannelFlags>(key: K) {
|
||||||
return this.flags[key];
|
return this.flags[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1231,7 +1297,7 @@ export class Channel extends EventEmitter {
|
||||||
* Set the flags on this channel
|
* Set the flags on this channel
|
||||||
* @param flags Flags to set
|
* @param flags Flags to set
|
||||||
**/
|
**/
|
||||||
public setFlags(flags: Record<string, any>) {
|
public setFlags(flags: TChannelFlags) {
|
||||||
this.flags = flags;
|
this.flags = flags;
|
||||||
this.save();
|
this.save();
|
||||||
this.emit("update", this);
|
this.emit("update", this);
|
||||||
|
@ -1244,8 +1310,8 @@ export class Channel extends EventEmitter {
|
||||||
public getNextLobbyID() {
|
public getNextLobbyID() {
|
||||||
try {
|
try {
|
||||||
const id = this.getID();
|
const id = this.getID();
|
||||||
if (id == "lobby") return "lobby2";
|
if (id === "lobby") return "lobby2";
|
||||||
const num = parseInt(id.substring(5));
|
const num = Number.parseInt(id.substring(5));
|
||||||
return `lobby${num + 1}`;
|
return `lobby${num + 1}`;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return config.fullChannel;
|
return config.fullChannel;
|
||||||
|
@ -1259,7 +1325,7 @@ export class Channel extends EventEmitter {
|
||||||
**/
|
**/
|
||||||
public getBanTime(userId: string) {
|
public getBanTime(userId: string) {
|
||||||
for (const ban of this.bans) {
|
for (const ban of this.bans) {
|
||||||
if (userId == ban.userId) {
|
if (userId === ban.userId) {
|
||||||
return { endTime: ban.endTime, startTime: ban.startTime };
|
return { endTime: ban.endTime, startTime: ban.startTime };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1270,7 +1336,13 @@ export class Channel extends EventEmitter {
|
||||||
**/
|
**/
|
||||||
public printMemoryInChat() {
|
public printMemoryInChat() {
|
||||||
const mem = heapStats();
|
const mem = heapStats();
|
||||||
this.sendChatAdmin(`Used: ${(mem.heapSize / 1000 / 1000).toFixed(2)}M / Allocated: ${(mem.heapCapacity / 1000 / 1000).toFixed(2)}M`);
|
this.sendChatAdmin(
|
||||||
|
`Used: ${(mem.heapSize / 1000 / 1000).toFixed(2)}M / Allocated: ${(
|
||||||
|
mem.heapCapacity /
|
||||||
|
1000 /
|
||||||
|
1000
|
||||||
|
).toFixed(2)}M`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { loadConfig } from "../util/config";
|
import { ConfigManager } from "../util/config";
|
||||||
import { IChannelSettings } from "../util/types";
|
import { IChannelSettings } from "../util/types";
|
||||||
|
|
||||||
interface ChannelConfig {
|
interface ChannelConfig {
|
||||||
|
@ -13,7 +13,9 @@ interface ChannelConfig {
|
||||||
channelDestroyTimeout: number;
|
channelDestroyTimeout: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const config = loadConfig<ChannelConfig>("config/channels.yml", {
|
export const config = ConfigManager.loadConfig<ChannelConfig>(
|
||||||
|
"config/channels.yml",
|
||||||
|
{
|
||||||
forceLoad: ["lobby", "test/awkward"],
|
forceLoad: ["lobby", "test/awkward"],
|
||||||
lobbySettings: {
|
lobbySettings: {
|
||||||
lobby: true,
|
lobby: true,
|
||||||
|
@ -31,10 +33,17 @@ export const config = loadConfig<ChannelConfig>("config/channels.yml", {
|
||||||
visible: true
|
visible: true
|
||||||
},
|
},
|
||||||
// Here's a terrifying fact: Brandon used parseInt to check lobby names
|
// Here's a terrifying fact: Brandon used parseInt to check lobby names
|
||||||
lobbyRegexes: ["^lobby[0-9][0-9]$", "^lobby[0-9]$", "^lobby$", "^lobbyNaN$", "^test/.+$"],
|
lobbyRegexes: [
|
||||||
|
"^lobby[0-9][0-9]$",
|
||||||
|
"^lobby[0-9]$",
|
||||||
|
"^lobby$",
|
||||||
|
"^lobbyNaN$",
|
||||||
|
"^test/.+$"
|
||||||
|
],
|
||||||
lobbyBackdoor: "lolwutsecretlobbybackdoor",
|
lobbyBackdoor: "lolwutsecretlobbybackdoor",
|
||||||
fullChannel: "test/awkward",
|
fullChannel: "test/awkward",
|
||||||
sendLimit: false,
|
sendLimit: false,
|
||||||
chownOnRejoin: true,
|
chownOnRejoin: true,
|
||||||
channelDestroyTimeout: 1000
|
channelDestroyTimeout: 1000
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { IChannelSettings } from "../util/types";
|
import type { IChannelSettings } from "~/util/types";
|
||||||
|
|
||||||
type Validator = "boolean" | "string" | "number" | ((val: unknown) => boolean);
|
type Validator = "boolean" | "string" | "number" | ((val: unknown) => boolean);
|
||||||
|
|
||||||
|
@ -40,11 +40,11 @@ const adminOnlyKeys = [
|
||||||
*/
|
*/
|
||||||
export function validateChannelSettings(set: Partial<IChannelSettings>, admin = false) {
|
export function validateChannelSettings(set: Partial<IChannelSettings>, admin = false) {
|
||||||
// Create record
|
// Create record
|
||||||
let record: Partial<Record<keyof IChannelSettings, boolean>> = {};
|
const record: Partial<Record<keyof IChannelSettings, boolean>> = {};
|
||||||
|
|
||||||
for (const key of Object.keys(set)) {
|
for (const key of Object.keys(set)) {
|
||||||
let val = (set as Record<string, any>)[key];
|
const val = (set as Record<string, unknown>)[key];
|
||||||
let validator = (
|
const validator = (
|
||||||
validationRecord as Record<string, Validator | undefined>
|
validationRecord as Record<string, Validator | undefined>
|
||||||
)[key];
|
)[key];
|
||||||
|
|
||||||
|
@ -66,12 +66,15 @@ export function validateChannelSettings(set: Partial<IChannelSettings>, admin =
|
||||||
|
|
||||||
export default validateChannelSettings;
|
export default validateChannelSettings;
|
||||||
|
|
||||||
export function validate(value: any, validator: Validator) {
|
export function validate(value: unknown, validator: Validator) {
|
||||||
// What type of validator?
|
// What type of validator?
|
||||||
if (typeof validator === "function") {
|
if (typeof validator === "function") {
|
||||||
// Run the function
|
// Run the function
|
||||||
return validator(value) === true;
|
return validator(value) === true;
|
||||||
} else if (typeof value === validator) {
|
}
|
||||||
|
|
||||||
|
// biome-ignore lint/suspicious/useValidTypeof: biome is dumb
|
||||||
|
if (typeof value === validator) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,119 @@
|
||||||
|
import { ConfigManager } from "~/util/config";
|
||||||
|
import { prisma } from "./prisma";
|
||||||
|
import { getRoles } from "./role";
|
||||||
|
import { permission } from "process";
|
||||||
|
import { Logger } from "~/util/Logger";
|
||||||
|
|
||||||
|
export const config = ConfigManager.loadConfig<Record<string, string[]>>(
|
||||||
|
"config/permissions.yml",
|
||||||
|
{
|
||||||
|
admin: [
|
||||||
|
"clearChat",
|
||||||
|
"vanish",
|
||||||
|
"chsetAnywhere",
|
||||||
|
"chownAnywhere",
|
||||||
|
"usersetOthers",
|
||||||
|
"siteBan",
|
||||||
|
"siteBanAnyReason",
|
||||||
|
"siteBanAnyDuration"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const logger = new Logger("Permission Handler");
|
||||||
|
|
||||||
|
export async function getRolePermissions(roleId: string) {
|
||||||
|
const permissions = await prisma.rolePermission.findMany({
|
||||||
|
where: { roleId }
|
||||||
|
});
|
||||||
|
|
||||||
|
return permissions;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function hasPermission(roleId: string, permission: string) {
|
||||||
|
const permissions = await getRolePermissions(roleId);
|
||||||
|
|
||||||
|
if (permissions.find(p => p.permission === permission)) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function addRolePermission(roleId: string, permission: string) {
|
||||||
|
return await prisma.rolePermission.create({
|
||||||
|
data: {
|
||||||
|
roleId,
|
||||||
|
permission
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function removeRolePermission(roleId: string, permission: string) {
|
||||||
|
return await prisma.rolePermission.deleteMany({
|
||||||
|
where: {
|
||||||
|
roleId,
|
||||||
|
permission
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function removeAllRolePermissions(roleId?: string) {
|
||||||
|
return await prisma.rolePermission.deleteMany({
|
||||||
|
where: {
|
||||||
|
roleId
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getUserPermissions(userId: string) {
|
||||||
|
const roles = await getRoles(userId);
|
||||||
|
let collectivePerms: string[] = [];
|
||||||
|
|
||||||
|
for (const role of roles) {
|
||||||
|
const perms = await getRolePermissions(role.roleId);
|
||||||
|
collectivePerms.push(...perms.map(p => p.permission));
|
||||||
|
}
|
||||||
|
|
||||||
|
return collectivePerms;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function validatePermission(permission1: string, permission2: string) {
|
||||||
|
let perm1 = permission1.split(".");
|
||||||
|
let perm2 = permission2.split(".");
|
||||||
|
|
||||||
|
let length = Math.max(perm1.length, perm2.length);
|
||||||
|
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
let p1 = perm1[i];
|
||||||
|
let p2 = perm2[i];
|
||||||
|
|
||||||
|
if (p1 === "*" || p2 === "*") break;
|
||||||
|
if (p1 !== p2) return false;
|
||||||
|
|
||||||
|
if (i === length - 1) {
|
||||||
|
return true;
|
||||||
|
} else if (p1 === p2) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function loadDefaultPermissions() {
|
||||||
|
logger.info("Loading default permissions...");
|
||||||
|
|
||||||
|
for (const roleId of Object.keys(config)) {
|
||||||
|
// logger.debug("Adding roles for", roleId);
|
||||||
|
const permissions = config[roleId];
|
||||||
|
|
||||||
|
for (const permission of permissions) {
|
||||||
|
if (await hasPermission(roleId, permission)) {
|
||||||
|
// logger.debug("Permission already exists:", roleId, permission);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// logger.debug("Adding permission:", roleId, permission);
|
||||||
|
await addRolePermission(roleId, permission);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("Loaded default permissions");
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
import { IRole } from "~/util/types";
|
||||||
|
import { prisma } from "./prisma";
|
||||||
|
|
||||||
|
export async function getRoles(userId: string) {
|
||||||
|
const roles = await prisma.role.findMany({
|
||||||
|
where: { userId }
|
||||||
|
});
|
||||||
|
|
||||||
|
return roles as IRole[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function hasRole(userId: string, roleId: string) {
|
||||||
|
const roles = await getRoles(userId);
|
||||||
|
|
||||||
|
for (const role of roles) {
|
||||||
|
if (role.roleId === roleId) return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function giveRole(userId: string, roleId: string) {
|
||||||
|
return (await prisma.role.create({
|
||||||
|
data: {
|
||||||
|
userId,
|
||||||
|
roleId
|
||||||
|
}
|
||||||
|
})) as IRole;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function removeRole(userId: string, roleId: string) {
|
||||||
|
return await prisma.role.delete({
|
||||||
|
where: {
|
||||||
|
userId,
|
||||||
|
roleId
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
|
@ -19,6 +19,7 @@ import { loadForcedStartupChannels } from "./channel/forceLoad";
|
||||||
import { Logger } from "./util/Logger";
|
import { Logger } from "./util/Logger";
|
||||||
// docker hates this next one
|
// docker hates this next one
|
||||||
import { startReadline } from "./util/readline";
|
import { startReadline } from "./util/readline";
|
||||||
|
import { loadDefaultPermissions } from "./data/permission";
|
||||||
|
|
||||||
// wrapper for some reason
|
// wrapper for some reason
|
||||||
export function startServer() {
|
export function startServer() {
|
||||||
|
@ -28,6 +29,8 @@ export function startServer() {
|
||||||
logger.info("Forceloading startup channels...");
|
logger.info("Forceloading startup channels...");
|
||||||
loadForcedStartupChannels();
|
loadForcedStartupChannels();
|
||||||
|
|
||||||
|
loadDefaultPermissions();
|
||||||
|
|
||||||
// Break the console
|
// Break the console
|
||||||
startReadline();
|
startReadline();
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { existsSync, readFileSync, writeFileSync } from "fs";
|
import { existsSync, readFileSync, writeFileSync, watchFile } from "fs";
|
||||||
import { parse, stringify } from "yaml";
|
import { parse, stringify } from "yaml";
|
||||||
|
import { Logger } from "./Logger";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This file uses the synchronous functions from the fs
|
* This file uses the synchronous functions from the fs
|
||||||
|
@ -11,12 +12,22 @@ import { parse, stringify } from "yaml";
|
||||||
* program.
|
* program.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
export class ConfigManager {
|
||||||
|
// public static configCache = new Map<string, unknown>();
|
||||||
|
public static logger: Logger;
|
||||||
|
|
||||||
|
static {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.logger = new Logger("Config Loader");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load a YAML config file and set default values if config path is nonexistent
|
* Load a YAML config file and set default values if config path is nonexistent
|
||||||
*
|
*
|
||||||
* Usage:
|
* Usage:
|
||||||
* ```ts
|
* ```ts
|
||||||
* const config = loadConfig("config/services.yml", {
|
* const config = ConfigManager.loadConfig("config/services.yml", {
|
||||||
* enableMPP: false
|
* enableMPP: false
|
||||||
* });
|
* });
|
||||||
* ```
|
* ```
|
||||||
|
@ -24,7 +35,9 @@ import { parse, stringify } from "yaml";
|
||||||
* @param defaultConfig Config to use if none is present (will save to path if used)
|
* @param defaultConfig Config to use if none is present (will save to path if used)
|
||||||
* @returns Parsed YAML config
|
* @returns Parsed YAML config
|
||||||
*/
|
*/
|
||||||
export function loadConfig<T>(configPath: string, defaultConfig: T): T {
|
public static loadConfig<T>(configPath: string, defaultConfig: T): T {
|
||||||
|
const self = this;
|
||||||
|
|
||||||
// Config exists?
|
// Config exists?
|
||||||
if (existsSync(configPath)) {
|
if (existsSync(configPath)) {
|
||||||
// Load config
|
// Load config
|
||||||
|
@ -44,7 +57,10 @@ export function loadConfig<T>(configPath: string, defaultConfig: T): T {
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof obj[key] == "object" && !Array.isArray(obj[key])) {
|
if (
|
||||||
|
typeof obj[key] == "object" &&
|
||||||
|
!Array.isArray(obj[key])
|
||||||
|
) {
|
||||||
mix(
|
mix(
|
||||||
obj[key] as Record<string, unknown>,
|
obj[key] as Record<string, unknown>,
|
||||||
obj2[key] as Record<string, unknown>
|
obj2[key] as Record<string, unknown>
|
||||||
|
@ -57,14 +73,38 @@ export function loadConfig<T>(configPath: string, defaultConfig: T): T {
|
||||||
mix(config, defRecord);
|
mix(config, defRecord);
|
||||||
|
|
||||||
// Save config if modified
|
// Save config if modified
|
||||||
if (changed) writeConfig(configPath, config);
|
if (changed) this.writeConfig(configPath, config);
|
||||||
|
|
||||||
return config as T;
|
// File contents changed callback
|
||||||
|
// const watcher = watchFile(configPath, () => {
|
||||||
|
// this.logger.info(
|
||||||
|
// "Reloading config due to changes:",
|
||||||
|
// configPath
|
||||||
|
// );
|
||||||
|
// this.loadConfig(configPath, defaultConfig);
|
||||||
|
// });
|
||||||
|
|
||||||
|
// this.configCache.set(configPath, config);
|
||||||
|
|
||||||
|
// return this.getConfigProxy<T>(configPath);
|
||||||
|
return config;
|
||||||
} else {
|
} else {
|
||||||
// Write default config to disk and use that
|
// Write default config to disk and use that
|
||||||
//logger.warn(`Config file "${configPath}" not found, writing default config to disk`);
|
//logger.warn(`Config file "${configPath}" not found, writing default config to disk`);
|
||||||
writeConfig(configPath, defaultConfig);
|
this.writeConfig(configPath, defaultConfig);
|
||||||
return defaultConfig as T;
|
|
||||||
|
// File contents changed callback
|
||||||
|
// const watcher = watchFile(configPath, () => {
|
||||||
|
// this.logger.info(
|
||||||
|
// "Reloading config due to changes:",
|
||||||
|
// configPath
|
||||||
|
// );
|
||||||
|
// this.loadConfig(configPath, defaultConfig);
|
||||||
|
// });
|
||||||
|
|
||||||
|
// this.configCache.set(configPath, defaultConfig);
|
||||||
|
// return this.getConfigProxy<T>(configPath);
|
||||||
|
return defaultConfig;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,7 +113,7 @@ export function loadConfig<T>(configPath: string, defaultConfig: T): T {
|
||||||
* @param configPath
|
* @param configPath
|
||||||
* @param config
|
* @param config
|
||||||
*/
|
*/
|
||||||
export function writeConfig<T>(configPath: string, config: T) {
|
public static writeConfig<T>(configPath: string, config: T) {
|
||||||
// Write config to disk unconditionally
|
// Write config to disk unconditionally
|
||||||
writeFileSync(
|
writeFileSync(
|
||||||
configPath,
|
configPath,
|
||||||
|
@ -82,3 +122,30 @@ export function writeConfig<T>(configPath: string, config: T) {
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a proxy to a config (for updating config objects regardless of scope)
|
||||||
|
* @param configPath Path to config file
|
||||||
|
* @returns Config proxy object
|
||||||
|
*/
|
||||||
|
// protected static getConfigProxy<T>(configPath: string) {
|
||||||
|
// const self = this;
|
||||||
|
|
||||||
|
// return new Proxy(
|
||||||
|
// {},
|
||||||
|
// {
|
||||||
|
// get(_target: unknown, name: string) {
|
||||||
|
// // Get the updated in-memory version of the config
|
||||||
|
// const config = self.configCache.get(configPath) as T;
|
||||||
|
|
||||||
|
// if (config) {
|
||||||
|
// if (config.hasOwnProperty(name))
|
||||||
|
// return (config as Record<string, unknown>)[
|
||||||
|
// name
|
||||||
|
// ] as T[keyof T];
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// ) as T;
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,14 @@
|
||||||
|
import { getRoles, giveRole, removeRole } from "~/data/role";
|
||||||
import { ChannelList } from "../../channel/ChannelList";
|
import { ChannelList } from "../../channel/ChannelList";
|
||||||
import { deleteUser, getUsers } from "../../data/user";
|
import { deleteUser, getUsers } from "../../data/user";
|
||||||
import Command from "./Command";
|
import Command from "./Command";
|
||||||
|
import {
|
||||||
|
addRolePermission,
|
||||||
|
getRolePermissions,
|
||||||
|
loadDefaultPermissions,
|
||||||
|
removeAllRolePermissions,
|
||||||
|
removeRolePermission
|
||||||
|
} from "~/data/permission";
|
||||||
|
|
||||||
Command.addCommand(
|
Command.addCommand(
|
||||||
new Command(["help", "h", "commands", "cmds"], "help", msg => {
|
new Command(["help", "h", "commands", "cmds"], "help", msg => {
|
||||||
|
@ -76,6 +84,63 @@ Command.addCommand(
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Command.addCommand(
|
||||||
|
new Command(
|
||||||
|
["role"],
|
||||||
|
"role <add, remove, list> <user id> [role id]",
|
||||||
|
async msg => {
|
||||||
|
if (!msg.args[2])
|
||||||
|
return "role <add, remove, list> <user id> [role id]";
|
||||||
|
|
||||||
|
if (msg.args[1] === "add") {
|
||||||
|
if (!msg.args[3]) return "No role id provided";
|
||||||
|
await giveRole(msg.args[2], msg.args[3]);
|
||||||
|
return `Gave user ${msg.args[2]} role ${msg.args[3]}`;
|
||||||
|
} else if (msg.args[1] === "remove") {
|
||||||
|
if (!msg.args[3]) return "No role id provided";
|
||||||
|
await removeRole(msg.args[2], msg.args[3]);
|
||||||
|
return `Removed role ${msg.args[3]} from ${msg.args[2]}`;
|
||||||
|
} else if (msg.args[1] === "list") {
|
||||||
|
const roles = await getRoles(msg.args[2]);
|
||||||
|
return `Roles of ${msg.args[2]}: ${roles
|
||||||
|
.map(r => r.roleId)
|
||||||
|
.join(", ")}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
Command.addCommand(
|
||||||
|
new Command(
|
||||||
|
["perms"],
|
||||||
|
"perms <add, remove, list, clear> [role id] [permission]",
|
||||||
|
async msg => {
|
||||||
|
if (msg.args[1] === "add") {
|
||||||
|
if (!msg.args[3]) return "No permission provided";
|
||||||
|
await addRolePermission(msg.args[2], msg.args[3]);
|
||||||
|
return `Added permission ${msg.args[3]} to role ${msg.args[2]}`;
|
||||||
|
} else if (msg.args[1] === "remove") {
|
||||||
|
if (!msg.args[3]) return "No role id provided";
|
||||||
|
await removeRolePermission(msg.args[2], msg.args[3]);
|
||||||
|
return `Remove permission ${msg.args[3]} from role ${msg.args[2]}`;
|
||||||
|
} else if (msg.args[1] === "list") {
|
||||||
|
const perms = await getRolePermissions(msg.args[2]);
|
||||||
|
return `Permissions of ${msg.args[1]}: ${perms
|
||||||
|
.map(p => p.permission)
|
||||||
|
.join(", ")}`;
|
||||||
|
} else if (msg.args[1] === "clear") {
|
||||||
|
await removeAllRolePermissions(msg.args[2]);
|
||||||
|
if (msg.args[2]) {
|
||||||
|
return `Permissions of ${msg.args[2]} cleared`;
|
||||||
|
} else {
|
||||||
|
await loadDefaultPermissions();
|
||||||
|
return `All permissions reset`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
Command.addCommand(
|
Command.addCommand(
|
||||||
new Command(["js", "eval"], "js <code>", async msg => {
|
new Command(["js", "eval"], "js <code>", async msg => {
|
||||||
function roughSizeOfObject(object: any) {
|
function roughSizeOfObject(object: any) {
|
||||||
|
@ -87,16 +152,16 @@ Command.addCommand(
|
||||||
const value = stack.pop();
|
const value = stack.pop();
|
||||||
|
|
||||||
switch (typeof value) {
|
switch (typeof value) {
|
||||||
case 'boolean':
|
case "boolean":
|
||||||
bytes += 4;
|
bytes += 4;
|
||||||
break;
|
break;
|
||||||
case 'string':
|
case "string":
|
||||||
bytes += value.length * 2;
|
bytes += value.length * 2;
|
||||||
break;
|
break;
|
||||||
case 'number':
|
case "number":
|
||||||
bytes += 8;
|
bytes += 8;
|
||||||
break;
|
break;
|
||||||
case 'object':
|
case "object":
|
||||||
if (!objectList.includes(value)) {
|
if (!objectList.includes(value)) {
|
||||||
objectList.push(value);
|
objectList.push(value);
|
||||||
for (const prop in value) {
|
for (const prop in value) {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Socket } from "../ws/Socket";
|
import type { Socket } from "../ws/Socket";
|
||||||
|
|
||||||
declare type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
|
declare type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
|
||||||
|
|
||||||
|
@ -25,6 +25,7 @@ declare type UserFlags = Partial<{
|
||||||
|
|
||||||
type ChannelFlags = Partial<{
|
type ChannelFlags = Partial<{
|
||||||
limit: number;
|
limit: number;
|
||||||
|
owner_id: string;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
declare interface Tag {
|
declare interface Tag {
|
||||||
|
@ -57,7 +58,8 @@ declare type IChannelSettings = {
|
||||||
|
|
||||||
limit: number;
|
limit: number;
|
||||||
noindex: boolean;
|
noindex: boolean;
|
||||||
}>;
|
}> &
|
||||||
|
Record<string, unknown>;
|
||||||
|
|
||||||
declare type ChannelSettingValue = Partial<string | number | boolean>;
|
declare type ChannelSettingValue = Partial<string | number | boolean>;
|
||||||
|
|
||||||
|
@ -106,6 +108,13 @@ declare interface Crown {
|
||||||
}
|
}
|
||||||
|
|
||||||
declare interface ServerEvents {
|
declare interface ServerEvents {
|
||||||
|
hi: {
|
||||||
|
m: "hi";
|
||||||
|
token?: string;
|
||||||
|
login?: { type: string; code: string };
|
||||||
|
code?: string;
|
||||||
|
};
|
||||||
|
|
||||||
a: {
|
a: {
|
||||||
m: "a";
|
m: "a";
|
||||||
message: string;
|
message: string;
|
||||||
|
@ -133,13 +142,13 @@ declare interface ServerEvents {
|
||||||
|
|
||||||
custom: {
|
custom: {
|
||||||
m: "custom";
|
m: "custom";
|
||||||
data: any;
|
data: unknown;
|
||||||
target: CustomTarget;
|
target: CustomTarget;
|
||||||
};
|
};
|
||||||
|
|
||||||
devices: {
|
devices: {
|
||||||
m: "devices";
|
m: "devices";
|
||||||
list: any[];
|
list: unknown[];
|
||||||
};
|
};
|
||||||
|
|
||||||
dm: {
|
dm: {
|
||||||
|
@ -148,13 +157,6 @@ declare interface ServerEvents {
|
||||||
_id: string;
|
_id: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
hi: {
|
|
||||||
m: "hi";
|
|
||||||
token?: string;
|
|
||||||
login?: { type: string; code: string };
|
|
||||||
code?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
kickban: {
|
kickban: {
|
||||||
m: "kickban";
|
m: "kickban";
|
||||||
_id: string;
|
_id: string;
|
||||||
|
@ -207,7 +209,7 @@ declare interface ServerEvents {
|
||||||
"admin message": {
|
"admin message": {
|
||||||
m: "admin message";
|
m: "admin message";
|
||||||
password: string;
|
password: string;
|
||||||
msg: ServerEvents<keyof ServerEvents>;
|
msg: ServerEvents[keyof ServerEvents];
|
||||||
};
|
};
|
||||||
|
|
||||||
b: {
|
b: {
|
||||||
|
@ -244,7 +246,7 @@ declare interface ServerEvents {
|
||||||
text: string;
|
text: string;
|
||||||
color: string;
|
color: string;
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
clear_chat: {
|
clear_chat: {
|
||||||
m: "clear_chat";
|
m: "clear_chat";
|
||||||
|
@ -269,7 +271,7 @@ declare interface ServerEvents {
|
||||||
m: "ch_flag";
|
m: "ch_flag";
|
||||||
_id?: string;
|
_id?: string;
|
||||||
key: string;
|
key: string;
|
||||||
value: any;
|
value: unknown;
|
||||||
};
|
};
|
||||||
|
|
||||||
move: {
|
move: {
|
||||||
|
@ -277,23 +279,23 @@ declare interface ServerEvents {
|
||||||
ch: string;
|
ch: string;
|
||||||
_id?: string;
|
_id?: string;
|
||||||
set?: Partial<IChannelSettings>;
|
set?: Partial<IChannelSettings>;
|
||||||
}
|
};
|
||||||
|
|
||||||
rename_channel: {
|
rename_channel: {
|
||||||
m: "rename_channel";
|
m: "rename_channel";
|
||||||
_id: string;
|
_id: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
admin_chat: {
|
admin_chat: {
|
||||||
m: "admin_chat";
|
m: "admin_chat";
|
||||||
_id?: string;
|
_id?: string;
|
||||||
message: string;
|
message: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
eval: {
|
eval: {
|
||||||
m: "eval";
|
m: "eval";
|
||||||
str: string;
|
str: string;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
declare interface ClientEvents {
|
declare interface ClientEvents {
|
||||||
|
@ -323,7 +325,7 @@ declare interface ClientEvents {
|
||||||
|
|
||||||
custom: {
|
custom: {
|
||||||
m: "custom";
|
m: "custom";
|
||||||
data: any;
|
data: unknown;
|
||||||
p: string;
|
p: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -331,9 +333,9 @@ declare interface ClientEvents {
|
||||||
m: "hi";
|
m: "hi";
|
||||||
t: number;
|
t: number;
|
||||||
u: User;
|
u: User;
|
||||||
permissions: any;
|
permissions: unknown;
|
||||||
token?: any;
|
token?: string;
|
||||||
accountInfo: any;
|
accountInfo: unknown;
|
||||||
motd?: string;
|
motd?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -345,8 +347,8 @@ declare interface ClientEvents {
|
||||||
|
|
||||||
m: {
|
m: {
|
||||||
m: "m";
|
m: "m";
|
||||||
x: string;
|
x: string | number;
|
||||||
y: string;
|
y: string | number;
|
||||||
id: string;
|
id: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -393,11 +395,11 @@ declare interface ClientEvents {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
declare type ServerEventCallback<EventID extends keyof ServerEvents> = (msg: ServerEvents[EventID], socket: Socket) => Promise<void>;
|
type EventID = ServerEvents[keyof ServerEvents]["m"];
|
||||||
|
|
||||||
declare type ServerEventListener<EventID extends keyof ServerEvents> = {
|
declare type ServerEventListener<E extends EventID> = {
|
||||||
id: EventID;
|
id: E;
|
||||||
callback: ServerEventCallback<EventID>;
|
callback: (msg: ServerEvents[E], socket: Socket) => Promise<void>;
|
||||||
};
|
};
|
||||||
|
|
||||||
declare type Vector2<T = number> = {
|
declare type Vector2<T = number> = {
|
||||||
|
@ -426,3 +428,8 @@ declare interface IChannelInfo {
|
||||||
settings: Partial<IChannelSettings>;
|
settings: Partial<IChannelSettings>;
|
||||||
crown?: ICrown;
|
crown?: ICrown;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare interface IRole {
|
||||||
|
userId: string;
|
||||||
|
roleId: string;
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { loadConfig } from "./config";
|
import { ConfigManager } from "./config";
|
||||||
|
|
||||||
export const config = loadConfig("config/util.yml", {
|
export const config = ConfigManager.loadConfig("config/util.yml", {
|
||||||
enableLogFiles: true
|
enableLogFiles: true
|
||||||
});
|
});
|
||||||
|
|
149
src/ws/Socket.ts
149
src/ws/Socket.ts
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
import { createColor, createID, createUserID } from "../util/id";
|
import { createColor, createID, createUserID } from "../util/id";
|
||||||
import EventEmitter from "events";
|
import EventEmitter from "events";
|
||||||
import {
|
import type {
|
||||||
IChannelInfo,
|
IChannelInfo,
|
||||||
IChannelSettings,
|
IChannelSettings,
|
||||||
ClientEvents,
|
ClientEvents,
|
||||||
|
@ -23,15 +23,26 @@ import { eventGroups } from "./events";
|
||||||
import { Gateway } from "./Gateway";
|
import { Gateway } from "./Gateway";
|
||||||
import { Channel } from "../channel/Channel";
|
import { Channel } from "../channel/Channel";
|
||||||
import { ChannelList } from "../channel/ChannelList";
|
import { ChannelList } from "../channel/ChannelList";
|
||||||
import { ServerWebSocket } from "bun";
|
import type { ServerWebSocket } from "bun";
|
||||||
import { Logger } from "../util/Logger";
|
import { Logger } from "../util/Logger";
|
||||||
import { RateLimitConstructorList, RateLimitList } from "./ratelimit/config";
|
import type {
|
||||||
|
RateLimitConstructorList,
|
||||||
|
RateLimitList
|
||||||
|
} from "./ratelimit/config";
|
||||||
import { adminLimits } from "./ratelimit/limits/admin";
|
import { adminLimits } from "./ratelimit/limits/admin";
|
||||||
import { userLimits } from "./ratelimit/limits/user";
|
import { userLimits } from "./ratelimit/limits/user";
|
||||||
import { NoteQuota } from "./ratelimit/NoteQuota";
|
import { NoteQuota } from "./ratelimit/NoteQuota";
|
||||||
import { config } from "./usersConfig";
|
import { config } from "./usersConfig";
|
||||||
import { config as channelConfig } from "../channel/config";
|
import { config as channelConfig } from "../channel/config";
|
||||||
import { crownLimits } from "./ratelimit/limits/crown";
|
import { crownLimits } from "./ratelimit/limits/crown";
|
||||||
|
import type { RateLimit } from "./ratelimit/RateLimit";
|
||||||
|
import type { RateLimitChain } from "./ratelimit/RateLimitChain";
|
||||||
|
import {
|
||||||
|
getUserPermissions,
|
||||||
|
hasPermission,
|
||||||
|
validatePermission
|
||||||
|
} from "~/data/permission";
|
||||||
|
import { getRoles } from "~/data/role";
|
||||||
|
|
||||||
const logger = new Logger("Sockets");
|
const logger = new Logger("Sockets");
|
||||||
|
|
||||||
|
@ -77,7 +88,9 @@ export class Socket extends EventEmitter {
|
||||||
this.ip = ws.data.ip;
|
this.ip = ws.data.ip;
|
||||||
} else {
|
} else {
|
||||||
// Fake user
|
// Fake user
|
||||||
this.ip = `::ffff:${Math.random() * 255}.${Math.random() * 255}.${Math.random() * 255}.${Math.random() * 255}`;
|
this.ip = `::ffff:${Math.random() * 255}.${Math.random() * 255}.${
|
||||||
|
Math.random() * 255
|
||||||
|
}.${Math.random() * 255}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// User ID
|
// User ID
|
||||||
|
@ -86,13 +99,13 @@ export class Socket extends EventEmitter {
|
||||||
|
|
||||||
// Check if we're already connected
|
// Check if we're already connected
|
||||||
// We need to skip ourselves, so we loop here instead of using a helper
|
// We need to skip ourselves, so we loop here instead of using a helper
|
||||||
let foundSocket;
|
let foundSocket: Socket | undefined;
|
||||||
let count = 0;
|
let count = 0;
|
||||||
|
|
||||||
// big boi loop
|
// big boi loop
|
||||||
for (const socket of socketsBySocketID.values()) {
|
for (const socket of socketsByUUID.values()) {
|
||||||
// Skip us
|
// Skip us
|
||||||
if (socket.socketID == this.socketID) continue;
|
if (socket.socketID === this.socketID) continue;
|
||||||
|
|
||||||
// Are they real?
|
// Are they real?
|
||||||
if (socket.ws) {
|
if (socket.ws) {
|
||||||
|
@ -101,7 +114,7 @@ export class Socket extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Same user ID?
|
// Same user ID?
|
||||||
if (socket.getUserID() == this.getUserID()) {
|
if (socket.getUserID() === this.getUserID()) {
|
||||||
foundSocket = socket;
|
foundSocket = socket;
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
|
@ -142,13 +155,15 @@ export class Socket extends EventEmitter {
|
||||||
this.bindEventListeners();
|
this.bindEventListeners();
|
||||||
|
|
||||||
// Send a challenge to the browser for MPP.net frontends
|
// Send a challenge to the browser for MPP.net frontends
|
||||||
if (config.browserChallenge == "basic") {
|
if (config.browserChallenge === "basic") {
|
||||||
// Basic function
|
// Basic function
|
||||||
this.sendArray([{
|
this.sendArray([
|
||||||
|
{
|
||||||
m: "b",
|
m: "b",
|
||||||
code: `~return btoa(JSON.stringify([true, navigator.userAgent]));`
|
code: "~return btoa(JSON.stringify([true, navigator.userAgent]));"
|
||||||
}]);
|
}
|
||||||
} else if (config.browserChallenge == "obf") {
|
]);
|
||||||
|
} else if (config.browserChallenge === "obf") {
|
||||||
// Obfuscated challenge building
|
// Obfuscated challenge building
|
||||||
// TODO
|
// TODO
|
||||||
}
|
}
|
||||||
|
@ -186,15 +201,18 @@ export class Socket extends EventEmitter {
|
||||||
* Move this socket to a channel
|
* Move this socket to a channel
|
||||||
* @param _id Target channel ID
|
* @param _id Target channel ID
|
||||||
* @param set Channel settings, if the channel is instantiated
|
* @param set Channel settings, if the channel is instantiated
|
||||||
* @param force Whether to make this socket join regardless of channel properties
|
* @param f Whether to make this socket join regardless of channel properties
|
||||||
**/
|
**/
|
||||||
public setChannel(_id: string, set?: Partial<IChannelSettings>, force = false) {
|
public setChannel(_id: string, set?: Partial<IChannelSettings>, f = false) {
|
||||||
|
let desiredChannelID = _id;
|
||||||
|
let force = f;
|
||||||
|
|
||||||
// Do we exist?
|
// Do we exist?
|
||||||
if (this.isDestroyed()) return;
|
if (this.isDestroyed()) return;
|
||||||
// Are we trying to join the same channel like an idiot?
|
// Are we trying to join the same channel like an idiot?
|
||||||
if (this.currentChannelID === _id) return;
|
if (this.currentChannelID === desiredChannelID) return;
|
||||||
|
|
||||||
this.desiredChannel._id = _id;
|
this.desiredChannel._id = desiredChannelID;
|
||||||
this.desiredChannel.set = set;
|
this.desiredChannel.set = set;
|
||||||
|
|
||||||
let channel: Channel | undefined;
|
let channel: Channel | undefined;
|
||||||
|
@ -203,20 +221,20 @@ export class Socket extends EventEmitter {
|
||||||
//logger.debug("Desired:", this.desiredChannel._id, "| Matching:", channelConfig.lobbyBackdoor, ",", this.desiredChannel._id == channelConfig.lobbyBackdoor);
|
//logger.debug("Desired:", this.desiredChannel._id, "| Matching:", channelConfig.lobbyBackdoor, ",", this.desiredChannel._id == channelConfig.lobbyBackdoor);
|
||||||
|
|
||||||
// Are we joining the lobby backdoor?
|
// Are we joining the lobby backdoor?
|
||||||
if (this.desiredChannel._id == channelConfig.lobbyBackdoor) {
|
if (this.desiredChannel._id === channelConfig.lobbyBackdoor) {
|
||||||
// This is very likely not the original way the backdoor worked,
|
// This is very likely not the original way the backdoor worked,
|
||||||
// but considering the backdoor was changed sometime this decade
|
// but considering the backdoor was changed sometime this decade
|
||||||
// and the person who owns the original server is literally a
|
// and the person who owns the original server is literally a
|
||||||
// Chinese scammer, we don't really have much choice but to guess
|
// Chinese scammer, we don't really have much choice but to guess
|
||||||
// at this point, unless a screenshot descends from the heavens above
|
// at this point, unless a screenshot descends from the heavens above
|
||||||
// and magically gives us all the info we need and we can fix it here.
|
// and magically gives us all the info we need and we can fix it here.
|
||||||
_id = "lobby";
|
desiredChannelID = "lobby";
|
||||||
force = true;
|
force = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the first channel that matches the desired ID
|
// Find the first channel that matches the desired ID
|
||||||
for (const ch of ChannelList.getList()) {
|
for (const ch of ChannelList.getList()) {
|
||||||
if (ch.getID() == _id) {
|
if (ch.getID() === desiredChannelID) {
|
||||||
channel = ch;
|
channel = ch;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -259,7 +277,7 @@ export class Socket extends EventEmitter {
|
||||||
**/
|
**/
|
||||||
private bindEventListeners() {
|
private bindEventListeners() {
|
||||||
for (const group of eventGroups) {
|
for (const group of eventGroups) {
|
||||||
if (group.id == "admin") {
|
if (group.id === "admin") {
|
||||||
for (const event of group.eventList) {
|
for (const event of group.eventList) {
|
||||||
this.admin.on(event.id, event.callback);
|
this.admin.on(event.id, event.callback);
|
||||||
}
|
}
|
||||||
|
@ -382,9 +400,9 @@ export class Socket extends EventEmitter {
|
||||||
id: this.getParticipantID(),
|
id: this.getParticipantID(),
|
||||||
tag: config.enableTags ? tag : undefined
|
tag: config.enableTags ? tag : undefined
|
||||||
};
|
};
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private destroyed = false;
|
private destroyed = false;
|
||||||
|
@ -421,7 +439,7 @@ export class Socket extends EventEmitter {
|
||||||
* @returns Whether this socket is destroyed
|
* @returns Whether this socket is destroyed
|
||||||
**/
|
**/
|
||||||
public isDestroyed() {
|
public isDestroyed() {
|
||||||
return this.destroyed == true;
|
return this.destroyed === true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -442,12 +460,15 @@ export class Socket extends EventEmitter {
|
||||||
* @param x X coordinate
|
* @param x X coordinate
|
||||||
* @param y Y coordinate
|
* @param y Y coordinate
|
||||||
**/
|
**/
|
||||||
public setCursorPos(x: CursorValue, y: CursorValue) {
|
public setCursorPos(xpos: CursorValue, ypos: CursorValue) {
|
||||||
if (typeof x == "number") {
|
let x = xpos;
|
||||||
|
let y = ypos;
|
||||||
|
|
||||||
|
if (typeof x === "number") {
|
||||||
x = x.toFixed(2);
|
x = x.toFixed(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof y == "number") {
|
if (typeof y === "number") {
|
||||||
y = y.toFixed(2);
|
y = y.toFixed(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -462,7 +483,7 @@ export class Socket extends EventEmitter {
|
||||||
const part = this.getParticipant();
|
const part = this.getParticipant();
|
||||||
if (!part) return;
|
if (!part) return;
|
||||||
|
|
||||||
let pos = {
|
const pos = {
|
||||||
x: this.cursorPos.x,
|
x: this.cursorPos.x,
|
||||||
y: this.cursorPos.y,
|
y: this.cursorPos.y,
|
||||||
id: this.getParticipantID()
|
id: this.getParticipantID()
|
||||||
|
@ -476,7 +497,7 @@ export class Socket extends EventEmitter {
|
||||||
**/
|
**/
|
||||||
public getCurrentChannel() {
|
public getCurrentChannel() {
|
||||||
return ChannelList.getList().find(
|
return ChannelList.getList().find(
|
||||||
ch => ch.getID() == this.currentChannelID
|
ch => ch.getID() === this.currentChannelID
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -500,11 +521,7 @@ export class Socket extends EventEmitter {
|
||||||
* @param color Desired color
|
* @param color Desired color
|
||||||
* @param admin Whether to force this change
|
* @param admin Whether to force this change
|
||||||
**/
|
**/
|
||||||
public async userset(
|
public async userset(name?: string, color?: string, admin = false) {
|
||||||
name?: string,
|
|
||||||
color?: string,
|
|
||||||
admin: boolean = false
|
|
||||||
) {
|
|
||||||
let isColor = false;
|
let isColor = false;
|
||||||
|
|
||||||
// Color changing
|
// Color changing
|
||||||
|
@ -517,7 +534,7 @@ export class Socket extends EventEmitter {
|
||||||
if (name.length > 40) return;
|
if (name.length > 40) return;
|
||||||
|
|
||||||
await updateUser(this._id, {
|
await updateUser(this._id, {
|
||||||
name: typeof name == "string" ? name : undefined,
|
name: typeof name === "string" ? name : undefined,
|
||||||
color: color && isColor ? color : undefined
|
color: color && isColor ? color : undefined
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -526,8 +543,8 @@ export class Socket extends EventEmitter {
|
||||||
const ch = this.getCurrentChannel();
|
const ch = this.getCurrentChannel();
|
||||||
|
|
||||||
if (ch) {
|
if (ch) {
|
||||||
let part = this.getParticipant() as Participant;
|
const part = this.getParticipant() as Participant;
|
||||||
let cursorPos = this.getCursorPos();
|
const cursorPos = this.getCursorPos();
|
||||||
|
|
||||||
ch.sendArray([
|
ch.sendArray([
|
||||||
{
|
{
|
||||||
|
@ -555,11 +572,15 @@ export class Socket extends EventEmitter {
|
||||||
} as RateLimitList;
|
} as RateLimitList;
|
||||||
|
|
||||||
for (const key of Object.keys(list.normal)) {
|
for (const key of Object.keys(list.normal)) {
|
||||||
(this.rateLimits.normal as any)[key] = (list.normal as any)[key]();
|
(this.rateLimits.normal as Record<string, RateLimit>)[key] = (
|
||||||
|
list.normal as Record<string, () => RateLimit>
|
||||||
|
)[key]();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const key of Object.keys(list.chains)) {
|
for (const key of Object.keys(list.chains)) {
|
||||||
(this.rateLimits.chains as any)[key] = (list.chains as any)[key]();
|
(this.rateLimits.chains as Record<string, RateLimitChain>)[key] = (
|
||||||
|
list.chains as Record<string, () => RateLimitChain>
|
||||||
|
)[key]();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -569,7 +590,7 @@ export class Socket extends EventEmitter {
|
||||||
public resetRateLimits() {
|
public resetRateLimits() {
|
||||||
// TODO Permissions
|
// TODO Permissions
|
||||||
let isAdmin = false;
|
let isAdmin = false;
|
||||||
let ch = this.getCurrentChannel();
|
const ch = this.getCurrentChannel();
|
||||||
let hasNoteRateLimitBypass = false;
|
let hasNoteRateLimitBypass = false;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -581,7 +602,9 @@ export class Socket extends EventEmitter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.warn("Unable to get user flags while processing rate limits");
|
logger.warn(
|
||||||
|
"Unable to get user flags while processing rate limits"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isAdmin) {
|
if (isAdmin) {
|
||||||
|
@ -590,8 +613,8 @@ export class Socket extends EventEmitter {
|
||||||
} else if (this.isOwner()) {
|
} else if (this.isOwner()) {
|
||||||
this.setRateLimits(crownLimits);
|
this.setRateLimits(crownLimits);
|
||||||
this.setNoteQuota(NoteQuota.PARAMS_RIDICULOUS);
|
this.setNoteQuota(NoteQuota.PARAMS_RIDICULOUS);
|
||||||
} else if (ch && ch.isLobby()) {
|
} else if (ch?.isLobby()) {
|
||||||
this.setRateLimits(userLimits)
|
this.setRateLimits(userLimits);
|
||||||
this.setNoteQuota(NoteQuota.PARAMS_LOBBY);
|
this.setNoteQuota(NoteQuota.PARAMS_LOBBY);
|
||||||
} else {
|
} else {
|
||||||
this.setRateLimits(userLimits);
|
this.setRateLimits(userLimits);
|
||||||
|
@ -604,7 +627,7 @@ export class Socket extends EventEmitter {
|
||||||
* @param params Note quota params object
|
* @param params Note quota params object
|
||||||
**/
|
**/
|
||||||
public setNoteQuota(params = NoteQuota.PARAMS_NORMAL) {
|
public setNoteQuota(params = NoteQuota.PARAMS_NORMAL) {
|
||||||
this.noteQuota.setParams(params as any); // TODO why any
|
this.noteQuota.setParams(params); // TODO why any
|
||||||
|
|
||||||
// Send note quota to client
|
// Send note quota to client
|
||||||
this.sendArray([
|
this.sendArray([
|
||||||
|
@ -748,7 +771,8 @@ export class Socket extends EventEmitter {
|
||||||
* ```
|
* ```
|
||||||
**/
|
**/
|
||||||
public sendNotification(notif: Notification) {
|
public sendNotification(notif: Notification) {
|
||||||
this.sendArray([{
|
this.sendArray([
|
||||||
|
{
|
||||||
m: "notification",
|
m: "notification",
|
||||||
id: notif.id,
|
id: notif.id,
|
||||||
target: notif.target,
|
target: notif.target,
|
||||||
|
@ -757,7 +781,8 @@ export class Socket extends EventEmitter {
|
||||||
title: notif.title,
|
title: notif.title,
|
||||||
text: notif.text,
|
text: notif.text,
|
||||||
html: notif.html
|
html: notif.html
|
||||||
}]);
|
}
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -779,6 +804,7 @@ export class Socket extends EventEmitter {
|
||||||
**/
|
**/
|
||||||
public eval(str: string) {
|
public eval(str: string) {
|
||||||
try {
|
try {
|
||||||
|
// biome-ignore lint/security/noGlobalEval: configured
|
||||||
const output = eval(str);
|
const output = eval(str);
|
||||||
logger.info(output);
|
logger.info(output);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -801,16 +827,29 @@ export class Socket extends EventEmitter {
|
||||||
|
|
||||||
this.sendNotification({
|
this.sendNotification({
|
||||||
title: "Notice",
|
title: "Notice",
|
||||||
text: `You have been banned from the server for ${Math.floor(duration / 1000 / 60)} minutes. Reason: ${reason}`,
|
text: `You have been banned from the server for ${Math.floor(
|
||||||
|
duration / 1000 / 60
|
||||||
|
)} minutes. Reason: ${reason}`,
|
||||||
duration: 20000,
|
duration: 20000,
|
||||||
target: "#room",
|
target: "#room",
|
||||||
class: "classic"
|
class: "classic"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async hasPermission(perm: string) {
|
||||||
|
if (!this.user) return false;
|
||||||
|
|
||||||
|
const permissions = await getUserPermissions(this.user.id);
|
||||||
|
|
||||||
|
for (const permission of permissions) {
|
||||||
|
if (validatePermission(perm, permission)) return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const socketsBySocketID = new Map<string, Socket>();
|
export const socketsByUUID = new Map<Socket["uuid"], Socket>();
|
||||||
(globalThis as any).socketsBySocketID = socketsBySocketID;
|
// biome-ignore lint/suspicious/noExplicitAny: global access for console
|
||||||
|
(globalThis as any).socketsByUUID = socketsByUUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find a socket by their participant ID
|
* Find a socket by their participant ID
|
||||||
|
@ -819,8 +858,8 @@ export const socketsBySocketID = new Map<string, Socket>();
|
||||||
* @returns Socket object
|
* @returns Socket object
|
||||||
**/
|
**/
|
||||||
export function findSocketByPartID(id: string) {
|
export function findSocketByPartID(id: string) {
|
||||||
for (const socket of socketsBySocketID.values()) {
|
for (const socket of socketsByUUID.values()) {
|
||||||
if (socket.getParticipantID() == id) return socket;
|
if (socket.getParticipantID() === id) return socket;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -833,9 +872,9 @@ export function findSocketByPartID(id: string) {
|
||||||
export function findSocketsByUserID(_id: string) {
|
export function findSocketsByUserID(_id: string) {
|
||||||
const sockets = [];
|
const sockets = [];
|
||||||
|
|
||||||
for (const socket of socketsBySocketID.values()) {
|
for (const socket of socketsByUUID.values()) {
|
||||||
// logger.debug("User ID:", socket.getUserID());
|
// logger.debug("User ID:", socket.getUserID());
|
||||||
if (socket.getUserID() == _id) sockets.push(socket);
|
if (socket.getUserID() === _id) sockets.push(socket);
|
||||||
}
|
}
|
||||||
|
|
||||||
return sockets;
|
return sockets;
|
||||||
|
@ -848,8 +887,8 @@ export function findSocketsByUserID(_id: string) {
|
||||||
* @returns Socket object
|
* @returns Socket object
|
||||||
**/
|
**/
|
||||||
export function findSocketByIP(ip: string) {
|
export function findSocketByIP(ip: string) {
|
||||||
for (const socket of socketsBySocketID.values()) {
|
for (const socket of socketsByUUID.values()) {
|
||||||
if (socket.getIP() == ip) {
|
if (socket.getIP() === ip) {
|
||||||
return socket;
|
return socket;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,22 +1,22 @@
|
||||||
import { ServerEventListener, ServerEvents } from "../util/types";
|
import type { ServerEventListener, ServerEvents } from "../util/types";
|
||||||
|
|
||||||
export class EventGroup {
|
export class EventGroup {
|
||||||
public eventList = new Array<ServerEventListener<any>>();
|
public eventList = new Array<ServerEventListener<keyof ServerEvents>>();
|
||||||
constructor(public id: string) {}
|
constructor(public id: string) {}
|
||||||
|
|
||||||
public add(listener: ServerEventListener<any>) {
|
public add(listener: ServerEventListener<keyof ServerEvents>) {
|
||||||
this.eventList.push(listener);
|
this.eventList.push(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
public addMany(...listeners: ServerEventListener<any>[]) {
|
public addMany(...listeners: ServerEventListener<keyof ServerEvents>[]) {
|
||||||
listeners.forEach(l => this.add(l));
|
for (const l of listeners) this.add(l);
|
||||||
}
|
}
|
||||||
|
|
||||||
public remove(listener: ServerEventListener<any>) {
|
public remove(listener: ServerEventListener<keyof ServerEvents>) {
|
||||||
this.eventList.splice(this.eventList.indexOf(listener), 1);
|
this.eventList.splice(this.eventList.indexOf(listener), 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const eventGroups = new Array<EventGroup>();
|
export const eventGroups = new Array<EventGroup>();
|
||||||
|
|
||||||
import "./events.inc";
|
require("./events.inc");
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { ServerEventListener } from "../../../../util/types";
|
import { ServerEventListener } from "../../../../util/types";
|
||||||
import { socketsBySocketID } from "../../../Socket";
|
import { socketsByUUID } from "../../../Socket";
|
||||||
|
|
||||||
export const move: ServerEventListener<"move"> = {
|
export const move: ServerEventListener<"move"> = {
|
||||||
id: "move",
|
id: "move",
|
||||||
|
@ -15,7 +15,7 @@ export const move: ServerEventListener<"move"> = {
|
||||||
if (typeof set !== "object" && typeof set !== "undefined") return;
|
if (typeof set !== "object" && typeof set !== "undefined") return;
|
||||||
|
|
||||||
// Loop through every socket
|
// Loop through every socket
|
||||||
for (const sock of socketsBySocketID.values()) {
|
for (const sock of socketsByUUID.values()) {
|
||||||
// Check their user ID
|
// Check their user ID
|
||||||
if (sock.getUserID() == id) {
|
if (sock.getUserID() == id) {
|
||||||
// Forcefully move to channel
|
// Forcefully move to channel
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import { ChannelList } from "../../../../channel/ChannelList";
|
import { ChannelList } from "../../../../channel/ChannelList";
|
||||||
import { loadConfig } from "../../../../util/config";
|
import { ConfigManager } from "../../../../util/config";
|
||||||
import { ServerEventListener } from "../../../../util/types";
|
import { ServerEventListener } from "../../../../util/types";
|
||||||
import { socketsBySocketID } from "../../../Socket";
|
import { socketsByUUID } from "../../../Socket";
|
||||||
|
|
||||||
const config = loadConfig<{
|
const config = ConfigManager.loadConfig<{
|
||||||
allowXSS: boolean;
|
allowXSS: boolean;
|
||||||
maxDuration: number;
|
maxDuration: number;
|
||||||
defaultDuration: number;
|
defaultDuration: number;
|
||||||
|
@ -19,24 +19,32 @@ export const notification: ServerEventListener<"notification"> = {
|
||||||
id: "notification",
|
id: "notification",
|
||||||
callback: async (msg, socket) => {
|
callback: async (msg, socket) => {
|
||||||
// Send notification to user/channel
|
// Send notification to user/channel
|
||||||
if (typeof msg.targetChannel == "undefined" && typeof msg.targetUser == "undefined") return;
|
if (
|
||||||
|
typeof msg.targetChannel == "undefined" &&
|
||||||
|
typeof msg.targetUser == "undefined"
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
|
||||||
if (msg.duration) {
|
if (msg.duration) {
|
||||||
if (msg.duration > config.maxDuration) msg.duration = config.maxDuration;
|
if (msg.duration > config.maxDuration)
|
||||||
|
msg.duration = config.maxDuration;
|
||||||
} else {
|
} else {
|
||||||
msg.duration = config.defaultDuration;
|
msg.duration = config.defaultDuration;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof msg.targetChannel !== "undefined") {
|
if (typeof msg.targetChannel !== "undefined") {
|
||||||
for (const ch of ChannelList.getList().values()) {
|
for (const ch of ChannelList.getList().values()) {
|
||||||
if (ch.getID() == msg.targetChannel || msg.targetChannel == config.allTarget) {
|
if (
|
||||||
|
ch.getID() == msg.targetChannel ||
|
||||||
|
msg.targetChannel == config.allTarget
|
||||||
|
) {
|
||||||
ch.sendNotification(msg);
|
ch.sendNotification(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof msg.targetUser !== "undefined") {
|
if (typeof msg.targetUser !== "undefined") {
|
||||||
for (const socket of socketsBySocketID.values()) {
|
for (const socket of socketsByUUID.values()) {
|
||||||
if (socket.getUserID() == msg.targetUser) {
|
if (socket.getUserID() == msg.targetUser) {
|
||||||
socket.sendNotification(msg);
|
socket.sendNotification(msg);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { ChannelList } from "../../../../channel/ChannelList";
|
import { ChannelList } from "../../../../channel/ChannelList";
|
||||||
import { ServerEventListener } from "../../../../util/types";
|
import { ServerEventListener } from "../../../../util/types";
|
||||||
import { socketsBySocketID } from "../../../Socket";
|
import { socketsByUUID } from "../../../Socket";
|
||||||
|
|
||||||
export const rename_channel: ServerEventListener<"rename_channel"> = {
|
export const rename_channel: ServerEventListener<"rename_channel"> = {
|
||||||
id: "rename_channel",
|
id: "rename_channel",
|
||||||
|
@ -39,7 +39,7 @@ export const rename_channel: ServerEventListener<"rename_channel"> = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const sock of socketsBySocketID.values()) {
|
for (const sock of socketsByUUID.values()) {
|
||||||
// Are they in this channel?
|
// Are they in this channel?
|
||||||
if (sock.currentChannelID !== oldID) continue;
|
if (sock.currentChannelID !== oldID) continue;
|
||||||
// Move them forcefully
|
// Move them forcefully
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { readUser, updateUser } from "../../../../data/user";
|
import { readUser, updateUser } from "../../../../data/user";
|
||||||
import { ServerEventListener } from "../../../../util/types";
|
import { ServerEventListener } from "../../../../util/types";
|
||||||
import { findSocketsByUserID, socketsBySocketID } from "../../../Socket";
|
import { findSocketsByUserID, socketsByUUID } from "../../../Socket";
|
||||||
|
|
||||||
let timeout: Timer;
|
let timeout: Timer;
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ export const restart: ServerEventListener<"restart"> = {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Let everyone know
|
// Let everyone know
|
||||||
for (const sock of socketsBySocketID.values()) {
|
for (const sock of socketsByUUID.values()) {
|
||||||
sock.sendNotification({
|
sock.sendNotification({
|
||||||
id: "server-restart",
|
id: "server-restart",
|
||||||
target: "#piano",
|
target: "#piano",
|
||||||
|
|
|
@ -12,8 +12,11 @@ export const user_flag: ServerEventListener<"user_flag"> = {
|
||||||
// User flag modification (changing some real specific shit)
|
// User flag modification (changing some real specific shit)
|
||||||
if (typeof msg._id !== "string") return;
|
if (typeof msg._id !== "string") return;
|
||||||
if (typeof msg.key !== "string") return;
|
if (typeof msg.key !== "string") return;
|
||||||
if (typeof msg.remove !== "boolean" && typeof msg.value == "undefined") {
|
if (
|
||||||
return
|
typeof msg.remove !== "boolean" &&
|
||||||
|
typeof msg.value == "undefined"
|
||||||
|
) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// socket.getCurrentChannel()?.logger.debug(msg);
|
// socket.getCurrentChannel()?.logger.debug(msg);
|
||||||
|
@ -43,7 +46,5 @@ export const user_flag: ServerEventListener<"user_flag"> = {
|
||||||
for (const ch of ChannelList.getList()) {
|
for (const ch of ChannelList.getList()) {
|
||||||
ch.emit("user data update", user);
|
ch.emit("user data update", user);
|
||||||
}
|
}
|
||||||
|
|
||||||
// socket.getCurrentChannel()?.logger.debug("socks:", socks);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { ServerEventListener } from "../../../../util/types";
|
import type { ServerEventListener } from "~/util/types";
|
||||||
|
|
||||||
export const devices: ServerEventListener<"devices"> = {
|
export const devices: ServerEventListener<"devices"> = {
|
||||||
id: "devices",
|
id: "devices",
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
import { Logger } from "../../../../util/Logger";
|
import { getUserPermissions } from "~/data/permission";
|
||||||
import { getMOTD } from "../../../../util/motd";
|
import { Logger } from "~/util/Logger";
|
||||||
import { createToken, getToken, validateToken } from "../../../../util/token";
|
import { getMOTD } from "~/util/motd";
|
||||||
import { ClientEvents, ServerEventListener } from "../../../../util/types";
|
import { createToken, getToken, validateToken } from "~/util/token";
|
||||||
import { config } from "../../../usersConfig";
|
import type { ServerEventListener, ServerEvents } from "~/util/types";
|
||||||
|
import type { Socket } from "~/ws/Socket";
|
||||||
|
import { config, usersConfigPath } from "~/ws/usersConfig";
|
||||||
|
|
||||||
const logger = new Logger("Hi handler");
|
const logger = new Logger("Hi handler");
|
||||||
|
|
||||||
|
@ -16,7 +18,7 @@ export const hi: ServerEventListener<"hi"> = {
|
||||||
if (socket.gateway.hasProcessedHi) return;
|
if (socket.gateway.hasProcessedHi) return;
|
||||||
|
|
||||||
// Browser challenge
|
// Browser challenge
|
||||||
if (config.browserChallenge == "basic") {
|
if (config.browserChallenge === "basic") {
|
||||||
try {
|
try {
|
||||||
if (typeof msg.code !== "string") return;
|
if (typeof msg.code !== "string") return;
|
||||||
const code = atob(msg.code);
|
const code = atob(msg.code);
|
||||||
|
@ -30,14 +32,21 @@ export const hi: ServerEventListener<"hi"> = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.warn("Unable to parse basic browser challenge code:", err);
|
logger.warn(
|
||||||
|
"Unable to parse basic browser challenge code:",
|
||||||
|
err
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else if (config.browserChallenge == "obf") {
|
} else if (config.browserChallenge === "obf") {
|
||||||
// TODO
|
// TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
// Is the browser challenge enabled and has the user completed it?
|
// Is the browser challenge enabled and has the user completed it?
|
||||||
if (config.browserChallenge !== "none" && !socket.gateway.hasCompletedBrowserChallenge) return socket.ban(60000, "Browser challenge not completed");
|
if (
|
||||||
|
config.browserChallenge !== "none" &&
|
||||||
|
!socket.gateway.hasCompletedBrowserChallenge
|
||||||
|
)
|
||||||
|
return socket.ban(60000, "Browser challenge not completed");
|
||||||
|
|
||||||
let token: string | undefined;
|
let token: string | undefined;
|
||||||
let generatedToken = false;
|
let generatedToken = false;
|
||||||
|
@ -50,18 +59,26 @@ export const hi: ServerEventListener<"hi"> = {
|
||||||
token = await getToken(socket.getUserID());
|
token = await getToken(socket.getUserID());
|
||||||
if (typeof token !== "string") {
|
if (typeof token !== "string") {
|
||||||
// Generate a new one
|
// Generate a new one
|
||||||
token = await createToken(socket.getUserID(), socket.gateway);
|
token = await createToken(
|
||||||
|
socket.getUserID(),
|
||||||
|
socket.gateway
|
||||||
|
);
|
||||||
socket.gateway.isTokenValid = true;
|
socket.gateway.isTokenValid = true;
|
||||||
|
|
||||||
if (typeof token !== "string") {
|
if (typeof token !== "string") {
|
||||||
logger.warn(`Unable to generate token for user ${socket.getUserID()}`);
|
logger.warn(
|
||||||
|
`Unable to generate token for user ${socket.getUserID()}`
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
generatedToken = true;
|
generatedToken = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Validate the token
|
// Validate the token
|
||||||
const valid = await validateToken(socket.getUserID(), msg.token);
|
const valid = await validateToken(
|
||||||
|
socket.getUserID(),
|
||||||
|
msg.token
|
||||||
|
);
|
||||||
if (!valid) {
|
if (!valid) {
|
||||||
//socket.ban(60000, "Invalid token");
|
//socket.ban(60000, "Invalid token");
|
||||||
//return;
|
//return;
|
||||||
|
@ -86,20 +103,27 @@ export const hi: ServerEventListener<"hi"> = {
|
||||||
|
|
||||||
//logger.debug("Tag:", part.tag);
|
//logger.debug("Tag:", part.tag);
|
||||||
|
|
||||||
socket.sendArray([{
|
const permissions: Record<string, boolean> = {};
|
||||||
|
(await getUserPermissions(socket.getUserID())).map(perm => {
|
||||||
|
permissions[perm] = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.sendArray([
|
||||||
|
{
|
||||||
m: "hi",
|
m: "hi",
|
||||||
accountInfo: undefined,
|
accountInfo: undefined,
|
||||||
permissions: undefined,
|
permissions,
|
||||||
t: Date.now(),
|
t: Date.now(),
|
||||||
u: {
|
u: {
|
||||||
_id: part._id,
|
_id: part._id,
|
||||||
color: part.color,
|
color: part.color,
|
||||||
name: part.name,
|
name: part.name,
|
||||||
tag: part.tag
|
tag: config.enableTags ? part.tag : undefined
|
||||||
},
|
},
|
||||||
motd: getMOTD(),
|
motd: getMOTD(),
|
||||||
token
|
token
|
||||||
}]);
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
socket.gateway.hasProcessedHi = true;
|
socket.gateway.hasProcessedHi = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { Logger } from "../util/Logger";
|
import { Logger } from "../util/Logger";
|
||||||
import { Socket } from "./Socket";
|
import type { Socket } from "./Socket";
|
||||||
import { hasOwn } from "../util/helpers";
|
import { hasOwn } from "../util/helpers";
|
||||||
|
|
||||||
// const logger = new Logger("Message Handler");
|
// const logger = new Logger("Message Handler");
|
||||||
|
|
|
@ -35,15 +35,15 @@ export class NoteQuota {
|
||||||
params: {
|
params: {
|
||||||
allowance: number;
|
allowance: number;
|
||||||
max: number;
|
max: number;
|
||||||
maxHistLen: number;
|
maxHistLen?: number;
|
||||||
} = NoteQuota.PARAMS_OFFLINE
|
} = NoteQuota.PARAMS_OFFLINE
|
||||||
) {
|
) {
|
||||||
let allowance: number =
|
const allowance: number =
|
||||||
params.allowance ||
|
params.allowance ||
|
||||||
this.allowance ||
|
this.allowance ||
|
||||||
NoteQuota.PARAMS_OFFLINE.allowance;
|
NoteQuota.PARAMS_OFFLINE.allowance;
|
||||||
let max = params.max || this.max || NoteQuota.PARAMS_OFFLINE.max;
|
const max = params.max || this.max || NoteQuota.PARAMS_OFFLINE.max;
|
||||||
let maxHistLen =
|
const maxHistLen =
|
||||||
params.maxHistLen ||
|
params.maxHistLen ||
|
||||||
this.maxHistLen ||
|
this.maxHistLen ||
|
||||||
NoteQuota.PARAMS_OFFLINE.maxHistLen;
|
NoteQuota.PARAMS_OFFLINE.maxHistLen;
|
||||||
|
@ -90,19 +90,18 @@ export class NoteQuota {
|
||||||
|
|
||||||
public spend(needed: number) {
|
public spend(needed: number) {
|
||||||
let sum = 0;
|
let sum = 0;
|
||||||
|
let numNeeded = needed;
|
||||||
|
|
||||||
for (const i in this.history) {
|
for (const i in this.history) {
|
||||||
sum += this.history[i];
|
sum += this.history[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sum <= 0) needed *= this.allowance;
|
if (sum <= 0) numNeeded *= this.allowance;
|
||||||
|
if (this.points < numNeeded) return false;
|
||||||
|
|
||||||
if (this.points < needed) {
|
this.points -= numNeeded;
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
this.points -= needed;
|
|
||||||
if (this.cb) this.cb(this.points);
|
if (this.cb) this.cb(this.points);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
// Thank you Brandon for this thing
|
// Thank you Brandon for this thing
|
||||||
export class RateLimit {
|
export class RateLimit {
|
||||||
public after: number = 0;
|
public after = 0;
|
||||||
constructor(private interval_ms: number = 0) {}
|
constructor(private interval_ms = 0) {}
|
||||||
|
|
||||||
public attempt(time: number = Date.now()) {
|
public attempt(time = Date.now()) {
|
||||||
if (time < this.after) return false;
|
if (time < this.after) return false;
|
||||||
|
|
||||||
this.after = time + this.interval_ms;
|
this.after = time + this.interval_ms;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { loadConfig } from "../../util/config";
|
import { ConfigManager } from "../../util/config";
|
||||||
import { RateLimit } from "./RateLimit";
|
import type { RateLimit } from "./RateLimit";
|
||||||
import { RateLimitChain } from "./RateLimitChain";
|
import type { RateLimitChain } from "./RateLimitChain";
|
||||||
|
|
||||||
export interface RateLimitConfigList<
|
export interface RateLimitConfigList<
|
||||||
RL = number,
|
RL = number,
|
||||||
|
@ -44,7 +44,9 @@ export interface RateLimitsConfig {
|
||||||
admin: RateLimitConfigList;
|
admin: RateLimitConfigList;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const config = loadConfig<RateLimitsConfig>("config/ratelimits.yml", {
|
export const config = ConfigManager.loadConfig<RateLimitsConfig>(
|
||||||
|
"config/ratelimits.yml",
|
||||||
|
{
|
||||||
user: {
|
user: {
|
||||||
normal: {
|
normal: {
|
||||||
a: 6000 / 4,
|
a: 6000 / 4,
|
||||||
|
@ -141,4 +143,5 @@ export const config = loadConfig<RateLimitsConfig>("config/ratelimits.yml", {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { RateLimit } from "../RateLimit";
|
import { RateLimit } from "../RateLimit";
|
||||||
import { RateLimitChain } from "../RateLimitChain";
|
import { RateLimitChain } from "../RateLimitChain";
|
||||||
import { RateLimitConstructorList, config } from "../config";
|
import { type RateLimitConstructorList, config } from "../config";
|
||||||
|
|
||||||
export const adminLimits: RateLimitConstructorList = {
|
export const adminLimits: RateLimitConstructorList = {
|
||||||
normal: {
|
normal: {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { RateLimit } from "../RateLimit";
|
import { RateLimit } from "../RateLimit";
|
||||||
import { RateLimitChain } from "../RateLimitChain";
|
import { RateLimitChain } from "../RateLimitChain";
|
||||||
import { RateLimitConstructorList, config } from "../config";
|
import { type RateLimitConstructorList, config } from "../config";
|
||||||
|
|
||||||
export const crownLimits: RateLimitConstructorList = {
|
export const crownLimits: RateLimitConstructorList = {
|
||||||
normal: {
|
normal: {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { RateLimit } from "../RateLimit";
|
import { RateLimit } from "../RateLimit";
|
||||||
import { RateLimitChain } from "../RateLimitChain";
|
import { RateLimitChain } from "../RateLimitChain";
|
||||||
import { RateLimitConstructorList, config } from "../config";
|
import { type RateLimitConstructorList, config } from "../config";
|
||||||
|
|
||||||
export const userLimits: RateLimitConstructorList = {
|
export const userLimits: RateLimitConstructorList = {
|
||||||
normal: {
|
normal: {
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import { Logger } from "../util/Logger";
|
import { Logger } from "../util/Logger";
|
||||||
import { createSocketID, createUserID } from "../util/id";
|
import { createSocketID, createUserID } from "../util/id";
|
||||||
import fs from "fs";
|
import fs from "node:fs";
|
||||||
import path from "path";
|
import path from "node:path";
|
||||||
import { handleMessage } from "./message";
|
import { handleMessage } from "./message";
|
||||||
import { Socket, socketsBySocketID } from "./Socket";
|
import { Socket, socketsByUUID } from "./Socket";
|
||||||
import env from "../util/env";
|
import env from "../util/env";
|
||||||
import { getMOTD } from "../util/motd";
|
import { getMOTD } from "../util/motd";
|
||||||
import nunjucks from "nunjucks";
|
import nunjucks from "nunjucks";
|
||||||
import { metrics } from "../util/metrics";
|
import type { ServerWebSocket } from "bun";
|
||||||
|
|
||||||
const logger = new Logger("WebSocket Server");
|
const logger = new Logger("WebSocket Server");
|
||||||
|
|
||||||
|
@ -38,6 +38,8 @@ async function getIndex() {
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ServerWebSocketMPP = ServerWebSocket<{ ip: string, socket: Socket }>
|
||||||
|
|
||||||
export const app = Bun.serve<{ ip: string }>({
|
export const app = Bun.serve<{ ip: string }>({
|
||||||
port: env.PORT,
|
port: env.PORT,
|
||||||
hostname: "0.0.0.0",
|
hostname: "0.0.0.0",
|
||||||
|
@ -78,31 +80,31 @@ export const app = Bun.serve<{ ip: string }>({
|
||||||
// Return the file
|
// Return the file
|
||||||
if (data) {
|
if (data) {
|
||||||
return new Response(data);
|
return new Response(data);
|
||||||
} else {
|
}
|
||||||
|
|
||||||
return getIndex();
|
return getIndex();
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// Return the index file, since it's a channel name or something
|
// Return the index file, since it's a channel name or something
|
||||||
return getIndex();
|
return getIndex();
|
||||||
}
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// Return the index file as a coverup of our extreme failure
|
// Return the index file as a coverup of our extreme failure
|
||||||
return getIndex();
|
return getIndex();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
websocket: {
|
websocket: {
|
||||||
open: ws => {
|
open: (ws: ServerWebSocketMPP) => {
|
||||||
// swimming in the pool
|
// swimming in the pool
|
||||||
const socket = new Socket(ws, createSocketID());
|
const socket = new Socket(ws, createSocketID());
|
||||||
|
|
||||||
(ws as unknown as any).socket = socket;
|
ws.data.socket = socket;
|
||||||
// logger.debug("Connection at " + socket.getIP());
|
// logger.debug("Connection at " + socket.getIP());
|
||||||
|
|
||||||
if (socket.socketID == undefined) {
|
if (socket.socketID === undefined) {
|
||||||
socket.socketID = createSocketID();
|
socket.socketID = createSocketID();
|
||||||
}
|
}
|
||||||
|
|
||||||
socketsBySocketID.set(socket.socketID, socket);
|
socketsByUUID.set(socket.getUUID(), socket);
|
||||||
|
|
||||||
const ip = socket.getIP();
|
const ip = socket.getIP();
|
||||||
|
|
||||||
|
@ -121,29 +123,29 @@ export const app = Bun.serve<{ ip: string }>({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
message: (ws, message) => {
|
message: (ws: ServerWebSocketMPP, message: string) => {
|
||||||
// Fucking string
|
// Fucking string
|
||||||
const msg = message.toString();
|
const msg = message.toString();
|
||||||
|
|
||||||
// Let's find out wtf they even sent
|
// Let's find out wtf they even sent
|
||||||
handleMessage((ws as unknown as any).socket, msg);
|
handleMessage(ws.data.socket, msg);
|
||||||
},
|
},
|
||||||
|
|
||||||
close: (ws, code, message) => {
|
close: (ws: ServerWebSocketMPP, code, message) => {
|
||||||
// This usually gets called when someone leaves,
|
// This usually gets called when someone leaves,
|
||||||
// but it's also used internally just in case
|
// but it's also used internally just in case
|
||||||
// some dickhead can't close their tab like a
|
// some dickhead can't close their tab like a
|
||||||
// normal person.
|
// normal person.
|
||||||
|
|
||||||
const socket = (ws as unknown as any).socket as Socket;
|
const socket = ws.data.socket as Socket;
|
||||||
if (socket) {
|
if (socket) {
|
||||||
socket.destroy();
|
socket.destroy();
|
||||||
|
|
||||||
for (const sockID of socketsBySocketID.keys()) {
|
for (const sockID of socketsByUUID.keys()) {
|
||||||
const sock = socketsBySocketID.get(sockID);
|
const sock = socketsByUUID.get(sockID);
|
||||||
|
|
||||||
if (sock == socket) {
|
if (sock === socket) {
|
||||||
socketsBySocketID.delete(sockID);
|
socketsByUUID.delete(sockID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { loadConfig } from "../util/config";
|
import { ConfigManager } from "../util/config";
|
||||||
import { Participant, UserFlags } from "../util/types";
|
import type { Participant, UserFlags } from "../util/types";
|
||||||
|
|
||||||
export interface UsersConfig {
|
export interface UsersConfig {
|
||||||
defaultName: string;
|
defaultName: string;
|
||||||
|
@ -45,7 +45,7 @@ export const defaultUsersConfig: UsersConfig = {
|
||||||
// Not dealing with it. The code somehow runs, and I'm not
|
// Not dealing with it. The code somehow runs, and I'm not
|
||||||
// going to fuck with the order of loading things until bun
|
// going to fuck with the order of loading things until bun
|
||||||
// pushes an update and fucks all this stuff up.
|
// pushes an update and fucks all this stuff up.
|
||||||
export const config = loadConfig<UsersConfig>(
|
export const config = ConfigManager.loadConfig<UsersConfig>(
|
||||||
usersConfigPath,
|
usersConfigPath,
|
||||||
defaultUsersConfig
|
defaultUsersConfig
|
||||||
);
|
);
|
||||||
|
|
|
@ -36,6 +36,9 @@
|
||||||
"moduleResolution": "Bundler",
|
"moduleResolution": "Bundler",
|
||||||
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||||
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||||
|
"paths": {
|
||||||
|
"~/*": ["./src/*"]
|
||||||
|
},
|
||||||
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||||
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
|
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
|
||||||
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
|
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
|
||||||
|
|
Loading…
Reference in New Issue