Implement tags and modernize CSS

This commit is contained in:
Hri7566 2024-10-24 03:09:30 -04:00
parent ea11adef63
commit 0ce0179f5a
10 changed files with 196 additions and 45 deletions

View File

@ -81,20 +81,20 @@ This has always been the future intention of this project.
- [ ] MPP.com data message
- Implement based on `spooky.js` given there is no official documentation
- [ ] No cussing setting
- badwords.txt
- [ ] Decide on "lyrical notes"
- [x] Full server-wide event bus
- [ ] Channel events
- [ ] Socket events
- [ ] User data events
- [ ] Permission-related events
- [ ] Redo ratelimits
- [ ] Test every frontend
- [ ] Test fishing bot
- [ ] Remote console
- [x] Modify frontend to use templating
- [x] index.html
- [ ] js files
- [ ] Completley reorganize script.js
- [x] Load configs on client
- [x] Tags
- [ ] Update tags live when changing from server console
## Backlog/Notes

View File

@ -1,3 +1,6 @@
topButtons: none
disableChat: false
winter: false
enableSlide: false
createdRoomSocialLinks: false
playingAloneSocialLinks: false

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.
@ -37,7 +37,7 @@ enableAdminEval: true
# The token validation scheme. Valid values are "none", "jwt" and "uuid".
# This server will still validate existing tokens generated with other schemes if not set to "none", mimicking MPP.net's server.
# This is set to "none" by default because MPP.com does not have a token system.
tokenAuth: none
tokenAuth: jwt
# The browser challenge scheme. Valid options are "none", "obf" and "basic".
# This is to change what is sent in the "b" message.

View File

@ -23,7 +23,7 @@ import {
getChatHistory,
deleteChatHistory
} from "../data/history";
import { mixin, darken, spoop_text } from "../util/helpers";
import { mixin, darken, spoop_text, hsl2hex } from "../util/helpers";
import type { User } from "@prisma/client";
import { heapStats } from "bun:jsc";
import {
@ -203,6 +203,10 @@ export class Channel extends EventEmitter {
this.printMemoryInChat();
}, 1000);
}
if (this.getID() === "rainbowfest") {
this.setFlag("rainbow", true);
}
}
private alreadyBound = false;
@ -221,6 +225,12 @@ export class Channel extends EventEmitter {
this.settings.owner_id = this.flags.owner_id;
}
if (this.flags.rainbow) {
this.startRainbow();
} else {
this.stopRainbow();
}
// this.logger.debug("update");
// Send updated info
for (const socket of socketsByUUID.values()) {
@ -1399,6 +1409,30 @@ export class Channel extends EventEmitter {
this.stays = enable;
this.save();
}
private startedRainbow = false;
private rainbowInterval: Timer | undefined;
private rainbowHue = 0;
public startRainbow() {
if (this.startedRainbow) return;
this.startedRainbow = true;
this.rainbowInterval = setInterval(() => {
this.rainbowHue++;
while (this.rainbowHue > 360) this.rainbowHue -= 360;
while (this.rainbowHue < 0) this.rainbowHue += 360;
this.changeSettings({
color: hsl2hex(this.rainbowHue, 100, 75),
color2: hsl2hex(this.rainbowHue, 100, 25)
});
}, 1000 / 5);
}
public stopRainbow() {
if (!this.startedRainbow) return;
this.startedRainbow = false;
clearInterval(this.rainbowInterval);
}
}
export default Channel;

View File

@ -46,20 +46,13 @@ export function hasOwn(obj: any, property: string | number | Symbol) {
* @returns Darkened hex color
*/
export function darken(color: string, amount = 0x40) {
const r = Math.max(
0,
parseInt(color.substring(1, 3), 16) - amount
);
const g = Math.max(
0,
parseInt(color.substring(3, 5), 16) - amount
);
const b = Math.max(
0,
parseInt(color.substring(5, 7), 16) - amount
);
const r = Math.max(0, parseInt(color.substring(1, 3), 16) - amount);
const g = Math.max(0, parseInt(color.substring(3, 5), 16) - amount);
const b = Math.max(0, parseInt(color.substring(5, 7), 16) - amount);
return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
return `#${r.toString(16).padStart(2, "0")}${g
.toString(16)
.padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
}
// spooky.jsaurus
@ -96,3 +89,26 @@ export function mixin(obj1: any, obj2: any) {
obj1[key] = obj2[key];
}
}
/**
* HSL to hex color converter
* https://stackoverflow.com/questions/36721830/convert-hsl-to-rgb-and-hex
* @param h Hue (degrees)
* @param s Saturation (percentage)
* @param l Lightness (percentage)
* @returns
*/
export function hsl2hex(h: number, s: number, l: number) {
l /= 100;
const a = (s * Math.min(l, 1 - l)) / 100;
const f = (n: number) => {
const k = (n + h / 30) % 12;
const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
return Math.round(255 * color)
.toString(16)
.padStart(2, "0"); // convert to Hex and prefix "0" if needed
};
return `#${f(0)}${f(8)}${f(4)}`;
}

View File

@ -9,7 +9,7 @@ import {
removeAllRolePermissions,
removeRolePermission
} from "~/data/permissions";
import { builtinTags, removeTag, setBuiltinTag } from "../tags";
import { builtinTags, getTag, removeTag, setBuiltinTag, setTag } from "../tags";
import logger from "./logger";
import { socketsByUUID } from "~/ws/Socket";
@ -251,3 +251,62 @@ Command.addCommand(
}
)
);
Command.addCommand(
new Command(
["tag"],
"tag <set, remove, list> <user id> <builtin, custom> <[builtin id], [text] [color]>",
async msg => {
const method = msg.args[1];
const userId = msg.args[2];
if (!method || !userId) return "tag <set, remove, list> <user id>";
if (method === "set" || method === "change" || method === "add") {
const type = msg.args[3];
if (!type)
return "tag <set> <user id> <builtin, custom> [<builtin id>, <text> <color>]";
if (type == "builtin") {
const tag = msg.args[4];
if (!tag)
return "tag <set> <user id> <builtin> <builtin id>";
await setBuiltinTag(userId, tag);
return `Set builtin tag ${tag} for ${userId}`;
} else if (type == "custom") {
if (!msg.args[4])
return "tag <set> <user id> <custom> <text> <color>";
const newargs = msg.args.slice(4);
logger.debug(newargs);
const comargs = newargs.join(" ").split(",");
logger.debug(comargs);
const text = comargs[0];
const color = comargs.slice(1).join(", ");
logger.debug(text, color);
if (!text || !color)
return "tag <set> <user id> <custom> <text> <color>";
await setTag(userId, {
text,
color
});
return `Set custom tag [${text}, ${color}] for ${userId}`;
} else {
return "tag <set> <user id> <builtin, custom> <[builtin id], [text] [color]>";
}
} else if (method === "remove" || method === "unset") {
if (Object.keys(builtinTags).includes(msg.args[3])) {
await removeTag(userId);
}
return `Removed tag from ${userId}`;
} else if (method === "list") {
const tag = await getTag(userId);
return `Tag of ${userId}: ${JSON.stringify(tag)}`;
}
}
)
);

