Merge stashed changes

This commit is contained in:
Hri7566 2024-09-19 10:59:31 -04:00
parent fb693d3b02
commit 8c8af2edcc
20 changed files with 378 additions and 161 deletions

View File

@ -63,6 +63,12 @@ This has always been the future intention of this project.
- Ability to rename channels
- Chat clearing similar to MPP.net
- Channel forceloading message
- YAML configs
- Automatic reloading of configs during runtime via file watching
- Interfacing handled by JS Proxy objects
- Templating on frontend
- Handles changing things on page based on config
- Requires the use of `mpp-frontend-dev` to function properly
## TODO
@ -119,60 +125,69 @@ Don't expect these instructions to stay the same. They might not even be up to d
$ curl -fsSL https://bun.sh/install | bash
```
1. Clone the repo and setup Git submodules
1. Clone the repository and setup Git submodules
This step is subject to change, due to the necessity of testing different frontends, where the frontend may or may not be a git submodule.
This will probably be updated in the near future. Expect a step asking to download the frontend manually.
If you are forking this repository, you can just setup a new submodule for the frontend.
The frontend files go in the `public` folder.
If you are forking this repository, you can just setup a new submodule for the frontend, **however, templating will likely not function properly.**
If you would like to use a different repository for the frontend, the files go in the `public` folder.
I am also considering using handlebars or something similar for templating, where the frontend will require completely different code.
The reason behind this decision is that I would like different things to change on the frontend based on the server's config files,
such as enabling the color changing option in the userset modal menu, or sending separate code to server admins/mods/webmasters.
However, if you would like the templating features and want the frontend to change based on the server's configuration, setting up git submodules is required for full compatability.
- Clone the repository
```
$ git clone https://git.hri7566.info/Hri7566/mpp-server-dev2
```
- Setup git submodules
```
$ cd mpp-server-dev2
$ git submodule update --init --recursive
```
2. Configure
- Copy environment variables
- Copy default environment variables
```
$ cp .env.template .env
```
Edit `.env` to your needs. Some variables are required for certain features to work.
Edit `.env` to your needs. Some variables are required for certain features to work. Most of this is self-explanatory if you have set up other large projects.
- `DATABASE_URL`: Database URI for prisma to connect to (as of right now, this is required to be a sqlite path)
- `PORT`: TCP port the HTTP/WS server will run on
- `ADMIN_PASS`: Admin password for the server
- `SALT`: Hashing salt for creating general-purpose IDs/user IDs
- `COLOR_SALT`: Hashing salt for creating user colors
- Edit the files in the `config` folder to match your needs
For token auth, there are a few options to consider. In `config/users.yml`, you can set `tokenAuth` to a few different values:
For token authentication, there are a few options to consider. In `config/users.yml`, you can set `tokenAuth` to a few different values:
- `jwt`: Use JWT token authentication
- `uuid`: Use UUID token authentication
- `none`: Disable token authentication
If you are using UUID token authentication, the server will generate a UUID token for each user when they first connect.
If you are using UUID token authentication, the server will generate a UUID token for each user when they first connect. This option is relatively simple and could be considered less secure.
If you are using JWT token authentication, you will need to generate a key for the server to use.
This can be done by running the following command:
If you are using JWT token authentication, the server will generate a JSON Web Token for each user when they first connect.
You will need to generate a key in the file `mppkey` for the server to use.
This can be done by running the following command, given `openssl` is installed:
```
$ openssl genrsa -out mppkey 2048
```
For antibot/browser detection there are also a few options to consider. In `config/users.yml`, you can set `browserChallenge` to a few different values:
For antibot/browser detection there are also a few options to consider.
In `config/users.yml`, you can set `browserChallenge` to a few different values:
- `none`: Disable browser challenge
- `basic`: Use a simple function to detect browsers
- `obf`: Use an obfuscated function to detect browsers - TODO: implement this
- `obf`: Use an obfuscated function to detect browsers - this is not implemented as of yet
The `basic` option only sends a simple function to the client, and the `obf` option sends an obfuscated mess to the client.
This option requires the newer-style (MPP.net) frontend to be used.
Token authentication is only supported on most frontends newer than 2020.
3. Install packages
@ -195,7 +210,7 @@ such as enabling the color changing option in the userset modal menu, or sending
## Background Info on Feature Implementation Decisions
To avoid various controversies or confusion, I will attempt to explain why certain features were implemented in this section.
To avoid various controversies or mass confusion, I will attempt to explain why certain features were implemented in this section.
### General Explanation
@ -228,7 +243,7 @@ My fork was hosted at `mpp.hri7566.info`.
Also around 2019-2020, I helped Foonix create a server known then as multiplayerpiano.net, hosted at `multiplayerpiano.net`.
This server was heavily based on my fork of BopItFreak's server, but it slightly diverged when I added features to each site.
The site was renamed to `multiplayerpiano.dev` due to lack of care for domain maintenance on Foonix's part.
**This is where the `dev` in the name of this project comes from.**
**Since the original server for this site was called `mpp-server-dev`, this is where the `dev2` in the name of this project comes from.**
In August 2020, a server was developed by a user named aeiou (now known as LapisHusky) called MPPClone, hosted at `mppclone.com`.
This server was eventually handed off to multiple other users, and is still up and running to this day at `multiplayerpiano.net`.

BIN
bun.lockb

Binary file not shown.

View File

@ -1,44 +1,33 @@
# Rate limit config file
# Difference between rate limits and rate limit chains:
# Rate limits will not allow anything to be sent until the rate limit interval has passed.
# Rate limit chains, on the other hand, will allow messages to be sent until the rate limit chain's limit has been reached.
# This is useful for rate limiting messages that are sent in rapid succession, like note messages.
# This is also the basis for note quota, however that is handled in a separate way due to the way it is implemented.
# Rate limits for normal users.
user:
# Rate limits
normal:
a: 1500 # Chat messages
m: 50 # Cursor messages
ch: 1000 # Channel join messages
kickban: 125 # Kickban messages
unban: 125 # Unban messages
t: 7.8125 # Ping messages
+ls: 16.666666666666668 # Channel list subscription messages
-ls: 16.666666666666668 # Channel list unsubscription messages
chown: 2000 # Channel ownership messages
hi: 50 # Handshake messages
bye: 50 # Disconnection messages
devices: 50 # MIDI device messages
admin message: 50 # Admin passthrough messages
# Rate limit chains
a: 1500
m: 50
ch: 1000
kickban: 125
unban: 125
t: 7.8125
+ls: 16.666666666666668
-ls: 16.666666666666668
chown: 2000
hi: 50
bye: 50
devices: 50
admin message: 50
+custom: 16.666666666666668
-custom: 16.666666666666668
chains:
userset: # Username/color update messages
userset:
interval: 1800000
num: 1000
chset: # Channel settings messages
chset:
interval: 1800000
num: 1024
n: # Note messages
# TODO is this correct?
n:
interval: 1000
num: 512
custom:
interval: 1000
num: 512
# The other rate limits are like the above messages, but for other types of users.
# Rate limits for users with a crown.
crown:
normal:
a: 600
@ -54,6 +43,8 @@ crown:
bye: 50
devices: 50
admin message: 50
+custom: 16.666666666666668
-custom: 16.666666666666668
chains:
userset:
interval: 1800000
@ -64,8 +55,9 @@ crown:
n:
interval: 1000
num: 512
# Rate limits for admins.
custom:
interval: 1000
num: 512
admin:
normal:
a: 120
@ -81,6 +73,8 @@ admin:
bye: 50
devices: 50
admin message: 16.666666666666668
+custom: 8.333333333333334
-custom: 8.333333333333334
chains:
userset:
interval: 500
@ -91,3 +85,6 @@ admin:
n:
interval: 50
num: 512
custom:
interval: 60000
num: 20000

View File

@ -11,7 +11,7 @@ defaultFlags:
# Whether or not to allow users to change their color.
# Based on some reports, the MPP.com server stopped allowing this around 2016.
enableColorChanging: false
enableColorChanging: true
# Whether to allow custom data inside note messages.
# This was in the original server, but not in MPP.net's server do to stricter sanitization.
@ -20,7 +20,7 @@ enableCustomNoteData: true
# Whether or not to enable tags that are sent publicly.
# This won't prevent admins from changing tags internally, but they will not be sent to clients if set to false.
enableTags: true
enableTags: false
# This is the user data that the server will use to send admin chat messages with.
# This is a feature available on MPP.com, but was unknown to the MPP.net developers, therefore not implemented on MPP.net.

2
public

@ -1 +1 @@
Subproject commit 1dc00c7f885ac919a1bda7d4c749d33bd594c42f
Subproject commit a4e9210048ef2e7648c5614a9187e5788a6cc827

View File

@ -3,13 +3,13 @@ import { Logger } from "../util/Logger";
import type {
IChannelSettings,
OutgoingSocketEvents,
Participant,
IncomingSocketEvents,
IParticipant,
IChannelInfo,
Notification,
UserFlags,
Tag,
ChannelFlags as TChannelFlags
TChannelFlags
} from "../util/types";
import type { Socket } from "../ws/Socket";
import { validateChannelSettings } from "./settings";
@ -50,7 +50,7 @@ interface ExtraPartData {
flags: UserFlags;
}
type ExtraPart = Participant & ExtraPartData;
type ExtraPart = IParticipant & ExtraPartData;
export class Channel extends EventEmitter {
private settings: Partial<IChannelSettings>;
@ -173,7 +173,8 @@ export class Channel extends EventEmitter {
}
}
// We are not a lobby, so we must have a crown
// We are not a lobby, so we probably have a crown
// this.getFlag("no_crown");
this.crown = new Crown();
// ...and, possibly, an owner, too
@ -246,15 +247,18 @@ export class Channel extends EventEmitter {
if (typeof msg.message !== "string") return;
const userFlags = socket.getUserFlags();
let overrideColor: string | undefined;
if (userFlags) {
if (userFlags.cant_chat == 1) return;
if (userFlags.chat_curse_1 == 1)
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)
if (userFlags.chat_curse_2 === 1)
msg.message = spoop_text(msg.message);
if (typeof userFlags.chat_color === "string")
overrideColor = userFlags.chat_color;
}
if (!this.settings.chat) return;
@ -281,7 +285,7 @@ export class Channel extends EventEmitter {
.replace(/(\p{Mc}{5})\p{Mc}+/gu, "$1")
.trim();
const part = socket.getParticipant() as Participant;
const part = socket.getParticipant() as IParticipant;
const outgoing: OutgoingSocketEvents["a"] = {
m: "a",
@ -290,6 +294,9 @@ export class Channel extends EventEmitter {
p: part
};
if (typeof overrideColor !== "undefined")
outgoing.p.color = overrideColor;
this.sendArray([outgoing]);
this.chatHistory.push(outgoing);
await saveChatHistory(this.getID(), this.chatHistory);
@ -547,7 +554,7 @@ export class Channel extends EventEmitter {
*/
public join(socket: Socket, force = false): void {
if (this.isDestroyed()) return;
const part = socket.getParticipant() as Participant;
const part = socket.getParticipant() as IParticipant;
let hasChangedChannel = false;
@ -718,7 +725,7 @@ export class Channel extends EventEmitter {
*/
public leave(socket: Socket) {
// this.logger.debug("Leave called");
const part = socket.getParticipant() as Participant;
const part = socket.getParticipant() as IParticipant;
let dupeCount = 0;
for (const s of socketsByUUID.values()) {
@ -947,7 +954,7 @@ export class Channel extends EventEmitter {
* Change ownership (don't forget to use crown.canBeSetBy if you're letting a user call this)
* @param part Participant to give crown to (or undefined to drop crown)
*/
public chown(part?: Participant) {
public chown(part?: IParticipant) {
if (this.crown) {
if (part) {
this.giveCrown(part);
@ -962,7 +969,7 @@ export class Channel extends EventEmitter {
* @param part Participant to give crown to
* @param force Whether or not to force-create a crown (useful for lobbies)
*/
public giveCrown(part: Participant, force = false, update = true) {
public giveCrown(part: IParticipant, force = false, update = true) {
if (force) {
if (!this.crown) this.crown = new Crown();
}
@ -1039,7 +1046,7 @@ export class Channel extends EventEmitter {
if (!banChannel) return;
// Check if they are on the server at all
let bannedPart: Participant | undefined;
let bannedPart: IParticipant | undefined;
const bannedUUIDs: string[] = [];
for (const sock of socketsByUUID.values()) {
if (sock.getUserID() === _id) {
@ -1229,7 +1236,7 @@ export class Channel extends EventEmitter {
* @param msg Chat message event to send
* @param p Participant who is "sending the message"
**/
public async sendChat(msg: IncomingSocketEvents["a"], p: Participant) {
public async sendChat(msg: IncomingSocketEvents["a"], p: IParticipant) {
if (!msg.message) return;
if (msg.message.length > 512) return;

View File

@ -1,4 +1,4 @@
import { Participant, Vector2 } from "../util/types";
import { IParticipant, Vector2 } from "../util/types";
import { Socket } from "../ws/Socket";
// shiny hat

84
src/event/behaviors.ts Normal file
View File

@ -0,0 +1,84 @@
import { ChannelList } from "~/channel/ChannelList";
import { bus } from "./bus";
import type { ClientEvents, ServerEvents } from "~/util/types";
import { socketsByUUID, type Socket } from "~/ws/Socket";
export function loadBehaviors() {
bus.on("hamburger", () => {
for (const ch of ChannelList.getList()) {
ch.sendChatAdmin("🍔");
}
});
bus.on("ls", () => {});
bus.on("custom", (msg: ServerEvents["custom"], sender: Socket) => {
if (typeof msg !== "object") return;
if (typeof msg.data === "undefined") return;
if (typeof msg.target !== "object") return;
if (typeof msg.target.mode !== "string") return;
if (
typeof msg.target.global !== "undefined" &&
typeof msg.target.global !== "boolean"
)
return;
for (const receiver of socketsByUUID.values()) {
if (receiver.isDestroyed()) return;
if (!receiver.isCustomSubbed()) return;
if (sender.isDestroyed()) return;
if (!sender.isCustomSubbed()) return;
if (
msg.target.global !== true ||
typeof msg.target.global === "undefined"
) {
const ch = sender.getCurrentChannel();
if (!ch) return;
const ch2 = receiver.getCurrentChannel();
if (!ch2) return;
if (ch.getID() !== ch2.getID()) return;
}
if (msg.target.mode === "id") {
if (typeof msg.target.id !== "string") return;
if (receiver.getUserID() === msg.target.id) {
receiver.sendArray([
{
m: "custom",
data: msg.data,
p: sender.getUserID()
} as ClientEvents["custom"]
]);
}
} else if (msg.target.mode === "ids") {
if (typeof msg.target.ids !== "object") return;
if (!Array.isArray(msg.target.ids)) return;
if (msg.target.ids.includes(receiver.getUserID())) {
receiver.sendArray([
{
m: "custom",
data: msg.data,
p: sender.getUserID()
} as ClientEvents["custom"]
]);
}
} else if (msg.target.mode === "subscribed") {
receiver.sendArray([
{
m: "custom",
data: msg.data,
p: sender.getUserID()
} as ClientEvents["custom"]
]);
}
}
});
bus.emit("ready");
}

View File

@ -6,4 +6,4 @@ class EventBus extends EventEmitter {
}
}
export const eventBus = new EventBus();
export const bus = new EventBus();

View File

@ -20,21 +20,24 @@ import { Logger } from "./util/Logger";
// docker hates this next one
import { startReadline } from "./util/readline";
import { loadDefaultPermissions } from "./data/permissions";
import { loadBehaviors } from "./event/behaviors";
// wrapper for some reason
export function startServer() {
// Let's construct an entire object just for one thing to be printed
// and then keep it in memory for the entirety of runtime
const logger = new Logger("Main");
logger.info("Forceloading startup channels...");
loadForcedStartupChannels();
logger.info("Finished forceloading");
logger.info("Loading behaviors...");
loadBehaviors();
logger.info("Finished loading behaviors");
loadDefaultPermissions();
// Break the console
logger.info("Starting REPL");
startReadline();
// Nevermind, two things are printed
logger.info("Ready");
}

View File

@ -13,7 +13,7 @@ import { Logger } from "./Logger";
*/
export class ConfigManager {
// public static configCache = new Map<string, unknown>();
public static configCache = new Map<string, unknown>();
public static logger: Logger;
static {
@ -32,10 +32,15 @@ export class ConfigManager {
* });
* ```
* @param configPath Path to load config from
* @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, see saveDefault)
* @param saveDefault Whether to save the default config if none is present
* @returns Parsed YAML config
*/
public static loadConfig<T>(configPath: string, defaultConfig: T): T {
public static loadConfig<T>(
configPath: string,
defaultConfig: T,
saveDefault = true
): T {
const self = this;
// Config exists?
@ -73,38 +78,43 @@ export class ConfigManager {
mix(config, defRecord);
// Save config if modified
if (changed) this.writeConfig(configPath, config);
if (saveDefault && changed) this.writeConfig(configPath, config);
if (!this.configCache.has(configPath)) {
// File contents changed callback
// const watcher = watchFile(configPath, () => {
// this.logger.info(
// "Reloading config due to changes:",
// configPath
// );
// this.loadConfig(configPath, defaultConfig);
// });
const watcher = watchFile(configPath, () => {
this.logger.info(
"Reloading config due to changes:",
configPath
);
// this.configCache.set(configPath, config);
this.loadConfig(configPath, defaultConfig, false);
});
}
// return this.getConfigProxy<T>(configPath);
return config;
this.configCache.set(configPath, config);
return this.getConfigProxy<T>(configPath);
// return config;
} else {
// Write default config to disk and use that
//logger.warn(`Config file "${configPath}" not found, writing default config to disk`);
this.writeConfig(configPath, defaultConfig);
if (saveDefault) this.writeConfig(configPath, defaultConfig);
if (!this.configCache.has(configPath)) {
// File contents changed callback
// const watcher = watchFile(configPath, () => {
// this.logger.info(
// "Reloading config due to changes:",
// configPath
// );
// this.loadConfig(configPath, defaultConfig);
// });
const watcher = watchFile(configPath, () => {
this.logger.info(
"Reloading config due to changes:",
configPath
);
this.loadConfig(configPath, defaultConfig, false);
});
}
// this.configCache.set(configPath, defaultConfig);
// return this.getConfigProxy<T>(configPath);
return defaultConfig;
this.configCache.set(configPath, defaultConfig);
return this.getConfigProxy<T>(configPath);
// return defaultConfig;
}
}
@ -128,24 +138,24 @@ export class ConfigManager {
* @param configPath Path to config file
* @returns Config proxy object
*/
// protected static getConfigProxy<T>(configPath: string) {
// const self = this;
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;
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;
// }
if (config) {
if (config.hasOwnProperty(name))
return (config as Record<string, unknown>)[
name
] as T[keyof T];
}
}
}
) as T;
}
}

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

