"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var moderation_exports = {}; __export(moderation_exports, { commands: () => commands, runCrisisDemote: () => runCrisisDemote, runPromote: () => runPromote }); module.exports = __toCommonJS(moderation_exports); var import_lib = require("../../lib"); var import_room_settings = require("./room-settings"); /** * Moderation commands * Pokemon Showdown - http://pokemonshowdown.com/ * * These are commands for staff. * * For the API, see chat-plugins/COMMANDS.md * * @license MIT */ const MAX_REASON_LENGTH = 600; const MUTE_LENGTH = 7 * 60 * 1e3; const HOURMUTE_LENGTH = 60 * 60 * 1e3; const DAY = 24 * 60 * 60 * 1e3; const REQUIRE_REASONS = true; function runPromote(promoter, room, userid, symbol, username, force) { const targetUser = Users.getExact(userid); username = username || userid; if (!username) return; if (userid.length > 18) { throw new Chat.ErrorMessage(`User '${username}' does not exist (the username is too long).`); } if (!targetUser && !Users.isUsernameKnown(userid) && !force) { throw new Chat.ErrorMessage(`User '${username}' is offline and unrecognized, and so can't be promoted.`); } if (targetUser && !targetUser.registered) { throw new Chat.ErrorMessage(`User '${username}' is unregistered, and so can't be promoted.`); } let currentSymbol = room.auth.getDirect(userid); if (room.auth.has(userid) && currentSymbol === Users.Auth.defaultSymbol()) { currentSymbol = "whitelist"; } const currentGroup = Users.Auth.getGroup(currentSymbol); const currentGroupName = currentGroup.name || "regular user"; const nextGroup = Config.groups[symbol]; if (currentSymbol === symbol) { throw new Chat.ErrorMessage(`User '${username}' is already a ${nextGroup?.name || symbol || "regular user"} in this room.`); } if (!promoter.can("makeroom")) { if (currentGroup.id && !promoter.can(`room${currentGroup.id || "voice"}`, null, room)) { throw new Chat.ErrorMessage(`Access denied for promoting/demoting ${username} from ${currentGroupName}.`); } if (symbol !== " " && !promoter.can(`room${nextGroup.id || "voice"}`, null, room)) { throw new Chat.ErrorMessage(`Access denied for promoting/demoting ${username} to ${nextGroup.name}.`); } } if (targetUser?.locked && room.persist && room.settings.isPrivate !== true && nextGroup.rank > 2) { throw new Chat.ErrorMessage(`${username} is locked and can't be promoted.`); } if (symbol === Users.Auth.defaultSymbol()) { room.auth.delete(userid); } else { room.auth.set(userid, symbol); } if (targetUser) { targetUser.updateIdentity(room.roomid); if (room.subRooms) { for (const subRoom of room.subRooms.values()) { targetUser.updateIdentity(subRoom.roomid); } } } if (targetUser && room.users[targetUser.id] && room.persist && room.settings.isPrivate !== true) { return targetUser; } return null; } function runCrisisDemote(userid) { const from = []; const section = Users.globalAuth.sectionLeaders.get(userid); if (section) { from.push(`Section Leader (${import_room_settings.RoomSections.sectionNames[section] || section})`); Users.globalAuth.deleteSection(userid); } const globalGroup = Users.globalAuth.get(userid); if (globalGroup && globalGroup !== " ") { from.push(globalGroup); Users.globalAuth.delete(userid); } for (const room of Rooms.global.chatRooms) { if (!room.settings.isPrivate && room.auth.isStaff(userid)) { let oldGroup = room.auth.getDirect(userid); if (oldGroup === " ") { oldGroup = "whitelist in "; } else { room.auth.set(userid, "+"); } from.push(`${oldGroup}${room.roomid}`); } } return from; } Punishments.addPunishmentType({ type: "YEARLOCK", desc: "Locked for a year", onActivate: (user, punishment) => { user.locked = user.id; Chat.punishmentfilter(user, punishment); } }); const commands = { roomowner(target, room, user) { room = this.requireRoom(); if (!room.persist) { return this.sendReply("/roomowner - This room isn't designed for per-room moderation to be added"); } if (!target) return this.parse("/help roomowner"); const { targetUser, targetUsername, rest } = this.splitUser(target, { exactName: true }); if (rest) return this.errorReply(`This command does not support specifying a reason.`); const userid = toID(targetUsername); if (!Users.isUsernameKnown(userid)) { return this.errorReply(`User '${targetUsername}' is offline and unrecognized, and so can't be promoted.`); } this.checkCan("makeroom"); if (room.auth.getDirect(userid) === "#") return this.errorReply(`${targetUsername} is already a room owner.`); room.auth.set(userid, "#"); const message = `${targetUsername} was appointed Room Owner by ${user.name}.`; if (room.settings.isPrivate === true) { this.addModAction(message); Rooms.get(`upperstaff`)?.addByUser(user, `<<${room.roomid}>> ${message}`).update(); } else { this.addGlobalModAction(message); } this.modlog("ROOMOWNER", userid); if (targetUser) { targetUser.popup(`You were appointed Room Owner by ${user.name} in ${room.roomid}.`); room.onUpdateIdentity(targetUser); if (room.subRooms) { for (const subRoom of room.subRooms.values()) { subRoom.onUpdateIdentity(targetUser); } } } room.saveSettings(); }, roomownerhelp: [`/roomowner [username] - Appoints [username] as a room owner. Requires: ~`], roomdemote: "roompromote", forceroompromote: "roompromote", forceroomdemote: "roompromote", roompromote(target, room, user, connection, cmd) { if (!room) { return this.errorReply("This command is only available in rooms"); } this.checkChat(); if (!target) return this.parse("/help roompromote"); const force = cmd.startsWith("force"); const users = target.split(",").map((part) => part.trim()); let nextSymbol = users.pop(); if (nextSymbol === "deauth") nextSymbol = Users.Auth.defaultSymbol(); const nextGroup = Users.Auth.getGroup(nextSymbol); if (!nextSymbol) { return this.errorReply("Please specify a group such as /roomvoice or /roomdeauth"); } if (!Config.groups[nextSymbol]) { if (!force || !user.can("bypassall")) { this.errorReply(`Group '${nextSymbol}' does not exist.`); if (user.can("bypassall")) { this.errorReply(`If you want to promote to a nonexistent group, use /forceroompromote`); } return; } else if (!Users.Auth.isValidSymbol(nextSymbol)) { return this.errorReply(`Admins can forcepromote to nonexistent groups only if they are one character long`); } } if (!force && (nextGroup.globalonly || nextGroup.battleonly && !room.battle)) { return this.errorReply(`Group 'room${nextGroup.id || nextSymbol}' does not exist as a room rank.`); } const nextGroupName = nextGroup.name || "regular user"; for (const toPromote of users) { const userid = toID(toPromote); if (!userid) return this.parse("/help roompromote"); const oldSymbol = room.auth.getDirect(userid); let shouldPopup; try { shouldPopup = runPromote(user, room, userid, nextSymbol, toPromote, force); } catch (err) { if (err.name?.endsWith("ErrorMessage")) { this.errorReply(err.message); continue; } throw err; } const targetUser = Users.getExact(userid); const name = targetUser?.name || toPromote; if (this.pmTarget && targetUser) { const text = `${targetUser.name} was invited (and promoted to Room ${nextGroupName}) by ${user.name}.`; room.add(`|c|${user.getIdentity(room)}|/log ${text}`).update(); this.modlog("INVITE", targetUser, null, { noip: 1, noalts: 1 }); } else if (nextSymbol in Config.groups && oldSymbol in Config.groups && nextGroup.rank < Config.groups[oldSymbol].rank) { if (targetUser && room.users[targetUser.id] && !nextGroup.modlog) { targetUser.send(`>${room.roomid} (You were demoted to Room ${nextGroupName} by ${user.name}.)`); } this.privateModAction(`${name} was demoted to Room ${nextGroupName} by ${user.name}.`); this.modlog(`ROOM${nextGroupName.toUpperCase()}`, userid, "(demote)"); shouldPopup?.popup(`You were demoted to Room ${nextGroupName} by ${user.name} in ${room.roomid}.`); } else if (nextSymbol === "#") { this.addModAction(`${name} was promoted to ${nextGroupName} by ${user.name}.`); const logRoom = Rooms.get(room.settings.isPrivate === true ? "upperstaff" : "staff"); logRoom?.addByUser(user, `<<${room.roomid}>> ${name} was appointed Room Owner by ${user.name}`); this.modlog("ROOM OWNER", userid); shouldPopup?.popup(`You were promoted to ${nextGroupName} by ${user.name} in ${room.roomid}.`); } else { this.addModAction(`${name} was promoted to Room ${nextGroupName} by ${user.name}.`); this.modlog(`ROOM${nextGroupName.toUpperCase()}`, userid); shouldPopup?.popup(`You were promoted to Room ${nextGroupName} by ${user.name} in ${room.roomid}.`); } if (targetUser) { targetUser.updateIdentity(room.roomid); if (room.subRooms) { for (const subRoom of room.subRooms.values()) { targetUser.updateIdentity(subRoom.roomid); } } if (targetUser.trusted && !Users.isTrusted(targetUser.id)) { targetUser.trusted = ""; } } } room.saveSettings(); }, roompromotehelp: [ `/roompromote OR /roomdemote [comma-separated usernames], [group symbol] - Promotes/demotes the user(s) to the specified room rank. Requires: @ # ~`, `/room[group] [comma-separated usernames] - Promotes/demotes the user(s) to the specified room rank. Requires: @ # ~`, `/roomdeauth [comma-separated usernames] - Removes all room rank from the user(s). Requires: @ # ~` ], auth: "authority", stafflist: "authority", globalauth: "authority", authlist: "authority", authority(target, room, user, connection) { if (target && target !== "+") { const targetRoom = Rooms.search(target); const availableRoom = targetRoom?.checkModjoin(user); if (targetRoom && availableRoom) return this.parse(`/roomauth1 ${target}`); return this.parse(`/userauth ${target}`); } const showAll = !!target; const rankLists = {}; for (const [id, symbol] of Users.globalAuth) { if (symbol === " " || symbol === "+" && !showAll) continue; if (!rankLists[symbol]) rankLists[symbol] = []; rankLists[symbol].push(Users.globalAuth.usernames.get(id) || id); } const buffer = import_lib.Utils.sortBy( Object.entries(rankLists), ([symbol]) => -Users.Auth.getGroup(symbol).rank ).map( ([symbol, names]) => `${Config.groups[symbol] ? `**${Config.groups[symbol].name}s** (${symbol})` : symbol}: ` + import_lib.Utils.sortBy(names, (name) => toID(name)).join(", ") ); if (!showAll) buffer.push(`(Use \`\`/auth +\`\` to show global voice users.)`); if (!buffer.length) return connection.popup("This server has no global authority."); connection.popup(buffer.join("\n\n")); }, authhelp: [ `/auth - Show global staff for the server.`, `/auth + - Show global staff for the server, including voices.`, `/auth [room] - Show what roomauth a room has.`, `/auth [user] - Show what global and roomauth a user has.` ], roomstaff: "roomauth", roomauth1: "roomauth", roomauth(target, room, user, connection, cmd) { let userLookup = ""; if (cmd === "roomauth1") userLookup = ` To look up auth for a user, use /userauth ${target}`; let targetRoom = room; if (target) targetRoom = Rooms.search(target); if (!targetRoom?.checkModjoin(user)) { return this.errorReply(`The room "${target}" does not exist.`); } const showAll = user.can("mute", null, targetRoom); const rankLists = {}; for (const [id, rank] of targetRoom.auth) { if (rank === " " && !showAll) continue; if (!rankLists[rank]) rankLists[rank] = []; rankLists[rank].push(id); } const buffer = import_lib.Utils.sortBy( Object.entries(rankLists), ([symbol]) => -Users.Auth.getGroup(symbol).rank ).map(([symbol, names]) => { let group = Config.groups[symbol] ? `${Config.groups[symbol].name}s (${symbol})` : symbol; if (symbol === " ") group = "Whitelisted (this list is only visible to staff)"; return `${group}: ` + import_lib.Utils.sortBy(names).map((userid) => { const isOnline = Users.get(userid)?.statusType === "online"; return userid in targetRoom.users && isOnline ? `**${userid}**` : userid; }).join(", "); }); let curRoom = targetRoom; while (curRoom.parent) { const modjoinSetting = curRoom.settings.modjoin === true ? curRoom.settings.modchat : curRoom.settings.modjoin; const roomType = modjoinSetting ? `modjoin ${modjoinSetting} ` : ""; const inheritedUserType = modjoinSetting ? ` of rank ${modjoinSetting} and above` : ""; if (curRoom.parent) { const also = buffer.length === 0 ? `` : ` also`; buffer.push(`${curRoom.title} is a ${roomType}subroom of ${curRoom.parent.title}, so ${curRoom.parent.title} users${inheritedUserType}${also} have authority in this room.`); } curRoom = curRoom.parent; } if (!buffer.length) { connection.popup(`The room '${targetRoom.title}' has no auth. ${userLookup}`); return; } if (!curRoom.settings.isPrivate) { buffer.push(`${curRoom.title} is a public room, so global auth with no relevant roomauth will have authority in this room.`); } else if (curRoom.settings.isPrivate === "hidden" || curRoom.settings.isPrivate === "voice") { buffer.push(`${curRoom.title} is a hidden room, so global auth with no relevant roomauth will have authority in this room.`); } buffer.push(`Names in **bold** are online.`); if (targetRoom !== room) buffer.unshift(`${targetRoom.title} room auth:`); connection.popup(`${buffer.join("\n\n")}${userLookup}`); }, roomauthhelp: [ `/roomauth [room] - Shows a list of the staff and authority in the given [room].`, `If no room is given, it defaults to the current room.` ], userauth(target, room, user, connection) { const targetId = toID(target) || user.id; const targetUser = Users.getExact(targetId); const targetUsername = targetUser?.name || target; const buffer = []; let innerBuffer = []; const group = Users.globalAuth.get(targetId); if (group !== " " || Users.isTrusted(targetId)) { buffer.push(`Global auth: ${group === " " ? "trusted" : group}`); } const sectionLeader = Users.globalAuth.sectionLeaders.get(targetId); if (sectionLeader) { buffer.push(`Section leader: ${import_room_settings.RoomSections.sectionNames[sectionLeader]}`); } for (const curRoom of Rooms.rooms.values()) { if (curRoom.settings.isPrivate) continue; if (!curRoom.auth.has(targetId)) continue; innerBuffer.push(curRoom.auth.getDirect(targetId).trim() + curRoom.roomid); } if (innerBuffer.length) { buffer.push(`Room auth: ${innerBuffer.join(", ")}`); } if (targetId === user.id || user.can("lock")) { innerBuffer = []; for (const curRoom of Rooms.rooms.values()) { if (!curRoom.settings.isPrivate) continue; if (curRoom.settings.isPrivate === true) continue; if (!curRoom.auth.has(targetId)) continue; innerBuffer.push(curRoom.auth.getDirect(targetId).trim() + curRoom.roomid); } if (innerBuffer.length) { buffer.push(`Hidden room auth: ${innerBuffer.join(", ")}`); } } if (targetId === user.id || user.can("makeroom")) { innerBuffer = []; for (const chatRoom of Rooms.global.chatRooms) { if (!chatRoom.settings.isPrivate) continue; if (chatRoom.settings.isPrivate !== true) continue; if (!chatRoom.auth.has(targetId)) continue; innerBuffer.push(chatRoom.auth.getDirect(targetId).trim() + chatRoom.roomid); } if (innerBuffer.length) { buffer.push(`Private room auth: ${innerBuffer.join(", ")}`); } } if (!buffer.length) { buffer.push("No global or room auth."); } buffer.unshift(`${targetUsername} user auth:`); connection.popup(buffer.join("\n\n")); }, userauthhelp: [ `/userauth [username] - Shows all authority visible to the user for the given [username].`, `If no username is given, it defaults to the current user.` ], sectionleaders(target, room, user, connection) { const usernames = Users.globalAuth.usernames; const buffer = []; const sections = /* @__PURE__ */ Object.create(null); for (const [id, username] of usernames) { const sectionid2 = Users.globalAuth.sectionLeaders.get(id); if (!sectionid2) continue; if (!sections[sectionid2]) sections[sectionid2] = /* @__PURE__ */ new Set(); sections[sectionid2].add(username); } let sectionid; for (sectionid in sections) { if (!sections[sectionid].size) continue; buffer.push(`**${import_room_settings.RoomSections.sectionNames[sectionid]}**: ` + import_lib.Utils.sortBy([...sections[sectionid]]).join(", ")); } if (!buffer.length) throw new Chat.ErrorMessage(`There are no Section Leaders currently.`); connection.popup(buffer.join(` `)); }, sectionleadershelp: [ `/sectionleaders - Shows the current room sections and their section leaders.` ], async autojoin(target, room, user, connection) { const targets = target.split(",").filter(Boolean); if (targets.length > 16 || connection.inRooms.size > 1) { return connection.popup("To prevent DoS attacks, you can only use /autojoin for 16 or fewer rooms, when you haven't joined any rooms yet. Please use /join for each room separately."); } Rooms.global.autojoinRooms(user, connection); const autojoins = []; const promises = targets.map( (roomid) => user.tryJoinRoom(roomid, connection).then((ret) => { if (ret === Rooms.RETRY_AFTER_LOGIN) { autojoins.push(roomid); } }) ); await Promise.all(promises); connection.autojoins = autojoins.join(","); }, autojoinhelp: [`/autojoin [rooms] - Automatically joins all the given rooms.`], joim: "join", j: "join", async join(target, room, user, connection) { target = target.trim(); if (!target) return this.parse("/help join"); if (target.startsWith("http://")) target = target.slice(7); if (target.startsWith("https://")) target = target.slice(8); if (target.startsWith(`${Config.routes.client}/`)) target = target.slice(Config.routes.client.length + 1); if (target.startsWith(`${Config.routes.replays}/`)) target = `battle-${target.slice(Config.routes.replays.length + 1)}`; if (target.startsWith("psim.us/")) target = target.slice(8); const numRooms = [...Rooms.rooms.values()].filter((r) => user.id in r.users).length; if (!user.can("altsself") && !target.startsWith("view-") && numRooms >= 50) { return connection.sendTo(target, `|noinit||You can only join 50 rooms at a time.`); } const ret = await user.tryJoinRoom(target, connection); if (ret === Rooms.RETRY_AFTER_LOGIN) { connection.sendTo( target, `|noinit|namerequired|The room '${target}' does not exist or requires a login to join.` ); } }, joinhelp: [`/join [roomname] - Attempt to join the room [roomname].`], leave: "part", part(target, room, user, connection) { const targetRoom = target ? Rooms.search(target) : room; if (!targetRoom) { if (target.startsWith("view-")) { connection.openPages?.delete(target.slice(5)); if (!connection.openPages?.size) connection.openPages = null; Chat.handleRoomClose(target, user, connection); return; } return this.errorReply(`The room '${target}' does not exist.`); } Chat.handleRoomClose(targetRoom.roomid, user, connection); user.leaveRoom(targetRoom, connection); }, leavehelp: [`/leave - Leave the current room, or a given room.`], /********************************************************* * Moderating: Punishments *********************************************************/ kick: "warn", k: "warn", warn(target, room, user) { if (!target) return this.parse("/help warn"); this.checkChat(); if (room?.settings.isPersonal && !user.can("warn")) { return this.errorReply("Warning is unavailable in group chats."); } const globalWarn = !room || ["staff", "adminlog"].includes(room.roomid) || room.roomid.startsWith("help-") || room.battle && (!room.parent || room.parent.type !== "chat"); const { targetUser, inputUsername, targetUsername, rest: reason } = this.splitUser(target); const targetID = toID(targetUsername); const { privateReason, publicReason } = this.parseSpoiler(reason); const saveReplay = globalWarn && room?.battle; if (!targetUser?.connected) { if (!globalWarn) return this.errorReply(`User '${targetUsername}' not found.`); if (room) { this.checkCan("warn", null, room); } else { this.checkCan("lock"); } this.addGlobalModAction( `${targetID} was warned by ${user.name} while offline.${publicReason ? ` (${publicReason})` : ``}` ); this.globalModlog("WARN OFFLINE", targetUser || targetID, privateReason); Punishments.offlineWarns.set(targetID, publicReason); if (saveReplay) this.parse("/savereplay forpunishment"); return; } if (!globalWarn && !(targetUser.id in room.users)) { return this.errorReply(`User ${targetUsername} is not in the room ${room.roomid}.`); } if (publicReason.length > MAX_REASON_LENGTH) { return this.errorReply(`The reason is too long. It cannot exceed ${MAX_REASON_LENGTH} characters.`); } if (room) { this.checkCan("warn", targetUser, room); } else { this.checkCan("lock", targetUser); } if (targetUser.can("makeroom")) return this.errorReply("You are not allowed to warn upper staff members."); const now = Date.now(); const timeout = now - targetUser.lastWarnedAt; if (timeout < 15 * 1e3) { const remainder = (15 - timeout / 1e3).toFixed(2); return this.errorReply(`You must wait ${remainder} more seconds before you can warn ${targetUser.name} again.`); } const logMessage = `${targetUser.name} was warned by ${user.name}.${publicReason ? ` (${publicReason})` : ``}`; if (globalWarn) { this.addGlobalModAction(logMessage); this.globalModlog("WARN", targetUser, privateReason); } else { this.addModAction(logMessage); this.modlog("WARN", targetUser, privateReason, { noalts: 1 }); } targetUser.send(`|c|~|/warn ${publicReason}`); const userid = targetUser.getLastId(); if (room) { this.add(`|hidelines|unlink|${userid}`); if (userid !== toID(inputUsername)) this.add(`|hidelines|unlink|${toID(inputUsername)}`); } targetUser.lastWarnedAt = now; if (saveReplay) this.parse("/savereplay forpunishment"); return true; }, warnhelp: [ `/warn OR /k [username], [reason] - Warns a user showing them the site rules and [reason] in an overlay.`, `/warn OR /k [username], [reason] spoiler: [private reason] - Warns a user, marking [private reason] only in the modlog.`, `Requires: % @ # ~` ], redirect: "redir", redir(target, room, user, connection) { room = this.requireRoom(); if (!target) return this.parse("/help redirect"); if (room.settings.isPrivate || room.settings.isPersonal) { return this.errorReply("Users cannot be redirected from private or personal rooms."); } const { targetUser, targetUsername, rest: targetRoomid } = this.splitUser(target); const targetRoom = Rooms.search(targetRoomid); if (!targetRoom || targetRoom.settings.modjoin || targetRoom.settings.staffRoom) { return this.errorReply(`The room "${targetRoomid}" does not exist.`); } this.checkCan("warn", targetUser, room); this.checkCan("warn", targetUser, targetRoom); if (!user.can("rangeban", targetUser)) { this.errorReply(`Redirects have been deprecated. Instead of /redirect, use <> or /invite to guide users to the correct room, and punish if users don't cooperate.`); return; } if (!targetUser?.connected) { return this.errorReply(`User ${targetUsername} not found.`); } if (targetRoom.roomid === "global") return this.errorReply(`Users cannot be redirected to the global room.`); if (targetRoom.settings.isPrivate || targetRoom.settings.isPersonal) { return this.errorReply(`The room "${targetRoom.title}" is not public.`); } if (targetUser.inRooms.has(targetRoom.roomid)) { return this.errorReply(`User ${targetUser.name} is already in the room ${targetRoom.title}!`); } if (!targetUser.inRooms.has(room.roomid)) { return this.errorReply(`User ${targetUsername} is not in the room ${room.roomid}.`); } targetUser.leaveRoom(room.roomid); targetUser.popup(`You are in the wrong room; please go to <<${targetRoom.roomid}>> instead`); this.addModAction(`${targetUser.name} was redirected to room ${targetRoom.title} by ${user.name}.`); this.modlog("REDIRECT", targetUser, `to ${targetRoom.title}`, { noip: 1, noalts: 1 }); targetUser.leaveRoom(room); }, redirhelp: [ `/redirect OR /redir [username], [roomname] - [DEPRECATED]`, `Attempts to redirect the [username] to the [roomname]. Requires: ~` ], m: "mute", mute(target, room, user, connection, cmd) { room = this.requireRoom(); if (!target) return this.parse("/help mute"); this.checkChat(); const { targetUser, inputUsername, targetUsername, rest: reason } = this.splitUser(target); if (!targetUser) return this.errorReply(`User '${targetUsername}' not found.`); if (reason.length > MAX_REASON_LENGTH) { return this.errorReply(`The reason is too long. It cannot exceed ${MAX_REASON_LENGTH} characters.`); } const { publicReason, privateReason } = this.parseSpoiler(reason); const muteDuration = cmd === "hm" || cmd === "hourmute" ? HOURMUTE_LENGTH : MUTE_LENGTH; this.checkCan("mute", targetUser, room); if (targetUser.can("makeroom")) return this.errorReply("You are not allowed to mute upper staff members."); const canBeMutedFurther = (room.getMuteTime(targetUser) || 0) <= muteDuration * 5 / 6; if (targetUser.locked || room.isMuted(targetUser) && !canBeMutedFurther || Punishments.isRoomBanned(targetUser, room.roomid)) { const alreadyPunishment = targetUser.locked ? "locked" : room.isMuted(targetUser) ? "muted" : "room banned"; const problem = ` but was already ${alreadyPunishment}`; if (!reason) { return this.privateModAction(`${targetUser.name} would be muted by ${user.name} ${problem}.`); } return this.addModAction(`${targetUser.name} would be muted by ${user.name} ${problem}. (${publicReason})`); } if (targetUser.id in room.users) { targetUser.popup(`|modal|${user.name} has muted you in ${room.roomid} for ${Chat.toDurationString(muteDuration)}. ${publicReason}`); } this.addModAction(`${targetUser.name} was muted by ${user.name} for ${Chat.toDurationString(muteDuration)}.${publicReason ? ` (${publicReason})` : ``}`); this.modlog(`${cmd.includes("h") ? "HOUR" : ""}MUTE`, targetUser, privateReason); this.update(); const ids = [targetUser.getLastId()]; if (ids[0] !== toID(inputUsername)) { ids.push(toID(inputUsername)); } room.hideText(ids); if (targetUser.autoconfirmed && targetUser.autoconfirmed !== targetUser.id) { const displayMessage = `${targetUser.name}'s ac account: ${targetUser.autoconfirmed}`; this.privateModAction(displayMessage); } Chat.runHandlers("onPunishUser", "MUTE", user, room); room.mute(targetUser, muteDuration); }, mutehelp: [`/mute OR /m [username], [reason] - Mutes a user with reason for 7 minutes. Requires: % @ # ~`], hm: "hourmute", hourmute(target) { if (!target) return this.parse("/help hourmute"); this.run("mute"); }, hourmutehelp: [`/hourmute OR /hm [username], [reason] - Mutes a user with reason for an hour. Requires: % @ # ~`], um: "unmute", unmute(target, room, user) { room = this.requireRoom(); if (!target) return this.parse("/help unmute"); const { targetUser, targetUsername, rest } = this.splitUser(target); if (rest) return this.errorReply(`This command does not support specifying a reason.`); this.checkChat(); this.checkCan("mute", null, room); const successfullyUnmuted = room.unmute( targetUser?.id || toID(targetUsername), `Your mute in '${room.title}' has been lifted.` ); if (successfullyUnmuted) { this.addModAction(`${targetUser ? targetUser.name : successfullyUnmuted} was unmuted by ${user.name}.`); this.modlog("UNMUTE", targetUser || successfullyUnmuted, null, { noip: 1, noalts: 1 }); } else { this.errorReply(`${targetUser ? targetUser.name : targetUsername} is not muted.`); } }, unmutehelp: [`/unmute [username] - Removes mute from user. Requires: % @ # ~`], rb: "ban", weekban: "ban", wb: "ban", wrb: "ban", forceroomban: "ban", forceweekban: "ban", weekroomban: "ban", forcerb: "ban", roomban: "ban", b: "ban", ban(target, room, user, connection, cmd) { room = this.requireRoom(); if (!target) return this.parse("/help ban"); this.checkChat(); const week = ["wrb", "wb"].includes(cmd) || cmd.includes("week"); const { targetUser, inputUsername, targetUsername, rest: reason } = this.splitUser(target); const { publicReason, privateReason } = this.parseSpoiler(reason); if (!targetUser) return this.errorReply(`User '${targetUsername}' not found.`); if (reason.length > MAX_REASON_LENGTH) { return this.errorReply(`The reason is too long. It cannot exceed ${MAX_REASON_LENGTH} characters.`); } this.checkCan("ban", targetUser, room); if (targetUser.can("makeroom")) return this.errorReply("You are not allowed to ban upper staff members."); if (Punishments.hasRoomPunishType(room, toID(targetUsername), "BLACKLIST")) { return this.errorReply(`This user is already blacklisted from ${room.roomid}.`); } const name = targetUser.getLastName(); const userid = targetUser.getLastId(); const force = cmd.startsWith("force"); if (targetUser.trusted) { if (!force) { return this.sendReply( `${name} is a trusted user. If you are sure you would like to ban them, use /force${week ? "week" : "room"}ban.` ); } } else if (force) { return this.errorReply(`Use /${week ? "week" : "room"}ban; ${name} is not a trusted user.`); } if (!reason && !week && Punishments.isRoomBanned(targetUser, room.roomid)) { const problem = " but was already banned"; return this.privateModAction(`${name} would be banned by ${user.name} ${problem}.`); } if (targetUser.trusted && room.settings.isPrivate !== true && !room.settings.isPersonal) { Monitor.log(`[CrisisMonitor] Trusted user ${targetUser.name} ${targetUser.trusted !== targetUser.id ? ` (${targetUser.trusted})` : ``} was roombanned from ${room.roomid} by ${user.name}, and should probably be demoted.`); } if (targetUser.id in room.users || user.can("lock")) { targetUser.popup( `|modal||html|