View File

@ -76,3 +76,11 @@ export async function removeTag(userId: string) {
propogateUser(user);
}
export async function getTag(userId: string) {
const user = await readUser(userId);
if (!user) return;
if (typeof user.tag !== "string") return;
return JSON.parse(user.tag);
}

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

@ -28,6 +28,7 @@ type TChannelFlags = Partial<{
limit: number;
owner_id: string;
no_crown: boolean;
rainbow: boolean;
}>;
declare interface Tag {

View File

@ -597,6 +597,12 @@ export class Socket extends EventEmitter {
const ch = this.getCurrentChannel();
let hasNoteRateLimitBypass = false;
const flags = this.getUserFlags();
if (flags) {
if (flags.admin == 1) isAdmin = true;
}
try {
const flags = this.getUserFlags();

View File

@ -9,7 +9,8 @@ import { getMOTD } from "../util/motd";
import nunjucks from "nunjucks";
import type { Server, ServerWebSocket } from "bun";
import { ConfigManager } from "~/util/config";
import { config as usersConfig, usersConfigPath } from "./usersConfig";
import { usersConfigPath } from "./usersConfig";
import { ChannelList } from "~/channel/ChannelList";
const logger = new Logger("WebSocket Server");
@ -18,24 +19,33 @@ const logger = new Logger("WebSocket Server");
export const httpIpCache = new Map<string, number>();
interface IFrontendConfig {
topButtons: "original" | "none";
topButtons: "original" | "mppnet" | "none";
disableChat: boolean;
winter: boolean;
enableSlide: boolean;
createdRoomSocialLinks: boolean;
playingAloneSocialLinks: boolean;
}
const configPath = "config/frontend.yml";
export const frontendConfigPath = "config/frontend.yml";
const config = ConfigManager.loadConfig<IFrontendConfig>(configPath, {
topButtons: "original",
disableChat: false,
winter: false
});
export const frontendConfig = ConfigManager.loadConfig<IFrontendConfig>(
frontendConfigPath,
{
topButtons: "original",
disableChat: false,
winter: false,
enableSlide: false,
createdRoomSocialLinks: false,
playingAloneSocialLinks: false
}
);
/**
* Get a rendered version of the index file
* @returns Response with html in it
*/
async function getIndex() {
async function getIndex(path?: string, userID?: string) {
// This tiny function took like an hour to write because
// nobody realistically uses templates in 2024 and documents
// it well enough to say what library they used
@ -44,19 +54,27 @@ async function getIndex() {
const index = Bun.file("./public/index.html");
const base64config = btoa(
JSON.stringify({
config: ConfigManager.getOriginalConfigObject(configPath),
usersConfig: ConfigManager.getOriginalConfigObject(usersConfigPath)
})
);
const rendered = nunjucks.renderString(await index.text(), {
const configs: Record<string, unknown> = {
motd: getMOTD(),
config,
usersConfig,
base64config
});
config: ConfigManager.getOriginalConfigObject(frontendConfigPath),
usersConfig: ConfigManager.getOriginalConfigObject(usersConfigPath)
};
try {
if (typeof path === "string") {
const ch = ChannelList.getChannel(path.substring(1));
if (typeof ch !== "undefined") {
configs.urlChannel = ch.getInfo(userID);
}
}
} catch (err) {}
const base64config = btoa(JSON.stringify(configs));
configs.base64config = base64config;
logger.debug(configs);
const rendered = nunjucks.renderString(await index.text(), configs);
const response = new Response(rendered);
response.headers.set("Content-Type", "text/html");
@ -99,6 +117,12 @@ export function startHTTPServer() {
const file = path.join("./public/", url);
let _id;
try {
_id = createUserID(ip);
} catch (err) {}
// Time for unreadable blocks of confusion
try {
// Is it a file?
@ -111,14 +135,14 @@ export function startHTTPServer() {
return new Response(data);
}
return getIndex();
return getIndex(url, _id);
}
// Return the index file, since it's a channel name or something
return getIndex();
return getIndex(url, _id);
} catch (err) {
// Return the index file as a coverup of our extreme failure
return getIndex();
return getIndex(url, _id);
}
},
websocket: {