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
|
||||
- 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`.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
2
public
|
@ -1 +1 @@
|
|||
Subproject commit 1dc00c7f885ac919a1bda7d4c749d33bd594c42f
|
||||
Subproject commit a4e9210048ef2e7648c5614a9187e5788a6cc827
|
|
@ -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;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Participant, Vector2 } from "../util/types";
|
||||
import { IParticipant, Vector2 } from "../util/types";
|
||||
import { Socket } from "../ws/Socket";
|
||||
|
||||
// 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
|
||||
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");
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
// File contents changed callback
|
||||
// const watcher = watchFile(configPath, () => {
|
||||
// this.logger.info(
|
||||
// "Reloading config due to changes:",
|
||||
// configPath
|
||||
// );
|
||||
// this.loadConfig(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.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);
|
||||
|
||||
// File contents changed callback
|
||||
// const watcher = watchFile(configPath, () => {
|
||||
// this.logger.info(
|
||||
// "Reloading config due to changes:",
|
||||
// configPath
|
||||
// );
|
||||
// this.loadConfig(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, 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>();
|
||||
|
|
|
@ -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]
|
||||
);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
|
|
Loading…
Reference in New Issue