${import_lib.Utils.escapeHTML(user.name)} has banned you from the room ${room.roomid} ${room.subRooms ? ` and its subrooms` : ``}${week ? " for a week" : ""}.

${publicReason ? `

Reason: ${import_lib.Utils.escapeHTML(publicReason)}

` : ``}

To appeal the ban, PM the staff member that banned you${room.persist ? ` or a room owner.

` : `.

`}` ); } this.addModAction(`${name} was banned${week ? " for a week" : ""} from ${room.title} by ${user.name}.${publicReason ? ` (${publicReason})` : ``}`); const time = week ? Date.now() + 7 * 24 * 60 * 60 * 1e3 : null; const affected = Punishments.roomBan(room, targetUser, time, null, privateReason); for (const u of affected) Chat.runHandlers("onPunishUser", "ROOMBAN", u, room); if (!room.settings.isPrivate && room.persist) { const acAccount = targetUser.autoconfirmed !== userid && targetUser.autoconfirmed; let displayMessage = ""; if (affected.length > 1) { displayMessage = `${name}'s ${acAccount ? ` ac account: ${acAccount}, ` : ``} banned alts: ${affected.slice(1).map((curUser) => curUser.getLastName()).join(", ")}`; this.privateModAction(displayMessage); } else if (acAccount) { displayMessage = `${name}'s ac account: ${acAccount}`; this.privateModAction(displayMessage); } } room.hideText([ ...affected.map((u) => u.id), toID(inputUsername) ]); if (room.settings.isPrivate !== true && room.persist) { this.globalModlog(`${week ? "WEEK" : ""}ROOMBAN`, targetUser, privateReason); } else { this.modlog(`${week ? "WEEK" : ""}ROOMBAN`, targetUser, privateReason); } return true; }, banhelp: [ `/ban [username], [reason] - Bans the user from the room you are in. Requires: @ # ~`, `/weekban [username], [reason] - Bans the user from the room you are in for a week. Requires: @ # ~` ], unroomban: "unban", roomunban: "unban", unban(target, room, user, connection) { room = this.requireRoom(); if (!target) return this.parse("/help unban"); this.checkCan("ban", null, room); const name = Punishments.roomUnban(room, target); if (name) { this.addModAction(`${name} was unbanned from ${room.title} by ${user.name}.`); if (room.settings.isPrivate !== true && room.persist) { this.globalModlog("UNROOMBAN", name); } } else { this.errorReply(`User '${target}' is not banned from this room.`); } }, unbanhelp: [`/unban [username] - Unbans the user from the room you are in. Requires: @ # ~`], forcelock: "lock", forceweeklock: "lock", forcemonthlock: "lock", l: "lock", ipmute: "lock", wl: "lock", weeklock: "lock", monthlock: "lock", async lock(target, room, user, connection, cmd) { const week = cmd === "wl" || cmd.includes("week"); const month = cmd.includes("month"); const force = cmd.includes("force"); if (!target) { if (week) return this.parse("/help weeklock"); return this.parse("/help lock"); } const { targetUser, inputUsername, targetUsername, rest: reason } = this.splitUser(target); let userid = toID(targetUsername); if (!targetUser && !Punishments.search(userid).length && !force) { return this.errorReply( `User '${targetUsername}' not found. Use \`\`/force${month ? "month" : week ? "week" : ""}lock\`\` if you need to to lock them anyway.` ); } if (reason.length > MAX_REASON_LENGTH) { return this.errorReply(`The reason is too long. It cannot exceed ${MAX_REASON_LENGTH} characters.`); } this.checkCan("lock", userid); if (month) this.checkCan("rangeban"); let name; if (targetUser) { name = targetUser.getLastName(); userid = targetUser.getLastId(); if (targetUser.locked && !targetUser.locked.startsWith("#") && !week && !month) { return this.privateModAction(`${name} would be locked by ${user.name} but was already locked.`); } } else { name = targetUsername; userid = toID(targetUsername); } if (Users.isTrusted(userid)) { if (force) { const from = runCrisisDemote(userid); Monitor.log(`[CrisisMonitor] ${name} was locked by ${user.name} and demoted from ${from.join(", ")}.`); this.globalModlog("CRISISDEMOTE", targetUser, ` from ${from.join(", ")}`); } else { return this.sendReply(`${name} is a trusted user. If you are sure you would like to lock them use /force${month ? "month" : week ? "week" : ""}lock.`); } } else if (force && targetUser) { return this.errorReply(`Use /lock; ${name} is not a trusted user and is online.`); } const { privateReason, publicReason } = this.parseSpoiler(reason); const duration = week ? Date.now() + 7 * 24 * 60 * 60 * 1e3 : month ? Date.now() + 30 * 24 * 60 * 60 * 1e3 : null; let affected = []; if (targetUser) { const ignoreAlts = Punishments.isSharedIp(targetUser.latestIp); affected = await Punishments.lock(targetUser, duration, null, ignoreAlts, publicReason); } else { affected = await Punishments.lock(userid, duration, null, false, publicReason); } for (const u of affected) Chat.runHandlers("onPunishUser", "LOCK", u, room); this.globalModlog( (force ? `FORCE` : ``) + (week ? "WEEKLOCK" : month ? "MONTHLOCK" : "LOCK"), targetUser || userid, privateReason ); const durationMsg = week ? " for a week" : month ? " for a month" : ""; this.addGlobalModAction(`${name} was locked from talking${durationMsg} by ${user.name}.` + (publicReason ? ` (${publicReason})` : "")); if (room && !room.settings.isHelp) { room.hideText([ ...affected.map((u) => u.id), toID(inputUsername) ]); } const acAccount = targetUser && targetUser.autoconfirmed !== userid && targetUser.autoconfirmed; let displayMessage = ""; if (affected.length > 1) { displayMessage = `${name}'s ${acAccount ? ` ac account: ${acAccount}, ` : ""} locked alts: ${affected.slice(1).map((curUser) => curUser.getLastName()).join(", ")}`; this.privateModAction(displayMessage); } else if (acAccount) { displayMessage = `${name}'s ac account: ${acAccount}`; this.privateModAction(displayMessage); } if (targetUser) { let message = `|popup||html|${user.name} has locked you from talking in chats, battles, and PMing regular users${durationMsg}`; if (publicReason) message += ` Reason: ${publicReason}`; let appeal = ""; if (Chat.pages.help) { appeal += ``; } else if (Config.appealurl) { appeal += `appeal: ${Config.appealurl}`; } if (appeal) message += ` If you feel that your lock was unjustified, you can ${appeal}.`; message += ` Your lock will expire in a few days.`; targetUser.send(message); const roomauth = Rooms.global.destroyPersonalRooms(userid); if (roomauth.length) { Monitor.log(`[CrisisMonitor] Locked user ${name} has public roomauth (${roomauth.join(", ")}), and should probably be demoted.`); } } if (room?.battle) this.parse("/savereplay forpunishment"); return true; }, lockhelp: [ `/lock OR /l [username], [reason] - Locks the user from talking in all chats. Requires: % @ ~`, `/weeklock OR /wl [username], [reason] - Same as /lock, but locks users for a week.`, `/lock OR /l [username], [reason] spoiler: [private reason] - Marks [private reason] in modlog only.` ], unlock(target, room, user) { if (!target) return this.parse("/help unlock"); this.checkCan("lock"); const targetUser = Users.get(target); if (targetUser?.namelocked) { return this.errorReply(`User ${targetUser.name} is namelocked, not locked. Use /unnamelock to unnamelock them.`); } let reason = ""; if (targetUser?.locked && targetUser.locked.startsWith("#")) { reason = ` (${targetUser.locked})`; } const unlocked = Punishments.unlock(target); if (unlocked) { this.addGlobalModAction(`${unlocked.join(", ")} ${unlocked.length > 1 ? "were" : "was"} unlocked by ${user.name}.${reason}`); if (!reason) this.globalModlog("UNLOCK", toID(target)); if (targetUser) targetUser.popup(`${user.name} has unlocked you.`); } else { this.errorReply(`User '${target}' is not locked.`); } }, unlockname(target, room, user) { if (!target) return this.parse("/help unlock"); this.checkCan("lock"); const userid = toID(target); if (userid.startsWith("guest")) { return this.errorReply(`You cannot unlock the guest userid - provide their original username instead.`); } const punishment = Punishments.userids.getByType(userid, "LOCK") || Punishments.userids.getByType(userid, "NAMELOCK"); if (!punishment) return this.errorReply("This name isn't locked."); if (punishment.id === userid || Users.get(userid)?.previousIDs.includes(punishment.id)) { return this.errorReply(`"${userid}" was specifically locked by a staff member (check the global modlog). Use /unlock if you really want to unlock this name.`); } Punishments.userids.delete(userid); Punishments.savePunishments(); for (const curUser of Users.findUsers([userid], [])) { const locked = Punishments.hasPunishType(curUser.id, ["LOCK", "NAMELOCK"], curUser.latestIp); if (curUser.locked && !curUser.locked.startsWith("#") && !locked) { curUser.locked = null; curUser.namelocked = null; curUser.destroyPunishmentTimer(); curUser.updateIdentity(); } } this.addGlobalModAction(`The name '${target}' was unlocked by ${user.name}.`); this.globalModlog("UNLOCKNAME", userid); }, unrangelock: "unlockip", rangeunlock: "unlockip", unlockip(target, room, user) { target = target.trim(); if (!target) return this.parse("/help unlock"); this.checkCan("globalban"); const range = target.endsWith("*"); if (range) this.checkCan("rangeban"); if (!(range ? IPTools.ipRangeRegex : IPTools.ipRegex).test(target)) { return this.errorReply("Please enter a valid IP address."); } const punishment = Punishments.ips.get(target); if (!punishment) return this.errorReply(`${target} is not a locked/banned IP or IP range.`); Punishments.ips.delete(target); Punishments.savePunishments(); for (const curUser of Users.findUsers([], [target])) { if ((range ? curUser.locked === "#rangelock" : !curUser.locked?.startsWith("#")) && !Punishments.getPunishType(curUser.id)) { curUser.locked = null; if (curUser.namelocked) { curUser.namelocked = null; curUser.resetName(); } curUser.destroyPunishmentTimer(); curUser.updateIdentity(); } } this.privateGlobalModAction(`${user.name} unlocked the ${range ? "IP range" : "IP"}: ${target}`); this.globalModlog(`UNLOCK${range ? "RANGE" : "IP"}`, null, null, target); }, unlockiphelp: [`/unlockip [ip] - Unlocks a punished ip while leaving the original punishment intact. Requires: @ ~`], unlocknamehelp: [`/unlockname [name] - Unlocks a punished alt, leaving the original lock intact. Requires: % @ ~`], unlockhelp: [ `/unlock [username] - Unlocks the user. Requires: % @ ~`, `/unlockname [username] - Unlocks a punished alt while leaving the original punishment intact. Requires: % @ ~`, `/unlockip [ip] - Unlocks a punished ip while leaving the original punishment intact. Requires: @ ~` ], forceglobalban: "globalban", gban: "globalban", async globalban(target, room, user, connection, cmd) { if (!target) return this.parse("/help globalban"); const force = cmd.includes("force"); const { targetUser, inputUsername, targetUsername, rest: reason } = this.splitUser(target); let userid = toID(targetUsername); if (!targetUser && !force) { return this.errorReply(`User '${targetUsername}' not found. Use /forceglobalban to ban them anyway.`); } if (reason.length > MAX_REASON_LENGTH) { return this.errorReply(`The reason is too long. It cannot exceed ${MAX_REASON_LENGTH} characters.`); } if (!reason && REQUIRE_REASONS) { return this.errorReply("Global bans require a reason."); } this.checkCan("globalban", targetUser); let name; if (targetUser) { name = targetUser.getLastName(); userid = targetUser.getLastId(); } else { name = targetUsername; } if (Users.isTrusted(userid)) { if (force) { const from = runCrisisDemote(userid); Monitor.log(`[CrisisMonitor] ${name} was globally banned by ${user.name} and demoted from ${from?.join(", ")}.`); this.globalModlog("CRISISDEMOTE", targetUser, ` from ${from?.join(", ")}`); } else { return this.sendReply(`${name} is a trusted user. If you are sure you would like to ban them use /forceglobalban.`); } } const roomauth = Rooms.global.destroyPersonalRooms(userid); if (roomauth.length) { Monitor.log(`[CrisisMonitor] Globally banned user ${name} has public roomauth (${roomauth.join(", ")}), and should probably be demoted.`); } const { privateReason, publicReason } = this.parseSpoiler(reason); targetUser?.popup( `|modal|${user.name} has globally banned you.${publicReason ? ` Reason: ${publicReason}` : ``} ${Config.appealurl ? ` If you feel that your ban was unjustified, you can appeal: ${Config.appealurl}` : ``} Your ban will expire in a few days.` ); this.addGlobalModAction(`${name} was globally banned by ${user.name}.${publicReason ? ` (${publicReason})` : ``}`); const affected = await Punishments.ban(userid, null, null, false, publicReason); for (const u of affected) Chat.runHandlers("onPunishUser", "BAN", u, room); const acAccount = targetUser && targetUser.autoconfirmed !== userid && targetUser.autoconfirmed; let displayMessage = ""; if (affected.length > 1) { let guests = affected.length - 1; const affectedAlts = affected.slice(1).map((curUser) => curUser.getLastName()).filter((alt) => !alt.startsWith("[Guest ")); guests -= affectedAlts.length; displayMessage = `${name}'s ${acAccount ? `ac account: ${acAccount}, ` : ``} banned alts: ${affectedAlts.join(", ")} ${guests ? ` [${guests} guests]` : ``}`; this.privateModAction(displayMessage); for (const id of affectedAlts) { this.add(`|hidelines|unlink|${toID(id)}`); } } else if (acAccount) { displayMessage = `${name}'s ac account: ${acAccount}`; this.privateModAction(displayMessage); } room?.hideText([ ...affected.map((u) => u.id), toID(inputUsername) ]); this.globalModlog(`${force ? `FORCE` : ""}BAN`, targetUser, privateReason); return true; }, globalbanhelp: [ `/globalban OR /gban [username], [reason] - Kick user from all rooms and ban user's IP address with reason. Requires: @ ~`, `/globalban OR /gban [username], [reason] spoiler: [private reason] - Marks [private reason] in modlog only.` ], globalunban: "unglobalban", unglobalban(target, room, user) { if (!target) return this.parse(`/help unglobalban`); this.checkCan("globalban"); const name = Punishments.unban(target); if (!name) { return this.errorReply(`User '${target}' is not globally banned.`); } this.addGlobalModAction(`${name} was globally unbanned by ${user.name}.`); this.globalModlog("UNBAN", target); }, unglobalbanhelp: [`/unglobalban [username] - Unban a user. Requires: @ ~`], deroomvoiceall(target, room, user) { room = this.requireRoom(); this.checkCan("editroom", null, room); if (!room.auth.size) return this.errorReply("Room does not have roomauth."); if (!target) { user.lastCommand = "/deroomvoiceall"; this.errorReply("THIS WILL DEROOMVOICE ALL ROOMVOICED USERS."); this.errorReply("To confirm, use: /deroomvoiceall confirm"); return; } if (user.lastCommand !== "/deroomvoiceall" || target !== "confirm") { return this.parse("/help deroomvoiceall"); } user.lastCommand = ""; let count = 0; for (const [userid, symbol] of room.auth) { if (symbol === "+") { room.auth.delete(userid); if (userid in room.users) room.users[userid].updateIdentity(room.roomid); count++; } } if (!count) { return this.sendReply("(This room has zero roomvoices)"); } room.saveSettings(); this.addModAction(`All ${count} roomvoices have been cleared by ${user.name}.`); this.modlog("DEROOMVOICEALL"); }, deroomvoiceallhelp: [`/deroomvoiceall - Devoice all roomvoiced users. Requires: # ~`], // this is a separate command for two reasons // a - yearticketban is preferred over /ht yearban // b - it would be messy to switch // from both Punishments.punishRange and #punish in /ht ban // since this takes ips / userids async yearticketban(target, room, user) { this.checkCan("rangeban"); target = target.trim(); let reason = ""; [target, reason] = this.splitOne(target); let isIP = false; let descriptor = ""; if (IPTools.ipRangeRegex.test(target)) { isIP = true; if (IPTools.ipRegex.test(target)) { descriptor = "the IP "; } else { descriptor = "the IP range "; } } else { target = toID(target); } if (!target) return this.parse(`/help yearticketban`); const expireTime = Date.now() + 365 * 24 * 60 * 60 * 1e3; if (isIP) { Punishments.punishRange(target, reason, expireTime, "TICKETBAN"); } else { await Punishments.punish(target, { type: "TICKETBAN", id: target, expireTime, reason, rest: [] }, true); } this.addGlobalModAction( `${user.name} banned ${descriptor}${target} from opening tickets for a year${reason ? ` (${reason})` : ""}` ); this.globalModlog( "YEARTICKETBAN", isIP ? null : target, reason, isIP ? target : void 0 ); }, yearticketbanhelp: [ `/yearticketban [IP/userid] - Ban an IP or a userid from opening tickets for a year. `, `Accepts wildcards to ban ranges. Requires: ~` ], rangeban: "banip", yearbanip: "banip", banip(target, room, user, connection, cmd) { const [ip, reason] = this.splitOne(target); if (!ip || !/^[0-9.]+(?:\.\*)?$/.test(ip)) return this.parse("/help banip"); if (!reason) return this.errorReply("/banip requires a ban reason"); this.checkCan("rangeban"); const ipDesc = `IP ${ip.endsWith("*") ? `range ` : ``}${ip}`; const year = cmd.startsWith("year"); const time = year ? Date.now() + 365 * 24 * 60 * 60 * 1e3 : null; const curPunishment = Punishments.ipSearch(ip, "BAN"); if (curPunishment?.type === "BAN" && !time) { return this.errorReply(`The ${ipDesc} is already temporarily banned.`); } Punishments.punishRange(ip, reason, time, "BAN"); const duration = year ? "year" : "hour"; if (!this.room || this.room.roomid !== "staff") { this.sendReply(`You ${duration}-banned the ${ipDesc}.`); } this.room = Rooms.get("staff") || null; this.addGlobalModAction( `${user.name} ${duration}-banned the ${ipDesc}: ${reason}` ); this.globalModlog( `${year ? "YEAR" : ""}RANGEBAN`, null, `${ip.endsWith("*") ? ip : `[${ip}]`}: ${reason}` ); }, baniphelp: [ `/banip [ip] OR /yearbanip [ip] - Globally bans this IP or IP range for an hour. Accepts wildcards to ban ranges.`, `Existing users on the IP will not be banned. Requires: ~` ], unrangeban: "unbanip", unbanip(target, room, user) { target = target.trim(); if (!target) { return this.parse("/help unbanip"); } this.checkCan("rangeban"); if (!Punishments.ips.has(target)) { return this.errorReply(`${target} is not a locked/banned IP or IP range.`); } Punishments.ips.delete(target); this.addGlobalModAction(`${user.name} unbanned the ${target.endsWith("*") ? "IP range" : "IP"}: ${target}`); this.modlog("UNRANGEBAN", null, target); }, unbaniphelp: [`/unbanip [ip] - Unbans. Accepts wildcards to ban ranges. Requires: ~`], forceyearlockname: "yearlockname", yearlockid: "yearlockname", forceyearlockid: "yearlockname", yearlockuserid: "yearlockname", forceyearlockuserid: "yearlockname", yearlockname(target, room, user) { this.checkCan("rangeban"); const [targetUsername, rest] = import_lib.Utils.splitFirst(target, ",").map((k) => k.trim()); const targetUser = Users.get(targetUsername); const targetUserid = toID(targetUsername); if (!targetUserid || targetUserid.length > 18) { return this.errorReply(`Invalid userid.`); } const force = this.cmd.includes("force"); if (targetUser?.registered && !force) { return this.errorReply(`That user is registered. Either permalock them normally or use /forceyearlockname.`); } const punishment = { type: "YEARLOCK", id: targetUserid, expireTime: Date.now() + 365 * 24 * 60 * 60 * 1e3, reason: rest || "" }; Punishments.userids.add(targetUserid, punishment); Punishments.savePunishments(); this.addGlobalModAction(`${user.name} locked the userid '${targetUserid}' for a year${rest ? ` (${rest})` : ""}.`); this.globalModlog(`${force ? `FORCE` : ""}YEARLOCKNAME`, targetUserid, rest); if (targetUser) { Chat.punishmentfilter(targetUser, punishment); targetUser.locked = targetUserid; } }, rangelock: "lockip", yearlockip: "lockip", yearnamelockip: "lockip", lockip(target, room, user, connection, cmd) { const [ip, reason] = this.splitOne(target); if (!ip || !/^[0-9.]+(?:\.\*)?$/.test(ip)) return this.parse("/help lockip"); if (!reason) return this.errorReply("/lockip requires a lock reason"); this.checkCan("rangeban"); const ipDesc = ip.endsWith("*") ? `IP range ${ip}` : `IP ${ip}`; const year = cmd.startsWith("year"); const curPunishment = Punishments.byWeight(Punishments.ipSearch(ip) || [])[0]; if (!year && curPunishment && (curPunishment.type === "BAN" || curPunishment.type === "LOCK")) { const punishDesc = curPunishment.type === "BAN" ? `temporarily banned` : `temporarily locked`; return this.errorReply(`The ${ipDesc} is already ${punishDesc}.`); } const time = year ? Date.now() + 365 * 24 * 60 * 60 * 1e3 : null; const type = cmd.includes("name") ? "NAMELOCK" : "LOCK"; Punishments.punishRange(ip, reason, time, type); this.addGlobalModAction(`${user.name} ${year ? "year" : "hour"}-${type.toLowerCase()}ed the ${ipDesc}: ${reason}`); this.globalModlog( `${year ? "YEAR" : "RANGE"}${type}`, null, `${ip.endsWith("*") ? ip : `[${ip}]`}: ${reason}`, ip.endsWith("*") ? `${ip.slice(0, -2)}` : ip ); }, lockiphelp: [ `/lockip [ip] - Globally locks this IP or IP range for an hour. Accepts wildcards to ban ranges.`, `/yearlockip [ip] - Globally locks this IP or IP range for one year. Accepts wildcards to ban ranges.`, `/yearnamelockip [ip] - Namelocks this IP or IP range for one year. Accepts wildcards to ban ranges.`, `Existing users on the IP will not be banned. Requires: ~` ], /********************************************************* * Moderating: Other *********************************************************/ mn: "modnote", modnote(target, room, user, connection) { room = this.requireRoom(); if (!target) return this.parse("/help modnote"); this.checkChat(); if (target.length > MAX_REASON_LENGTH) { return this.errorReply(`The note is too long. It cannot exceed ${MAX_REASON_LENGTH} characters.`); } this.checkCan("receiveauthmessages", null, room); target = target.replace(/\n/g, "; "); let targeted = /\[([^\]]+)\]/.exec(target)?.[1] || null; if (!targeted) { targeted = target.split(/[,-]/)[0]?.trim() || ""; if (!targeted || !(Users.get(targeted) || Punishments.search(target).length || IPTools.ipRegex.test(targeted)) || toID(targeted) === toID(target)) { targeted = null; } } let targetUserid, targetIP; if (targeted) { if (IPTools.ipRegex.test(targeted)) { targetIP = targeted; } else { targetUserid = toID(targeted); } } if (["staff", "upperstaff"].includes(room.roomid) || Rooms.Modlog.getSharedID(room.roomid) && user.can("modlog")) { this.globalModlog("NOTE", targetUserid || null, target, targetIP); } else { this.modlog("NOTE", targetUserid || null, target); } this.privateModAction(`${user.name} notes: ${target}`); }, modnotehelp: [ `/modnote - Adds a moderator note that can be read through modlog. Requires: % @ # ~`, `/modnote [] - Adds a moderator note to a user's modlog that can be read through modlog. Requires: % @ # ~` ], globalpromote: "promote", promote(target, room, user, connection, cmd) { if (!target) return this.parse("/help promote"); const { targetUser, targetUsername, rest: nextGroupName } = this.splitUser(target, { exactName: true }); const userid = toID(targetUsername); const name = targetUser?.name || targetUsername; if (!userid) return this.parse("/help promote"); const currentGroup = targetUser?.tempGroup || Users.globalAuth.get(userid); let nextGroup = nextGroupName; if (nextGroupName === "deauth") nextGroup = Users.Auth.defaultSymbol(); if (!nextGroup) { return this.errorReply("Please specify a group such as /globalvoice or /globaldeauth"); } if (!Config.groups[nextGroup]) { return this.errorReply(`Group '${nextGroup}' does not exist.`); } if (!cmd.startsWith("global")) { let groupid = Config.groups[nextGroup].id; if (!groupid && nextGroup === Users.Auth.defaultSymbol()) groupid = "deauth"; if (Config.groups[nextGroup].globalonly) return this.errorReply(`Did you mean "/global${groupid}"?`); if (Config.groups[nextGroup].roomonly) return this.errorReply(`Did you mean "/room${groupid}"?`); return this.errorReply(`Did you mean "/room${groupid}" or "/global${groupid}"?`); } if (Config.groups[nextGroup].roomonly || Config.groups[nextGroup].battleonly) { return this.errorReply(`Group '${nextGroup}' does not exist as a global rank.`); } const groupName = Config.groups[nextGroup].name || "regular user"; if (currentGroup === nextGroup) { return this.errorReply(`User '${name}' is already a ${groupName}`); } if (!Users.Auth.hasPermission(user, "promote", currentGroup)) { this.errorReply(`/${cmd} - Access denied for promoting from ${currentGroup}`); this.errorReply(`You can only promote to/from: ${Users.Auth.listJurisdiction(user, "promote")}`); return; } if (!Users.Auth.hasPermission(user, "promote", nextGroup)) { this.errorReply(`/${cmd} - Access denied for promoting to ${groupName}`); this.errorReply(`You can only promote to/from: ${Users.Auth.listJurisdiction(user, "promote")}`); return; } if (!Users.isUsernameKnown(userid)) { return this.errorReply(`/globalpromote - WARNING: '${name}' is offline and unrecognized. The username might be misspelled (either by you or the person who told you) or unregistered. Use /forcepromote if you're sure you want to risk it.`); } if (targetUser && !targetUser.registered) { return this.errorReply(`User '${name}' is unregistered, and so can't be promoted.`); } if (nextGroup === Users.Auth.defaultSymbol()) { Users.globalAuth.delete(targetUser ? targetUser.id : userid); } else { Users.globalAuth.set(targetUser ? targetUser.id : userid, nextGroup); } if (Users.Auth.getGroup(nextGroup).rank < Users.Auth.getGroup(currentGroup).rank) { this.privateGlobalModAction(`${name} was demoted to Global ${groupName} by ${user.name}.`); this.globalModlog(`GLOBAL ${groupName.toUpperCase()}`, userid, `(demote)`); if (targetUser) targetUser.popup(`You were demoted to Global ${groupName} by ${user.name}.`); } else { this.addGlobalModAction(`${name} was promoted to Global ${groupName} by ${user.name}.`); this.globalModlog(`GLOBAL ${groupName.toUpperCase()}`, userid); if (targetUser) targetUser.popup(`You were promoted to Global ${groupName} by ${user.name}.`); } if (targetUser) { targetUser.updateIdentity(); Rooms.global.checkAutojoin(targetUser); if (targetUser.trusted && !Users.isTrusted(targetUser.id)) { targetUser.trusted = ""; } } }, promotehelp: [`/promote [username], [group] - Promotes the user to the specified group. Requires: ~`], untrustuser: "trustuser", unconfirmuser: "trustuser", confirmuser: "trustuser", forceconfirmuser: "trustuser", forcetrustuser: "trustuser", trustuser(target, room, user, connection, cmd) { if (!target) return this.parse("/help trustuser"); this.checkCan("promote"); const force = cmd.includes("force"); const untrust = cmd.includes("un"); const { targetUser, targetUsername, rest } = this.splitUser(target, { exactName: true }); if (rest) return this.errorReply(`This command does not support specifying a reason.`); const userid = toID(targetUsername); const name = targetUser?.name || targetUsername; const currentGroup = Users.globalAuth.get(userid); if (untrust) { if (currentGroup !== Users.Auth.defaultSymbol()) { return this.errorReply(`User '${name}' is trusted indirectly through global rank ${currentGroup}. Demote them from that rank to remove trusted status.`); } const trustedSourceRooms = Rooms.global.chatRooms.filter((authRoom) => authRoom.persist && authRoom.settings.isPrivate !== true && authRoom.auth.isStaff(userid)).map((authRoom) => authRoom.auth.get(userid) + authRoom.roomid).join(" "); if (trustedSourceRooms.length && !Users.globalAuth.has(userid)) { return this.errorReply(`User '${name}' is trusted indirectly through room ranks ${trustedSourceRooms}. Demote them from those ranks to remove trusted status.`); } if (!Users.globalAuth.has(userid)) return this.errorReply(`User '${name}' is not trusted.`); if (targetUser) { targetUser.setGroup(Users.Auth.defaultSymbol()); } else { Users.globalAuth.delete(userid); } this.privateGlobalModAction(`${name} was set to no longer be a trusted user by ${user.name}.`); this.globalModlog("UNTRUSTUSER", userid); } else { if (!targetUser && !force) return this.errorReply(`User '${name}' is offline. Use /force${cmd} if you're sure.`); if (currentGroup) { if (Users.globalAuth.has(userid)) { if (currentGroup === Users.Auth.defaultSymbol()) return this.errorReply(`User '${name}' is already trusted.`); return this.errorReply(`User '${name}' has a global rank higher than trusted.`); } } if (targetUser) { targetUser.setGroup(Users.Auth.defaultSymbol(), true); } else { Users.globalAuth.set(userid, Users.Auth.defaultSymbol()); } this.privateGlobalModAction(`${name} was set as a trusted user by ${user.name}.`); this.globalModlog("TRUSTUSER", userid); } }, trustuserhelp: [ `/trustuser [username] - Trusts the user (makes them immune to locks). Requires: ~`, `/untrustuser [username] - Removes the trusted user status from the user. Requires: ~` ], desectionleader: "sectionleader", sectionleader(target, room, user, connection, cmd) { this.checkCan("gdeclare"); room = this.requireRoom(); const demoting = cmd === "desectionleader"; if (!target || target.split(",").length < 2 && !demoting) return this.parse(`/help sectionleader`); const { targetUser, targetUsername, rest: sectionid } = this.splitUser(target, { exactName: true }); const userid = toID(targetUsername); const section = demoting ? Users.globalAuth.sectionLeaders.get(userid) : room.validateSection(sectionid); const name = targetUser ? targetUser.name : targetUsername; if (Users.globalAuth.sectionLeaders.has(targetUser?.id || userid) && !demoting) { throw new Chat.ErrorMessage(`${name} is already a Section Leader of ${import_room_settings.RoomSections.sectionNames[section]}.`); } else if (!Users.globalAuth.sectionLeaders.has(targetUser?.id || userid) && demoting) { throw new Chat.ErrorMessage(`${name} is not a Section Leader.`); } if (!demoting) { Users.globalAuth.setSection(userid, section); this.addGlobalModAction(`${name} was appointed Section Leader of ${import_room_settings.RoomSections.sectionNames[section]} by ${user.name}.`); this.globalModlog(`SECTION LEADER`, userid, section); targetUser?.popup(`You were appointed Section Leader of ${import_room_settings.RoomSections.sectionNames[section]} by ${user.name}.`); } else { const group = Users.globalAuth.get(userid); Users.globalAuth.deleteSection(userid); this.privateGlobalModAction(`${name} was demoted from Section Leader of ${import_room_settings.RoomSections.sectionNames[section]} by ${user.name}.`); if (group === " ") this.sendReply(`They are also no longer manually trusted. If they should be, use '/trustuser'.`); this.globalModlog(`DESECTION LEADER`, userid, section); targetUser?.popup(`You were demoted from Section Leader of ${import_room_settings.RoomSections.sectionNames[section]} by ${user.name}.`); } if (targetUser) { targetUser.updateIdentity(); Rooms.global.checkAutojoin(targetUser); if (targetUser.trusted && !Users.isTrusted(targetUser.id)) { targetUser.trusted = ""; } } }, sectionleaderhelp: [ `/sectionleader [target user], [sectionid] - Appoints [target user] Section Leader.`, `/desectionleader [target user] - Demotes [target user] from Section Leader.`, `Valid sections: ${import_room_settings.RoomSections.sections.join(", ")}`, `If you want to change which section someone leads, demote them and then re-promote them in the desired section.`, `Requires: ~` ], globaldemote: "demote", demote(target) { if (!target) return this.parse("/help demote"); this.run("promote"); }, demotehelp: [`/demote [username], [group] - Demotes the user to the specified group. Requires: ~`], forcepromote(target, room, user, connection) { this.checkCan("forcepromote"); const { targetUsername, rest: nextGroupName } = this.splitUser(target, { exactName: true }); let name = this.filter(targetUsername); if (!name) return; name = name.slice(0, 18); const nextGroup = nextGroupName; if (!Config.groups[nextGroup]) return this.errorReply(`Group '${nextGroup}' does not exist.`); if (Config.groups[nextGroup].roomonly || Config.groups[nextGroup].battleonly) { return this.errorReply(`Group '${nextGroup}' does not exist as a global rank.`); } if (Users.isUsernameKnown(name)) { return this.errorReply("/forcepromote - Don't forcepromote unless you have to."); } Users.globalAuth.set(name, nextGroup); this.addGlobalModAction(`${name} was promoted to Global ${Config.groups[nextGroup].name || "regular user"} by ${user.name}.`); this.globalModlog(`GLOBAL${(Config.groups[nextGroup].name || "regular").toUpperCase()}`, toID(name)); }, devoice: "deauth", deauth(target, room, user) { return this.parse(`/demote ${target}, deauth`); }, deglobalvoice: "globaldeauth", deglobalauth: "globaldeauth", globaldevoice: "globaldeauth", globaldeauth(target, room, user) { return this.parse(`/globaldemote ${target}, deauth`); }, deroomvoice: "roomdeauth", roomdevoice: "roomdeauth", deroomauth: "roomdeauth", roomdeauth(target, room, user) { return this.parse(`/roomdemote ${target}, deauth`); }, declare(target, room, user) { target = target.trim(); if (!target) return this.parse("/help declare"); room = this.requireRoom(); this.checkCan("declare", null, room); this.checkChat(); if (target.length > 2e3) return this.errorReply("Declares should not exceed 2000 characters."); for (const id in room.users) { room.users[id].sendTo(room, `|notify|${room.title} announcement!|${target}`); } this.add(import_lib.Utils.html`|raw|
${target}
`); this.modlog("DECLARE", null, target); }, declarehelp: [`/declare [message] - Anonymously announces a message. Requires: # * ~`], htmldeclare(target, room, user) { if (!target) return this.parse("/help htmldeclare"); room = this.requireRoom(); this.checkCan("declare", null, room); this.checkChat(); this.checkHTML(target); for (const u in room.users) { Users.get(u)?.sendTo( room, `|notify|${room.title} announcement!|${import_lib.Utils.stripHTML(target)}` ); } this.add(`|raw|
${target}
`); this.modlog(`HTMLDECLARE`, null, target); }, htmldeclarehelp: [`/htmldeclare [message] - Anonymously announces a message using safe HTML. Requires: # * ~`], gdeclare: "globaldeclare", globaldeclare(target, room, user) { if (!target) return this.parse("/help globaldeclare"); this.checkCan("gdeclare"); this.checkHTML(target); for (const u of Users.users.values()) { if (u.connected) u.send(`|pm|~|${u.tempGroup}${u.name}|/raw
${target}
`); } this.globalModlog(`GLOBALDECLARE`, null, target); }, globaldeclarehelp: [`/globaldeclare [message] - Anonymously sends a private message to all the users on the site. Requires: ~`], cdeclare: "chatdeclare", chatdeclare(target, room, user) { if (!target) return this.parse("/help chatdeclare"); this.checkCan("gdeclare"); this.checkHTML(target); for (const curRoom of Rooms.rooms.values()) { if (curRoom.type !== "battle") { curRoom.addRaw(`
${target}
`).update(); } } this.globalModlog(`CHATDECLARE`, null, target); }, chatdeclarehelp: [`/cdeclare [message] - Anonymously announces a message to all chatrooms on the server. Requires: ~`], wall: "announce", announce(target, room, user) { if (!target) return this.parse("/help announce"); if (room) this.checkCan("announce", null, room); this.checkChat(target); return `/announce ${target}`; }, announcehelp: [`/announce OR /wall [message] - Makes an announcement. Requires: % @ # ~`], notifyoffrank: "notifyrank", notifyrank(target, room, user, connection, cmd) { room = this.requireRoom(); if (!target) return this.parse(`/help notifyrank`); this.checkCan("addhtml", null, room); this.checkChat(); let [rank, titleNotification] = this.splitOne(target); if (rank === "all") rank = ` `; if (!(rank in Config.groups)) return this.errorReply(`Group '${rank}' does not exist.`); const id = `${room.roomid}-rank-${Config.groups[rank].id || `all`}`; if (cmd === "notifyoffrank") { if (rank === " ") { room.send(`|tempnotifyoff|${id}`); } else { room.sendRankedUsers(`|tempnotifyoff|${id}`, rank); } } else { let [title, notificationHighlight] = this.splitOne(titleNotification); if (!title) title = `${room.title} ${Config.groups[rank].name ? `${Config.groups[rank].name}+ ` : ``}message!`; if (!user.can("addhtml")) { title += ` (notification from ${user.name})`; } const [notification, highlight] = this.splitOne(notificationHighlight); if (notification.length > 300) return this.errorReply(`Notifications should not exceed 300 characters.`); const message = `|tempnotify|${id}|${title}|${notification}${highlight ? `|${highlight}` : ``}`; if (rank === " ") { room.send(message); } else { room.sendRankedUsers(message, rank); } this.modlog(`NOTIFYRANK`, null, target); } }, notifyrankhelp: [ `/notifyrank [rank], [title], [message], [highlight] - Sends a notification to users who are [rank] or higher (and highlight on [highlight], if specified). Requires: # * ~`, `/notifyoffrank [rank] - Closes the notification previously sent with /notifyrank [rank]. Requires: # * ~` ], notifyoffuser: "notifyuser", notifyuser(target, room, user, connection, cmd) { room = this.requireRoom(); if (!target) return this.parse(`/help notifyuser`); this.checkCan("addhtml", null, room); this.checkChat(); const { targetUser, targetUsername, rest: titleNotification } = this.splitUser(target); if (!targetUser?.connected) return this.errorReply(`User '${targetUsername}' not found.`); const id = `${room.roomid}-user-${toID(targetUsername)}`; if (cmd === "notifyoffuser") { room.sendUser(targetUser, `|tempnotifyoff|${id}`); this.sendReply(`Closed the notification previously sent to ${targetUser.name}.`); } else { let [title, notification] = this.splitOne(titleNotification); if (!title) title = `${room.title} notification!`; if (!user.can("addhtml")) { title += ` (notification from ${user.name})`; } if (notification.length > 300) return this.errorReply(`Notifications should not exceed 300 characters.`); const message = `|tempnotify|${id}|${title}|${notification}`; room.sendUser(targetUser, message); this.sendReply(`Sent a notification to ${targetUser.name}.`); } }, notifyuserhelp: [ `/notifyuser [username], [title], [message] - Sends a notification to [user]. Requires: # * ~`, `/notifyoffuser [user] - Closes the notification previously sent with /notifyuser [user]. Requires: # * ~` ], fr: "forcerename", ofr: "forcerename", offlineforcerename: "forcerename", forcerename(target, room, user) { if (!target) return this.parse("/help forcerename"); const { targetUser, targetUsername, rest: reason } = this.splitUser(target, { exactName: true }); const offline = this.cmd.startsWith("o"); const targetID = targetUser?.id || toID(targetUsername); if (!targetUser && !offline) { const { targetUser: targetUserInexact, inputUsername } = this.splitUser(target); if (targetUserInexact) { return this.errorReply(`User has already changed their name to '${targetUserInexact.name}'.`); } return this.errorReply(`User '${inputUsername}' not found. (use /offlineforcerename to rename anyway.)`); } if (Punishments.namefilterwhitelist.has(targetID)) { this.errorReply(`That name is blocked from being forcerenamed.`); if (user.can("bypassall")) { this.errorReply(`Use /noforcerename remove to remove it from the list if you wish to rename it.`); } return false; } this.checkCan("forcerename", targetID); const { publicReason, privateReason } = this.parseSpoiler(reason); Monitor.forceRenames.set(targetID, false); let forceRenameMessage; if (targetUser?.connected) { forceRenameMessage = `was forced to choose a new name by ${user.name}${publicReason ? `: ${publicReason}` : ``}`; this.globalModlog("FORCERENAME", targetUser, reason); Ladders.cancelSearches(targetUser); targetUser.send(`|nametaken||${user.name} considers your name inappropriate${publicReason ? `: ${publicReason}` : ``}`); } else { forceRenameMessage = `was forced to choose a new name by ${user.name} while offline${publicReason ? `: ${publicReason}` : ``}`; this.globalModlog("FORCERENAME OFFLINE", targetUser, privateReason); } Monitor.forceRenames.set(targetID, false); if (room?.roomid !== "staff") { if (room?.roomid.startsWith("help-")) { this.addModAction(`${targetUser?.name || targetID} ${forceRenameMessage}`); } else { this.privateModAction(`${targetUser?.name || targetID} ${forceRenameMessage}`); } } const roomMessage = this.pmTarget ? `` : room && room.roomid !== "staff" ? `\xAB${room.roomid}\xBB ` : ""; const rankMessage = targetUser?.getAccountStatusString() || ""; Rooms.global.notifyRooms( ["staff"], `|html|${roomMessage}` + import_lib.Utils.html`${targetUser?.name || targetID} ${rankMessage} ${forceRenameMessage}` ); targetUser?.resetName(true); return true; }, forcerenamehelp: [ `/forcerename OR /fr [username], [reason] - Forcibly change a user's name and shows them the [reason]. Requires: % @ ~`, `/allowname [username] - Unmarks a forcerenamed username, stopping staff from being notified when it is used. Requires % @ ~` ], nfr: "noforcerename", noforcerename: { add(target, room, user) { const [targetUsername, rest] = import_lib.Utils.splitFirst(target, ",").map((f) => f.trim()); const targetId = toID(targetUsername); if (!targetId) return this.parse("/help noforcerename"); this.checkCan("bypassall"); if (!Punishments.whitelistName(targetId, user.name)) { return this.errorReply(`${targetUsername} is already on the noforcerename list.`); } this.addGlobalModAction(`${user.name} added the name ${targetId} to the no forcerename list.${rest ? ` (${rest})` : ""}`); this.globalModlog("NOFORCERENAME", targetId, rest); }, remove(target, room, user) { const { targetUsername, rest } = this.splitUser(target); const targetId = toID(targetUsername); if (!targetId) return this.parse("/help noforcerename"); this.checkCan("bypassall"); if (!Punishments.namefilterwhitelist.has(targetId)) { return this.errorReply(`${targetUsername} is not on the noforcerename list.`); } Punishments.unwhitelistName(targetId); this.addGlobalModAction(`${user.name} removed ${targetId} from the no forcerename list.${rest ? ` (${rest})` : ""}`); this.globalModlog("UNNOFORCERENAME", targetId, rest); } }, noforcerenamehelp: [ `/noforcerename add OR /nfr add [username] - Adds [username] to the list of users who can't be forcerenamed by staff.`, `/noforcerename remove OR /nfr remove [username] - Removes [username] from the list of users who can't be forcerenamed by staff.` ], forceclearstatus(target, room, user) { const { targetUser, rest: reason } = this.requireUser(target, { allowOffline: true }); this.checkCan("forcerename", targetUser); if (!targetUser.userMessage) return this.errorReply(this.tr`${targetUser.name} does not have a status set.`); const displayReason = reason ? `: ${reason}` : ``; this.privateGlobalModAction(this.tr`${targetUser.name}'s status "${targetUser.userMessage}" was cleared by ${user.name}${displayReason}.`); this.globalModlog("CLEARSTATUS", targetUser, ` from "${targetUser.userMessage}"${displayReason}`); targetUser.clearStatus(); targetUser.popup(`${user.name} has cleared your status message for being inappropriate${displayReason || "."}`); }, nl: "namelock", forcenamelock: "namelock", weeknamelock: "namelock", wnl: "namelock", fwnl: "namelock", forceweeknamelock: "namelock", async namelock(target, room, user, connection, cmd) { if (!target) return this.parse("/help namelock"); const week = cmd.includes("w"); const force = cmd.includes("f"); const { targetUser, inputUsername, targetUsername, rest: reason } = this.splitUser(target); const userid = toID(targetUsername); if (!targetUser && !force) { return this.errorReply( `User '${targetUsername}' not found. Use \`\`/force${week ? "week" : ""}namelock\`\` if you need to namelock them anyway.` ); } if (targetUser && targetUser.id !== toID(inputUsername) && !force) { return this.errorReply(`${inputUsername} has already changed their name to ${targetUser.name}. To namelock anyway, use /force${week ? "week" : ""}namelock.`); } this.checkCan("forcerename", userid); if (targetUser?.namelocked && !week) { return this.errorReply(`User '${targetUser.name}' is already namelocked.`); } if (!force && !week) { const existingPunishments = Punishments.search(userid); for (const [, , punishment] of existingPunishments) { if (punishment.type === "LOCK" && punishment.expireTime - Date.now() > 2 * DAY) { this.errorReply(`User '${userid}' is already normally locked for more than 2 days.`); this.errorReply(`Use /weeknamelock to namelock them instead, so you don't decrease the existing punishment.`); return this.errorReply(`If you really need to override this, use /forcenamelock.`); } } } const { privateReason, publicReason } = this.parseSpoiler(reason); const reasonText = publicReason ? ` (${publicReason})` : `.`; this.privateGlobalModAction(`${targetUser?.name || userid} was ${week ? "week" : ""}namelocked by ${user.name}${reasonText}`); this.globalModlog(`${force ? `FORCE` : ``}${week ? "WEEK" : ""}NAMELOCK`, targetUser || userid, privateReason); const roomauth = Rooms.global.destroyPersonalRooms(userid); if (roomauth.length) { Monitor.log(`[CrisisMonitor] Namelocked user ${userid} has public roomauth (${roomauth.join(", ")}), and should probably be demoted.`); } if (targetUser) { Ladders.cancelSearches(targetUser); targetUser.popup(`|modal|${user.name} has locked your name and you can't change names anymore${reasonText}`); } const duration = week ? 7 * 24 * 60 * 60 * 1e3 : 48 * 60 * 60 * 1e3; await Punishments.namelock(userid, Date.now() + duration, null, false, publicReason); if (targetUser) Chat.runHandlers("onPunishUser", "NAMELOCK", targetUser, room); if (room?.battle) this.parse("/savereplay forpunishment"); Monitor.forceRenames.set(userid, false); if (connection.openPages) { for (const page of connection.openPages) { if (page.includes("usersearch-")) { this.refreshPage(page); } } } return true; }, namelockhelp: [`/namelock OR /nl [user], [reason] - Name locks a [user] and shows the [reason]. Requires: % @ ~`], unl: "unnamelock", unnamelock(target, room, user) { if (!target) return this.parse("/help unnamelock"); this.checkCan("forcerename"); const targetUser = Users.get(target); let reason = ""; if (targetUser?.namelocked) { reason = ` (${targetUser.namelocked})`; } const unlocked = Punishments.unnamelock(target); if (!unlocked) { return this.errorReply(`User '${target}' is not namelocked.`); } this.addGlobalModAction(`${unlocked} was unnamelocked by ${user.name}.${reason}`); this.globalModlog("UNNAMELOCK", toID(target)); this.parse(`/allowname ${toID(target)}`); if (targetUser) targetUser.popup(`${user.name} has unnamelocked you.`); }, unnamelockhelp: [`/unnamelock [username] - Unnamelocks the user. Requires: % @ ~`], hidetextalts: "hidetext", hidealttext: "hidetext", hidealtstext: "hidetext", htext: "hidetext", forcehidetext: "hidetext", hidelines: "hidetext", hlines: "hidetext", cleartext: "hidetext", ctext: "hidetext", clearaltstext: "hidetext", clearlines: "hidetext", forcecleartext: "hidetext", hidetext(target, room, user, connection, cmd) { if (!target) return this.parse(`/help hidetext`); room = this.requireRoom(); const hasLineCount = cmd.includes("lines"); const hideRevealButton = cmd.includes("clear") || cmd === "ctext"; let { targetUser, inputUsername, targetUsername: name, rest: reason } = this.splitUser(target); let lineCount = 0; if (/^[0-9]+\s*(,|$)/.test(reason)) { if (hasLineCount) { let lineCountString; [lineCountString, reason] = import_lib.Utils.splitFirst(reason, ",").map((p) => p.trim()); lineCount = parseInt(lineCountString); } else if (!cmd.includes("force")) { return this.errorReply(`Your reason was a number; use /hidelines if you wanted to clear a specific number of lines, or /forcehidetext if you really wanted your reason to be a number.`); } } const showAlts = cmd.includes("alt"); if (!lineCount && hasLineCount) { return this.errorReply(`You must specify a number of messages to clear. To clear all messages, use /hidetext.`); } if (reason.length > MAX_REASON_LENGTH) { return this.errorReply(`The reason is too long. It cannot exceed ${MAX_REASON_LENGTH} characters.`); } if (!targetUser && !room.log.hasUsername(name)) { return this.errorReply(`User ${name} not found or has no roomlogs.`); } if (lineCount && showAlts) { return this.errorReply(`You can't specify a line count when using /hidealtstext.`); } const userid = toID(inputUsername); this.checkCan("mute", null, room); const sender = user === targetUser ? null : user; let message = ""; if (targetUser && showAlts) { message = `${name}'s alts messages were cleared from ${room.title} by ${user.name}.${reason ? ` (${reason})` : ``}`; room.sendByUser(sender, message); this.modlog("HIDEALTSTEXT", targetUser, reason, { noip: 1 }); room.hideText([ userid, ...targetUser.previousIDs, ...targetUser.getAltUsers(true).map((curUser) => curUser.getLastId()) ]); } else { if (lineCount > 0) { message = `${lineCount} of ${name}'s messages were cleared from ${room.title} by ${user.name}.${reason ? ` (${reason})` : ``}`; room.sendByUser(sender, message); } else { message = `${name}'s messages were cleared from ${room.title} by ${user.name}.${reason ? ` (${reason})` : ``}`; room.sendByUser(sender, message); } this.modlog("HIDETEXT", targetUser || userid, reason, { noip: 1, noalts: 1 }); room.hideText([userid], lineCount, hideRevealButton); this.roomlog(`|c|${user.getIdentity()}|/log ${message}`); } }, hidetexthelp: [ `/hidetext [username], [optional reason] - Removes a user's messages from chat, with an optional reason. Requires: % @ # ~`, `/hidealtstext [username], [optional reason] - Removes a user's messages and their alternate accounts' messages from the chat, with an optional reason. Requires: % @ # ~`, `/hidelines [username], [number], [optional reason] - Removes the [number] most recent messages from a user, with an optional reason. Requires: % @ # ~`, `Use /cleartext, /clearaltstext, and /clearlines to remove messages without displaying a button to reveal them.` ], ab: "blacklist", bl: "blacklist", forceblacklist: "blacklist", forcebl: "blacklist", permanentblacklist: "blacklist", permablacklist: "blacklist", permabl: "blacklist", blacklist(target, room, user, connection, cmd) { room = this.requireRoom(); if (!target) return this.parse("/help blacklist"); this.checkChat(); if (toID(target) === "show") return this.errorReply(`You're looking for /showbl`); const { targetUser, targetUsername, rest: reason } = this.splitUser(target); if (!targetUser) { this.errorReply(`User ${targetUsername} not found.`); return this.errorReply(`If you want to blacklist an offline account by name (not IP), consider /blacklistname`); } this.checkCan("editroom", targetUser, room); if (!room.persist) { return this.errorReply(`This room is not going to last long enough for a blacklist to matter - just ban the user`); } const punishment = Punishments.isRoomBanned(targetUser, room.roomid); if (punishment && punishment.type === "BLACKLIST") { return this.errorReply(`This user is already blacklisted from this room.`); } const force = cmd === "forceblacklist" || cmd === "forcebl"; if (targetUser.trusted) { if (!force) { return this.sendReply( `${targetUser.name} is a trusted user. If you are sure you would like to blacklist them use /forceblacklist.` ); } } else if (force) { return this.errorReply(`Use /blacklist; ${targetUser.name} is not a trusted user.`); } if (!reason && REQUIRE_REASONS) { return this.errorReply(`Blacklists require a reason.`); } if (reason.length > MAX_REASON_LENGTH) { return this.errorReply(`The reason is too long. It cannot exceed ${MAX_REASON_LENGTH} characters.`); } const name = targetUser.getLastName(); const userid = targetUser.getLastId(); if (targetUser.trusted && room.settings.isPrivate !== true) { Monitor.log(`[CrisisMonitor] Trusted user ${targetUser.name}${targetUser.trusted !== targetUser.id ? ` (${targetUser.trusted})` : ""} was blacklisted from ${room.roomid} by ${user.name}, and should probably be demoted.`); } if (targetUser.id in room.users || user.can("lock")) { targetUser.popup( `|modal||html|

