diff --git a/config/frontend.yml b/config/frontend.yml new file mode 100644 index 0000000..4dedbe5 --- /dev/null +++ b/config/frontend.yml @@ -0,0 +1 @@ +topButtons: none diff --git a/config/tags.yml b/config/tags.yml new file mode 100644 index 0000000..2761373 --- /dev/null +++ b/config/tags.yml @@ -0,0 +1,18 @@ +admin: + text: ADMIN + color: "#ff5555" +trialmod: + text: TRIAL MOD + color: "#ffbb00" +mod: + text: MOD + color: "#00aa00" +media: + text: MEDIA + color: "#ff55ff" +bot: + text: BOT + color: "#5555ff" +owner: + text: OWNER + color: "#aa0000" diff --git a/config/users.yml b/config/users.yml index 383fedd..f2f3da0 100644 --- a/config/users.yml +++ b/config/users.yml @@ -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: true +enableColorChanging: false # 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. diff --git a/prisma/schema.prisma b/prisma/schema.prisma index f393962..64538f6 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -12,13 +12,13 @@ datasource db { } model User { - id String @id @unique @map("_id") - name String @default("Anonymous") - color String @default("#ffffff") - flags String @default("{}") // JSON flags object - tag String // JSON tag - tokens String @default("[]") // JSON tokens - group String @default("default") // Permission group + id String @id @unique @map("_id") + name String @default("Anonymous") + color String @default("#ffffff") + flags String @default("{}") // JSON flags object + tag String? // JSON tag + tokens String @default("[]") // JSON tokens + group String @default("default") // Permission group } model ChatHistory { @@ -34,7 +34,8 @@ model Channel { } model Role { - userId String @unique + id Int @id @unique @default(autoincrement()) + userId String roleId String } diff --git a/src/channel/Channel.ts b/src/channel/Channel.ts index c68defe..140ecf4 100644 --- a/src/channel/Channel.ts +++ b/src/channel/Channel.ts @@ -321,6 +321,7 @@ export class Channel extends EventEmitter { this.on("user data update", (user: User) => { try { + if (!this.ppl.map(p => p._id).includes(user.id)) return; if (typeof user.name !== "string") return; if (typeof user.color !== "string") return; if (typeof user.id !== "string") return; diff --git a/src/data/permission.ts b/src/data/permissions.ts similarity index 98% rename from src/data/permission.ts rename to src/data/permissions.ts index 4420381..f13ccad 100644 --- a/src/data/permission.ts +++ b/src/data/permissions.ts @@ -15,7 +15,8 @@ export const config = ConfigManager.loadConfig>( "usersetOthers", "siteBan", "siteBanAnyReason", - "siteBanAnyDuration" + "siteBanAnyDuration", + "event.admin.*" ] } ); diff --git a/src/data/role.ts b/src/data/role.ts index 440d2cc..06991fb 100644 --- a/src/data/role.ts +++ b/src/data/role.ts @@ -29,7 +29,7 @@ export async function giveRole(userId: string, roleId: string) { } export async function removeRole(userId: string, roleId: string) { - return await prisma.role.delete({ + return await prisma.role.deleteMany({ where: { userId, roleId diff --git a/src/index.ts b/src/index.ts index 4060508..0dff84d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -19,7 +19,7 @@ import { loadForcedStartupChannels } from "./channel/forceLoad"; import { Logger } from "./util/Logger"; // docker hates this next one import { startReadline } from "./util/readline"; -import { loadDefaultPermissions } from "./data/permission"; +import { loadDefaultPermissions } from "./data/permissions"; // wrapper for some reason export function startServer() { diff --git a/src/util/readline/commands.ts b/src/util/readline/commands.ts index 0ede9db..c05059e 100644 --- a/src/util/readline/commands.ts +++ b/src/util/readline/commands.ts @@ -8,7 +8,8 @@ import { loadDefaultPermissions, removeAllRolePermissions, removeRolePermission -} from "~/data/permission"; +} from "~/data/permissions"; +import { builtinTags, removeTag, setBuiltinTag } from "../tags"; Command.addCommand( new Command(["help", "h", "commands", "cmds"], "help", msg => { @@ -94,11 +95,19 @@ Command.addCommand( if (msg.args[1] === "add") { if (!msg.args[3]) return "No role id provided"; + await giveRole(msg.args[2], msg.args[3]); + await setBuiltinTag(msg.args[2], msg.args[3]); + return `Gave user ${msg.args[2]} role ${msg.args[3]}`; } else if (msg.args[1] === "remove") { if (!msg.args[3]) return "No role id provided"; await removeRole(msg.args[2], msg.args[3]); + + if (Object.keys(builtinTags).includes(msg.args[3])) { + await removeTag(msg.args[2]); + } + return `Removed role ${msg.args[3]} from ${msg.args[2]}`; } else if (msg.args[1] === "list") { const roles = await getRoles(msg.args[2]); diff --git a/src/util/tags.ts b/src/util/tags.ts new file mode 100644 index 0000000..17ccaac --- /dev/null +++ b/src/util/tags.ts @@ -0,0 +1,78 @@ +import { Socket } from "~/ws/Socket"; +import { ConfigManager } from "./config"; +import { Tag } from "./types"; +import { readUser, updateUser } from "~/data/user"; +import { prisma } from "~/data/prisma"; +import { User } from "@prisma/client"; +import { ChannelList } from "~/channel/ChannelList"; + +export const builtinTags = ConfigManager.loadConfig>( + "config/tags.yml", + { + admin: { + text: "ADMIN", + color: "#ff5555" + }, + trialmod: { + text: "TRIAL MOD", + color: "#ffbb00" + }, + mod: { + text: "MOD", + color: "#00aa00" + }, + media: { + text: "MEDIA", + color: "#ff55ff" + }, + bot: { + text: "BOT", + color: "#5555ff" + }, + owner: { + text: "OWNER", + color: "#aa0000" + } + } +); + +function propogateUser(user: User) { + const channelList = ChannelList.getList(); + + for (const ch of channelList) { + ch.emit("user data update", user); + } +} + +export async function setBuiltinTag(userId: string, tagId: string) { + const user = await readUser(userId); + if (!user) return; + + const tag = builtinTags[tagId]; + if (!tag) return; + + user.tag = JSON.stringify(tag); + await updateUser(user.id, user); + + propogateUser(user); +} + +export async function setTag(userId: string, tag: Tag) { + const user = await readUser(userId); + if (!user) return; + + user.tag = JSON.stringify(tag); + await updateUser(user.id, user); + + propogateUser(user); +} + +export async function removeTag(userId: string) { + const user = await readUser(userId); + if (!user) return; + + user.tag = ""; + await updateUser(user.id, user); + + propogateUser(user); +} diff --git a/src/util/types.d.ts b/src/util/types.d.ts index d2140a6..5b5cb77 100644 --- a/src/util/types.d.ts +++ b/src/util/types.d.ts @@ -231,6 +231,18 @@ declare interface ServerEvents { name: string; }; + setcolor: { + m: "setcolor"; + _id: string; + color: string; + }; + + setname: { + m: "setname"; + _id: string; + color: string; + }; + user_flag: { m: "user_flag"; _id: string; diff --git a/src/ws/Socket.ts b/src/ws/Socket.ts index 17f7baa..6342cab 100644 --- a/src/ws/Socket.ts +++ b/src/ws/Socket.ts @@ -41,8 +41,9 @@ import { getUserPermissions, hasPermission, validatePermission -} from "~/data/permission"; +} from "~/data/permissions"; import { getRoles } from "~/data/role"; +import { setTag } from "~/util/tags"; const logger = new Logger("Sockets"); @@ -792,10 +793,8 @@ export class Socket extends EventEmitter { **/ public setTag(text: string, color: string) { //logger.debug("Setting tag:", text, color); - const user = this.getUser(); - if (!user) return; - user.tag = JSON.stringify({ text, color }); - updateUser(this.getUserID(), user); + if (!this.user) return; + setTag(this.user.id, { text, color }); } /** @@ -836,6 +835,11 @@ export class Socket extends EventEmitter { }); } + /** + * Check if this socket has a given permission + * @param perm Permission string + * @returns Whether this socket has the given permission + */ public async hasPermission(perm: string) { if (!this.user) return false; @@ -844,6 +848,8 @@ export class Socket extends EventEmitter { for (const permission of permissions) { if (validatePermission(perm, permission)) return true; } + + return false; } } diff --git a/src/ws/events.ts b/src/ws/events.ts index 9b6de1d..856064a 100644 --- a/src/ws/events.ts +++ b/src/ws/events.ts @@ -1,18 +1,18 @@ -import type { ServerEventListener, ServerEvents } from "../util/types"; +import type { EventID, ServerEventListener, ServerEvents } from "../util/types"; export class EventGroup { - public eventList = new Array>(); + public eventList = new Array>(); constructor(public id: string) {} - public add(listener: ServerEventListener) { + public add(listener: ServerEventListener) { this.eventList.push(listener); } - public addMany(...listeners: ServerEventListener[]) { + public addMany(...listeners: ServerEventListener[]) { for (const l of listeners) this.add(l); } - public remove(listener: ServerEventListener) { + public remove(listener: ServerEventListener) { this.eventList.splice(this.eventList.indexOf(listener), 1); } } diff --git a/src/ws/events/user/handlers/hi.ts b/src/ws/events/user/handlers/hi.ts index 5c48e1d..78bb0a8 100644 --- a/src/ws/events/user/handlers/hi.ts +++ b/src/ws/events/user/handlers/hi.ts @@ -1,4 +1,4 @@ -import { getUserPermissions } from "~/data/permission"; +import { getUserPermissions } from "~/data/permissions"; import { Logger } from "~/util/Logger"; import { getMOTD } from "~/util/motd"; import { createToken, getToken, validateToken } from "~/util/token"; diff --git a/src/ws/message.ts b/src/ws/message.ts index 1bbde64..de14423 100644 --- a/src/ws/message.ts +++ b/src/ws/message.ts @@ -14,7 +14,7 @@ export function handleMessage(socket: Socket, text: string) { logger.warn( "Received message that isn't an array! --", transmission, - " -- from", + "-- from", socket.getUserID() ); } else { diff --git a/src/ws/server.ts b/src/ws/server.ts index d7a1b76..2751611 100644 --- a/src/ws/server.ts +++ b/src/ws/server.ts @@ -8,6 +8,8 @@ import env from "../util/env"; import { getMOTD } from "../util/motd"; import nunjucks from "nunjucks"; import type { ServerWebSocket } from "bun"; +import { ConfigManager } from "~/util/config"; +import { config as usersConfig } from "./usersConfig"; const logger = new Logger("WebSocket Server"); @@ -15,6 +17,17 @@ const logger = new Logger("WebSocket Server"); // for checking if they visited the site and are also connected to the websocket const httpIpCache = new Map(); +interface IFrontendConfig { + topButtons: "original" | "none"; +} + +const config = ConfigManager.loadConfig( + "config/frontend.yml", + { + topButtons: "original" + } +); + /** * Get a rendered version of the index file * @returns Response with html in it @@ -29,7 +42,9 @@ async function getIndex() { const index = Bun.file("./public/index.html"); const rendered = nunjucks.renderString(await index.text(), { - motd: getMOTD() + motd: getMOTD(), + config, + usersConfig }); const response = new Response(rendered); @@ -38,7 +53,7 @@ async function getIndex() { return response; } -type ServerWebSocketMPP = ServerWebSocket<{ ip: string, socket: Socket }> +type ServerWebSocketMPP = ServerWebSocket<{ ip: string; socket: Socket }>; export const app = Bun.serve<{ ip: string }>({ port: env.PORT, @@ -81,7 +96,7 @@ export const app = Bun.serve<{ ip: string }>({ if (data) { return new Response(data); } - + return getIndex(); }