@ -21,11 +21,13 @@ declare type UserFlags = Partial<{
mod: number;
admin: number;
vanish: number;
chat_color: string;
}>;
type ChannelFlags = Partial<{
type TChannelFlags = Partial<{
limit: number;
owner_id: string;
no_crown: boolean;
}>;
declare interface Tag {
@ -40,7 +42,7 @@ declare interface User {
tag?: Tag;
}
declare interface Participant extends User {
declare interface IParticipant extends User {
id: string; // participant id (same as user id on mppclone)
}

View File

@ -65,6 +65,12 @@ export class Gateway {
// Whether the user has sent a channel list subscription request, a.k.a. opened the channel list
public hasOpenedChannelList = false; // implemented
// Whether the user has sent a custom message subscription request (+custom)
public hasSentCustomSub = false; // implemented
// Whether the user has sent a custom message unsubscription request (-custom)
public hasSentCustomUnsub = false; // implemented
// Whether the user has changed their name/color this session (not just changed from default)
public hasChangedName = false; // implemented
public hasChangedColor = false; // implemented

View File

@ -10,7 +10,7 @@ import type {
IChannelInfo,
IChannelSettings,
OutgoingSocketEvents,
Participant,
IParticipant,
IncomingSocketEvents,
UserFlags,
Vector2,
@ -44,6 +44,7 @@ import {
} from "~/data/permissions";
import { getRoles } from "~/data/role";
import { setTag } from "~/util/tags";
import { bus } from "~/event/bus";
const logger = new Logger("Sockets");
@ -506,7 +507,7 @@ export class Socket extends EventEmitter {
/**
* Send this socket a channel update message
**/
public sendChannelUpdate(ch: IChannelInfo, ppl: Participant[]) {
public sendChannelUpdate(ch: IChannelInfo, ppl: IParticipant[]) {
this.sendArray([
{
m: "ch",
@ -545,7 +546,7 @@ export class Socket extends EventEmitter {
const ch = this.getCurrentChannel();
if (ch) {
const part = this.getParticipant() as Participant;
const part = this.getParticipant() as IParticipant;
const cursorPos = this.getCursorPos();
ch.sendArray([
@ -852,6 +853,28 @@ export class Socket extends EventEmitter {
return false;
}
private isSubscribedToCustom = false;
public isCustomSubbed() {
return this.isSubscribedToCustom === true;
}
/**
* Start sending this socket the list of channels periodically
**/
public subscribeToCustom() {
if (this.isSubscribedToCustom) return;
this.isSubscribedToCustom = true;
}
/**
* Stop sending this socket the list of channels periodically
**/
public unsubscribeFromCustom() {
if (!this.isSubscribedToCustom) return;
this.isSubscribedToCustom = false;
}
}
export const socketsByUUID = new Map<Socket["uuid"], Socket>();

View File

@ -1,5 +1,5 @@
import { ChannelList } from "../../../../channel/ChannelList";
import { ServerEventListener } from "../../../../util/types";
import { ServerEventListener, TChannelFlags } from "../../../../util/types";
export const ch_flag: ServerEventListener<"ch_flag"> = {
id: "ch_flag",
@ -14,9 +14,15 @@ export const ch_flag: ServerEventListener<"ch_flag"> = {
chid = ch.getID();
}
if (typeof msg.key !== "string") return;
if (typeof msg.value === "undefined") return;
const ch = ChannelList.getList().find(c => c.getID() == chid);
if (!ch) return;
ch.setFlag(msg.key, msg.value);
ch.setFlag(
msg.key as keyof TChannelFlags,
msg.value as TChannelFlags[keyof TChannelFlags]
);
}
};

View File

@ -0,0 +1,15 @@
import { ServerEventListener } from "../../../../util/types";
export const plus_custom: ServerEventListener<"+custom"> = {
id: "+custom",
callback: async (msg, socket) => {
// Custom message subscribe
if (socket.rateLimits) {
if (!socket.rateLimits.normal["+custom"].attempt()) return;
}
socket.gateway.hasSentCustomSub = true;
socket.subscribeToCustom();
}
};

View File

@ -0,0 +1,15 @@
import { ServerEventListener } from "../../../../util/types";
export const minus_custom: ServerEventListener<"-ls"> = {
id: "-ls",
callback: async (msg, socket) => {
// Unsubscribe from custom messages
if (socket.rateLimits) {
if (!socket.rateLimits.normal["-custom"].attempt()) return;
}
socket.gateway.hasSentCustomUnsub = true;
socket.unsubscribeFromCustom();
}
};

View File

@ -0,0 +1,9 @@
import type { ServerEventListener } from "~/util/types";
export const custom: ServerEventListener<"custom"> = {
id: "custom",
callback: async (msg, socket) => {
// Custom message
if (!socket.isCustomSubbed()) return;
}
};

View File

@ -17,6 +17,9 @@ export interface RateLimitConfigList<
"-ls": RL;
chown: RL;
"+custom": RL;
"-custom": RL;
// weird limits
hi: RL;
bye: RL;
@ -28,6 +31,7 @@ export interface RateLimitConfigList<
userset: RLC;
chset: RLC;
n: RLC; // not to be confused with NoteQuota
custom: RLC;
};
}
@ -59,37 +63,8 @@ export const config = ConfigManager.loadConfig<RateLimitsConfig>(
"-ls": 1000 / 60,
chown: 2000,
hi: 1000 / 20,
bye: 1000 / 20,
devices: 1000 / 20,
"admin message": 1000 / 20
},
chains: {
userset: {
interval: 1000 * 60 * 30,
num: 1000
},
chset: {
interval: 1000 * 60 * 30,
num: 1024
},
n: {
interval: 1000,
num: 512
}
}
},
crown: {
normal: {
a: 6000 / 10,
m: 1000 / 20,
ch: 1000 / 1,
kickban: 1000 / 8,
unban: 1000 / 8,
t: 1000 / 128,
"+ls": 1000 / 60,
"-ls": 1000 / 60,
chown: 2000,
"+custom": 1000 / 60,
"-custom": 1000 / 60,
hi: 1000 / 20,
bye: 1000 / 20,
@ -108,6 +83,49 @@ export const config = ConfigManager.loadConfig<RateLimitsConfig>(
n: {
interval: 1000,
num: 512
},
custom: {
interval: 1000,
num: 512
}
}
},
crown: {
normal: {
a: 6000 / 10,
m: 1000 / 20,
ch: 1000 / 1,
kickban: 1000 / 8,
unban: 1000 / 8,
t: 1000 / 128,
"+ls": 1000 / 60,
"-ls": 1000 / 60,
chown: 2000,
"+custom": 1000 / 60,
"-custom": 1000 / 60,
hi: 1000 / 20,
bye: 1000 / 20,
devices: 1000 / 20,
"admin message": 1000 / 20
},
chains: {
userset: {
interval: 1000 * 60 * 30,
num: 1000
},
chset: {
interval: 1000 * 60 * 30,
num: 1024
},
n: {
interval: 1000,
num: 512
},
custom: {
interval: 1000,
num: 512
}
}
},
@ -123,6 +141,9 @@ export const config = ConfigManager.loadConfig<RateLimitsConfig>(
"-ls": 1000 / 60,
chown: 500,
"+custom": 1000 / 120,
"-custom": 1000 / 120,
hi: 1000 / 20,
bye: 1000 / 20,
devices: 1000 / 20,
@ -140,6 +161,10 @@ export const config = ConfigManager.loadConfig<RateLimitsConfig>(
n: {
interval: 50,
num: 512
},
custom: {
interval: 1000 * 60,
num: 20000
}
}
}

View File

@ -1,5 +1,5 @@
import { ConfigManager } from "../util/config";
import type { Participant, UserFlags } from "../util/types";
import type { IParticipant, UserFlags } from "../util/types";
export interface UsersConfig {
defaultName: string;
@ -7,7 +7,7 @@ export interface UsersConfig {
enableColorChanging: boolean;
enableCustomNoteData: boolean;
enableTags: boolean;
adminParticipant: Participant;
adminParticipant: IParticipant;
enableAdminEval: boolean;
tokenAuth: "jwt" | "uuid" | "none";
browserChallenge: "none" | "obf" | "basic";