Spaces:
Running
Running
{ | |
"version": 3, | |
"sources": ["../../server/punishments.ts"], | |
"sourcesContent": ["/**\n * Punishments\n * Pokemon Showdown - http://pokemonshowdown.com/\n *\n * Handles the punishing of users on PS.\n *\n * There are four types of global punishments on PS. Locks, bans, namelocks and rangelocks.\n * This file contains the lists of users that have been punished (both IPs and usernames),\n * as well as the functions that handle the execution of said punishments.\n *\n * @license MIT license\n */\n\nimport { FS, Utils } from '../lib';\nimport type { AddressRange } from './ip-tools';\n\nconst PUNISHMENT_FILE = 'config/punishments.tsv';\nconst ROOM_PUNISHMENT_FILE = 'config/room-punishments.tsv';\nconst SHAREDIPS_FILE = 'config/sharedips.tsv';\nconst SHAREDIPS_BLACKLIST_FILE = 'config/sharedips-blacklist.tsv';\nconst WHITELISTED_NAMES_FILE = 'config/name-whitelist.tsv';\n\nconst RANGELOCK_DURATION = 60 * 60 * 1000; // 1 hour\nconst LOCK_DURATION = 48 * 60 * 60 * 1000; // 48 hours\nconst GLOBALBAN_DURATION = 7 * 24 * 60 * 60 * 1000; // 1 week\nconst BATTLEBAN_DURATION = 48 * 60 * 60 * 1000; // 48 hours\nconst GROUPCHATBAN_DURATION = 7 * 24 * 60 * 60 * 1000; // 1 week\nconst MOBILE_PUNISHMENT_DURATIION = 6 * 60 * 60 * 1000; // 6 hours\n\nconst ROOMBAN_DURATION = 48 * 60 * 60 * 1000; // 48 hours\nconst BLACKLIST_DURATION = 365 * 24 * 60 * 60 * 1000; // 1 year\n\nconst USERID_REGEX = /^[a-z0-9]+$/;\nconst PUNISH_TRUSTED = false;\n\nconst PUNISHMENT_POINT_VALUES: { [k: string]: number } = { MUTE: 2, BLACKLIST: 3, ROOMBAN: 4 };\nconst AUTOLOCK_POINT_THRESHOLD = 8;\n\nconst AUTOWEEKLOCK_THRESHOLD = 5; // number of global punishments to upgrade autolocks to weeklocks\nconst AUTOWEEKLOCK_DAYS_TO_SEARCH = 60;\n\n/** The longest amount of time any individual timeout will be set for. */\nconst MAX_PUNISHMENT_TIMER_LENGTH = 24 * 60 * 60 * 1000; // 24 hours\n\n/**\n * The number of users from a groupchat whose creator was banned from using groupchats\n * who may join a new groupchat before the GroupchatMonitor activates.\n */\nconst GROUPCHAT_PARTICIPANT_OVERLAP_THRESHOLD = 5;\n/**\n * The minimum amount of time that must pass between activations of the GroupchatMonitor.\n */\nconst GROUPCHAT_MONITOR_INTERVAL = 10 * 60 * 1000; // 10 minutes\n\nexport interface Punishment {\n\ttype: string;\n\tid: ID | PunishType;\n\texpireTime: number;\n\treason: string;\n\trest?: any[];\n}\n\n/**\n * Info on punishment types. Can be used for either room punishment types or global punishments\n * extended by plugins. The `desc` is the /alts display, and the `callback` is what to do if the user\n * _does_ have the punishment (can be used to add extra effects in lieu of something like a loginfilter.)\n * Rooms will be specified for room punishments, otherwise it's null for global punishments\n */\nexport interface PunishInfo {\n\tdesc: string;\n\tonActivate?: (user: User, punishment: Punishment, room: Room | null, isExactMatch: boolean) => void;\n\t/** For room punishments - should they count for punishmentmonitor? default to no. */\n\tactivatePunishMonitor?: boolean;\n}\n\ninterface PunishmentEntry {\n\tips: string[];\n\tuserids: ID[];\n\tpunishType: string;\n\texpireTime: number;\n\treason: string;\n\trest: any[];\n}\n\nclass PunishmentMap extends Map<string, Punishment[]> {\n\troomid?: RoomID;\n\tconstructor(roomid?: RoomID) {\n\t\tsuper();\n\t\tthis.roomid = roomid;\n\t}\n\tremoveExpiring(punishments: Punishment[]) {\n\t\tfor (const [i, punishment] of punishments.entries()) {\n\t\t\tif (Date.now() > punishment.expireTime) {\n\t\t\t\tpunishments.splice(i, 1);\n\t\t\t}\n\t\t}\n\t}\n\tget(k: string) {\n\t\tconst punishments = super.get(k);\n\t\tif (punishments) {\n\t\t\tthis.removeExpiring(punishments);\n\t\t\tif (punishments.length) return punishments;\n\t\t\tthis.delete(k);\n\t\t}\n\t\treturn undefined;\n\t}\n\thas(k: string) {\n\t\treturn !!this.get(k);\n\t}\n\tgetByType(k: string, type: string) {\n\t\t// safely 0 since a user only ever has one punishment of each type\n\t\treturn this.get(k)?.filter(p => p.type === type)?.[0];\n\t}\n\teach(callback: (punishment: Punishment, id: string, map: PunishmentMap) => void) {\n\t\tfor (const [k, punishments] of super.entries()) {\n\t\t\tthis.removeExpiring(punishments);\n\t\t\tif (punishments.length) {\n\t\t\t\tfor (const punishment of punishments) {\n\t\t\t\t\tcallback(punishment, k, this);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tthis.delete(k);\n\t\t\t}\n\t\t}\n\t}\n\tdeleteOne(k: string, punishment: Punishment) {\n\t\tconst list = this.get(k);\n\t\tif (!list) return;\n\t\tfor (let i = list.length - 1; i >= 0; i--) {\n\t\t\tconst cur = list[i];\n\t\t\tif (punishment.type === cur.type && cur.id === punishment.id) {\n\t\t\t\tlist.splice(i, 1);\n\t\t\t}\n\t\t}\n\t\tif (!list.length) {\n\t\t\tthis.delete(k);\n\t\t}\n\t\treturn true;\n\t}\n\tadd(k: string, punishment: Punishment) {\n\t\tlet list = this.get(k);\n\t\tif (!list) {\n\t\t\tlist = [];\n\t\t\tthis.set(k, list);\n\t\t}\n\t\tfor (let i = list.length - 1; i >= 0; i--) {\n\t\t\tconst curPunishment = list[i];\n\t\t\tif (punishment.type === curPunishment.type) {\n\t\t\t\tif (curPunishment.expireTime >= punishment.expireTime) {\n\t\t\t\t\tcurPunishment.reason = punishment.reason;\n\t\t\t\t\t// if we already have a punishment of the same type with a higher expiration date\n\t\t\t\t\t// we want to just update the reason and ignore it\n\t\t\t\t\treturn this;\n\t\t\t\t}\n\t\t\t\tlist.splice(i, 1);\n\t\t\t}\n\t\t}\n\t\tlist.push(punishment);\n\t\treturn this;\n\t}\n}\n\nclass NestedPunishmentMap extends Map<RoomID, PunishmentMap> {\n\tnestedSet(k1: RoomID, k2: string, value: Punishment) {\n\t\tif (!this.get(k1)) {\n\t\t\tthis.set(k1, new PunishmentMap(k1));\n\t\t}\n\t\t// guaranteed above\n\t\tthis.get(k1)!.add(k2, value);\n\t}\n\tnestedGet(k1: RoomID, k2: string) {\n\t\tconst subMap = this.get(k1);\n\t\tif (!subMap) return subMap;\n\t\tconst punishments = subMap.get(k2);\n\t\tif (punishments?.length) {\n\t\t\treturn punishments;\n\t\t}\n\t\treturn undefined;\n\t}\n\tnestedGetByType(k1: RoomID, k2: string, type: string) {\n\t\treturn this.nestedGet(k1, k2)?.find(p => p.type === type);\n\t}\n\tnestedHas(k1: RoomID, k2: string) {\n\t\treturn !!this.nestedGet(k1, k2);\n\t}\n\tnestedDelete(k1: RoomID, k2: string) {\n\t\tconst subMap = this.get(k1);\n\t\tif (!subMap) return;\n\t\tsubMap.delete(k2);\n\t\tif (!subMap.size) this.delete(k1);\n\t}\n\tnestedEach(callback: (punishment: Punishment, roomid: RoomID, userid: string) => void) {\n\t\tfor (const [k1, subMap] of this.entries()) {\n\t\t\tfor (const [k2, punishments] of subMap.entries()) {\n\t\t\t\tsubMap.removeExpiring(punishments);\n\t\t\t\tif (punishments.length) {\n\t\t\t\t\tfor (const punishment of punishments) {\n\t\t\t\t\t\tcallback(punishment, k1, k2);\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tthis.nestedDelete(k1, k2);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n/*********************************************************\n * Persistence\n *********************************************************/\n\nexport const Punishments = new class {\n\t/**\n\t * ips is an ip:punishment Map\n\t */\n\treadonly ips = new PunishmentMap();\n\t/**\n\t * userids is a userid:punishment Map\n\t */\n\treadonly userids = new PunishmentMap();\n\t/**\n\t * roomUserids is a roomid:userid:punishment nested Map\n\t */\n\treadonly roomUserids = new NestedPunishmentMap();\n\t/**\n\t * roomIps is a roomid:ip:punishment Map\n\t */\n\treadonly roomIps = new NestedPunishmentMap();\n\t/**\n\t * sharedIps is an ip:note Map\n\t */\n\treadonly sharedIps = new Map<string, string>();\n\t/**\n\t * AddressRange:note map. In a separate map so we iterate a massive map a lot less.\n\t * (AddressRange is a bit of a premature optimization, but it saves us a conversion call on some trafficked spots)\n\t */\n\tsharedRanges = new Map<AddressRange, string>();\n\t/**\n\t * sharedIpBlacklist is an ip:note Map\n\t */\n\treadonly sharedIpBlacklist = new Map<string, string>();\n\t/**\n\t * namefilterwhitelist is a whitelistedname:whitelister Map\n\t */\n\treadonly namefilterwhitelist = new Map<string, string>();\n\t/**\n\t * Connection flood table. Separate table from IP bans.\n\t */\n\treadonly cfloods = new Set<string>();\n\t/**\n\t * Participants in groupchats whose creators were banned from using groupchats.\n\t * Object keys are roomids of groupchats; values are Sets of user IDs.\n\t */\n\treadonly bannedGroupchatParticipants: { [k: string]: Set<ID> } = {};\n\t/** roomid:timestamp map */\n\treadonly lastGroupchatMonitorTime: { [k: string]: number } = {};\n\t/**\n\t * Map<userid that has been warned, reason they were warned for>\n\t */\n\treadonly offlineWarns = new Map<ID, string>();\n\t/**\n\t * punishType is an allcaps string, for global punishments they can be\n\t * anything in the punishmentTypes map.\n\t *\n\t * This map can be extended with custom punishments by chat plugins.\n\t *\n\t * Keys in the map correspond to PunishInfo */\n\treadonly punishmentTypes: Map<string, PunishInfo> = new Map<string, PunishInfo>([\n\t\t...(global.Punishments?.punishmentTypes || []),\n\t\t['LOCK', { desc: 'locked' }],\n\t\t['BAN', { desc: 'globally banned' }],\n\t\t['NAMELOCK', { desc: 'namelocked' }],\n\t\t['GROUPCHATBAN', { desc: 'banned from using groupchats' }],\n\t\t['BATTLEBAN', { desc: 'banned from battling' }],\n\t]);\n\t/**\n\t * For room punishments, they can be anything in the roomPunishmentTypes map.\n\t *\n\t * This map can be extended with custom punishments by chat plugins.\n\t *\n\t * Keys in the map correspond to punishTypes, values signify the way they\n\t * should be displayed in /alt.\n\t * By default, this includes:\n\t * - 'ROOMBAN'\n\t * - 'BLACKLIST'\n\t * - 'MUTE' (used by getRoomPunishments)\n\t *\n\t */\n\treadonly roomPunishmentTypes: Map<string, PunishInfo> = new Map<string, PunishInfo>([\n\t\t// references to global.Punishments? are here because if you hotpatch punishments without hotpatching chat,\n\t\t// old punishment types won't be loaded into here, which might cause issues. This guards against that.\n\t\t...(global.Punishments?.roomPunishmentTypes || []),\n\t\t['ROOMBAN', { desc: 'banned', activatePunishMonitor: true }],\n\t\t['BLACKLIST', { desc: 'blacklisted', activatePunishMonitor: true }],\n\t\t['MUTE', { desc: 'muted', activatePunishMonitor: true }],\n\t]);\n\tconstructor() {\n\t\tsetImmediate(() => {\n\t\t\tvoid Punishments.loadPunishments();\n\t\t\tvoid Punishments.loadRoomPunishments();\n\t\t\tvoid Punishments.loadBanlist();\n\t\t\tvoid Punishments.loadSharedIps();\n\t\t\tvoid Punishments.loadSharedIpBlacklist();\n\t\t\tvoid Punishments.loadWhitelistedNames();\n\t\t});\n\t}\n\n\t// punishments.tsv is in the format:\n\t// punishType, userid, ips/usernames, expiration time, reason\n\t// room-punishments.tsv is in the format:\n\t// punishType, roomid:userid, ips/usernames, expiration time, reason\n\tasync loadPunishments() {\n\t\tconst data = await FS(PUNISHMENT_FILE).readIfExists();\n\t\tif (!data) return;\n\t\tfor (const row of data.split(\"\\n\")) {\n\t\t\tif (!row || row === '\\r') continue;\n\t\t\tconst [type, id, altKeys, expireTimeStr, ...reason] = row.trim().split(\"\\t\");\n\t\t\tconst expireTime = Number(expireTimeStr);\n\t\t\tif (type === \"Punishment\") continue;\n\t\t\tconst keys = altKeys.split(',').concat(id);\n\n\t\t\tconst punishment = { type, id, expireTime, reason: reason.join('\\t') } as Punishment;\n\t\t\tif (Date.now() >= expireTime) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tfor (const key of keys) {\n\t\t\t\tif (!key.trim()) continue; // ignore empty ips / userids\n\t\t\t\tif (!USERID_REGEX.test(key)) {\n\t\t\t\t\tPunishments.ips.add(key, punishment);\n\t\t\t\t} else {\n\t\t\t\t\tPunishments.userids.add(key, punishment);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tasync loadRoomPunishments() {\n\t\tconst data = await FS(ROOM_PUNISHMENT_FILE).readIfExists();\n\t\tif (!data) return;\n\t\tfor (const row of data.split(\"\\n\")) {\n\t\t\tif (!row || row === '\\r') continue;\n\t\t\tconst [type, id, altKeys, expireTimeStr, ...reason] = row.trim().split(\"\\t\");\n\t\t\tconst expireTime = Number(expireTimeStr);\n\t\t\tif (type === \"Punishment\") continue;\n\t\t\tconst [roomid, userid] = id.split(':');\n\t\t\tif (!userid) continue; // invalid format\n\t\t\tconst keys = altKeys.split(',').concat(userid);\n\n\t\t\tconst punishment = { type, id: userid, expireTime, reason: reason.join('\\t') } as Punishment;\n\t\t\tif (Date.now() >= expireTime) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tfor (const key of keys) {\n\t\t\t\tif (!USERID_REGEX.test(key)) {\n\t\t\t\t\tPunishments.roomIps.nestedSet(roomid as RoomID, key, punishment);\n\t\t\t\t} else {\n\t\t\t\t\tPunishments.roomUserids.nestedSet(roomid as RoomID, key, punishment);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tsavePunishments() {\n\t\tFS(PUNISHMENT_FILE).writeUpdate(() => {\n\t\t\tconst saveTable = Punishments.getPunishments();\n\t\t\tlet buf = 'Punishment\\tUser ID\\tIPs and alts\\tExpires\\tReason\\r\\n';\n\t\t\tfor (const [id, entry] of saveTable) {\n\t\t\t\tbuf += Punishments.renderEntry(entry, id);\n\t\t\t}\n\t\t\treturn buf;\n\t\t}, { throttle: 5000 });\n\t}\n\n\tsaveRoomPunishments() {\n\t\tFS(ROOM_PUNISHMENT_FILE).writeUpdate(() => {\n\t\t\tconst saveTable: [string, PunishmentEntry][] = [];\n\t\t\tfor (const roomid of Punishments.roomIps.keys()) {\n\t\t\t\tfor (const [userid, punishment] of Punishments.getPunishments(roomid, true)) {\n\t\t\t\t\tsaveTable.push([`${roomid}:${userid}`, punishment]);\n\t\t\t\t}\n\t\t\t}\n\t\t\tlet buf = 'Punishment\\tRoom ID:User ID\\tIPs and alts\\tExpires\\tReason\\r\\n';\n\t\t\tfor (const [id, entry] of saveTable) {\n\t\t\t\tbuf += Punishments.renderEntry(entry, id);\n\t\t\t}\n\t\t\treturn buf;\n\t\t}, { throttle: 5000 });\n\t}\n\n\tgetEntry(entryId: string) {\n\t\tlet entry: PunishmentEntry | null = null;\n\t\tPunishments.ips.each((punishment, ip) => {\n\t\t\tconst { type, id, expireTime, reason, rest } = punishment;\n\t\t\tif (id !== entryId) return;\n\t\t\tif (entry) {\n\t\t\t\tentry.ips.push(ip);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tentry = {\n\t\t\t\tuserids: [],\n\t\t\t\tips: [ip],\n\t\t\t\tpunishType: type,\n\t\t\t\texpireTime,\n\t\t\t\treason,\n\t\t\t\trest: rest || [],\n\t\t\t};\n\t\t});\n\t\tPunishments.userids.each((punishment, userid) => {\n\t\t\tconst { type, id, expireTime, reason, rest } = punishment;\n\t\t\tif (id !== entryId) return;\n\n\t\t\tif (!entry) {\n\t\t\t\tentry = {\n\t\t\t\t\tuserids: [],\n\t\t\t\t\tips: [],\n\t\t\t\t\tpunishType: type,\n\t\t\t\t\texpireTime,\n\t\t\t\t\treason,\n\t\t\t\t\trest: rest || [],\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tif (userid !== id) entry.userids.push(toID(userid));\n\t\t});\n\n\t\treturn entry;\n\t}\n\n\tappendPunishment(entry: PunishmentEntry, id: string, filename: string, allowNonUserIDs?: boolean) {\n\t\tif (!allowNonUserIDs && id.startsWith('#')) return;\n\t\tconst buf = Punishments.renderEntry(entry, id);\n\t\treturn FS(filename).append(buf);\n\t}\n\n\trenderEntry(entry: PunishmentEntry, id: string) {\n\t\tconst keys = entry.ips.concat(entry.userids).join(',');\n\t\tconst row = [entry.punishType, id, keys, entry.expireTime, entry.reason, ...entry.rest];\n\t\treturn row.join('\\t') + '\\r\\n';\n\t}\n\n\tasync loadBanlist() {\n\t\tconst data = await FS('config/ipbans.txt').readIfExists();\n\t\tif (!data) return;\n\t\tconst rangebans = [];\n\t\tfor (const row of data.split(\"\\n\")) {\n\t\t\tconst ip = row.split('#')[0].trim();\n\t\t\tif (!ip) continue;\n\t\t\tif (ip.includes('/')) {\n\t\t\t\trangebans.push(ip);\n\t\t\t} else if (!Punishments.ips.has(ip)) {\n\t\t\t\tPunishments.ips.add(ip, { type: 'LOCK', id: '#ipban', expireTime: Infinity, reason: '' });\n\t\t\t}\n\t\t}\n\t\tPunishments.checkRangeBanned = IPTools.checker(rangebans);\n\t}\n\n\t/**\n\t * sharedips.tsv is in the format:\n\t * IP, type (in this case always SHARED), note\n\t */\n\tasync loadSharedIps() {\n\t\tconst data = await FS(SHAREDIPS_FILE).readIfExists();\n\t\tif (!data) return;\n\t\tlet needsSave = false; // do we need to re-save to fix malformed data?\n\t\tfor (const row of data.replace('\\r', '').split(\"\\n\")) {\n\t\t\tif (!row) continue;\n\t\t\tconst [ip, type, note] = row.trim().split(\"\\t\");\n\t\t\tif (ip === 'IP') {\n\t\t\t\tcontinue; // first row\n\t\t\t}\n\t\t\tif (IPTools.ipRegex.test(note)) {\n\t\t\t\t// this is handling a bug where data accidentally got reversed\n\t\t\t\t// (into note,shared,ip format instead of ip,shared,note format)\n\t\t\t\tPunishments.sharedIps.set(note, ip);\n\t\t\t\tneedsSave = true;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (!IPTools.ipRegex.test(ip)) {\n\t\t\t\tconst pattern = IPTools.stringToRange(ip);\n\t\t\t\tif (pattern) {\n\t\t\t\t\tPunishments.sharedRanges.set(pattern, note);\n\t\t\t\t} else {\n\t\t\t\t\tMonitor.adminlog(`Invalid range data in '${SHAREDIPS_FILE}': \"${row}\".`);\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (type !== 'SHARED') continue;\n\n\t\t\tPunishments.sharedIps.set(ip, note);\n\t\t}\n\t\tif (needsSave) {\n\t\t\tvoid Punishments.saveSharedIps();\n\t\t}\n\t}\n\n\tappendSharedIp(ip: string, note: string) {\n\t\tconst pattern = IPTools.stringToRange(ip);\n\t\tlet ipString = ip;\n\t\tif (pattern && pattern.minIP !== pattern.maxIP) {\n\t\t\tipString = IPTools.rangeToString(pattern);\n\t\t}\n\t\tconst buf = `${ipString}\\tSHARED\\t${note}\\r\\n`;\n\t\treturn FS(SHAREDIPS_FILE).append(buf);\n\t}\n\n\tsaveSharedIps() {\n\t\tlet buf = 'IP\\tType\\tNote\\r\\n';\n\t\tfor (const [ip, note] of Punishments.sharedIps) {\n\t\t\tbuf += `${ip}\\tSHARED\\t${note}\\r\\n`;\n\t\t}\n\t\tfor (const [range, note] of Punishments.sharedRanges) {\n\t\t\tbuf += `${IPTools.rangeToString(range)}\\tSHARED\\t${note}\\r\\n`;\n\t\t}\n\n\t\treturn FS(SHAREDIPS_FILE).write(buf);\n\t}\n\n\t/**\n\t * sharedips.tsv is in the format:\n\t * IP, type (in this case always SHARED), note\n\t */\n\tasync loadSharedIpBlacklist() {\n\t\tconst data = await FS(SHAREDIPS_BLACKLIST_FILE).readIfExists();\n\t\tif (!data) return;\n\t\tfor (const row of data.replace('\\r', '').split(\"\\n\")) {\n\t\t\tif (!row) continue;\n\t\t\tconst [ip, reason] = row.trim().split(\"\\t\");\n\t\t\t// it can be an ip or a range\n\t\t\tif (!IPTools.ipRangeRegex.test(ip)) continue;\n\t\t\tif (!reason) continue;\n\n\t\t\tPunishments.sharedIpBlacklist.set(ip, reason);\n\t\t}\n\t}\n\n\tappendSharedIpBlacklist(ip: string, reason: string) {\n\t\tconst buf = `${ip}\\t${reason}\\r\\n`;\n\t\treturn FS(SHAREDIPS_BLACKLIST_FILE).append(buf);\n\t}\n\n\tsaveSharedIpBlacklist() {\n\t\tlet buf = `IP\\tReason\\r\\n`;\n\t\tPunishments.sharedIpBlacklist.forEach((reason, ip) => {\n\t\t\tbuf += `${ip}\\t${reason}\\r\\n`;\n\t\t});\n\t\treturn FS(SHAREDIPS_BLACKLIST_FILE).write(buf);\n\t}\n\n\tasync loadWhitelistedNames() {\n\t\tconst data = await FS(WHITELISTED_NAMES_FILE).readIfExists();\n\t\tif (!data) return;\n\t\tconst lines = data.split('\\n');\n\t\tlines.shift();\n\t\tfor (const line of lines) {\n\t\t\tconst [userid, whitelister] = line.split('\\t');\n\t\t\tthis.namefilterwhitelist.set(toID(userid), toID(whitelister));\n\t\t}\n\t}\n\n\tappendWhitelistedName(name: string, whitelister: string) {\n\t\treturn FS(WHITELISTED_NAMES_FILE).append(`${toID(name)}\\t${toID(whitelister)}\\r\\n`);\n\t}\n\n\tsaveNameWhitelist() {\n\t\tlet buf = `Userid\\tWhitelister\\t\\r\\n`;\n\t\tPunishments.namefilterwhitelist.forEach((userid, whitelister) => {\n\t\t\tbuf += `${userid}\\t${whitelister}\\r\\n`;\n\t\t});\n\t\treturn FS(WHITELISTED_NAMES_FILE).write(buf);\n\t}\n\n\t/*********************************************************\n\t * Adding and removing\n\t *********************************************************/\n\n\tasync punish(user: User | ID, punishment: Punishment, ignoreAlts: boolean, bypassPunishmentfilter = false) {\n\t\tuser = Users.get(user) || user;\n\t\tif (typeof user === 'string') {\n\t\t\treturn Punishments.punishName(user, punishment);\n\t\t}\n\n\t\tPunishments.checkInteractions(user.getLastId(), punishment);\n\n\t\tif (!punishment.id) punishment.id = user.getLastId();\n\n\t\tconst userids = new Set<ID>();\n\t\tconst ips = new Set<string>();\n\t\tconst mobileIps = new Set<string>();\n\t\tconst affected = ignoreAlts ? [user] : user.getAltUsers(PUNISH_TRUSTED, true);\n\t\tfor (const alt of affected) {\n\t\t\tawait this.punishInner(alt, punishment, userids, ips, mobileIps);\n\t\t}\n\n\t\tconst { type, id, expireTime, reason, rest } = punishment;\n\t\tuserids.delete(id as ID);\n\t\tvoid Punishments.appendPunishment({\n\t\t\tuserids: [...userids],\n\t\t\tips: [...ips],\n\t\t\tpunishType: type,\n\t\t\texpireTime,\n\t\t\treason,\n\t\t\trest: rest || [],\n\t\t}, id, PUNISHMENT_FILE);\n\n\t\tif (mobileIps.size) {\n\t\t\tconst mobileExpireTime = Date.now() + MOBILE_PUNISHMENT_DURATIION;\n\t\t\tconst mobilePunishment = { type, id, expireTime: mobileExpireTime, reason, rest } as Punishment;\n\t\t\tfor (const mobileIp of mobileIps) {\n\t\t\t\tPunishments.ips.add(mobileIp, mobilePunishment);\n\t\t\t}\n\t\t}\n\n\t\tif (!bypassPunishmentfilter) Chat.punishmentfilter(user, punishment);\n\t\treturn affected;\n\t}\n\n\tasync punishInner(user: User, punishment: Punishment, userids: Set<ID>, ips: Set<string>, mobileIps: Set<string>) {\n\t\tconst existingPunishment = Punishments.userids.getByType(user.locked || toID(user.name), punishment.type);\n\t\tif (existingPunishment) {\n\t\t\t// don't reduce the duration of an existing punishment\n\t\t\tif (existingPunishment.expireTime > punishment.expireTime) {\n\t\t\t\tpunishment.expireTime = existingPunishment.expireTime;\n\t\t\t}\n\n\t\t\t// don't override stronger punishment types\n\t\t\tconst types = ['LOCK', 'NAMELOCK', 'BAN'];\n\t\t\tif (types.indexOf(existingPunishment.type) > types.indexOf(punishment.type)) {\n\t\t\t\tpunishment.type = existingPunishment.type;\n\t\t\t}\n\t\t}\n\n\t\tfor (const ip of user.ips) {\n\t\t\tconst { hostType } = await IPTools.lookup(ip);\n\t\t\tif (hostType !== 'mobile') {\n\t\t\t\tPunishments.ips.add(ip, punishment);\n\t\t\t\tips.add(ip);\n\t\t\t} else {\n\t\t\t\tmobileIps.add(ip);\n\t\t\t}\n\t\t}\n\t\tconst lastUserId = user.getLastId();\n\t\tif (!lastUserId.startsWith('guest')) {\n\t\t\tPunishments.userids.add(lastUserId, punishment);\n\t\t}\n\t\tif (user.locked && !user.locked.startsWith('#')) {\n\t\t\tPunishments.userids.add(user.locked, punishment);\n\t\t\tuserids.add(user.locked as ID);\n\t\t}\n\t\tif (user.autoconfirmed) {\n\t\t\tPunishments.userids.add(user.autoconfirmed, punishment);\n\t\t\tuserids.add(user.autoconfirmed);\n\t\t}\n\t\tif (user.trusted) {\n\t\t\tPunishments.userids.add(user.trusted, punishment);\n\t\t\tuserids.add(user.trusted);\n\t\t}\n\t}\n\n\tpunishName(userid: ID, punishment: Punishment) {\n\t\tif (!punishment.id) punishment.id = userid;\n\n\t\tconst foundKeys = Punishments.search(userid).map(([key]) => key);\n\t\tconst userids = new Set<ID>([userid]);\n\t\tconst ips = new Set<string>();\n\n\t\tPunishments.checkInteractions(userid, punishment);\n\n\t\tfor (const key of foundKeys) {\n\t\t\tif (key.includes('.')) {\n\t\t\t\tips.add(key);\n\t\t\t} else {\n\t\t\t\tuserids.add(key as ID);\n\t\t\t}\n\t\t}\n\t\tfor (const id of userids) {\n\t\t\tPunishments.userids.add(id, punishment);\n\t\t}\n\t\tfor (const ip of ips) {\n\t\t\tPunishments.ips.add(ip, punishment);\n\t\t}\n\t\tconst { type, id, expireTime, reason, rest } = punishment;\n\t\tconst affected = Users.findUsers([...userids], [...ips], { includeTrusted: PUNISH_TRUSTED, forPunishment: true });\n\t\tuserids.delete(id as ID);\n\t\tvoid Punishments.appendPunishment({\n\t\t\tuserids: [...userids],\n\t\t\tips: [...ips],\n\t\t\tpunishType: type,\n\t\t\texpireTime,\n\t\t\treason,\n\t\t\trest: rest || [],\n\t\t}, id, PUNISHMENT_FILE);\n\n\t\tChat.punishmentfilter(userid, punishment);\n\t\treturn affected;\n\t}\n\n\tunpunish(id: string, punishType: string) {\n\t\tid = toID(id);\n\t\tconst punishment = Punishments.userids.getByType(id, punishType);\n\t\tif (punishment) {\n\t\t\tid = punishment.id;\n\t\t}\n\t\t// in theory we can stop here if punishment doesn't exist, but\n\t\t// in case of inconsistent state, we'll try anyway\n\n\t\tlet success: false | string = false;\n\t\tPunishments.ips.each((cur, key) => {\n\t\t\tconst { type: curPunishmentType, id: curId } = cur;\n\t\t\tif (curId === id && curPunishmentType === punishType) {\n\t\t\t\tPunishments.ips.deleteOne(key, cur);\n\t\t\t\tsuccess = id;\n\t\t\t}\n\t\t});\n\t\tPunishments.userids.each((cur, key) => {\n\t\t\tconst { type: curPunishmentType, id: curId } = cur;\n\t\t\tif (curId === id && curPunishmentType === punishType) {\n\t\t\t\tPunishments.userids.deleteOne(key, cur);\n\t\t\t\tsuccess = id;\n\t\t\t}\n\t\t});\n\t\tif (success) {\n\t\t\tPunishments.savePunishments();\n\t\t}\n\t\treturn success;\n\t}\n\n\troomPunish(room: Room | RoomID, user: User | ID, punishment: Punishment) {\n\t\tif (typeof user === 'string') {\n\t\t\treturn Punishments.roomPunishName(room, user, punishment);\n\t\t}\n\n\t\tif (!punishment.id) punishment.id = user.getLastId();\n\n\t\tPunishments.checkInteractions(punishment.id as ID, punishment, toID(room) as RoomID);\n\n\t\tconst roomid = typeof room !== 'string' ? (room as Room).roomid : room;\n\t\tconst userids = new Set<ID>();\n\t\tconst ips = new Set<string>();\n\t\tconst affected = user.getAltUsers(PUNISH_TRUSTED, true);\n\t\tfor (const curUser of affected) {\n\t\t\tthis.roomPunishInner(roomid, curUser, punishment, userids, ips);\n\t\t}\n\n\t\tconst { type, id, expireTime, reason, rest } = punishment;\n\t\tuserids.delete(id as ID);\n\t\tvoid Punishments.appendPunishment({\n\t\t\tuserids: [...userids],\n\t\t\tips: [...ips],\n\t\t\tpunishType: type,\n\t\t\texpireTime,\n\t\t\treason,\n\t\t\trest: rest || [],\n\t\t}, roomid + ':' + id, ROOM_PUNISHMENT_FILE);\n\n\t\tif (typeof room !== 'string') {\n\t\t\troom = room as Room;\n\t\t\tif (!(room.settings.isPrivate === true || room.settings.isPersonal)) {\n\t\t\t\tvoid Punishments.monitorRoomPunishments(user);\n\t\t\t}\n\t\t}\n\n\t\treturn affected;\n\t}\n\n\troomPunishInner(roomid: RoomID, user: User, punishment: Punishment, userids: Set<string>, ips: Set<string>) {\n\t\tfor (const ip of user.ips) {\n\t\t\tPunishments.roomIps.nestedSet(roomid, ip, punishment);\n\t\t\tips.add(ip);\n\t\t}\n\t\tif (!user.id.startsWith('guest')) {\n\t\t\tPunishments.roomUserids.nestedSet(roomid, user.id, punishment);\n\t\t}\n\t\tif (user.autoconfirmed) {\n\t\t\tPunishments.roomUserids.nestedSet(roomid, user.autoconfirmed, punishment);\n\t\t\tuserids.add(user.autoconfirmed);\n\t\t}\n\t\tif (user.trusted) {\n\t\t\tPunishments.roomUserids.nestedSet(roomid, user.trusted, punishment);\n\t\t\tuserids.add(user.trusted);\n\t\t}\n\t}\n\n\tcheckInteractions(userid: ID, punishment: Punishment, room?: RoomID) {\n\t\tconst punishments = Punishments.search(userid);\n\t\tconst results = [];\n\t\tconst info = Punishments[room ? 'roomInteractions' : 'interactions'][punishment.type];\n\t\tif (!info) return;\n\t\tfor (const [k, curRoom, curPunishment] of punishments) {\n\t\t\tif (k !== userid || (room && curRoom !== room)) continue;\n\t\t\tif (info.overrides.includes(curPunishment.type)) {\n\t\t\t\tresults.push(curPunishment);\n\t\t\t\tif (room) {\n\t\t\t\t\tPunishments.roomUnpunish(room, userid, curPunishment.type);\n\t\t\t\t} else {\n\t\t\t\t\tPunishments.unpunish(userid, curPunishment.type);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn results;\n\t}\n\n\troomPunishName(room: Room | RoomID, userid: ID, punishment: Punishment) {\n\t\tif (!punishment.id) punishment.id = userid;\n\n\t\tconst roomid = typeof room !== 'string' ? (room as Room).roomid : room;\n\t\tconst foundKeys = Punishments.search(userid).map(([key]) => key);\n\t\tPunishments.checkInteractions(userid, punishment, roomid);\n\t\tconst userids = new Set<ID>([userid]);\n\t\tconst ips = new Set<string>();\n\t\tfor (const key of foundKeys) {\n\t\t\tif (key.includes('.')) {\n\t\t\t\tips.add(key);\n\t\t\t} else {\n\t\t\t\tuserids.add(key as ID);\n\t\t\t}\n\t\t}\n\t\tfor (const id of userids) {\n\t\t\tPunishments.roomUserids.nestedSet(roomid, id, punishment);\n\t\t}\n\t\tfor (const ip of ips) {\n\t\t\tPunishments.roomIps.nestedSet(roomid, ip, punishment);\n\t\t}\n\t\tconst { type, id, expireTime, reason, rest } = punishment;\n\t\tconst affected = Users.findUsers([...userids], [...ips], { includeTrusted: PUNISH_TRUSTED, forPunishment: true });\n\t\tuserids.delete(id as ID);\n\t\tvoid Punishments.appendPunishment({\n\t\t\tuserids: [...userids],\n\t\t\tips: [...ips],\n\t\t\tpunishType: type,\n\t\t\texpireTime,\n\t\t\treason,\n\t\t\trest: rest || [],\n\t\t}, roomid + ':' + id, ROOM_PUNISHMENT_FILE);\n\n\t\tif (typeof room !== 'string') {\n\t\t\troom = room as Room;\n\t\t\tif (!(room.settings.isPrivate === true || room.settings.isPersonal)) {\n\t\t\t\tvoid Punishments.monitorRoomPunishments(userid);\n\t\t\t}\n\t\t}\n\t\treturn affected;\n\t}\n\n\t/**\n\t * @param ignoreWrite skip persistent storage\n\t */\n\troomUnpunish(room: Room | RoomID, id: string, punishType: string, ignoreWrite = false) {\n\t\tconst roomid = typeof room !== 'string' ? (room as Room).roomid : room;\n\t\tid = toID(id);\n\t\tconst punishment = Punishments.roomUserids.nestedGetByType(roomid, id, punishType);\n\t\tif (punishment) {\n\t\t\tid = punishment.id;\n\t\t}\n\t\t// in theory we can stop here if punishment doesn't exist, but\n\t\t// in case of inconsistent state, we'll try anyway\n\n\t\tlet success;\n\t\tconst ipSubMap = Punishments.roomIps.get(roomid);\n\t\tif (ipSubMap) {\n\t\t\tfor (const [key, punishmentList] of ipSubMap) {\n\t\t\t\tfor (const [i, cur] of punishmentList.entries()) {\n\t\t\t\t\tif (cur.id === id && cur.type === punishType) {\n\t\t\t\t\t\tpunishmentList.splice(i, 1);\n\t\t\t\t\t\tsuccess = id;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (!punishmentList.length) {\n\t\t\t\t\tipSubMap.delete(key);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tconst useridSubMap = Punishments.roomUserids.get(roomid);\n\t\tif (useridSubMap) {\n\t\t\tfor (const [key, punishmentList] of useridSubMap) {\n\t\t\t\tfor (const [i, cur] of punishmentList.entries()) {\n\t\t\t\t\tif (cur.id === id && cur.type === punishType) {\n\t\t\t\t\t\tpunishmentList.splice(i, 1);\n\t\t\t\t\t\tsuccess = id;\n\t\t\t\t\t}\n\t\t\t\t\tif (!punishmentList.length) {\n\t\t\t\t\t\tuseridSubMap.delete(key);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (success && !ignoreWrite) {\n\t\t\tPunishments.saveRoomPunishments();\n\t\t}\n\t\treturn success;\n\t}\n\n\taddRoomPunishmentType(\n\t\topts: PunishInfo & { type: string } | string,\n\t\t// backwards compat - todo make only PunishInfo & {type: string}\n\t\tdesc?: string,\n\t\tcallback?: PunishInfo['onActivate']\n\t) {\n\t\tif (typeof opts === 'string') {\n\t\t\tif (!desc) throw new Error('Desc argument must be provided if type is string');\n\t\t\topts = { onActivate: callback, desc, type: opts };\n\t\t}\n\t\tthis.roomPunishmentTypes.set(opts.type, opts);\n\t\tif (!this.sortedRoomTypes.includes(opts.type)) this.sortedRoomTypes.unshift(opts.type);\n\t}\n\n\taddPunishmentType(\n\t\topts: PunishInfo & { type: string } | string,\n\t\t// backwards compat - todo make only PunishInfo & {type: string}\n\t\tdesc?: string,\n\t\tcallback?: PunishInfo['onActivate']\n\t) {\n\t\tif (typeof opts === 'string') {\n\t\t\tif (!desc) throw new Error('Desc argument must be provided if type is string');\n\t\t\topts = { onActivate: callback, desc, type: opts };\n\t\t}\n\t\tthis.punishmentTypes.set(opts.type, opts);\n\t\tif (!this.sortedTypes.includes(opts.type)) this.sortedTypes.unshift(opts.type);\n\t}\n\n\t/*********************************************************\n\t * Specific punishments\n\t *********************************************************/\n\n\tasync ban(\n\t\tuser: User | ID, expireTime: number | null, id: ID | PunishType | null, ignoreAlts: boolean, ...reason: string[]\n\t) {\n\t\tif (!expireTime) expireTime = Date.now() + GLOBALBAN_DURATION;\n\t\tconst punishment = { type: 'BAN', id, expireTime, reason: reason.join(' ') } as Punishment;\n\n\t\tconst affected = await Punishments.punish(user, punishment, ignoreAlts);\n\t\tfor (const curUser of affected) {\n\t\t\tcurUser.locked = punishment.id;\n\t\t\tcurUser.disconnectAll();\n\t\t}\n\n\t\treturn affected;\n\t}\n\tunban(name: string) {\n\t\treturn Punishments.unpunish(name, 'BAN');\n\t}\n\tasync lock(\n\t\tuser: User | ID,\n\t\texpireTime: number | null,\n\t\tid: ID | PunishType | null,\n\t\tignoreAlts: boolean,\n\t\treason: string,\n\t\tbypassPunishmentfilter = false,\n\t\trest?: any[]\n\t) {\n\t\tif (!expireTime) expireTime = Date.now() + LOCK_DURATION;\n\t\tconst punishment = { type: 'LOCK', id, expireTime, reason, rest } as Punishment;\n\n\t\tconst userObject = Users.get(user);\n\t\t// This makes it easier for unit tests to tell if a user was locked\n\t\tif (userObject) userObject.locked = punishment.id;\n\n\t\tconst affected = await Punishments.punish(user, punishment, ignoreAlts, bypassPunishmentfilter);\n\n\t\tfor (const curUser of affected) {\n\t\t\tPunishments.checkPunishmentTime(curUser, punishment);\n\t\t\tcurUser.locked = punishment.id;\n\t\t\tcurUser.updateIdentity();\n\t\t}\n\n\t\treturn affected;\n\t}\n\tasync autolock(\n\t\tuser: User | ID,\n\t\troom: Room | RoomID,\n\t\tsource: string,\n\t\treason: string,\n\t\tmessage: string | null,\n\t\tweek = false,\n\t\tnamelock?: string\n\t) {\n\t\tif (!message) message = reason;\n\n\t\tlet punishment = `LOCK`;\n\t\tlet expires = null;\n\t\tif (week) {\n\t\t\texpires = Date.now() + 7 * 24 * 60 * 60 * 1000;\n\t\t\tpunishment = `WEEKLOCK`;\n\t\t}\n\n\t\tconst userid = toID(user);\n\t\tif (Users.get(user)?.locked) return false;\n\t\tconst name = typeof user === 'string' ? user : user.name;\n\t\tif (namelock) {\n\t\t\tpunishment = `NAME${punishment}`;\n\t\t\tawait Punishments.namelock(user, expires, toID(namelock), false, `Autonamelock: ${name}: ${reason}`);\n\t\t} else {\n\t\t\tawait Punishments.lock(user, expires, userid, false, `Autolock: ${name}: ${reason}`);\n\t\t}\n\t\tMonitor.log(`[${source}] ${punishment}ED: ${message}`);\n\n\t\tconst logEntry = {\n\t\t\taction: `AUTO${punishment}`,\n\t\t\tvisualRoomID: typeof room !== 'string' ? (room as Room).roomid : room,\n\t\t\tip: typeof user !== 'string' ? user.latestIp : null,\n\t\t\tuserid,\n\t\t\tnote: reason,\n\t\t\tisGlobal: true,\n\t\t};\n\t\tif (typeof user !== 'string') logEntry.ip = user.latestIp;\n\n\t\tconst roomObject = Rooms.get(room);\n\t\tconst userObject = Users.get(user);\n\n\t\tif (roomObject) {\n\t\t\troomObject.modlog(logEntry);\n\t\t} else {\n\t\t\tRooms.global.modlog(logEntry);\n\t\t}\n\n\t\tif (roomObject?.battle && userObject?.connections[0]) {\n\t\t\tChat.parse('/savereplay forpunishment', roomObject, userObject, userObject.connections[0]);\n\t\t}\n\n\t\tconst roomauth = Rooms.global.destroyPersonalRooms(userid);\n\t\tif (roomauth.length) {\n\t\t\tMonitor.log(`[CrisisMonitor] Autolocked user ${name} has public roomauth (${roomauth.join(', ')}), and should probably be demoted.`);\n\t\t}\n\t}\n\tunlock(name: string) {\n\t\tconst user = Users.get(name);\n\t\tlet id: string = toID(name);\n\t\tconst success: string[] = [];\n\t\tif (user?.locked && !user.namelocked) {\n\t\t\tid = user.locked;\n\t\t\tuser.locked = null;\n\t\t\tuser.namelocked = null;\n\t\t\tuser.destroyPunishmentTimer();\n\t\t\tuser.updateIdentity();\n\t\t\tsuccess.push(user.getLastName());\n\t\t}\n\t\tif (!id.startsWith('#')) {\n\t\t\tfor (const curUser of Users.users.values()) {\n\t\t\t\tif (curUser.locked === id) {\n\t\t\t\t\tcurUser.locked = null;\n\t\t\t\t\tcurUser.namelocked = null;\n\t\t\t\t\tcurUser.destroyPunishmentTimer();\n\t\t\t\t\tcurUser.updateIdentity();\n\t\t\t\t\tsuccess.push(curUser.getLastName());\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (['LOCK', 'YEARLOCK'].some(type => Punishments.unpunish(name, type))) {\n\t\t\tif (!success.length) success.push(name);\n\t\t}\n\t\tif (!success.length) return undefined;\n\t\tif (!success.some(v => toID(v) === id)) {\n\t\t\tsuccess.push(id);\n\t\t}\n\t\treturn success;\n\t}\n\t/**\n\t * Sets the punishment timer for a user,\n\t * to either MAX_PUNISHMENT_TIMER_LENGTH or the amount of time left on the punishment.\n\t * It also expires a punishment if the time is up.\n\t */\n\tcheckPunishmentTime(user: User, punishment: Punishment) {\n\t\tif (user.punishmentTimer) {\n\t\t\tclearTimeout(user.punishmentTimer);\n\t\t\tuser.punishmentTimer = null;\n\t\t}\n\n\t\t// Don't unlock users who have non-time-based locks such as #hostfilter\n\t\t// Optional chaining doesn't seem to work properly in callbacks of setTimeout\n\t\tif (user.locked && user.locked.startsWith('#')) return;\n\n\t\tconst { id, expireTime } = punishment;\n\n\t\tconst timeLeft = expireTime - Date.now();\n\t\tif (timeLeft <= 1) {\n\t\t\tif (user.locked === id) Punishments.unlock(user.id);\n\t\t\treturn;\n\t\t}\n\t\tconst waitTime = Math.min(timeLeft, MAX_PUNISHMENT_TIMER_LENGTH);\n\t\tuser.punishmentTimer = setTimeout(() => {\n\t\t\t// make sure we're not referencing a pre-hotpatch Punishments instance\n\t\t\tglobal.Punishments.checkPunishmentTime(user, punishment);\n\t\t}, waitTime);\n\t}\n\tasync namelock(\n\t\tuser: User | ID, expireTime: number | null, id: ID | PunishType | null, ignoreAlts: boolean, ...reason: string[]\n\t) {\n\t\tif (!expireTime) expireTime = Date.now() + LOCK_DURATION;\n\t\tconst punishment = { type: 'NAMELOCK', id, expireTime, reason: reason.join(' ') } as Punishment;\n\n\t\tconst affected = await Punishments.punish(user, punishment, ignoreAlts);\n\t\tfor (const curUser of affected) {\n\t\t\tPunishments.checkPunishmentTime(curUser, punishment);\n\t\t\tcurUser.locked = punishment.id;\n\t\t\tcurUser.namelocked = punishment.id;\n\t\t\tcurUser.resetName(true);\n\t\t\tcurUser.updateIdentity();\n\t\t}\n\n\t\treturn affected;\n\t}\n\tunnamelock(name: string) {\n\t\tconst user = Users.get(name);\n\t\tlet id: string = toID(name);\n\t\tconst success: string[] = [];\n\t\tif (user?.namelocked) name = user.namelocked;\n\n\t\tconst unpunished = Punishments.unpunish(name, 'NAMELOCK');\n\t\tif (user?.locked) {\n\t\t\tid = user.locked;\n\t\t\tuser.locked = null;\n\t\t\tuser.namelocked = null;\n\t\t\tuser.destroyPunishmentTimer();\n\t\t\tuser.resetName();\n\t\t\tsuccess.push(user.getLastName());\n\t\t}\n\t\tif (!id.startsWith('#')) {\n\t\t\tfor (const curUser of Users.users.values()) {\n\t\t\t\tif (curUser.locked === id) {\n\t\t\t\t\tcurUser.locked = null;\n\t\t\t\t\tcurUser.namelocked = null;\n\t\t\t\t\tcurUser.destroyPunishmentTimer();\n\t\t\t\t\tcurUser.resetName();\n\t\t\t\t\tsuccess.push(curUser.getLastName());\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (unpunished && !success.length) success.push(name);\n\t\tif (!success.length) return false;\n\t\tif (!success.some(v => toID(v) === id)) {\n\t\t\tsuccess.push(id);\n\t\t}\n\t\treturn success;\n\t}\n\tbattleban(user: User, expireTime: number | null, id: ID | null, ...reason: string[]) {\n\t\tif (!expireTime) expireTime = Date.now() + BATTLEBAN_DURATION;\n\t\tconst punishment = { type: 'BATTLEBAN', id, expireTime, reason: reason.join(' ') } as Punishment;\n\n\t\t// Handle tournaments the user was in before being battle banned\n\t\tfor (const games of user.games.keys()) {\n\t\t\tconst game = Rooms.get(games)!.getGame(Tournaments.Tournament);\n\t\t\tif (!game) continue; // this should never happen\n\t\t\tif (game.isTournamentStarted) {\n\t\t\t\tgame.disqualifyUser(user.id, null, null);\n\t\t\t} else if (!game.isTournamentStarted) {\n\t\t\t\tgame.removeUser(user.id);\n\t\t\t}\n\t\t}\n\n\t\treturn Punishments.punish(user, punishment, false);\n\t}\n\tunbattleban(userid: string) {\n\t\tconst user = Users.get(userid);\n\t\tif (user) {\n\t\t\tconst punishment = Punishments.isBattleBanned(user);\n\t\t\tif (punishment) userid = punishment.id;\n\t\t}\n\t\treturn Punishments.unpunish(userid, 'BATTLEBAN');\n\t}\n\tisBattleBanned(user: User) {\n\t\tif (!user) throw new Error(`Trying to check if a non-existent user is battlebanned.`);\n\n\t\tlet punishment = Punishments.userids.getByType(user.id, 'BATTLEBAN');\n\t\tif (punishment) return punishment;\n\n\t\tif (user.autoconfirmed) {\n\t\t\tpunishment = Punishments.userids.getByType(user.autoconfirmed, 'BATTLEBAN');\n\t\t\tif (punishment) return punishment;\n\t\t}\n\n\t\tfor (const ip of user.ips) {\n\t\t\tpunishment = Punishments.ips.getByType(ip, 'BATTLEBAN');\n\t\t\tif (punishment) {\n\t\t\t\tif (Punishments.isSharedIp(ip) && user.autoconfirmed) return;\n\t\t\t\treturn punishment;\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Bans a user from using groupchats. Returns an array of roomids of the groupchat they created, if any.\n\t * We don't necessarily want to delete these, since we still need to warn the participants,\n\t * and make a modnote of the participant names, which doesn't seem appropriate for a Punishments method.\n\t */\n\tasync groupchatBan(user: User | ID, expireTime: number | null, id: ID | null, reason: string | null) {\n\t\tif (!expireTime) expireTime = Date.now() + GROUPCHATBAN_DURATION;\n\t\tconst punishment = { type: 'GROUPCHATBAN', id, expireTime, reason } as Punishment;\n\n\t\tconst groupchatsCreated = [];\n\t\tconst targetUser = Users.get(user);\n\t\tif (targetUser) {\n\t\t\tfor (const roomid of targetUser.inRooms || []) {\n\t\t\t\tconst targetRoom = Rooms.get(roomid);\n\t\t\t\tif (!targetRoom?.roomid.startsWith('groupchat-')) continue;\n\t\t\t\ttargetRoom.game?.removeBannedUser?.(targetUser);\n\t\t\t\ttargetUser.leaveRoom(targetRoom.roomid);\n\n\t\t\t\t// Handle groupchats that the user created\n\t\t\t\tif (targetRoom.auth.get(targetUser) === Users.HOST_SYMBOL) {\n\t\t\t\t\tgroupchatsCreated.push(targetRoom.roomid);\n\t\t\t\t\tPunishments.bannedGroupchatParticipants[targetRoom.roomid] = new Set(\n\t\t\t\t\t\t// Room#users is a UserTable where the keys are IDs,\n\t\t\t\t\t\t// but typed as strings so that they can be used as object keys.\n\t\t\t\t\t\tObject.keys(targetRoom.users).filter(u => !targetRoom.users[u].can('lock')) as ID[]\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tawait Punishments.punish(user, punishment, false);\n\t\treturn groupchatsCreated;\n\t}\n\n\tgroupchatUnban(user: User | ID) {\n\t\tlet userid = (typeof user === 'object' ? user.id : user);\n\n\t\tconst punishment = Punishments.isGroupchatBanned(user);\n\t\tif (punishment) userid = punishment.id as ID;\n\n\t\treturn Punishments.unpunish(userid, 'GROUPCHATBAN');\n\t}\n\n\tisGroupchatBanned(user: User | ID) {\n\t\tconst userid = toID(user);\n\t\tconst targetUser = Users.get(user);\n\n\t\tlet punishment = Punishments.userids.getByType(userid, 'GROUPCHATBAN');\n\t\tif (punishment) return punishment;\n\n\t\tif (targetUser?.autoconfirmed) {\n\t\t\tpunishment = Punishments.userids.getByType(targetUser.autoconfirmed, 'GROUPCHATBAN');\n\t\t\tif (punishment) return punishment;\n\t\t}\n\n\t\tif (targetUser && !targetUser.trusted) {\n\t\t\tfor (const ip of targetUser.ips) {\n\t\t\t\tpunishment = Punishments.ips.getByType(ip, 'GROUPCHATBAN');\n\t\t\t\tif (punishment) {\n\t\t\t\t\tif (Punishments.isSharedIp(ip) && targetUser.autoconfirmed) return;\n\t\t\t\t\treturn punishment;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tisTicketBanned(user: User | ID) {\n\t\tconst ips = [];\n\t\tif (typeof user === 'object') {\n\t\t\tips.push(...user.ips);\n\t\t\tips.unshift(user.latestIp);\n\t\t\tuser = user.id;\n\t\t}\n\t\tconst punishment = Punishments.userids.getByType(user, 'TICKETBAN');\n\t\tif (punishment) return punishment;\n\t\t// skip if the user is autoconfirmed and on a shared ip\n\t\t// [0] is forced to be the latestIp\n\t\tif (ips.some(ip => Punishments.isSharedIp(ip))) return false;\n\n\t\tfor (const ip of ips) {\n\t\t\tconst curPunishment = Punishments.ips.getByType(ip, 'TICKETBAN');\n\t\t\tif (curPunishment) return curPunishment;\n\t\t}\n\t\treturn false;\n\t}\n\n\t/**\n\t * Monitors a groupchat, watching in case too many users who had participated in\n\t * a groupchat that was deleted because its owner was groupchatbanned join.\n\t */\n\tmonitorGroupchatJoin(room: BasicRoom, newUser: User | ID) {\n\t\tif (Punishments.lastGroupchatMonitorTime[room.roomid] > (Date.now() - GROUPCHAT_MONITOR_INTERVAL)) return;\n\t\tconst newUserID = toID(newUser);\n\t\tfor (const [roomid, participants] of Object.entries(Punishments.bannedGroupchatParticipants)) {\n\t\t\tif (!participants.has(newUserID)) continue;\n\t\t\tlet overlap = 0;\n\t\t\tfor (const participant of participants) {\n\t\t\t\tif (participant in room.users || room.auth.has(participant)) overlap++;\n\t\t\t}\n\t\t\tif (overlap > GROUPCHAT_PARTICIPANT_OVERLAP_THRESHOLD) {\n\t\t\t\tlet html = `|html|[GroupchatMonitor] The groupchat \u00AB<a href=\"/${room.roomid}\">${room.roomid}</a>\u00BB `;\n\t\t\t\tif (Config.modloglink) html += `(<a href=\"${Config.modloglink(new Date(), room.roomid)}\">logs</a>) `;\n\n\t\t\t\thtml += `includes ${overlap} participants from forcibly deleted groupchat \u00AB<a href=\"/${roomid}\">${roomid}</a>\u00BB`;\n\t\t\t\tif (Config.modloglink) html += ` (<a href=\"${Config.modloglink(new Date(), roomid)}\">logs</a>)`;\n\t\t\t\thtml += `.`;\n\n\t\t\t\tRooms.global.notifyRooms(['staff'], html);\n\t\t\t\tPunishments.lastGroupchatMonitorTime[room.roomid] = Date.now();\n\t\t\t}\n\t\t}\n\t}\n\n\tpunishRange(\n\t\trange: string,\n\t\treason: string,\n\t\texpireTime?: number | null,\n\t\tpunishType?: string\n\t) {\n\t\tif (!expireTime) expireTime = Date.now() + RANGELOCK_DURATION;\n\t\tif (!punishType) punishType = 'LOCK';\n\t\tconst punishment = { type: punishType, id: '#rangelock', expireTime, reason } as Punishment;\n\t\tPunishments.ips.add(range, punishment);\n\n\t\tconst ips = [];\n\t\tconst parsedRange = IPTools.stringToRange(range);\n\t\tif (!parsedRange) throw new Error(`Invalid IP range: ${range}`);\n\t\tconst { minIP, maxIP } = parsedRange;\n\n\t\tfor (let ipNumber = minIP; ipNumber <= maxIP; ipNumber++) {\n\t\t\tips.push(IPTools.numberToIP(ipNumber)!); // range is already validated by stringToRange\n\t\t}\n\n\t\tvoid Punishments.appendPunishment({\n\t\t\tuserids: [],\n\t\t\tips,\n\t\t\tpunishType,\n\t\t\texpireTime,\n\t\t\treason,\n\t\t\trest: [],\n\t\t}, '#rangelock', PUNISHMENT_FILE, true);\n\t}\n\tbanRange(range: string, reason: string, expireTime?: number | null) {\n\t\tif (!expireTime) expireTime = Date.now() + RANGELOCK_DURATION;\n\t\tconst punishment = { type: 'BAN', id: '#rangelock', expireTime, reason } as Punishment;\n\t\tPunishments.ips.add(range, punishment);\n\t}\n\n\troomBan(room: Room, user: User, expireTime: number | null, id: string | null, ...reason: string[]) {\n\t\tif (!expireTime) expireTime = Date.now() + ROOMBAN_DURATION;\n\t\tconst punishment = { type: 'ROOMBAN', id, expireTime, reason: reason.join(' ') } as Punishment;\n\n\t\tconst affected = Punishments.roomPunish(room, user, punishment);\n\t\tfor (const curUser of affected) {\n\t\t\troom.game?.removeBannedUser?.(curUser);\n\t\t\tcurUser.leaveRoom(room.roomid);\n\t\t}\n\n\t\tif (room.subRooms) {\n\t\t\tfor (const subRoom of room.subRooms.values()) {\n\t\t\t\tfor (const curUser of affected) {\n\t\t\t\t\tsubRoom.game?.removeBannedUser?.(curUser);\n\t\t\t\t\tcurUser.leaveRoom(subRoom.roomid);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn affected;\n\t}\n\n\troomBlacklist(room: Room, user: User | ID, expireTime: number | null, id: ID | null, ...reason: string[]) {\n\t\tif (!expireTime) expireTime = Date.now() + BLACKLIST_DURATION;\n\t\tconst punishment = { type: 'BLACKLIST', id, expireTime, reason: reason.join(' ') } as Punishment;\n\n\t\tconst affected = Punishments.roomPunish(room, user, punishment);\n\n\t\tfor (const curUser of affected) {\n\t\t\t// ensure there aren't roombans so nothing gets mixed up\n\t\t\tPunishments.roomUnban(room, (curUser as any).id || curUser);\n\t\t\troom.game?.removeBannedUser?.(curUser);\n\t\t\tcurUser.leaveRoom(room.roomid);\n\t\t}\n\n\t\tif (room.subRooms) {\n\t\t\tfor (const subRoom of room.subRooms.values()) {\n\t\t\t\tfor (const curUser of affected) {\n\t\t\t\t\tsubRoom.game?.removeBannedUser?.(curUser);\n\t\t\t\t\tcurUser.leaveRoom(subRoom.roomid);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn affected;\n\t}\n\n\troomUnban(room: Room, userid: string) {\n\t\tconst user = Users.get(userid);\n\t\tif (user) {\n\t\t\tconst punishment = Punishments.isRoomBanned(user, room.roomid);\n\t\t\tif (punishment) userid = punishment.id;\n\t\t}\n\t\treturn Punishments.roomUnpunish(room, userid, 'ROOMBAN');\n\t}\n\n\t/**\n\t * @param ignoreWrite Flag to skip persistent storage.\n\t */\n\troomUnblacklist(room: Room, userid: string, ignoreWrite?: boolean) {\n\t\tconst user = Users.get(userid);\n\t\tif (user) {\n\t\t\tconst punishment = Punishments.isRoomBanned(user, room.roomid);\n\t\t\tif (punishment) userid = punishment.id;\n\t\t}\n\t\treturn Punishments.roomUnpunish(room, userid, 'BLACKLIST', ignoreWrite);\n\t}\n\n\troomUnblacklistAll(room: Room) {\n\t\tconst roombans = Punishments.roomUserids.get(room.roomid);\n\t\tif (!roombans) return false;\n\n\t\tconst unblacklisted: string[] = [];\n\n\t\troombans.each(({ type }, userid) => {\n\t\t\tif (type === 'BLACKLIST') {\n\t\t\t\tPunishments.roomUnblacklist(room, userid, true);\n\t\t\t\tunblacklisted.push(userid);\n\t\t\t}\n\t\t});\n\t\tif (unblacklisted.length === 0) return false;\n\t\tPunishments.saveRoomPunishments();\n\t\treturn unblacklisted;\n\t}\n\n\taddSharedIp(ip: string, note: string) {\n\t\tconst pattern = IPTools.stringToRange(ip);\n\t\tconst isRange = pattern && pattern.minIP !== pattern.maxIP;\n\t\tif (isRange) {\n\t\t\tPunishments.sharedRanges.set(pattern, note);\n\t\t} else {\n\t\t\tPunishments.sharedIps.set(ip, note);\n\t\t}\n\t\tvoid Punishments.appendSharedIp(ip, note);\n\n\t\tfor (const user of Users.users.values()) {\n\t\t\tconst sharedIp = user.ips.some(\n\t\t\t\tcurIP => (isRange ? IPTools.checkPattern([pattern], IPTools.ipToNumber(curIP)) : curIP === ip)\n\t\t\t);\n\t\t\tif (user.locked && user.locked !== user.id && sharedIp) {\n\t\t\t\tif (!user.autoconfirmed) {\n\t\t\t\t\tuser.semilocked = `#sharedip ${user.locked}` as PunishType;\n\t\t\t\t}\n\t\t\t\tuser.locked = null;\n\t\t\t\tuser.namelocked = null;\n\t\t\t\tuser.destroyPunishmentTimer();\n\n\t\t\t\tuser.updateIdentity();\n\t\t\t}\n\t\t}\n\t}\n\n\tisSharedIp(ip: string) {\n\t\tif (this.sharedIps.has(ip)) return true;\n\t\tconst num = IPTools.ipToNumber(ip);\n\t\tfor (const range of this.sharedRanges.keys()) {\n\t\t\tif (IPTools.checkPattern([range], num)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t}\n\n\tremoveSharedIp(ip: string) {\n\t\tconst pattern = IPTools.stringToRange(ip);\n\t\tif (pattern && pattern.minIP !== pattern.maxIP) {\n\t\t\t// i don't _like_ this, but map.delete on an object doesn't work.\n\t\t\tconst isMatch = (range: AddressRange) => (\n\t\t\t\trange.minIP === pattern.minIP && range.maxIP === pattern.maxIP\n\t\t\t);\n\t\t\tPunishments.sharedRanges = new Map([...Punishments.sharedRanges].filter(([range]) => !isMatch(range)));\n\t\t} else {\n\t\t\tPunishments.sharedIps.delete(ip);\n\t\t}\n\t\tvoid Punishments.saveSharedIps();\n\t}\n\n\taddBlacklistedSharedIp(ip: string, reason: string) {\n\t\tvoid Punishments.appendSharedIpBlacklist(ip, reason);\n\t\tPunishments.sharedIpBlacklist.set(ip, reason);\n\t}\n\n\tremoveBlacklistedSharedIp(ip: string) {\n\t\tPunishments.sharedIpBlacklist.delete(ip);\n\t\tvoid Punishments.saveSharedIpBlacklist();\n\t}\n\n\twhitelistName(name: string, whitelister: string) {\n\t\tif (this.namefilterwhitelist.has(name)) return false;\n\t\tname = toID(name);\n\t\twhitelister = toID(whitelister);\n\t\tthis.namefilterwhitelist.set(name, whitelister);\n\t\tvoid this.appendWhitelistedName(name, whitelister);\n\t\treturn true;\n\t}\n\n\tunwhitelistName(name: string) {\n\t\tname = toID(name);\n\t\tif (!this.namefilterwhitelist.has(name)) return false;\n\t\tthis.namefilterwhitelist.delete(name);\n\t\tvoid this.saveNameWhitelist();\n\t\treturn true;\n\t}\n\n\t/*********************************************************\n\t * Checking\n\t *********************************************************/\n\n\t/**\n\t * Returns an array of [key, roomid, punishment] pairs.\n\t *\n\t * @param searchId userid or IP\n\t */\n\tsearch(searchId: string) {\n\t\t/** [key, roomid, punishment][] */\n\t\tconst results: [string, RoomID, Punishment][] = [];\n\t\tPunishments.ips.each((punishment, ip) => {\n\t\t\tconst { id } = punishment;\n\n\t\t\tif (searchId === id || searchId === ip) {\n\t\t\t\tresults.push([ip, '', punishment]);\n\t\t\t}\n\t\t});\n\t\tPunishments.userids.each((punishment, userid) => {\n\t\t\tconst { id } = punishment;\n\n\t\t\tif (searchId === id || searchId === userid) {\n\t\t\t\tresults.push([userid, '', punishment]);\n\t\t\t}\n\t\t});\n\t\tPunishments.roomIps.nestedEach((punishment, roomid, ip) => {\n\t\t\tconst { id: punishUserid } = punishment;\n\n\t\t\tif (searchId === punishUserid || searchId === ip) {\n\t\t\t\tresults.push([ip, roomid, punishment]);\n\t\t\t}\n\t\t});\n\t\tPunishments.roomUserids.nestedEach((punishment, roomid, userid) => {\n\t\t\tconst { id: punishUserid } = punishment;\n\n\t\t\tif (searchId === punishUserid || searchId === userid) {\n\t\t\t\tresults.push([userid, roomid, punishment]);\n\t\t\t}\n\t\t});\n\n\t\treturn results;\n\t}\n\n\tgetPunishType(name: string) {\n\t\tlet punishment = Punishments.userids.get(toID(name));\n\t\tif (punishment) return punishment[0].type;\n\t\tconst user = Users.get(name);\n\t\tif (!user) return;\n\t\tpunishment = Punishments.ipSearch(user.latestIp);\n\t\tif (punishment) return punishment[0].type;\n\t\treturn '';\n\t}\n\n\thasPunishType(name: string, types: string | string[], ip?: string) {\n\t\tif (typeof types === 'string') types = [types];\n\t\tconst byName = Punishments.userids.get(name)?.some(p => types.includes(p.type));\n\t\tif (!ip) return byName;\n\t\treturn byName || Punishments.ipSearch(ip)?.some(p => types.includes(p.type));\n\t}\n\n\thasRoomPunishType(room: Room | RoomID, name: string, types: string | string[]) {\n\t\tif (typeof types === 'string') types = [types];\n\t\tif (typeof (room as Room).roomid === 'string') room = (room as Room).roomid;\n\t\treturn Punishments.roomUserids.nestedGet(room as RoomID, name)?.some(p => types.includes(p.type));\n\t}\n\n\tsortedTypes = ['TICKETBAN', 'LOCK', 'NAMELOCK', 'BAN'];\n\tsortedRoomTypes: string[] = [...(global.Punishments?.sortedRoomTypes || []), 'ROOMBAN', 'BLACKLIST'];\n\tbyWeight(punishments?: Punishment[], room = false) {\n\t\tif (!punishments) return [];\n\t\treturn Utils.sortBy(\n\t\t\tpunishments,\n\t\t\tp => -(room ? this.sortedRoomTypes : this.sortedTypes).indexOf(p.type)\n\t\t);\n\t}\n\n\tinteractions: { [k: string]: { overrides: string[] } } = {\n\t\tNAMELOCK: { overrides: ['LOCK'] },\n\t};\n\n\troomInteractions: { [k: string]: { overrides: string[] } } = {\n\t\tBLACKLIST: { overrides: ['ROOMBAN'] },\n\t};\n\n\t/**\n\t * Searches for IP in Punishments.ips\n\t *\n\t * For instance, if IP is '1.2.3.4', will return the value corresponding\n\t * to any of the keys in table match '1.2.3.4', '1.2.3.*', '1.2.*', or '1.*'\n\t *\n\t */\n\tipSearch(ip: string, type?: undefined): Punishment[] | undefined;\n\tipSearch(ip: string, type: string): Punishment | undefined;\n\tipSearch(ip: string, type?: string): Punishment | Punishment[] | undefined {\n\t\tconst allPunishments: Punishment[] = [];\n\n\t\tlet punishment = Punishments.ips.get(ip);\n\t\tif (punishment) {\n\t\t\tif (type) return punishment.find(p => p.type === type);\n\t\t\tallPunishments.push(...punishment);\n\t\t}\n\t\tlet dotIndex = ip.lastIndexOf('.');\n\t\tfor (let i = 0; i < 4 && dotIndex > 0; i++) {\n\t\t\tip = ip.substr(0, dotIndex);\n\t\t\tpunishment = Punishments.ips.get(ip + '.*');\n\t\t\tif (punishment) {\n\t\t\t\tif (type) return punishment.find(p => p.type === type);\n\t\t\t\tallPunishments.push(...punishment);\n\t\t\t}\n\t\t\tdotIndex = ip.lastIndexOf('.');\n\t\t}\n\t\treturn allPunishments.length ? allPunishments : undefined;\n\t}\n\n\t/** Defined in Punishments.loadBanlist */\n\tcheckRangeBanned(ip: string) {\n\t\treturn false;\n\t}\n\n\tcheckName(user: User, userid: string, registered: boolean) {\n\t\tif (userid.startsWith('guest')) return;\n\t\tfor (const roomid of user.inRooms) {\n\t\t\tPunishments.checkNewNameInRoom(user, userid, roomid);\n\t\t}\n\t\tlet punishments: Punishment[] = [];\n\n\t\tconst idPunishments = Punishments.userids.get(userid);\n\t\tif (idPunishments) {\n\t\t\tpunishments = idPunishments;\n\t\t}\n\n\t\tconst battleban = Punishments.isBattleBanned(user);\n\t\tif (battleban) punishments.push(battleban);\n\t\tif (user.namelocked) {\n\t\t\tlet punishment = Punishments.userids.get(user.namelocked)?.[0];\n\t\t\tif (!punishment) punishment = { type: 'NAMELOCK', id: user.namelocked, expireTime: 0, reason: '' };\n\t\t\tpunishments.push(punishment);\n\t\t}\n\t\tif (user.locked) {\n\t\t\tlet punishment = Punishments.userids.get(user.locked)?.[0];\n\t\t\tif (!punishment) punishment = { type: 'LOCK', id: user.locked, expireTime: 0, reason: '' };\n\t\t\tpunishments.push(punishment);\n\t\t}\n\n\t\tconst ticket = Chat.pages?.help ?\n\t\t\t`<a href=\"view-help-request--appeal\"><button class=\"button\"><strong>Appeal your punishment</strong></button></a>` : '';\n\n\t\tif (!punishments.length) return;\n\t\tPunishments.byWeight(punishments);\n\n\t\tfor (const punishment of punishments) {\n\t\t\tconst id = punishment.type;\n\t\t\tconst punishmentInfo = this.punishmentTypes.get(id);\n\t\t\tconst punishUserid = punishment.id;\n\t\t\tconst reason = punishment.reason ? Utils.html`\\n\\nReason: ${punishment.reason}` : '';\n\t\t\tlet appeal = ``;\n\t\t\tif (user.permalocked && Config.appealurl) {\n\t\t\t\tappeal += `\\n\\nPermanent punishments can be appealed: <a href=\"${Config.appealurl}\">${Config.appealurl}</a>`;\n\t\t\t} else if (ticket) {\n\t\t\t\tappeal += `\\n\\nIf you feel you were unfairly punished or wish to otherwise appeal, you can ${ticket}.`;\n\t\t\t} else if (Config.appealurl) {\n\t\t\t\tappeal += `\\n\\nIf you wish to appeal your punishment, please use: <a href=\"${Config.appealurl}\">${Config.appealurl}</a>`;\n\t\t\t}\n\t\t\tconst bannedUnder = punishUserid !== userid ? ` because you have the same IP as banned user: ${punishUserid}` : '';\n\n\t\t\tif (id === 'BATTLEBAN') {\n\t\t\t\tif (punishUserid !== user.id && Punishments.isSharedIp(user.latestIp) && user.autoconfirmed) {\n\t\t\t\t\tPunishments.unpunish(userid, 'BATTLEBAN');\n\t\t\t\t} else {\n\t\t\t\t\tvoid Punishments.punish(user, punishment, false);\n\t\t\t\t\tuser.cancelReady();\n\t\t\t\t\tif (!Punishments.userids.getByType(userid, 'BATTLEBAN')) {\n\t\t\t\t\t\tconst appealLink = ticket || (Config.appealurl ? `appeal at: ${Config.appealurl}` : ``);\n\t\t\t\t\t\t// Prioritize popups for other global punishments\n\t\t\t\t\t\tuser.send(\n\t\t\t\t\t\t\t`|popup||html|You are banned from battling` +\n\t\t\t\t\t\t\t`${punishment.id !== userid ? ` because you have the same IP as banned user: ${punishUserid}` : ''}. ` +\n\t\t\t\t\t\t\t`Your battle ban will expire in a few days.` +\n\t\t\t\t\t\t\t`${punishment.reason ? Utils.html`\\n\\nReason: ${punishment.reason}` : ``}` +\n\t\t\t\t\t\t\t`${appealLink ? `\\n\\nOr you can ${appealLink}.` : ``}`\n\t\t\t\t\t\t);\n\t\t\t\t\t\tuser.notified.punishment = true;\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif ((id === 'LOCK' || id === 'NAMELOCK') && punishUserid !== userid && Punishments.isSharedIp(user.latestIp)) {\n\t\t\t\tif (!user.autoconfirmed) {\n\t\t\t\t\tuser.semilocked = `#sharedip ${user.locked}` as PunishType;\n\t\t\t\t}\n\t\t\t\tuser.locked = null;\n\t\t\t\tuser.namelocked = null;\n\t\t\t\tuser.destroyPunishmentTimer();\n\t\t\t\tuser.updateIdentity();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (id === 'BAN') {\n\t\t\t\tconst appealUrl = Config.banappealurl || Config.appealurl;\n\t\t\t\tuser.popup(\n\t\t\t\t\t`Your username (${user.name}) is banned${bannedUnder}. Your ban will expire in a few days.${reason}` +\n\t\t\t\t\t`${appealUrl ? `||||Or you can appeal at: ${appealUrl}` : ``}`\n\t\t\t\t);\n\t\t\t\tuser.notified.punishment = true;\n\t\t\t\tif (registered) void Punishments.punish(user, punishment, false);\n\t\t\t\tuser.disconnectAll();\n\t\t\t\treturn; // end the loop here\n\t\t\t}\n\t\t\tif (id === 'NAMELOCK' || user.namelocked) {\n\t\t\t\tuser.send(`|popup||html|You are namelocked and can't have a username${bannedUnder}. Your namelock will expire in a few days.${reason}${appeal}`);\n\t\t\t\tuser.locked = punishUserid;\n\t\t\t\tuser.namelocked = punishUserid;\n\t\t\t\tuser.resetName();\n\t\t\t\tuser.updateIdentity();\n\t\t\t} else if (id === 'LOCK') {\n\t\t\t\tif (punishUserid === '#hostfilter' || punishUserid === '#ipban') {\n\t\t\t\t\tuser.send(`|popup||html|Your IP (${user.latestIp}) is currently locked due to being a proxy. We automatically lock these connections since they are used to spam, hack, or otherwise attack our server. Disable any proxies you are using to connect to PS.\\n\\n<a href=\"view-help-request--appeal\"><button class=\"button\">Help me with a lock from a proxy</button></a>`);\n\t\t\t\t} else if (user.latestHostType === 'proxy' && user.locked !== user.id) {\n\t\t\t\t\tuser.send(`|popup||html|You are locked${bannedUnder} on the IP (${user.latestIp}), which is a proxy. We automatically lock these connections since they are used to spam, hack, or otherwise attack our server. Disable any proxies you are using to connect to PS.\\n\\n<a href=\"view-help-request--appeal\"><button class=\"button\">Help me with a lock from a proxy</button></a>`);\n\t\t\t\t} else if (!user.notified.lock) {\n\t\t\t\t\tuser.send(`|popup||html|You are locked${bannedUnder}. ${user.permalocked ? `This lock is permanent.` : `Your lock will expire in a few days.`}${reason}${appeal}`);\n\t\t\t\t}\n\t\t\t\tuser.notified.lock = true;\n\t\t\t\tuser.locked = punishUserid;\n\t\t\t\tuser.updateIdentity();\n\t\t\t} else if (punishmentInfo?.onActivate) {\n\t\t\t\tpunishmentInfo.onActivate.call(this, user, punishment, null, punishment.id === user.id);\n\t\t\t}\n\t\t\tPunishments.checkPunishmentTime(user, punishment);\n\t\t}\n\t}\n\n\tcheckIp(user: User, connection: Connection) {\n\t\tconst ip = connection.ip;\n\t\tlet punishments = Punishments.ipSearch(ip);\n\n\t\tif (!punishments && Punishments.checkRangeBanned(ip)) {\n\t\t\tpunishments = [{ type: 'LOCK', id: '#ipban', expireTime: Infinity, reason: '' }];\n\t\t}\n\n\t\tif (punishments) {\n\t\t\tconst isSharedIP = Punishments.isSharedIp(ip);\n\t\t\tlet sharedAndHasPunishment = false;\n\t\t\tfor (const punishment of punishments) {\n\t\t\t\tif (isSharedIP) {\n\t\t\t\t\tif (!user.locked && !user.autoconfirmed) {\n\t\t\t\t\t\tuser.semilocked = `#sharedip ${punishment.id}` as PunishType;\n\t\t\t\t\t}\n\t\t\t\t\tsharedAndHasPunishment = true;\n\t\t\t\t} else {\n\t\t\t\t\tif (['BAN', 'LOCK', 'NAMELOCK'].includes(punishment.type)) {\n\t\t\t\t\t\tuser.locked = punishment.id;\n\t\t\t\t\t\tif (punishment.type === 'NAMELOCK') {\n\t\t\t\t\t\t\tuser.namelocked = punishment.id;\n\t\t\t\t\t\t\tuser.resetName(true);\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tconst info = Punishments.punishmentTypes.get(punishment.type);\n\t\t\t\t\t\tinfo?.onActivate?.call(this, user, punishment, null, punishment.id === user.id);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (!sharedAndHasPunishment) Punishments.checkPunishmentTime(user, Punishments.byWeight(punishments)[0]);\n\t\t}\n\n\t\treturn IPTools.lookup(ip).then(({ dnsbl, host, hostType }) => {\n\t\t\tuser = connection.user || user;\n\n\t\t\tif (hostType === 'proxy' && !user.trusted && !user.locked) {\n\t\t\t\tuser.locked = '#hostfilter';\n\t\t\t} else if (dnsbl && !user.autoconfirmed) {\n\t\t\t\tuser.semilocked = '#dnsbl';\n\t\t\t}\n\t\t\tif (host) {\n\t\t\t\tuser.latestHost = host;\n\t\t\t\tuser.latestHostType = hostType;\n\t\t\t}\n\t\t\tChat.hostfilter(host || '', user, connection, hostType);\n\t\t});\n\t}\n\n\t/**\n\t * IP bans need to be checked separately since we don't even want to\n\t * make a User object if an IP is banned.\n\t */\n\tcheckIpBanned(connection: Connection) {\n\t\tconst ip = connection.ip;\n\t\tif (Punishments.cfloods.has(ip) || (Monitor.countConnection(ip) && Punishments.cfloods.add(ip))) {\n\t\t\tconnection.send(`|popup||modal|PS is under heavy load and cannot accommodate your connection right now.`);\n\t\t\treturn '#cflood';\n\t\t}\n\n\t\tif (Punishments.isSharedIp(ip)) return false;\n\n\t\tlet banned: false | string = false;\n\t\tconst punishment = Punishments.ipSearch(ip, 'BAN');\n\t\tif (punishment) {\n\t\t\tbanned = punishment.id;\n\t\t}\n\t\tif (!banned) return false;\n\n\t\tconst appealUrl = Config.banappealurl || Config.appealurl;\n\t\tconnection.send(\n\t\t\t`|popup||modal|You are banned because you have the same IP (${ip}) as banned user '${banned}'. ` +\n\t\t\t`Your ban will expire in a few days.` +\n\t\t\t`${appealUrl ? `||||Or you can appeal at: ${appealUrl}` : ``}`\n\t\t);\n\t\tMonitor.notice(`CONNECT BLOCKED - IP BANNED: ${ip} (${banned})`);\n\n\t\treturn banned;\n\t}\n\tcheckNameInRoom(user: User, roomid: RoomID): boolean {\n\t\tlet punishment = Punishments.roomUserids.nestedGet(roomid, user.id);\n\t\tif (!punishment && user.autoconfirmed) {\n\t\t\tpunishment = Punishments.roomUserids.nestedGet(roomid, user.autoconfirmed);\n\t\t}\n\t\tif (punishment?.some(p => p.type === 'ROOMBAN' || p.type === 'BLACKLIST')) {\n\t\t\treturn true;\n\t\t}\n\t\tconst room = Rooms.get(roomid)!;\n\t\tif (room.parent) {\n\t\t\treturn Punishments.checkNameInRoom(user, room.parent.roomid);\n\t\t}\n\t\treturn false;\n\t}\n\n\t/**\n\t * @param userid The name into which the user is renamed.\n\t */\n\tcheckNewNameInRoom(user: User, userid: string, roomid: RoomID): Punishment[] | null {\n\t\tlet punishments: Punishment[] | null = Punishments.roomUserids.nestedGet(roomid, userid) || null;\n\t\tif (!punishments) {\n\t\t\tconst room = Rooms.get(roomid)!;\n\t\t\tif (room.parent) {\n\t\t\t\tpunishments = Punishments.roomUserids.nestedGet(room.parent.roomid, userid) || null;\n\t\t\t}\n\t\t}\n\t\tif (punishments) {\n\t\t\tfor (const punishment of punishments) {\n\t\t\t\tconst info = this.roomPunishmentTypes.get(punishment.type);\n\t\t\t\tif (info?.onActivate) {\n\t\t\t\t\tinfo.onActivate.call(this, user, punishment, Rooms.get(roomid)!, punishment.id === user.id);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif (punishment.type !== 'ROOMBAN' && punishment.type !== 'BLACKLIST') return null;\n\t\t\t\tconst room = Rooms.get(roomid)!;\n\t\t\t\troom.game?.removeBannedUser?.(user);\n\t\t\t\tuser.leaveRoom(room.roomid);\n\t\t\t}\n\t\t\treturn punishments;\n\t\t}\n\t\treturn null;\n\t}\n\n\t/**\n\t * @return Descriptive text for the remaining time until the punishment expires, if any.\n\t */\n\tcheckLockExpiration(userid: string | null) {\n\t\tif (!userid) return ``;\n\t\tconst punishment = Punishments.userids.getByType(userid, 'LOCK');\n\t\tconst user = Users.get(userid);\n\t\tif (user?.permalocked) return ` (never expires; you are permalocked)`;\n\n\t\treturn Punishments.checkPunishmentExpiration(punishment);\n\t}\n\n\tcheckPunishmentExpiration(punishment: Punishment | undefined) {\n\t\tif (!punishment) return ``;\n\t\tconst expiresIn = new Date(punishment.expireTime).getTime() - Date.now();\n\t\tconst expiresDays = Math.round(expiresIn / 1000 / 60 / 60 / 24);\n\t\tlet expiresText = '';\n\t\tif (expiresDays >= 1) {\n\t\t\texpiresText = `in around ${Chat.count(expiresDays, \"days\")}`;\n\t\t} else {\n\t\t\texpiresText = `soon`;\n\t\t}\n\t\tif (expiresIn > 1) return ` (expires ${expiresText})`;\n\t}\n\n\tisRoomBanned(user: User, roomid: RoomID): Punishment | undefined {\n\t\tif (!user) throw new Error(`Trying to check if a non-existent user is room banned.`);\n\n\t\tlet punishments = Punishments.roomUserids.nestedGet(roomid, user.id);\n\t\tfor (const p of punishments || []) {\n\t\t\tif (p.type === 'ROOMBAN' || p.type === 'BLACKLIST') return p;\n\t\t}\n\n\t\tif (user.autoconfirmed) {\n\t\t\tpunishments = Punishments.roomUserids.nestedGet(roomid, user.autoconfirmed);\n\t\t\tfor (const p of punishments || []) {\n\t\t\t\tif (p.type === 'ROOMBAN' || p.type === 'BLACKLIST') return p;\n\t\t\t}\n\t\t}\n\n\t\tif (!user.trusted) {\n\t\t\tfor (const ip of user.ips) {\n\t\t\t\tpunishments = Punishments.roomIps.nestedGet(roomid, ip);\n\t\t\t\tif (punishments) {\n\t\t\t\t\tfor (const punishment of punishments) {\n\t\t\t\t\t\tif (punishment.type === 'ROOMBAN') {\n\t\t\t\t\t\t\treturn punishment;\n\t\t\t\t\t\t} else if (punishment.type === 'BLACKLIST') {\n\t\t\t\t\t\t\tif (Punishments.isSharedIp(ip) && user.autoconfirmed) continue;\n\n\t\t\t\t\t\t\treturn punishment;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tfor (const id of user.previousIDs) {\n\t\t\tpunishments = Punishments.roomUserids.nestedGet(roomid, id);\n\t\t\tfor (const p of punishments || []) {\n\t\t\t\tif (['ROOMBAN', 'BLACKLIST'].includes(p.type)) return p;\n\t\t\t}\n\t\t}\n\n\t\tconst room = Rooms.get(roomid);\n\t\tif (!room) throw new Error(`Trying to ban a user from a nonexistent room: ${roomid}`);\n\n\t\tif (room.parent) return Punishments.isRoomBanned(user, room.parent.roomid);\n\t}\n\n\tisGlobalBanned(user: User): Punishment | undefined {\n\t\tif (!user) throw new Error(`Trying to check if a non-existent user is global banned.`);\n\n\t\tconst punishment = Punishments.userids.getByType(user.id, \"BAN\") || Punishments.userids.getByType(user.id, \"FORCEBAN\");\n\t\tif (punishment) return punishment;\n\t}\n\n\tisBlacklistedSharedIp(ip: string) {\n\t\tconst pattern = IPTools.stringToRange(ip);\n\t\tif (!pattern) {\n\t\t\tthrow new Error(`Invalid IP address: '${ip}'`);\n\t\t}\n\t\tfor (const [blacklisted, reason] of this.sharedIpBlacklist) {\n\t\t\tconst range = IPTools.stringToRange(blacklisted);\n\t\t\tif (!range) throw new Error(\"Falsy range in sharedIpBlacklist\");\n\t\t\tif (IPTools.rangeIntersects(range, pattern)) return reason;\n\t\t}\n\t\treturn false;\n\t}\n\n\t/**\n\t * Returns an array of all room punishments associated with a user.\n\t *\n\t * options.publicOnly will make this only return public room punishments.\n\t * options.checkIps will also check the IP of the user for IP-based punishments.\n\t */\n\tgetRoomPunishments(user: User | string, options: Partial<{ checkIps: any, publicOnly: any }> = {}) {\n\t\tif (!user) return [];\n\t\tconst userid = toID(user);\n\n\t\tconst punishments: [Room, Punishment][] = [];\n\n\t\tfor (const curRoom of Rooms.global.chatRooms) {\n\t\t\tif (\n\t\t\t\t!curRoom || curRoom.settings.isPrivate === true ||\n\t\t\t\t(options.publicOnly && curRoom.settings.isPersonal)\n\t\t\t) continue;\n\t\t\tlet punishment = Punishments.roomUserids.nestedGet(curRoom.roomid, userid);\n\t\t\tif (punishment) {\n\t\t\t\tfor (const p of punishment) {\n\t\t\t\t\tpunishments.push([curRoom, p]);\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t} else if (options?.checkIps) {\n\t\t\t\tif (typeof user !== 'string') {\n\t\t\t\t\tlet longestIPPunishment;\n\t\t\t\t\tfor (const ip of user.ips) {\n\t\t\t\t\t\tpunishment = Punishments.roomIps.nestedGet(curRoom.roomid, ip);\n\t\t\t\t\t\tif (punishment && (!longestIPPunishment || punishment[2] > longestIPPunishment[2])) {\n\t\t\t\t\t\t\tlongestIPPunishment = punishment;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif (longestIPPunishment) {\n\t\t\t\t\t\tfor (const p of longestIPPunishment) {\n\t\t\t\t\t\t\tpunishments.push([curRoom, p]);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (typeof user !== 'string' && curRoom.muteQueue) {\n\t\t\t\t// check mutes\n\t\t\t\tfor (const entry of curRoom.muteQueue) {\n\t\t\t\t\tif (userid === entry.userid ||\n\t\t\t\t\t\tuser.guestNum === entry.guestNum ||\n\t\t\t\t\t\t(user.autoconfirmed && user.autoconfirmed === entry.autoconfirmed)) {\n\t\t\t\t\t\tpunishments.push([curRoom, { type: 'MUTE', id: entry.userid, expireTime: entry.time, reason: '' } as Punishment]);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn punishments;\n\t}\n\tgetPunishments(roomid?: RoomID, ignoreMutes?: boolean) {\n\t\tconst punishmentTable: [string, PunishmentEntry][] = [];\n\t\tif (roomid && (!Punishments.roomIps.has(roomid) || !Punishments.roomUserids.has(roomid))) return punishmentTable;\n\t\t// `Punishments.roomIps.get(roomid)` guaranteed to exist above\n\t\t(roomid ? Punishments.roomIps.get(roomid)! : Punishments.ips).each((punishment, ip) => {\n\t\t\tconst { type, id, expireTime, reason, rest } = punishment;\n\t\t\tif (id !== '#rangelock' && id.startsWith('#')) return;\n\t\t\tlet entry = punishmentTable.find(e => e[0] === id && e[1].punishType === type)?.[1];\n\n\t\t\tif (entry) {\n\t\t\t\tentry.ips.push(ip);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tentry = {\n\t\t\t\tuserids: [],\n\t\t\t\tips: [ip],\n\t\t\t\tpunishType: type,\n\t\t\t\texpireTime,\n\t\t\t\treason,\n\t\t\t\trest: rest || [],\n\t\t\t};\n\t\t\tpunishmentTable.push([id, entry]);\n\t\t});\n\t\t// `Punishments.roomIps.get(roomid)` guaranteed to exist above\n\t\t(roomid ? Punishments.roomUserids.get(roomid)! : Punishments.userids).each((punishment, userid) => {\n\t\t\tconst { type, id, expireTime, reason, rest } = punishment;\n\t\t\tif (id.startsWith('#')) return;\n\t\t\tlet entry = punishmentTable.find(([curId, cur]) => id === curId && cur.punishType === type)?.[1];\n\t\t\tif (!entry) {\n\t\t\t\tentry = {\n\t\t\t\t\tuserids: [],\n\t\t\t\t\tips: [],\n\t\t\t\t\tpunishType: type,\n\t\t\t\t\texpireTime,\n\t\t\t\t\treason,\n\t\t\t\t\trest: rest || [],\n\t\t\t\t};\n\t\t\t\tpunishmentTable.push([id, entry]);\n\t\t\t}\n\n\t\t\tif (userid !== id) entry.userids.push(userid as ID); // guaranteed as per above check\n\t\t});\n\t\tif (roomid && ignoreMutes !== false) {\n\t\t\tconst room = Rooms.get(roomid);\n\t\t\tif (room?.muteQueue) {\n\t\t\t\tfor (const mute of room.muteQueue) {\n\t\t\t\t\tpunishmentTable.push([mute.userid, {\n\t\t\t\t\t\tuserids: [], ips: [], punishType: \"MUTE\", expireTime: mute.time, reason: \"\", rest: [],\n\t\t\t\t\t}]);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn punishmentTable;\n\t}\n\tvisualizePunishments(punishments: Map<string, PunishmentEntry>, user: User) {\n\t\tlet buf = \"\";\n\t\tbuf += `<div class=\"ladder pad\"><h2>List of active punishments:</h2>`;\n\t\tbuf += `<table\">`;\n\t\tbuf += `<tr>`;\n\t\tbuf += `<th>Username</th>`;\n\t\tbuf += `<th>Punishment type</th>`;\n\t\tbuf += `<th>Expire time</th>`;\n\t\tbuf += `<th>Reason</th>`;\n\t\tbuf += `<th>Alts</th>`;\n\t\tif (user.can('ip')) buf += `<th>IPs</th>`;\n\t\tbuf += `</tr>`;\n\t\tfor (const [userid, punishment] of punishments) {\n\t\t\tconst expiresIn = new Date(punishment.expireTime).getTime() - Date.now();\n\t\t\tif (expiresIn < 1000) continue;\n\t\t\tconst expireString = Chat.toDurationString(expiresIn, { precision: 1 });\n\t\t\tbuf += `<tr>`;\n\t\t\tbuf += `<td>${userid}</td>`;\n\t\t\tbuf += `<td>${punishment.punishType}</td>`;\n\t\t\tbuf += `<td>${expireString}</td>`;\n\t\t\tbuf += `<td>${punishment.reason || ' - '}</td>`;\n\t\t\tbuf += `<td>${punishment.userids.join(\", \") || ' - '}</td>`;\n\t\t\tif (user.can('ip')) buf += `<td>${punishment.ips.join(\", \") || ' - '}</td>`;\n\t\t\tbuf += `</tr>`;\n\t\t}\n\t\tbuf += `</table>`;\n\t\tbuf += `</div>`;\n\t\treturn buf;\n\t}\n\t/**\n\t * Notifies staff if a user has three or more room punishments.\n\t */\n\tasync monitorRoomPunishments(user: User | ID) {\n\t\tif ((user as User).locked) return;\n\t\tconst userid = toID(user);\n\n\t\t/** Default to 3 if the Config option is not defined or valid */\n\t\tconst minPunishments = (typeof Config.monitorminpunishments === 'number' ? Config.monitorminpunishments : 3);\n\t\tif (!minPunishments) return;\n\n\t\tlet punishments = Punishments.getRoomPunishments(user, { checkIps: true, publicOnly: true });\n\t\tpunishments = punishments.filter(([room, punishment]) => (\n\t\t\tPunishments.roomPunishmentTypes.get(punishment.type)?.activatePunishMonitor\n\t\t));\n\n\t\tif (punishments.length >= minPunishments) {\n\t\t\tlet points = 0;\n\n\t\t\tconst punishmentText = punishments.map(([room, punishment]) => {\n\t\t\t\tconst { type: punishType, id: punishUserid, reason } = punishment;\n\t\t\t\tif (punishType in PUNISHMENT_POINT_VALUES) points += PUNISHMENT_POINT_VALUES[punishType];\n\t\t\t\tlet punishDesc = Punishments.roomPunishmentTypes.get(punishType)?.desc;\n\t\t\t\tif (!punishDesc) punishDesc = `punished`;\n\t\t\t\tif (punishUserid !== userid) punishDesc += ` as ${punishUserid}`;\n\n\t\t\t\t// Backwards compatibility for current punishments\n\t\t\t\tconst trimmedReason = reason?.trim();\n\t\t\t\tif (trimmedReason && !trimmedReason.startsWith('(PROOF:')) punishDesc += `: ${trimmedReason}`;\n\t\t\t\treturn `<<${room}>> (${punishDesc})`;\n\t\t\t}).join(', ');\n\n\t\t\tif (Config.punishmentautolock && points >= AUTOLOCK_POINT_THRESHOLD) {\n\t\t\t\tconst rooms = punishments.map(([room]) => room).join(', ');\n\t\t\t\tconst reason = `Autolocked for having punishments in ${punishments.length} rooms: ${rooms}`;\n\t\t\t\tconst message = `${(user as User).name || userid} was locked for having punishments in ${punishments.length} rooms: ${punishmentText}`;\n\n\t\t\t\tconst globalPunishments = await Rooms.Modlog.getGlobalPunishments(userid, AUTOWEEKLOCK_DAYS_TO_SEARCH);\n\t\t\t\t// null check in case SQLite is disabled\n\t\t\t\tconst isWeek = globalPunishments !== null && globalPunishments >= AUTOWEEKLOCK_THRESHOLD;\n\n\t\t\t\tvoid Punishments.autolock(user, 'staff', 'PunishmentMonitor', reason, message, isWeek);\n\t\t\t\tif (typeof user !== 'string') {\n\t\t\t\t\tuser.popup(\n\t\t\t\t\t\t`|modal|You've been locked for breaking the rules in multiple chatrooms.\\n\\n` +\n\t\t\t\t\t\t`If you feel that your lock was unjustified, you can still PM staff members (%, @, ~) to discuss it${\n\t\t\t\t\t\t\tConfig.appealurl ? ` or you can appeal:\\n${Config.appealurl}` : \".\"\n\t\t\t\t\t\t}\\n\\n` +\n\t\t\t\t\t\t`Your lock will expire in a few days.`\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tMonitor.log(`[PunishmentMonitor] ${(user as User).name || userid} currently has punishments in ${punishments.length} rooms: ${punishmentText}`);\n\t\t\t}\n\t\t}\n\t}\n\trenameRoom(oldID: RoomID, newID: RoomID) {\n\t\tfor (const table of [Punishments.roomUserids, Punishments.roomIps]) {\n\t\t\tconst entry = table.get(oldID);\n\t\t\tif (entry) {\n\t\t\t\ttable.set(newID, entry);\n\t\t\t\ttable.delete(oldID);\n\t\t\t}\n\t\t}\n\t\tPunishments.saveRoomPunishments();\n\t}\n\tPunishmentMap = PunishmentMap;\n\tNestedPunishmentMap = NestedPunishmentMap;\n}();\n"], | |
"mappings": "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", | |
"names": ["id"] | |
} | |