Merge stashed changes
This commit is contained in:
parent
fb693d3b02
commit
8c8af2edcc
53
README.md
53
README.md
|
@ -63,6 +63,12 @@ This has always been the future intention of this project.
|
||||||
- Ability to rename channels
|
- Ability to rename channels
|
||||||
- Chat clearing similar to MPP.net
|
- Chat clearing similar to MPP.net
|
||||||
- Channel forceloading message
|
- 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
|
## 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
|
$ 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.
|
If you are forking this repository, you can just setup a new submodule for the frontend, **however, templating will likely not function properly.**
|
||||||
This will probably be updated in the near future. Expect a step asking to download the frontend manually.
|
If you would like to use a different repository for the frontend, the files go in the `public` folder.
|
||||||
If you are forking this repository, you can just setup a new submodule for the frontend.
|
|
||||||
The frontend 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.
|
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.
|
||||||
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.
|
|
||||||
|
|
||||||
|
- Clone the repository
|
||||||
|
|
||||||
```
|
```
|
||||||
$ git clone https://git.hri7566.info/Hri7566/mpp-server-dev2
|
$ git clone https://git.hri7566.info/Hri7566/mpp-server-dev2
|
||||||
|
```
|
||||||
|
|
||||||
|
- Setup git submodules
|
||||||
|
|
||||||
|
```
|
||||||
$ cd mpp-server-dev2
|
$ cd mpp-server-dev2
|
||||||
$ git submodule update --init --recursive
|
$ git submodule update --init --recursive
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Configure
|
2. Configure
|
||||||
|
|
||||||
- Copy environment variables
|
- Copy default environment variables
|
||||||
|
|
||||||
```
|
```
|
||||||
$ cp .env.template .env
|
$ 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
|
- 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
|
- `jwt`: Use JWT token authentication
|
||||||
- `uuid`: Use UUID token authentication
|
- `uuid`: Use UUID token authentication
|
||||||
- `none`: Disable 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.
|
If you are using JWT token authentication, the server will generate a JSON Web Token for each user when they first connect.
|
||||||
This can be done by running the following command:
|
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
|
$ 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
|
- `none`: Disable browser challenge
|
||||||
- `basic`: Use a simple function to detect browsers
|
- `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.
|
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
|
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
|
## 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
|
### 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`.
|
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.
|
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.
|
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`.
|
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`.
|
This server was eventually handed off to multiple other users, and is still up and running to this day at `multiplayerpiano.net`.
|
||||||
|
|
|
@ -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:
|
user:
|
||||||
# Rate limits
|
|
||||||
normal:
|
normal:
|
||||||
a: 1500 # Chat messages
|
a: 1500
|
||||||
m: 50 # Cursor messages
|
m: 50
|
||||||
ch: 1000 # Channel join messages
|
ch: 1000
|
||||||
kickban: 125 # Kickban messages
|
kickban: 125
|
||||||
unban: 125 # Unban messages
|
unban: 125
|
||||||
t: 7.8125 # Ping messages
|
t: 7.8125
|
||||||
+ls: 16.666666666666668 # Channel list subscription messages
|
+ls: 16.666666666666668
|
||||||
-ls: 16.666666666666668 # Channel list unsubscription messages
|
-ls: 16.666666666666668
|
||||||
chown: 2000 # Channel ownership messages
|
chown: 2000
|
||||||
hi: 50 # Handshake messages
|
hi: 50
|
||||||
bye: 50 # Disconnection messages
|
bye: 50
|
||||||
devices: 50 # MIDI device messages
|
devices: 50
|
||||||
admin message: 50 # Admin passthrough messages
|
admin message: 50
|
||||||
|
+custom: 16.666666666666668
|
||||||
# Rate limit chains
|
-custom: 16.666666666666668
|
||||||
chains:
|
chains:
|
||||||
userset: # Username/color update messages
|
userset:
|
||||||
interval: 1800000
|
interval: 1800000
|
||||||
num: 1000
|
num: 1000
|
||||||
chset: # Channel settings messages
|
chset:
|
||||||
interval: 1800000
|
interval: 1800000
|
||||||
num: 1024
|
num: 1024
|
||||||
n: # Note messages
|
n:
|
||||||
# TODO is this correct?
|
interval: 1000
|
||||||
|
num: 512
|
||||||
|
custom:
|
||||||
interval: 1000
|
interval: 1000
|
||||||
num: 512
|
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:
|
crown:
|
||||||
normal:
|
normal:
|
||||||
a: 600
|
a: 600
|
||||||
|
@ -54,6 +43,8 @@ crown:
|
||||||
bye: 50
|
bye: 50
|
||||||
devices: 50
|
devices: 50
|
||||||
admin message: 50
|
admin message: 50
|
||||||
|
+custom: 16.666666666666668
|
||||||
|
-custom: 16.666666666666668
|
||||||
chains:
|
chains:
|
||||||
userset:
|
userset:
|
||||||
interval: 1800000
|
interval: 1800000
|
||||||
|
@ -64,8 +55,9 @@ crown:
|
||||||
n:
|
n:
|
||||||
interval: 1000
|
interval: 1000
|
||||||
num: 512
|
num: 512
|
||||||
|
custom:
|
||||||
# Rate limits for admins.
|
interval: 1000
|
||||||
|
num: 512
|
||||||
admin:
|
admin:
|
||||||
normal:
|
normal:
|
||||||
a: 120
|
a: 120
|
||||||
|
@ -81,6 +73,8 @@ admin:
|
||||||
bye: 50
|
bye: 50
|
||||||
devices: 50
|
devices: 50
|
||||||
admin message: 16.666666666666668
|
admin message: 16.666666666666668
|
||||||
|
+custom: 8.333333333333334
|
||||||
|
-custom: 8.333333333333334
|
||||||
chains:
|
chains:
|
||||||
userset:
|
userset:
|
||||||
interval: 500
|
interval: 500
|
||||||
|
@ -91,3 +85,6 @@ admin:
|
||||||
n:
|
n:
|
||||||
interval: 50
|
interval: 50
|
||||||
num: 512
|
num: 512
|
||||||
|
custom:
|
||||||
|
interval: 60000
|
||||||
|
num: 20000
|
||||||
|
|
|
@ -11,7 +11,7 @@ defaultFlags:
|
||||||
|
|
||||||
# Whether or not to allow users to change their color.
|
# Whether or not to allow users to change their color.
|
||||||
# Based on some reports, the MPP.com server stopped allowing this around 2016.
|
# 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.
|
# 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.
|
# 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.
|
# 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.
|
# 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 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.
|
# This is a feature available on MPP.com, but was unknown to the MPP.net developers, therefore not implemented on MPP.net.
|
||||||
|
|
2
public
2
public
|
@ -1 +1 @@
|
||||||
Subproject commit 1dc00c7f885ac919a1bda7d4c749d33bd594c42f
|
Subproject commit a4e9210048ef2e7648c5614a9187e5788a6cc827
|
|
@ -3,13 +3,13 @@ import { Logger } from "../util/Logger";
|
||||||
import type {
|
import type {
|
||||||
IChannelSettings,
|
IChannelSettings,
|
||||||
OutgoingSocketEvents,
|
OutgoingSocketEvents,
|
||||||
Participant,
|
|
||||||
IncomingSocketEvents,
|
IncomingSocketEvents,
|
||||||
|
IParticipant,
|
||||||
IChannelInfo,
|
IChannelInfo,
|
||||||
Notification,
|
Notification,
|
||||||
UserFlags,
|
UserFlags,
|
||||||
Tag,
|
Tag,
|
||||||
ChannelFlags as TChannelFlags
|
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";
|
||||||
|
@ -50,7 +50,7 @@ interface ExtraPartData {
|
||||||
flags: UserFlags;
|
flags: UserFlags;
|
||||||
}
|
}
|
||||||
|
|
||||||
type ExtraPart = Participant & ExtraPartData;
|
type ExtraPart = IParticipant & ExtraPartData;
|
||||||
|
|
||||||
export class Channel extends EventEmitter {
|
export class Channel extends EventEmitter {
|
||||||
private settings: Partial<IChannelSettings>;
|
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();
|
this.crown = new Crown();
|
||||||
|
|
||||||
// ...and, possibly, an owner, too
|
// ...and, possibly, an owner, too
|
||||||
|
@ -246,15 +247,18 @@ export class Channel extends EventEmitter {
|
||||||
if (typeof msg.message !== "string") return;
|
if (typeof msg.message !== "string") return;
|
||||||
|
|
||||||
const userFlags = socket.getUserFlags();
|
const userFlags = socket.getUserFlags();
|
||||||
|
let overrideColor: string | undefined;
|
||||||
|
|
||||||
if (userFlags) {
|
if (userFlags) {
|
||||||
if (userFlags.cant_chat == 1) return;
|
if (userFlags.cant_chat === 1) return;
|
||||||
if (userFlags.chat_curse_1 == 1)
|
if (userFlags.chat_curse_1 === 1)
|
||||||
msg.message = msg.message
|
msg.message = msg.message
|
||||||
.replace(/[aeiu]/g, "o")
|
.replace(/[aeiu]/g, "o")
|
||||||
.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);
|
msg.message = spoop_text(msg.message);
|
||||||
|
if (typeof userFlags.chat_color === "string")
|
||||||
|
overrideColor = userFlags.chat_color;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.settings.chat) return;
|
if (!this.settings.chat) return;
|
||||||
|
@ -281,7 +285,7 @@ export class Channel extends EventEmitter {
|
||||||
.replace(/(\p{Mc}{5})\p{Mc}+/gu, "$1")
|
.replace(/(\p{Mc}{5})\p{Mc}+/gu, "$1")
|
||||||
.trim();
|
.trim();
|
||||||
|
|
||||||
const part = socket.getParticipant() as Participant;
|
const part = socket.getParticipant() as IParticipant;
|
||||||
|
|
||||||
const outgoing: OutgoingSocketEvents["a"] = {
|
const outgoing: OutgoingSocketEvents["a"] = {
|
||||||
m: "a",
|
m: "a",
|
||||||
|
@ -290,6 +294,9 @@ export class Channel extends EventEmitter {
|
||||||
p: part
|
p: part
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (typeof overrideColor !== "undefined")
|
||||||
|
outgoing.p.color = overrideColor;
|
||||||
|
|
||||||
this.sendArray([outgoing]);
|
this.sendArray([outgoing]);
|
||||||
this.chatHistory.push(outgoing);
|
this.chatHistory.push(outgoing);
|
||||||
await saveChatHistory(this.getID(), this.chatHistory);
|
await saveChatHistory(this.getID(), this.chatHistory);
|
||||||
|
@ -547,7 +554,7 @@ export class Channel extends EventEmitter {
|
||||||
*/
|
*/
|
||||||
public join(socket: Socket, force = false): void {
|
public join(socket: Socket, force = false): void {
|
||||||
if (this.isDestroyed()) return;
|
if (this.isDestroyed()) return;
|
||||||
const part = socket.getParticipant() as Participant;
|
const part = socket.getParticipant() as IParticipant;
|
||||||
|
|
||||||
let hasChangedChannel = false;
|
let hasChangedChannel = false;
|
||||||
|
|
||||||
|
@ -718,7 +725,7 @@ export class Channel extends EventEmitter {
|
||||||
*/
|
*/
|
||||||
public leave(socket: Socket) {
|
public leave(socket: Socket) {
|
||||||
// this.logger.debug("Leave called");
|
// this.logger.debug("Leave called");
|
||||||
const part = socket.getParticipant() as Participant;
|
const part = socket.getParticipant() as IParticipant;
|
||||||
|
|
||||||
let dupeCount = 0;
|
let dupeCount = 0;
|
||||||
for (const s of socketsByUUID.values()) {
|
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)
|
* 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)
|
* @param part Participant to give crown to (or undefined to drop crown)
|
||||||
*/
|
*/
|
||||||
public chown(part?: Participant) {
|
public chown(part?: IParticipant) {
|
||||||
if (this.crown) {
|
if (this.crown) {
|
||||||
if (part) {
|
if (part) {
|
||||||
this.giveCrown(part);
|
this.giveCrown(part);
|
||||||
|
@ -962,7 +969,7 @@ export class Channel extends EventEmitter {
|
||||||
* @param part Participant to give crown to
|
* @param part Participant to give crown to
|
||||||
* @param force Whether or not to force-create a crown (useful for lobbies)
|
* @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 (force) {
|
||||||
if (!this.crown) this.crown = new Crown();
|
if (!this.crown) this.crown = new Crown();
|
||||||
}
|
}
|
||||||
|
@ -1039,7 +1046,7 @@ export class Channel extends EventEmitter {
|
||||||
if (!banChannel) return;
|
if (!banChannel) return;
|
||||||
|
|
||||||
// Check if they are on the server at all
|
// Check if they are on the server at all
|
||||||
let bannedPart: Participant | undefined;
|
let bannedPart: IParticipant | undefined;
|
||||||
const bannedUUIDs: string[] = [];
|
const bannedUUIDs: string[] = [];
|
||||||
for (const sock of socketsByUUID.values()) {
|
for (const sock of socketsByUUID.values()) {
|
||||||
if (sock.getUserID() === _id) {
|
if (sock.getUserID() === _id) {
|
||||||
|
@ -1229,7 +1236,7 @@ export class Channel extends EventEmitter {
|
||||||
* @param msg Chat message event to send
|
* @param msg Chat message event to send
|
||||||
* @param p Participant who is "sending the message"
|
* @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) return;
|
||||||
|
|
||||||
if (msg.message.length > 512) return;
|
if (msg.message.length > 512) return;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Participant, Vector2 } from "../util/types";
|
import { IParticipant, Vector2 } from "../util/types";
|
||||||
import { Socket } from "../ws/Socket";
|
import { Socket } from "../ws/Socket";
|
||||||
|
|
||||||
// shiny hat
|
// shiny hat
|
||||||
|
|
|
@ -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");
|
||||||
|
}
|
|
@ -6,4 +6,4 @@ class EventBus extends EventEmitter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const eventBus = new EventBus();
|
export const bus = new EventBus();
|
||||||
|
|
11
src/index.ts
11
src/index.ts
|
@ -20,21 +20,24 @@ 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/permissions";
|
import { loadDefaultPermissions } from "./data/permissions";
|
||||||
|
import { loadBehaviors } from "./event/behaviors";
|
||||||
|
|
||||||
// wrapper for some reason
|
// wrapper for some reason
|
||||||
export function startServer() {
|
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");
|
const logger = new Logger("Main");
|
||||||
logger.info("Forceloading startup channels...");
|
logger.info("Forceloading startup channels...");
|
||||||
loadForcedStartupChannels();
|
loadForcedStartupChannels();
|
||||||
|
logger.info("Finished forceloading");
|
||||||
|
|
||||||
|
logger.info("Loading behaviors...");
|
||||||
|
loadBehaviors();
|
||||||
|
logger.info("Finished loading behaviors");
|
||||||
|
|
||||||
loadDefaultPermissions();
|
loadDefaultPermissions();
|
||||||
|
|
||||||
// Break the console
|
// Break the console
|
||||||
|
logger.info("Starting REPL");
|
||||||
startReadline();
|
startReadline();
|
||||||
|
|
||||||
// Nevermind, two things are printed
|
|
||||||
logger.info("Ready");
|
logger.info("Ready");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ import { Logger } from "./Logger";
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export class ConfigManager {
|
export class ConfigManager {
|
||||||
// public static configCache = new Map<string, unknown>();
|
public static configCache = new Map<string, unknown>();
|
||||||
public static logger: Logger;
|
public static logger: Logger;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
|
@ -32,10 +32,15 @@ export class ConfigManager {
|
||||||
* });
|
* });
|
||||||
* ```
|
* ```
|
||||||
* @param configPath Path to load config from
|
* @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
|
* @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;
|
const self = this;
|
||||||
|
|
||||||
// Config exists?
|
// Config exists?
|
||||||
|
@ -73,38 +78,43 @@ export class ConfigManager {
|
||||||
mix(config, defRecord);
|
mix(config, defRecord);
|
||||||
|
|
||||||
// Save config if modified
|
// Save config if modified
|
||||||
if (changed) this.writeConfig(configPath, config);
|
if (saveDefault && changed) this.writeConfig(configPath, config);
|
||||||
|
|
||||||
// File contents changed callback
|
if (!this.configCache.has(configPath)) {
|
||||||
// const watcher = watchFile(configPath, () => {
|
// File contents changed callback
|
||||||
// this.logger.info(
|
const watcher = watchFile(configPath, () => {
|
||||||
// "Reloading config due to changes:",
|
this.logger.info(
|
||||||
// configPath
|
"Reloading config due to changes:",
|
||||||
// );
|
configPath
|
||||||
// this.loadConfig(configPath, defaultConfig);
|
);
|
||||||
// });
|
|
||||||
|
|
||||||
// this.configCache.set(configPath, config);
|
this.loadConfig(configPath, defaultConfig, false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// return this.getConfigProxy<T>(configPath);
|
this.configCache.set(configPath, config);
|
||||||
return 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`);
|
||||||
this.writeConfig(configPath, defaultConfig);
|
if (saveDefault) this.writeConfig(configPath, defaultConfig);
|
||||||
|
|
||||||
// File contents changed callback
|
if (!this.configCache.has(configPath)) {
|
||||||
// const watcher = watchFile(configPath, () => {
|
// File contents changed callback
|
||||||
// this.logger.info(
|
const watcher = watchFile(configPath, () => {
|
||||||
// "Reloading config due to changes:",
|
this.logger.info(
|
||||||
// configPath
|
"Reloading config due to changes:",
|
||||||
// );
|
configPath
|
||||||
// this.loadConfig(configPath, defaultConfig);
|
);
|
||||||
// });
|
this.loadConfig(configPath, defaultConfig, false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// this.configCache.set(configPath, defaultConfig);
|
this.configCache.set(configPath, defaultConfig);
|
||||||
// return this.getConfigProxy<T>(configPath);
|
return this.getConfigProxy<T>(configPath);
|
||||||
return defaultConfig;
|
// return defaultConfig;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,24 +138,24 @@ export class ConfigManager {
|
||||||
* @param configPath Path to config file
|
* @param configPath Path to config file
|
||||||
* @returns Config proxy object
|
* @returns Config proxy object
|
||||||
*/
|
*/
|
||||||
// protected static getConfigProxy<T>(configPath: string) {
|
protected static getConfigProxy<T>(configPath: string) {
|
||||||
// const self = this;
|
const self = this;
|
||||||
|
|
||||||
// return new Proxy(
|
return new Proxy(
|
||||||
// {},
|
{},
|
||||||
// {
|
{
|
||||||
// get(_target: unknown, name: string) {
|
get(_target: unknown, name: string) {
|
||||||
// // Get the updated in-memory version of the config
|
// Get the updated in-memory version of the config
|
||||||
// const config = self.configCache.get(configPath) as T;
|
const config = self.configCache.get(configPath) as T;
|
||||||
|
|
||||||
// if (config) {
|
if (config) {
|
||||||
// if (config.hasOwnProperty(name))
|
if (config.hasOwnProperty(name))
|
||||||
// return (config as Record<string, unknown>)[
|
return (config as Record<string, unknown>)[
|
||||||
// name
|
name
|
||||||
// ] as T[keyof T];
|
] as T[keyof T];
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
// ) as T;
|
) as T;
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,11 +21,13 @@ declare type UserFlags = Partial<{
|
||||||
mod: number;
|
mod: number;
|
||||||
admin: number;
|
admin: number;
|
||||||
vanish: number;
|
vanish: number;
|
||||||
|
chat_color: string;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
type ChannelFlags = Partial<{
|
type TChannelFlags = Partial<{
|
||||||
limit: number;
|
limit: number;
|
||||||
owner_id: string;
|
owner_id: string;
|
||||||
|
no_crown: boolean;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
declare interface Tag {
|
declare interface Tag {
|
||||||
|
@ -40,7 +42,7 @@ declare interface User {
|
||||||
tag?: Tag;
|
tag?: Tag;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare interface Participant extends User {
|
declare interface IParticipant extends User {
|
||||||
id: string; // participant id (same as user id on mppclone)
|
id: string; // participant id (same as user id on mppclone)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -65,6 +65,12 @@ export class Gateway {
|
||||||
// Whether the user has sent a channel list subscription request, a.k.a. opened the channel list
|
// Whether the user has sent a channel list subscription request, a.k.a. opened the channel list
|
||||||
public hasOpenedChannelList = false; // implemented
|
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)
|
// Whether the user has changed their name/color this session (not just changed from default)
|
||||||
public hasChangedName = false; // implemented
|
public hasChangedName = false; // implemented
|
||||||
public hasChangedColor = false; // implemented
|
public hasChangedColor = false; // implemented
|
||||||
|
|
|
@ -10,7 +10,7 @@ import type {
|
||||||
IChannelInfo,
|
IChannelInfo,
|
||||||
IChannelSettings,
|
IChannelSettings,
|
||||||
OutgoingSocketEvents,
|
OutgoingSocketEvents,
|
||||||
Participant,
|
IParticipant,
|
||||||
IncomingSocketEvents,
|
IncomingSocketEvents,
|
||||||
UserFlags,
|
UserFlags,
|
||||||
Vector2,
|
Vector2,
|
||||||
|
@ -44,6 +44,7 @@ import {
|
||||||
} from "~/data/permissions";
|
} from "~/data/permissions";
|
||||||
import { getRoles } from "~/data/role";
|
import { getRoles } from "~/data/role";
|
||||||
import { setTag } from "~/util/tags";
|
import { setTag } from "~/util/tags";
|
||||||
|
import { bus } from "~/event/bus";
|
||||||
|
|
||||||
const logger = new Logger("Sockets");
|
const logger = new Logger("Sockets");
|
||||||
|
|
||||||
|
@ -506,7 +507,7 @@ export class Socket extends EventEmitter {
|
||||||
/**
|
/**
|
||||||
* Send this socket a channel update message
|
* Send this socket a channel update message
|
||||||
**/
|
**/
|
||||||
public sendChannelUpdate(ch: IChannelInfo, ppl: Participant[]) {
|
public sendChannelUpdate(ch: IChannelInfo, ppl: IParticipant[]) {
|
||||||
this.sendArray([
|
this.sendArray([
|
||||||
{
|
{
|
||||||
m: "ch",
|
m: "ch",
|
||||||
|
@ -545,7 +546,7 @@ export class Socket extends EventEmitter {
|
||||||
const ch = this.getCurrentChannel();
|
const ch = this.getCurrentChannel();
|
||||||
|
|
||||||
if (ch) {
|
if (ch) {
|
||||||
const part = this.getParticipant() as Participant;
|
const part = this.getParticipant() as IParticipant;
|
||||||
const cursorPos = this.getCursorPos();
|
const cursorPos = this.getCursorPos();
|
||||||
|
|
||||||
ch.sendArray([
|
ch.sendArray([
|
||||||
|
@ -852,6 +853,28 @@ export class Socket extends EventEmitter {
|
||||||
|
|
||||||
return false;
|
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>();
|
export const socketsByUUID = new Map<Socket["uuid"], Socket>();
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { ChannelList } from "../../../../channel/ChannelList";
|
import { ChannelList } from "../../../../channel/ChannelList";
|
||||||
import { ServerEventListener } from "../../../../util/types";
|
import { ServerEventListener, TChannelFlags } from "../../../../util/types";
|
||||||
|
|
||||||
export const ch_flag: ServerEventListener<"ch_flag"> = {
|
export const ch_flag: ServerEventListener<"ch_flag"> = {
|
||||||
id: "ch_flag",
|
id: "ch_flag",
|
||||||
|
@ -14,9 +14,15 @@ export const ch_flag: ServerEventListener<"ch_flag"> = {
|
||||||
chid = ch.getID();
|
chid = ch.getID();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (typeof msg.key !== "string") return;
|
||||||
|
if (typeof msg.value === "undefined") return;
|
||||||
|
|
||||||
const ch = ChannelList.getList().find(c => c.getID() == chid);
|
const ch = ChannelList.getList().find(c => c.getID() == chid);
|
||||||
if (!ch) return;
|
if (!ch) return;
|
||||||
|
|
||||||
ch.setFlag(msg.key, msg.value);
|
ch.setFlag(
|
||||||
|
msg.key as keyof TChannelFlags,
|
||||||
|
msg.value as TChannelFlags[keyof TChannelFlags]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
};
|
|
@ -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();
|
||||||
|
}
|
||||||
|
};
|
|
@ -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;
|
||||||
|
}
|
||||||
|
};
|
|
@ -17,6 +17,9 @@ export interface RateLimitConfigList<
|
||||||
"-ls": RL;
|
"-ls": RL;
|
||||||
chown: RL;
|
chown: RL;
|
||||||
|
|
||||||
|
"+custom": RL;
|
||||||
|
"-custom": RL;
|
||||||
|
|
||||||
// weird limits
|
// weird limits
|
||||||
hi: RL;
|
hi: RL;
|
||||||
bye: RL;
|
bye: RL;
|
||||||
|
@ -28,6 +31,7 @@ export interface RateLimitConfigList<
|
||||||
userset: RLC;
|
userset: RLC;
|
||||||
chset: RLC;
|
chset: RLC;
|
||||||
n: RLC; // not to be confused with NoteQuota
|
n: RLC; // not to be confused with NoteQuota
|
||||||
|
custom: RLC;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,37 +63,8 @@ export const config = ConfigManager.loadConfig<RateLimitsConfig>(
|
||||||
"-ls": 1000 / 60,
|
"-ls": 1000 / 60,
|
||||||
chown: 2000,
|
chown: 2000,
|
||||||
|
|
||||||
hi: 1000 / 20,
|
"+custom": 1000 / 60,
|
||||||
bye: 1000 / 20,
|
"-custom": 1000 / 60,
|
||||||
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,
|
|
||||||
|
|
||||||
hi: 1000 / 20,
|
hi: 1000 / 20,
|
||||||
bye: 1000 / 20,
|
bye: 1000 / 20,
|
||||||
|
@ -108,6 +83,49 @@ export const config = ConfigManager.loadConfig<RateLimitsConfig>(
|
||||||
n: {
|
n: {
|
||||||
interval: 1000,
|
interval: 1000,
|
||||||
num: 512
|
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,
|
"-ls": 1000 / 60,
|
||||||
chown: 500,
|
chown: 500,
|
||||||
|
|
||||||
|
"+custom": 1000 / 120,
|
||||||
|
"-custom": 1000 / 120,
|
||||||
|
|
||||||
hi: 1000 / 20,
|
hi: 1000 / 20,
|
||||||
bye: 1000 / 20,
|
bye: 1000 / 20,
|
||||||
devices: 1000 / 20,
|
devices: 1000 / 20,
|
||||||
|
@ -140,6 +161,10 @@ export const config = ConfigManager.loadConfig<RateLimitsConfig>(
|
||||||
n: {
|
n: {
|
||||||
interval: 50,
|
interval: 50,
|
||||||
num: 512
|
num: 512
|
||||||
|
},
|
||||||
|
custom: {
|
||||||
|
interval: 1000 * 60,
|
||||||
|
num: 20000
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { ConfigManager } from "../util/config";
|
import { ConfigManager } from "../util/config";
|
||||||
import type { Participant, UserFlags } from "../util/types";
|
import type { IParticipant, UserFlags } from "../util/types";
|
||||||
|
|
||||||
export interface UsersConfig {
|
export interface UsersConfig {
|
||||||
defaultName: string;
|
defaultName: string;
|
||||||
|
@ -7,7 +7,7 @@ export interface UsersConfig {
|
||||||
enableColorChanging: boolean;
|
enableColorChanging: boolean;
|
||||||
enableCustomNoteData: boolean;
|
enableCustomNoteData: boolean;
|
||||||
enableTags: boolean;
|
enableTags: boolean;
|
||||||
adminParticipant: Participant;
|
adminParticipant: IParticipant;
|
||||||
enableAdminEval: boolean;
|
enableAdminEval: boolean;
|
||||||
tokenAuth: "jwt" | "uuid" | "none";
|
tokenAuth: "jwt" | "uuid" | "none";
|
||||||
browserChallenge: "none" | "obf" | "basic";
|
browserChallenge: "none" | "obf" | "basic";
|
||||||
|
|
Loading…
Reference in New Issue