Add note and time messages
This commit is contained in:
parent
e9407a5fee
commit
66c7560b54
|
@ -2,3 +2,4 @@ defaultName: "Anonymous"
|
||||||
defaultFlags:
|
defaultFlags:
|
||||||
volume: 100
|
volume: 100
|
||||||
enableColorChanging: false
|
enableColorChanging: false
|
||||||
|
enableCustomNoteData: true
|
||||||
|
|
|
@ -103,6 +103,7 @@ export class Channel extends EventEmitter {
|
||||||
set: Partial<ChannelSettings>,
|
set: Partial<ChannelSettings>,
|
||||||
admin: boolean = false
|
admin: boolean = false
|
||||||
) {
|
) {
|
||||||
|
if (this.isDestroyed()) return;
|
||||||
if (!admin) {
|
if (!admin) {
|
||||||
if (set.lobby) set.lobby = undefined;
|
if (set.lobby) set.lobby = undefined;
|
||||||
if (set.owner_id) set.owner_id = undefined;
|
if (set.owner_id) set.owner_id = undefined;
|
||||||
|
@ -125,6 +126,7 @@ export class Channel extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
public join(socket: Socket) {
|
public join(socket: Socket) {
|
||||||
|
if (this.isDestroyed()) return;
|
||||||
const part = socket.getParticipant() as Participant;
|
const part = socket.getParticipant() as Participant;
|
||||||
|
|
||||||
// Unknown side-effects, but for type safety...
|
// Unknown side-effects, but for type safety...
|
||||||
|
@ -289,6 +291,7 @@ export class Channel extends EventEmitter {
|
||||||
this.alreadyBound = true;
|
this.alreadyBound = true;
|
||||||
|
|
||||||
this.on("update", () => {
|
this.on("update", () => {
|
||||||
|
// Send updated info
|
||||||
for (const socket of socketsBySocketID.values()) {
|
for (const socket of socketsBySocketID.values()) {
|
||||||
for (const p of this.ppl) {
|
for (const p of this.ppl) {
|
||||||
if (socket.getParticipantID() == p.id) {
|
if (socket.getParticipantID() == p.id) {
|
||||||
|
@ -299,6 +302,10 @@ export class Channel extends EventEmitter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.ppl.length == 0) {
|
||||||
|
this.destroy();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.on("message", (msg: ServerEvents["a"], socket: Socket) => {
|
this.on("message", (msg: ServerEvents["a"], socket: Socket) => {
|
||||||
|
@ -315,6 +322,53 @@ export class Channel extends EventEmitter {
|
||||||
this.chatHistory.push(outgoing);
|
this.chatHistory.push(outgoing);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public playNotes(msg: ServerEvents["n"], socket: Socket) {
|
||||||
|
if (this.isDestroyed()) return;
|
||||||
|
const part = socket.getParticipant();
|
||||||
|
if (!part) return;
|
||||||
|
|
||||||
|
let clientMsg: ClientEvents["n"] = {
|
||||||
|
m: "n",
|
||||||
|
n: msg.n,
|
||||||
|
t: msg.t,
|
||||||
|
p: part.id
|
||||||
|
};
|
||||||
|
|
||||||
|
let sentSocketIDs = new Array<string>();
|
||||||
|
|
||||||
|
for (const p of this.ppl) {
|
||||||
|
socketLoop: for (const socket of socketsBySocketID.values()) {
|
||||||
|
if (socket.isDestroyed()) continue socketLoop;
|
||||||
|
if (socket.getParticipantID() != p.id) continue socketLoop;
|
||||||
|
if (socket.getParticipantID() == part.id) continue socketLoop;
|
||||||
|
if (sentSocketIDs.includes(socket.socketID))
|
||||||
|
continue socketLoop;
|
||||||
|
socket.sendArray([clientMsg]);
|
||||||
|
sentSocketIDs.push(socket.socketID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private destroyed = false;
|
||||||
|
|
||||||
|
public destroy() {
|
||||||
|
if (this.destroyed) return;
|
||||||
|
this.destroyed = true;
|
||||||
|
|
||||||
|
if (this.ppl.length > 0) {
|
||||||
|
for (const socket of socketsBySocketID.values()) {
|
||||||
|
if (socket.currentChannelID !== this.getID()) continue;
|
||||||
|
socket.setChannel(config.fullChannel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
channelList.splice(channelList.indexOf(this), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public isDestroyed() {
|
||||||
|
return this.destroyed == true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Forceloader
|
// Forceloader
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
import YAML from "yaml";
|
import YAML from "yaml";
|
||||||
import { readFileSync } from "fs";
|
import fs from "fs";
|
||||||
|
|
||||||
export function loadConfig<T>(filepath: string, def: T) {
|
export function loadConfig<T>(filepath: string, def: T) {
|
||||||
try {
|
try {
|
||||||
const data = readFileSync(filepath).toString();
|
const data = fs.readFileSync(filepath).toString();
|
||||||
const parsed = YAML.parse(data);
|
const parsed = YAML.parse(data);
|
||||||
|
|
||||||
return parsed as T;
|
return parsed as T;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Unable to load config:", err);
|
console.error("Unable to load config:", err);
|
||||||
|
|
|
@ -313,7 +313,7 @@ declare interface ClientEvents {
|
||||||
t: {
|
t: {
|
||||||
m: "t";
|
m: "t";
|
||||||
t: number;
|
t: number;
|
||||||
e: number;
|
e: number | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
bye: {
|
bye: {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
export class Gateway {
|
export class Gateway {
|
||||||
public hasProcessedHi: boolean = false;
|
public hasProcessedHi: boolean = false;
|
||||||
public hasSentDevices: boolean = false;
|
public hasSentDevices: boolean = false;
|
||||||
|
public lastPing: number = Date.now();
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,12 +5,12 @@ import {
|
||||||
ChannelSettings,
|
ChannelSettings,
|
||||||
ClientEvents,
|
ClientEvents,
|
||||||
Participant,
|
Participant,
|
||||||
|
ServerEvents,
|
||||||
UserFlags
|
UserFlags
|
||||||
} from "../util/types";
|
} from "../util/types";
|
||||||
import { User } from "@prisma/client";
|
import { User } from "@prisma/client";
|
||||||
import { createUser, readUser, updateUser } from "../data/user";
|
import { createUser, readUser, updateUser } from "../data/user";
|
||||||
import { eventGroups } from "./events";
|
import { eventGroups } from "./events";
|
||||||
import { loadConfig } from "../util/config";
|
|
||||||
import { Gateway } from "./Gateway";
|
import { Gateway } from "./Gateway";
|
||||||
import { Channel, channelList } from "../channel/Channel";
|
import { Channel, channelList } from "../channel/Channel";
|
||||||
import { ServerWebSocket } from "bun";
|
import { ServerWebSocket } from "bun";
|
||||||
|
@ -20,20 +20,7 @@ import { 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";
|
||||||
interface UsersConfig {
|
|
||||||
defaultName: string;
|
|
||||||
defaultFlags: UserFlags;
|
|
||||||
enableColorChanging: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const usersConfig = loadConfig<UsersConfig>("config/users.yml", {
|
|
||||||
defaultName: "Anonymous",
|
|
||||||
defaultFlags: {
|
|
||||||
volume: 100
|
|
||||||
},
|
|
||||||
enableColorChanging: false
|
|
||||||
});
|
|
||||||
|
|
||||||
const logger = new Logger("Sockets");
|
const logger = new Logger("Sockets");
|
||||||
|
|
||||||
|
@ -46,7 +33,7 @@ export class Socket extends EventEmitter {
|
||||||
public gateway = new Gateway();
|
public gateway = new Gateway();
|
||||||
|
|
||||||
public rateLimits: RateLimitList | undefined;
|
public rateLimits: RateLimitList | undefined;
|
||||||
public noteQuota = new NoteQuota(this.onQuota);
|
public noteQuota = new NoteQuota();
|
||||||
|
|
||||||
public desiredChannel: {
|
public desiredChannel: {
|
||||||
_id: string | undefined;
|
_id: string | undefined;
|
||||||
|
@ -176,9 +163,9 @@ export class Socket extends EventEmitter {
|
||||||
if (!user) {
|
if (!user) {
|
||||||
await createUser(
|
await createUser(
|
||||||
this._id,
|
this._id,
|
||||||
usersConfig.defaultName,
|
config.defaultName,
|
||||||
createColor(this.ip),
|
createColor(this.ip),
|
||||||
usersConfig.defaultFlags
|
config.defaultFlags
|
||||||
);
|
);
|
||||||
|
|
||||||
user = await readUser(this._id);
|
user = await readUser(this._id);
|
||||||
|
@ -316,7 +303,7 @@ export class Socket extends EventEmitter {
|
||||||
public async userset(name?: string, color?: string) {
|
public async userset(name?: string, color?: string) {
|
||||||
let isColor = false;
|
let isColor = false;
|
||||||
|
|
||||||
if (color && usersConfig.enableColorChanging) {
|
if (color && config.enableColorChanging) {
|
||||||
isColor =
|
isColor =
|
||||||
typeof color === "string" && !!color.match(/^#[0-9a-f]{6}$/i);
|
typeof color === "string" && !!color.match(/^#[0-9a-f]{6}$/i);
|
||||||
}
|
}
|
||||||
|
@ -373,5 +360,9 @@ export class Socket extends EventEmitter {
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public onQuota(points: number) {}
|
public playNotes(msg: ServerEvents["n"]) {
|
||||||
|
const ch = this.getCurrentChannel();
|
||||||
|
if (!ch) return;
|
||||||
|
ch.playNotes(msg, this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
import { ServerEventListener } from "../../../../util/types";
|
||||||
|
import { config } from "../../../usersConfig";
|
||||||
|
|
||||||
|
export const n: ServerEventListener<"n"> = {
|
||||||
|
id: "n",
|
||||||
|
callback: (msg, socket) => {
|
||||||
|
// Piano note
|
||||||
|
if (!Array.isArray(msg.n)) return;
|
||||||
|
if (typeof msg.t !== "number") return;
|
||||||
|
|
||||||
|
// Check note properties
|
||||||
|
for (const n of msg.n) {
|
||||||
|
if (typeof n.n != "string") return;
|
||||||
|
|
||||||
|
// TODO Check for config.enableCustomNoteData here
|
||||||
|
// For whatever reason, Bun likes to crash when we access that config object
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (n.s) {
|
||||||
|
if (typeof n.s !== "number") return;
|
||||||
|
if (n.s == 1) {
|
||||||
|
if (typeof n.d !== "number") return;
|
||||||
|
if (n.d < 0 || n.d > 200) return;
|
||||||
|
|
||||||
|
if (typeof n.v !== "number") return;
|
||||||
|
if (n.v < 0) n.v = 0;
|
||||||
|
if (n.v > 1) n.v = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let amount = msg.n.length;
|
||||||
|
|
||||||
|
// TODO Check crownsolo
|
||||||
|
if (socket.noteQuota.spend(amount)) {
|
||||||
|
socket.playNotes(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { ServerEventListener } from "../../../../util/types";
|
||||||
|
|
||||||
|
export const t: ServerEventListener<"t"> = {
|
||||||
|
id: "t",
|
||||||
|
callback: (msg, socket) => {
|
||||||
|
// Ping
|
||||||
|
if (msg.e) {
|
||||||
|
if (typeof msg.e !== "number") return;
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.sendArray([
|
||||||
|
{
|
||||||
|
m: "t",
|
||||||
|
t: Date.now(),
|
||||||
|
e: typeof msg.e == "number" ? msg.e : undefined
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
};
|
|
@ -8,6 +8,7 @@ import { ch } from "./handlers/ch";
|
||||||
import { m } from "./handlers/m";
|
import { m } from "./handlers/m";
|
||||||
import { a } from "./handlers/a";
|
import { a } from "./handlers/a";
|
||||||
import { userset } from "./handlers/userset";
|
import { userset } from "./handlers/userset";
|
||||||
|
import { n } from "./handlers/n";
|
||||||
|
|
||||||
EVENTGROUP_USER.add(hi);
|
EVENTGROUP_USER.add(hi);
|
||||||
EVENTGROUP_USER.add(devices);
|
EVENTGROUP_USER.add(devices);
|
||||||
|
@ -15,5 +16,6 @@ EVENTGROUP_USER.add(ch);
|
||||||
EVENTGROUP_USER.add(m);
|
EVENTGROUP_USER.add(m);
|
||||||
EVENTGROUP_USER.add(a);
|
EVENTGROUP_USER.add(a);
|
||||||
EVENTGROUP_USER.add(userset);
|
EVENTGROUP_USER.add(userset);
|
||||||
|
EVENTGROUP_USER.add(n);
|
||||||
|
|
||||||
eventGroups.push(EVENTGROUP_USER);
|
eventGroups.push(EVENTGROUP_USER);
|
||||||
|
|
|
@ -14,7 +14,7 @@ export class NoteQuota {
|
||||||
maxHistLen: 3
|
maxHistLen: 3
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(public cb: (points: number) => void) {
|
constructor(public cb?: (points: number) => void) {
|
||||||
this.setParams();
|
this.setParams();
|
||||||
this.resetPoints();
|
this.resetPoints();
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { loadConfig } from "../util/config";
|
||||||
|
import { UserFlags } from "../util/types";
|
||||||
|
|
||||||
|
export interface UsersConfig {
|
||||||
|
defaultName: string;
|
||||||
|
defaultFlags: UserFlags;
|
||||||
|
enableColorChanging: boolean;
|
||||||
|
enableCustomNoteData: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const usersConfigPath = "config/users.yml";
|
||||||
|
|
||||||
|
export const defaultUsersConfig = {
|
||||||
|
defaultName: "Anonymous",
|
||||||
|
defaultFlags: {
|
||||||
|
volume: 100
|
||||||
|
},
|
||||||
|
enableColorChanging: false,
|
||||||
|
enableCustomNoteData: true
|
||||||
|
};
|
||||||
|
|
||||||
|
// Importing this elsewhere causes bun to segfault
|
||||||
|
export const config = loadConfig<UsersConfig>(
|
||||||
|
usersConfigPath,
|
||||||
|
defaultUsersConfig
|
||||||
|
);
|
Loading…
Reference in New Issue