Implement tags and modernize CSS
This commit is contained in:
parent
ea11adef63
commit
0ce0179f5a
|
@ -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
|
||||
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
topButtons: none
|
||||
disableChat: false
|
||||
winter: false
|
||||
enableSlide: false
|
||||
createdRoomSocialLinks: false
|
||||
playingAloneSocialLinks: false
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)}`;
|
||||
}
|
||||
|
|
|
@ -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)}`;
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ type TChannelFlags = Partial<{
|
|||
limit: number;
|
||||
owner_id: string;
|
||||
no_crown: boolean;
|
||||
rainbow: boolean;
|
||||
}>;
|
||||
|
||||
declare interface Tag {
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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: {
|
||||
|
|
Loading…
Reference in New Issue