${import_lib.Utils.escapeHTML(user.name)} has blacklisted you from the room ${room.roomid}${room.subRooms ? ` and its subrooms` : ""}. Reason: ${import_lib.Utils.escapeHTML(reason)}

To appeal the ban, PM the staff member that blacklisted you${room.persist ? ` or a room owner.

` : `.

`}` ); } const expireTime = cmd.includes("perma") ? Date.now() + 10 * 365 * 24 * 60 * 60 * 1e3 : null; const action = expireTime ? "PERMABLACKLIST" : "BLACKLIST"; this.privateModAction( `${name} was blacklisted from ${room.title} by ${user.name}${expireTime ? " for ten years" : ""}.${reason ? ` (${reason})` : ""}` ); const affected = Punishments.roomBlacklist(room, targetUser, expireTime, null, reason); for (const u of affected) Chat.runHandlers("onPunishUser", "BLACKLIST", u, room); if (!room.settings.isPrivate && room.persist) { const acAccount = targetUser.autoconfirmed !== userid && targetUser.autoconfirmed; let displayMessage = ""; if (affected.length > 1) { displayMessage = `${name}'s ${acAccount ? ` ac account: ${acAccount},` : ""} blacklisted alts: ${affected.slice(1).map((curUser) => curUser.getLastName()).join(", ")}`; this.privateModAction(displayMessage); } else if (acAccount) { displayMessage = `${name}'s ac account: ${acAccount}`; this.privateModAction(displayMessage); } } if (!room.settings.isPrivate && room.persist) { this.globalModlog(action, targetUser, reason); } else { this.modlog(action, targetUser, reason); } return true; }, blacklisthelp: [ `/blacklist [username], [reason] - Blacklists the user from the room you are in for a year. Requires: # ~`, `/permablacklist OR /permabl - blacklist a user for 10 years. Requires: # ~`, `/unblacklist [username] - Unblacklists the user from the room you are in. Requires: # ~`, `/showblacklist OR /showbl - show a list of blacklisted users in the room. Requires: % @ # ~`, `/expiringblacklists OR /expiringbls - show a list of blacklisted users from the room whose blacklists are expiring in 3 months or less. Requires: % @ # ~` ], forcebattleban: "battleban", async battleban(target, room, user, connection, cmd) { room = this.requireRoom(); if (!target) return this.parse(`/help battleban`); const { targetUser, targetUsername, rest: reason } = this.splitUser(target); if (!targetUser) return this.errorReply(`User ${targetUsername} not found.`); if (target.length > MAX_REASON_LENGTH) { return this.errorReply(`The reason is too long. It cannot exceed ${MAX_REASON_LENGTH} characters.`); } if (!reason) { return this.errorReply(`Battle bans require a reason.`); } const includesUrl = reason.includes(`.${Config.routes.root}/`); if (!room.battle && !includesUrl && cmd !== "forcebattleban") { return this.errorReply(`Battle bans require a battle replay if used outside of a battle; if the battle has expired, use /forcebattleban.`); } if (!user.can("rangeban", targetUser)) { this.errorReply(`Battlebans have been deprecated. Alternatives:`); this.errorReply(`- timerstalling and bragging about it: lock`); this.errorReply(`- other timerstalling: they're not timerstalling, leave them alone`); this.errorReply(`- bad nicknames: lock, locks prevent nicknames from appearing; you should always have been locking for this`); this.errorReply(`- ladder cheating: gban, get a moderator if necessary`); this.errorReply(`- serious ladder cheating: permaban, get an administrator`); this.errorReply(`- other: get an administrator`); return; } if (Punishments.isBattleBanned(targetUser)) { return this.errorReply(`User '${targetUser.name}' is already banned from battling.`); } this.privateGlobalModAction(`${targetUser.name} was banned from starting new battles by ${user.name} (${reason})`); if (targetUser.trusted) { Monitor.log(`[CrisisMonitor] Trusted user ${targetUser.name} was banned from battling by ${user.name}, and should probably be demoted.`); } this.globalModlog("BATTLEBAN", targetUser, reason); Ladders.cancelSearches(targetUser); await Punishments.battleban(targetUser, null, null, reason); targetUser.popup(`|modal|${user.name} has prevented you from starting new battles for 2 days (${reason})`); if (room.battle) this.parse("/savereplay forpunishment"); return true; }, battlebanhelp: [ `/battleban [username], [reason] - [DEPRECATED]`, `Prevents the user from starting new battles for 2 days and shows them the [reason]. Requires: ~` ], unbattleban(target, room, user) { if (!target) return this.parse("/help unbattleban"); this.checkCan("lock"); const targetUser = Users.get(target); const unbanned = Punishments.unbattleban(target); if (unbanned) { this.addModAction(`${unbanned} was allowed to battle again by ${user.name}.`); this.globalModlog("UNBATTLEBAN", toID(target)); if (targetUser) targetUser.popup(`${user.name} has allowed you to battle again.`); } else { this.errorReply(`User ${target} is not banned from battling.`); } }, unbattlebanhelp: [`/unbattleban [username] - [DEPRECATED] Allows a user to battle again. Requires: % @ ~`], monthgroupchatban: "groupchatban", monthgcban: "groupchatban", gcban: "groupchatban", async groupchatban(target, room, user, connection, cmd) { room = this.requireRoom(); if (!target) return this.parse(`/help groupchatban`); if (!user.can("rangeban")) { return this.errorReply( `/groupchatban has been deprecated. For future groupchat misuse, lock the creator, it will take away their trusted status and their ability to make groupchats.` ); } const { targetUser, targetUsername, rest: reason } = this.splitUser(target); if (!targetUser) return this.errorReply(`User ${targetUsername} not found.`); if (target.length > MAX_REASON_LENGTH) { return this.errorReply(`The reason is too long. It cannot exceed ${MAX_REASON_LENGTH} characters.`); } const isMonth = cmd.startsWith("month"); if (!isMonth && Punishments.isGroupchatBanned(targetUser)) { return this.errorReply(`User '${targetUser.name}' is already banned from using groupchats.`); } const reasonText = reason ? `: ${reason}` : ``; this.privateGlobalModAction(`${targetUser.name} was banned from using groupchats for a ${isMonth ? "month" : "week"} by ${user.name}${reasonText}.`); if (targetUser.trusted) { Monitor.log(`[CrisisMonitor] Trusted user ${targetUser.name} was banned from using groupchats by ${user.name}, and should probably be demoted.`); } const createdGroupchats = await Punishments.groupchatBan( targetUser, isMonth ? Date.now() + 30 * DAY : null, null, reason ); targetUser.popup(`|modal|${user.name} has banned you from using groupchats for a ${isMonth ? "month" : "week"}${reasonText}`); this.globalModlog("GROUPCHATBAN", targetUser, ` by ${user.id}${reasonText}`); for (const roomid of createdGroupchats) { const targetRoom = Rooms.get(roomid); if (!targetRoom) continue; const participants = targetRoom.warnParticipants?.( `This groupchat (${targetRoom.title}) has been deleted due to inappropriate conduct by its creator, ${targetUser.name}. Do not attempt to recreate it, or you may be punished.${reason ? ` (reason: ${reason})` : ``}` ); if (participants) { const modlogEntry = { action: "NOTE", loggedBy: user.id, isGlobal: true, note: `participants in ${roomid} (creator: ${targetUser.id}): ${participants.join(", ")}` }; targetRoom.modlog(modlogEntry); } targetRoom.destroy(); } }, groupchatbanhelp: [ `/groupchatban [user], [optional reason]`, `/monthgroupchatban [user], [optional reason]`, `Bans the user from joining or creating groupchats for a week (or month). Requires: % @ ~` ], ungcban: "ungroupchatban", gcunban: "ungroupchatban", groucphatunban: "ungroupchatban", ungroupchatban(target, room, user) { if (!target) return this.parse("/help ungroupchatban"); this.checkCan("lock"); const targetUser = Users.get(target); const unbanned = Punishments.groupchatUnban(targetUser || toID(target)); if (unbanned) { this.addGlobalModAction(`${unbanned} was ungroupchatbanned by ${user.name}.`); this.globalModlog("UNGROUPCHATBAN", toID(target), ` by ${user.id}`); if (targetUser) targetUser.popup(`${user.name} has allowed you to use groupchats again.`); } else { this.errorReply(`User ${target} is not banned from using groupchats.`); } }, ungroupchatbanhelp: [`/ungroupchatban [user] - Allows a groupchatbanned user to use groupchats again. Requires: % @ ~`], nameblacklist: "blacklistname", permablacklistname: "blacklistname", blacklistname(target, room, user) { room = this.requireRoom(); if (!target) return this.parse("/help blacklistname"); this.checkChat(); this.checkCan("editroom", null, room); if (!room.persist) { return this.errorReply("This room is not going to last long enough for a blacklist to matter - just ban the user"); } const [targetStr, reason] = target.split("|").map((val) => val.trim()); if (!targetStr || !reason && REQUIRE_REASONS) { return this.errorReply("Usage: /blacklistname name1, name2, ... | reason"); } const targets = targetStr.split(",").map((s) => toID(s)); const duplicates = targets.filter((userid) => // can be asserted, room should always exist Punishments.roomUserids.nestedGetByType(room.roomid, userid, "BLACKLIST")); if (duplicates.length) { return this.errorReply(`[${duplicates.join(", ")}] ${Chat.plural(duplicates, "are", "is")} already blacklisted.`); } const expireTime = this.cmd.includes("perma") ? Date.now() + 10 * 365 * 24 * 60 * 60 * 1e3 : null; const action = expireTime ? "PERMANAMEBLACKLIST" : "NAMEBLACKLIST"; for (const userid of targets) { if (!userid) return this.errorReply(`User '${userid}' is not a valid userid.`); if (!Users.Auth.hasPermission(user, "ban", room.auth.get(userid), room)) { return this.errorReply(`/blacklistname - Access denied: ${userid} is of equal or higher authority than you.`); } Punishments.roomBlacklist(room, userid, expireTime, null, reason); const trusted = Users.isTrusted(userid); if (trusted && room.settings.isPrivate !== true) { Monitor.log(`[CrisisMonitor] Trusted user ${userid}${trusted !== userid ? ` (${trusted})` : ``} was nameblacklisted from ${room.roomid} by ${user.name}, and should probably be demoted.`); } if (!room.settings.isPrivate && room.persist) { this.globalModlog(action, userid, reason); } } this.privateModAction( `${targets.join(", ")}${Chat.plural(targets, " were", " was")} nameblacklisted from ${room.title} by ${user.name}${expireTime ? " for ten years" : ""}.` ); return true; }, blacklistnamehelp: [ `/blacklistname OR /nameblacklist [name1, name2, etc.] | reason - Blacklists all name(s) from the room you are in for a year. Requires: # ~`, `/permablacklistname [name1, name2, etc.] | reason - Blacklists all name(s) from the room you are in for 10 years. Requires: # ~` ], unab: "unblacklist", unblacklist(target, room, user) { room = this.requireRoom(); if (!target) return this.parse("/help unblacklist"); this.checkCan("editroom", null, room); const name = Punishments.roomUnblacklist(room, target); if (name) { this.privateModAction(`${name} was unblacklisted by ${user.name}.`); if (!room.settings.isPrivate && room.persist) { this.globalModlog("UNBLACKLIST", name); } } else { this.errorReply(`User '${target}' is not blacklisted.`); } }, unblacklisthelp: [`/unblacklist [username] - Unblacklists the user from the room you are in. Requires: # ~`], unblacklistall(target, room, user) { room = this.requireRoom(); this.checkCan("editroom", null, room); if (!target) { user.lastCommand = "/unblacklistall"; this.errorReply("THIS WILL UNBLACKLIST ALL BLACKLISTED USERS IN THIS ROOM."); this.errorReply("To confirm, use: /unblacklistall confirm"); return; } if (user.lastCommand !== "/unblacklistall" || target !== "confirm") { return this.parse("/help unblacklistall"); } user.lastCommand = ""; const unblacklisted = Punishments.roomUnblacklistAll(room); if (!unblacklisted) return this.errorReply("No users are currently blacklisted in this room to unblacklist."); this.addModAction(`All blacklists in this room have been lifted by ${user.name}.`); this.modlog("UNBLACKLISTALL"); this.roomlog(`Unblacklisted users: ${unblacklisted.join(", ")}`); }, unblacklistallhelp: [`/unblacklistall - Unblacklists all blacklisted users in the current room. Requires: # ~`], expiringbls: "showblacklist", expiringblacklists: "showblacklist", blacklists: "showblacklist", showbl: "showblacklist", showblacklist(target, room, user, connection, cmd) { if (target) room = Rooms.search(target); if (!room) return this.errorReply(`The room "${target}" was not found.`); this.checkCan("mute", null, room); const SOON_EXPIRING_TIME = 3 * 30 * 24 * 60 * 60 * 1e3; if (!room.persist) return this.errorReply("This room does not support blacklists."); const roomUserids = Punishments.roomUserids.get(room.roomid); if (!roomUserids || roomUserids.size === 0) { return this.sendReply("This room has no blacklisted users."); } const blMap = /* @__PURE__ */ new Map(); let ips = ""; for (const [userid, punishmentList] of roomUserids) { for (const punishment of punishmentList) { const { type, id, expireTime } = punishment; if (type === "BLACKLIST") { if (!blMap.has(id)) blMap.set(id, [expireTime]); if (id !== userid) blMap.get(id).push(userid); } } } if (user.can("ip")) { const roomIps = Punishments.roomIps.get(room.roomid); if (roomIps) { ips = "/ips"; for (const [ip, punishments] of roomIps) { for (const punishment of punishments) { const { type, id } = punishment; if (type === "BLACKLIST") { if (!blMap.has(id)) blMap.set(id, []); blMap.get(id).push(ip); } } } } } const soonExpiring = cmd === "expiringblacklists" || cmd === "expiringbls"; let buf = import_lib.Utils.html`Blacklist for ${room.title}${soonExpiring ? ` (expiring within 3 months)` : ""}:
`; for (const [userid, data] of blMap) { const [expireTime, ...alts] = data; if (soonExpiring && expireTime > Date.now() + SOON_EXPIRING_TIME) continue; const expiresIn = new Date(expireTime).getTime() - Date.now(); const expiresDays = Math.round(expiresIn / 1e3 / 60 / 60 / 24); buf += `- ${userid}, for ${Chat.count(expiresDays, "days")}`; if (alts.length) buf += `, alts${ips}: ${alts.join(", ")}`; buf += `
`; } this.sendReplyBox(buf); }, showblacklisthelp: [ `/showblacklist OR /showbl - show a list of blacklisted users in the room. Requires: % @ # ~`, `/expiringblacklists OR /expiringbls - show a list of blacklisted users from the room whose blacklists are expiring in 3 months or less. Requires: % @ # ~` ] }; //# sourceMappingURL=moderation.js.map