{ "version": 3, "sources": ["../../../server/users.ts"], "sourcesContent": ["/**\r\n * Users\r\n * Pokemon Showdown - http://pokemonshowdown.com/\r\n *\r\n * Most of the communication with users happens here.\r\n *\r\n * There are two object types this file introduces:\r\n * User and Connection.\r\n *\r\n * A User object is a user, identified by username. A guest has a\r\n * username in the form \"Guest 12\". Any user whose username starts\r\n * with \"Guest\" must be a guest; normal users are not allowed to\r\n * use usernames starting with \"Guest\".\r\n *\r\n * A User can be connected to Pokemon Showdown from any number of tabs\r\n * or computers at the same time. Each connection is represented by\r\n * a Connection object. A user tracks its connections in\r\n * user.connections - if this array is empty, the user is offline.\r\n *\r\n * `Users.users` is the global table of all users, a `Map` of `ID:User`.\r\n * Users should normally be accessed with `Users.get(userid)`\r\n *\r\n * `Users.connections` is the global table of all connections, a `Map` of\r\n * `string:Connection` (the string is mostly meaningless but see\r\n * `connection.id` for details). Connections are normally accessed through\r\n * `user.connections`.\r\n *\r\n * @license MIT\r\n */\r\n\r\ntype StatusType = 'online' | 'busy' | 'idle';\r\n\r\nconst THROTTLE_DELAY = 600;\r\nconst THROTTLE_DELAY_TRUSTED = 100;\r\nconst THROTTLE_DELAY_PUBLIC_BOT = 25;\r\nconst THROTTLE_BUFFER_LIMIT = 6;\r\nconst THROTTLE_MULTILINE_WARN = 3;\r\nconst THROTTLE_MULTILINE_WARN_STAFF = 6;\r\nconst THROTTLE_MULTILINE_WARN_ADMIN = 25;\r\n\r\nconst NAMECHANGE_THROTTLE = 2 * 60 * 1000; // 2 minutes\r\nconst NAMES_PER_THROTTLE = 3;\r\n\r\nconst PERMALOCK_CACHE_TIME = 30 * 24 * 60 * 60 * 1000; // 30 days\r\n\r\nconst DEFAULT_TRAINER_SPRITES = [1, 2, 101, 102, 169, 170, 265, 266];\r\n\r\nimport {FS, Utils, ProcessManager} from '../lib';\r\nimport {\r\n\tAuth, GlobalAuth, SECTIONLEADER_SYMBOL, PLAYER_SYMBOL, HOST_SYMBOL, RoomPermission, GlobalPermission,\r\n} from './user-groups';\r\n\r\nconst MINUTES = 60 * 1000;\r\nconst IDLE_TIMER = 60 * MINUTES;\r\nconst STAFF_IDLE_TIMER = 30 * MINUTES;\r\nconst CONNECTION_EXPIRY_TIME = 24 * 60 * MINUTES;\r\n\r\n\r\n/*********************************************************\r\n * Utility functions\r\n *********************************************************/\r\n\r\n// Low-level functions for manipulating Users.users and Users.prevUsers\r\n// Keeping them all here makes it easy to ensure they stay consistent\r\n\r\nfunction move(user: User, newUserid: ID) {\r\n\tif (user.id === newUserid) return true;\r\n\tif (!user) return false;\r\n\r\n\t// doing it this way mathematically ensures no cycles\r\n\tprevUsers.delete(newUserid);\r\n\tprevUsers.set(user.id, newUserid);\r\n\r\n\tusers.delete(user.id);\r\n\tuser.id = newUserid;\r\n\tusers.set(newUserid, user);\r\n\r\n\treturn true;\r\n}\r\nfunction add(user: User) {\r\n\tif (user.id) throw new Error(`Adding a user that already exists`);\r\n\r\n\tnumUsers++;\r\n\tuser.guestNum = numUsers;\r\n\tuser.name = `Guest ${numUsers}`;\r\n\tuser.id = toID(user.name);\r\n\r\n\tif (users.has(user.id)) throw new Error(`userid taken: ${user.id}`);\r\n\tusers.set(user.id, user);\r\n}\r\nfunction deleteUser(user: User) {\r\n\tprevUsers.delete('guest' + user.guestNum as ID);\r\n\tusers.delete(user.id);\r\n}\r\nfunction merge(toRemain: User, toDestroy: User) {\r\n\tprevUsers.delete(toRemain.id);\r\n\tprevUsers.set(toDestroy.id, toRemain.id);\r\n}\r\n\r\n/**\r\n * Get a user.\r\n *\r\n * Usage:\r\n * Users.get(userid or username)\r\n *\r\n * Returns the corresponding User object, or null if no matching\r\n * was found.\r\n *\r\n * By default, this function will track users across name changes.\r\n * For instance, if \"Some dude\" changed their name to \"Some guy\",\r\n * Users.get(\"Some dude\") will give you \"Some guy\"s user object.\r\n *\r\n * If this behavior is undesirable, use Users.getExact.\r\n */\r\nfunction getUser(name: string | User | null, exactName = false) {\r\n\tif (!name || name === '!') return null;\r\n\tif ((name as User).id) return name as User;\r\n\tlet userid = toID(name);\r\n\tlet i = 0;\r\n\tif (!exactName) {\r\n\t\twhile (userid && !users.has(userid) && i < 1000) {\r\n\t\t\tuserid = prevUsers.get(userid)!;\r\n\t\t\ti++;\r\n\t\t}\r\n\t}\r\n\treturn users.get(userid) || null;\r\n}\r\n\r\n/**\r\n * Get a user by their exact username.\r\n *\r\n * Usage:\r\n * Users.getExact(userid or username)\r\n *\r\n * Like Users.get, but won't track across username changes.\r\n *\r\n * Users.get(userid or username, true) is equivalent to\r\n * Users.getExact(userid or username).\r\n * The former is not recommended because it's less readable.\r\n */\r\nfunction getExactUser(name: string | User) {\r\n\treturn getUser(name, true);\r\n}\r\n\r\n/**\r\n * Get a list of all users matching a list of userids and ips.\r\n *\r\n * Usage:\r\n * Users.findUsers([userids], [ips])\r\n */\r\nfunction findUsers(userids: ID[], ips: string[], options: {forPunishment?: boolean, includeTrusted?: boolean} = {}) {\r\n\tconst matches: User[] = [];\r\n\tif (options.forPunishment) ips = ips.filter(ip => !Punishments.isSharedIp(ip));\r\n\tconst ipMatcher = IPTools.checker(ips);\r\n\tfor (const user of users.values()) {\r\n\t\tif (!options.forPunishment && !user.named && !user.connected) continue;\r\n\t\tif (!options.includeTrusted && user.trusted) continue;\r\n\t\tif (userids.includes(user.id)) {\r\n\t\t\tmatches.push(user);\r\n\t\t\tcontinue;\r\n\t\t}\r\n\t\tif (user.ips.some(ipMatcher)) {\r\n\t\t\tmatches.push(user);\r\n\t\t}\r\n\t}\r\n\treturn matches;\r\n}\r\n\r\n/*********************************************************\r\n * User groups\r\n*********************************************************/\r\nconst globalAuth = new GlobalAuth();\r\n\r\nfunction isUsernameKnown(name: string) {\r\n\tconst userid = toID(name);\r\n\tif (Users.get(userid)) return true;\r\n\tif (globalAuth.has(userid)) return true;\r\n\tfor (const room of Rooms.global.chatRooms) {\r\n\t\tif (room.auth.has(userid)) return true;\r\n\t}\r\n\treturn false;\r\n}\r\n\r\nfunction isUsername(name: string) {\r\n\treturn /[A-Za-z0-9]/.test(name.charAt(0)) && /[A-Za-z]/.test(name) && !name.includes(',');\r\n}\r\n\r\nfunction isTrusted(userid: ID) {\r\n\tif (globalAuth.has(userid)) return userid;\r\n\tfor (const room of Rooms.global.chatRooms) {\r\n\t\tif (room.persist && room.settings.isPrivate !== true && room.auth.isStaff(userid)) {\r\n\t\t\treturn userid;\r\n\t\t}\r\n\t}\r\n\tconst staffRoom = Rooms.get('staff');\r\n\tconst staffAuth = staffRoom && !!(staffRoom.auth.has(userid) || staffRoom.users[userid]);\r\n\treturn staffAuth ? userid : false;\r\n}\r\n\r\nfunction isPublicBot(userid: ID) {\r\n\tif (globalAuth.get(userid) === '*') return true;\r\n\tfor (const room of Rooms.global.chatRooms) {\r\n\t\tif (room.persist && !room.settings.isPrivate && room.auth.get(userid) === '*') {\r\n\t\t\treturn true;\r\n\t\t}\r\n\t}\r\n\treturn false;\r\n}\r\n\r\n/*********************************************************\r\n * User and Connection classes\r\n *********************************************************/\r\n\r\nconst connections = new Map();\r\n\r\nexport class Connection {\r\n\t/**\r\n\t * Connection IDs are mostly meaningless, beyond being known to be\r\n\t * unique among connections. They set in `socketConnect` to\r\n\t * `workerid-socketid`, so for instance `2-523` would be the 523th\r\n\t * connection to the 2nd socket worker process.\r\n\t */\r\n\treadonly id: string;\r\n\treadonly socketid: string;\r\n\treadonly worker: ProcessManager.StreamWorker;\r\n\treadonly inRooms: Set;\r\n\treadonly ip: string;\r\n\treadonly protocol: string;\r\n\treadonly connectedAt: number;\r\n\t/**\r\n\t * This can be null during initialization and after disconnecting,\r\n\t * but we're asserting it non-null for ease of use. The main risk\r\n\t * is async code, where you need to re-check that it's not null\r\n\t * before using it.\r\n\t */\r\n\tuser: User;\r\n\tchallenge: string;\r\n\tautojoins: string;\r\n\t/** The last bot html page this connection requested, formatted as `${bot.id}-${pageid}` */\r\n\tlastRequestedPage: string | null;\r\n\tlastActiveTime: number;\r\n\topenPages: null | Set;\r\n\tconstructor(\r\n\t\tid: string,\r\n\t\tworker: ProcessManager.StreamWorker,\r\n\t\tsocketid: string,\r\n\t\tuser: User | null,\r\n\t\tip: string | null,\r\n\t\tprotocol: string | null\r\n\t) {\r\n\t\tconst now = Date.now();\r\n\r\n\t\tthis.id = id;\r\n\t\tthis.socketid = socketid;\r\n\t\tthis.worker = worker;\r\n\t\tthis.inRooms = new Set();\r\n\r\n\t\tthis.ip = ip || '';\r\n\t\tthis.protocol = protocol || '';\r\n\r\n\t\tthis.connectedAt = now;\r\n\r\n\t\tthis.user = user!;\r\n\r\n\t\tthis.challenge = '';\r\n\t\tthis.autojoins = '';\r\n\t\tthis.lastRequestedPage = null;\r\n\t\tthis.lastActiveTime = now;\r\n\t\tthis.openPages = null;\r\n\t}\r\n\tsendTo(roomid: RoomID | BasicRoom | null, data: string) {\r\n\t\tif (roomid && typeof roomid !== 'string') roomid = roomid.roomid;\r\n\t\tif (roomid && roomid !== 'lobby') data = `>${roomid}\\n${data}`;\r\n\t\tSockets.socketSend(this.worker, this.socketid, data);\r\n\t\tMonitor.countNetworkUse(data.length);\r\n\t}\r\n\r\n\tsend(data: string) {\r\n\t\tSockets.socketSend(this.worker, this.socketid, data);\r\n\t\tMonitor.countNetworkUse(data.length);\r\n\t}\r\n\r\n\tdestroy() {\r\n\t\tSockets.socketDisconnect(this.worker, this.socketid);\r\n\t\tthis.onDisconnect();\r\n\t}\r\n\tonDisconnect() {\r\n\t\tconnections.delete(this.id);\r\n\t\tif (this.user) this.user.onDisconnect(this);\r\n\t\tthis.user = null!;\r\n\t}\r\n\r\n\tpopup(message: string) {\r\n\t\tthis.send(`|popup|` + message.replace(/\\n/g, '||'));\r\n\t}\r\n\r\n\tjoinRoom(room: Room) {\r\n\t\tif (this.inRooms.has(room.roomid)) return;\r\n\t\tthis.inRooms.add(room.roomid);\r\n\t\tSockets.roomAdd(this.worker, room.roomid, this.socketid);\r\n\t}\r\n\tleaveRoom(room: Room) {\r\n\t\tif (this.inRooms.has(room.roomid)) {\r\n\t\t\tthis.inRooms.delete(room.roomid);\r\n\t\t\tSockets.roomRemove(this.worker, room.roomid, this.socketid);\r\n\t\t}\r\n\t}\r\n\ttoString() {\r\n\t\tlet buf = this.user ? `${this.user.id}[${this.user.connections.indexOf(this)}]` : `[disconnected]`;\r\n\t\tbuf += `:${this.ip}`;\r\n\t\tif (this.protocol !== 'websocket') buf += `:${this.protocol}`;\r\n\t\treturn buf;\r\n\t}\r\n}\r\n\r\ntype ChatQueueEntry = [string, RoomID, Connection];\r\n\r\nexport interface UserSettings {\r\n\tblockChallenges: boolean | AuthLevel | 'friends';\r\n\tblockPMs: boolean | AuthLevel | 'friends';\r\n\tignoreTickets: boolean;\r\n\thideBattlesFromTrainerCard: boolean;\r\n\tblockInvites: AuthLevel | boolean;\r\n\tdoNotDisturb: boolean;\r\n\tblockFriendRequests: boolean;\r\n\tallowFriendNotifications: boolean;\r\n\tdisplayBattlesToFriends: boolean;\r\n\thideLogins: boolean;\r\n}\r\n\r\n// User\r\nexport class User extends Chat.MessageContext {\r\n\t/** In addition to needing it to implement MessageContext, this is also nice for compatibility with Connection. */\r\n\treadonly user: User;\r\n\treadonly inRooms: Set;\r\n\t/**\r\n\t * Set of room IDs\r\n\t */\r\n\treadonly games: Set;\r\n\tmmrCache: {[format: string]: number};\r\n\tguestNum: number;\r\n\tname: string;\r\n\tnamed: boolean;\r\n\tregistered: boolean;\r\n\tid: ID;\r\n\ttempGroup: GroupSymbol;\r\n\tavatar: string | number;\r\n\tlanguage: ID | null;\r\n\r\n\tconnected: boolean;\r\n\tconnections: Connection[];\r\n\tlatestHost: string;\r\n\tlatestHostType: string;\r\n\tips: string[];\r\n\tlatestIp: string;\r\n\tlocked: ID | PunishType | null;\r\n\tsemilocked: ID | PunishType | null;\r\n\tnamelocked: ID | PunishType | null;\r\n\tpermalocked: ID | PunishType | null;\r\n\tpunishmentTimer: NodeJS.Timer | null;\r\n\tpreviousIDs: ID[];\r\n\r\n\tlastChallenge: number;\r\n\tlastPM: string;\r\n\tlastMatch: ID;\r\n\r\n\tsettings: UserSettings;\r\n\r\n\tbattleSettings: {\r\n\t\tteam: string,\r\n\t\thidden: boolean,\r\n\t\tinviteOnly: boolean,\r\n\t\tspecial?: string,\r\n\t};\r\n\r\n\tisSysop: boolean;\r\n\tisStaff: boolean;\r\n\tisPublicBot: boolean;\r\n\tlastDisconnected: number;\r\n\tlastConnected: number;\r\n\tfoodfight?: {generatedTeam: string[], dish: string, ingredients: string[], timestamp: number};\r\n\tfriends?: Set;\r\n\r\n\tchatQueue: ChatQueueEntry[] | null;\r\n\tchatQueueTimeout: NodeJS.Timeout | null;\r\n\tlastChatMessage: number;\r\n\tlastCommand: string;\r\n\r\n\tnotified: {\r\n\t\tblockChallenges: boolean,\r\n\t\tblockPMs: boolean,\r\n\t\tblockInvites: boolean,\r\n\t\tpunishment: boolean,\r\n\t\tlock: boolean,\r\n\t};\r\n\r\n\tlastMessage: string;\r\n\tlastMessageTime: number;\r\n\tlastReportTime: number;\r\n\tlastNewNameTime = 0;\r\n\tnewNames = 0;\r\n\ts1: string;\r\n\ts2: string;\r\n\ts3: string;\r\n\r\n\tautoconfirmed: ID;\r\n\ttrusted: ID;\r\n\ttrackRename: string;\r\n\tstatusType: StatusType;\r\n\tuserMessage: string;\r\n\tlastWarnedAt: number;\r\n\tconstructor(connection: Connection) {\r\n\t\tsuper(connection.user);\r\n\t\tthis.user = this;\r\n\t\tthis.inRooms = new Set();\r\n\t\tthis.games = new Set();\r\n\t\tthis.mmrCache = Object.create(null);\r\n\t\tthis.guestNum = -1;\r\n\t\tthis.name = \"\";\r\n\t\tthis.named = false;\r\n\t\tthis.registered = false;\r\n\t\tthis.id = '';\r\n\t\tthis.tempGroup = Auth.defaultSymbol();\r\n\t\tthis.language = null;\r\n\r\n\t\tthis.avatar = DEFAULT_TRAINER_SPRITES[Math.floor(Math.random() * DEFAULT_TRAINER_SPRITES.length)];\r\n\r\n\t\tthis.connected = true;\r\n\t\tUsers.onlineCount++;\r\n\r\n\t\tif (connection.user) connection.user = this;\r\n\t\tthis.connections = [connection];\r\n\t\tthis.latestHost = '';\r\n\t\tthis.latestHostType = '';\r\n\t\tthis.ips = [connection.ip];\r\n\t\t// Note: Using the user's latest IP for anything will usually be\r\n\t\t// wrong. Most code should use all of the IPs contained in\r\n\t\t// the `ips` array, not just the latest IP.\r\n\t\tthis.latestIp = connection.ip;\r\n\t\tthis.locked = null;\r\n\t\tthis.semilocked = null;\r\n\t\tthis.namelocked = null;\r\n\t\tthis.permalocked = null;\r\n\t\tthis.punishmentTimer = null;\r\n\t\tthis.previousIDs = [];\r\n\r\n\t\t// misc state\r\n\t\tthis.lastChallenge = 0;\r\n\t\tthis.lastPM = '';\r\n\t\tthis.lastMatch = '';\r\n\r\n\t\t// settings\r\n\t\tthis.settings = {\r\n\t\t\tblockChallenges: false,\r\n\t\t\tblockPMs: false,\r\n\t\t\tignoreTickets: false,\r\n\t\t\thideBattlesFromTrainerCard: false,\r\n\t\t\tblockInvites: false,\r\n\t\t\tdoNotDisturb: false,\r\n\t\t\tblockFriendRequests: false,\r\n\t\t\tallowFriendNotifications: false,\r\n\t\t\tdisplayBattlesToFriends: false,\r\n\t\t\thideLogins: false,\r\n\t\t};\r\n\t\tthis.battleSettings = {\r\n\t\t\tteam: '',\r\n\t\t\thidden: false,\r\n\t\t\tinviteOnly: false,\r\n\t\t};\r\n\r\n\t\tthis.isSysop = false;\r\n\t\tthis.isStaff = false;\r\n\t\tthis.isPublicBot = false;\r\n\t\tthis.lastDisconnected = 0;\r\n\t\tthis.lastConnected = connection.connectedAt;\r\n\r\n\t\t// chat queue\r\n\t\tthis.chatQueue = null;\r\n\t\tthis.chatQueueTimeout = null;\r\n\t\tthis.lastChatMessage = 0;\r\n\t\tthis.lastCommand = '';\r\n\r\n\t\t// for the anti-spamming mechanism\r\n\t\tthis.lastMessage = ``;\r\n\t\tthis.lastMessageTime = 0;\r\n\t\tthis.lastReportTime = 0;\r\n\t\tthis.s1 = '';\r\n\t\tthis.s2 = '';\r\n\t\tthis.s3 = '';\r\n\r\n\t\tthis.notified = {\r\n\t\t\tblockChallenges: false,\r\n\t\t\tblockPMs: false,\r\n\t\t\tblockInvites: false,\r\n\t\t\tpunishment: false,\r\n\t\t\tlock: false,\r\n\t\t};\r\n\r\n\t\tthis.autoconfirmed = '';\r\n\t\tthis.trusted = '';\r\n\t\t// Used in punishments\r\n\t\tthis.trackRename = '';\r\n\t\tthis.statusType = 'online';\r\n\t\tthis.userMessage = '';\r\n\t\tthis.lastWarnedAt = 0;\r\n\r\n\t\t// initialize\r\n\t\tUsers.add(this);\r\n\t}\r\n\r\n\tsendTo(roomid: RoomID | BasicRoom | null, data: string) {\r\n\t\tif (roomid && typeof roomid !== 'string') roomid = roomid.roomid;\r\n\t\tif (roomid && roomid !== 'lobby') data = `>${roomid}\\n${data}`;\r\n\t\tfor (const connection of this.connections) {\r\n\t\t\tif (roomid && !connection.inRooms.has(roomid)) continue;\r\n\t\t\tconnection.send(data);\r\n\t\t\tMonitor.countNetworkUse(data.length);\r\n\t\t}\r\n\t}\r\n\tsend(data: string) {\r\n\t\tfor (const connection of this.connections) {\r\n\t\t\tconnection.send(data);\r\n\t\t\tMonitor.countNetworkUse(data.length);\r\n\t\t}\r\n\t}\r\n\tpopup(message: string) {\r\n\t\tthis.send(`|popup|` + message.replace(/\\n/g, '||'));\r\n\t}\r\n\tgetIdentity(room: BasicRoom | null = null) {\r\n\t\tconst punishgroups = Config.punishgroups || {locked: null, muted: null};\r\n\t\tif (this.locked || this.namelocked) {\r\n\t\t\tconst lockedSymbol = (punishgroups.locked && punishgroups.locked.symbol || '\\u203d');\r\n\t\t\treturn lockedSymbol + this.name;\r\n\t\t}\r\n\t\tif (room) {\r\n\t\t\tif (room.isMuted(this)) {\r\n\t\t\t\tconst mutedSymbol = (punishgroups.muted && punishgroups.muted.symbol || '!');\r\n\t\t\t\treturn mutedSymbol + this.name;\r\n\t\t\t}\r\n\t\t\treturn room.auth.get(this) + this.name;\r\n\t\t}\r\n\t\tif (this.semilocked) {\r\n\t\t\tconst mutedSymbol = (punishgroups.muted && punishgroups.muted.symbol || '!');\r\n\t\t\treturn mutedSymbol + this.name;\r\n\t\t}\r\n\t\treturn this.tempGroup + this.name;\r\n\t}\r\n\tgetIdentityWithStatus(room: BasicRoom | null = null) {\r\n\t\tconst identity = this.getIdentity(room);\r\n\t\tconst status = this.statusType === 'online' ? '' : '@!';\r\n\t\treturn `${identity}${status}`;\r\n\t}\r\n\tgetStatus() {\r\n\t\tconst statusMessage = this.statusType === 'busy' ? '!(Busy) ' : this.statusType === 'idle' ? '!(Idle) ' : '';\r\n\t\tconst status = statusMessage + (this.userMessage || '');\r\n\t\treturn status;\r\n\t}\r\n\tcan(permission: RoomPermission, target: User | null, room: BasicRoom, cmd?: string): boolean;\r\n\tcan(permission: GlobalPermission, target?: User | null): boolean;\r\n\tcan(permission: RoomPermission & GlobalPermission, target: User | null, room?: BasicRoom | null, cmd?: string): boolean;\r\n\tcan(permission: string, target: User | null = null, room: BasicRoom | null = null, cmd?: string): boolean {\r\n\t\treturn Auth.hasPermission(this, permission, target, room, cmd);\r\n\t}\r\n\t/**\r\n\t * Special permission check for system operators\r\n\t */\r\n\thasSysopAccess() {\r\n\t\tif (this.isSysop && Config.backdoor) {\r\n\t\t\t// This is the Pokemon Showdown system operator backdoor.\r\n\r\n\t\t\t// Its main purpose is for situations where someone calls for help, and\r\n\t\t\t// your server has no admins online, or its admins have lost their\r\n\t\t\t// access through either a mistake or a bug - a system operator such as\r\n\t\t\t// Zarel will be able to fix it.\r\n\r\n\t\t\t// This relies on trusting Pokemon Showdown. If you do not trust\r\n\t\t\t// Pokemon Showdown, feel free to disable it, but remember that if\r\n\t\t\t// you mess up your server in whatever way, our tech support will not\r\n\t\t\t// be able to help you.\r\n\t\t\treturn true;\r\n\t\t}\r\n\t\treturn false;\r\n\t}\r\n\t/**\r\n\t * Permission check for using the dev console\r\n\t *\r\n\t * The `console` permission is incredibly powerful because it allows the\r\n\t * execution of abitrary shell commands on the local computer As such, it\r\n\t * can only be used from a specified whitelist of IPs and userids. A\r\n\t * special permission check function is required to carry out this check\r\n\t * because we need to know which socket the client is connected from in\r\n\t * order to determine the relevant IP for checking the whitelist.\r\n\t */\r\n\thasConsoleAccess(connection: Connection) {\r\n\t\tif (this.hasSysopAccess()) return true;\r\n\t\tif (!this.can('console')) return false; // normal permission check\r\n\r\n\t\tconst whitelist = Config.consoleips || ['127.0.0.1'];\r\n\t\t// on the IP whitelist OR the userid whitelist\r\n\t\treturn whitelist.includes(connection.ip) || whitelist.includes(this.id);\r\n\t}\r\n\tresetName(isForceRenamed = false) {\r\n\t\treturn this.forceRename('Guest ' + this.guestNum, false, isForceRenamed);\r\n\t}\r\n\tupdateIdentity(roomid: RoomID | null = null) {\r\n\t\tif (roomid) {\r\n\t\t\treturn Rooms.get(roomid)!.onUpdateIdentity(this);\r\n\t\t}\r\n\t\tfor (const inRoomID of this.inRooms) {\r\n\t\t\tRooms.get(inRoomID)!.onUpdateIdentity(this);\r\n\t\t}\r\n\t}\r\n\tasync validateToken(token: string, name: string, userid: ID, connection: Connection) {\r\n\t\tif (!token && Config.noguestsecurity) {\r\n\t\t\tif (Users.isTrusted(userid)) {\r\n\t\t\t\tthis.send(`|nametaken|${name}|You need an authentication token to log in as a trusted user.`);\r\n\t\t\t\treturn null;\r\n\t\t\t}\r\n\t\t\treturn '1';\r\n\t\t}\r\n\r\n\t\tif (!token || token.startsWith(';')) {\r\n\t\t\tthis.send(`|nametaken|${name}|Your authentication token was invalid.`);\r\n\t\t\treturn null;\r\n\t\t}\r\n\r\n\t\tlet challenge = '';\r\n\t\tif (connection) {\r\n\t\t\tchallenge = connection.challenge;\r\n\t\t}\r\n\t\tif (!challenge) {\r\n\t\t\tMonitor.warn(`verification failed; no challenge`);\r\n\t\t\treturn null;\r\n\t\t}\r\n\r\n\t\tconst [tokenData, tokenSig] = Utils.splitFirst(token, ';');\r\n\t\tconst tokenDataSplit = tokenData.split(',');\r\n\t\tconst [signedChallenge, signedUserid, userType, signedDate, signedHostname] = tokenDataSplit;\r\n\r\n\t\tif (signedHostname && Config.legalhosts && !Config.legalhosts.includes(signedHostname)) {\r\n\t\t\tMonitor.warn(`forged assertion: ${tokenData}`);\r\n\t\t\tthis.send(`|nametaken|${name}|Your assertion is for the wrong server. This server is ${Config.legalhosts[0]}.`);\r\n\t\t\treturn null;\r\n\t\t}\r\n\r\n\t\tif (tokenDataSplit.length < 5) {\r\n\t\t\tMonitor.warn(`outdated assertion format: ${tokenData}`);\r\n\t\t\tthis.send(`|nametaken|${name}|The assertion you sent us is corrupt or incorrect. Please send the exact assertion given by the login server's JSON response.`);\r\n\t\t\treturn null;\r\n\t\t}\r\n\r\n\t\tif (signedUserid !== userid) {\r\n\t\t\t// userid mismatch\r\n\t\t\tthis.send(`|nametaken|${name}|Your verification signature doesn't match your new username.`);\r\n\t\t\treturn null;\r\n\t\t}\r\n\r\n\t\tif (signedChallenge !== challenge) {\r\n\t\t\t// a user sent an invalid token\r\n\t\t\tMonitor.debug(`verify token challenge mismatch: ${signedChallenge} <=> ${challenge}`);\r\n\t\t\tthis.send(`|nametaken|${name}|Your verification signature doesn't match your authentication token.`);\r\n\t\t\treturn null;\r\n\t\t}\r\n\r\n\t\tconst expiry = Config.tokenexpiry || 25 * 60 * 60;\r\n\t\tif (Math.abs(parseInt(signedDate) - Date.now() / 1000) > expiry) {\r\n\t\t\tMonitor.warn(`stale assertion: ${tokenData}`);\r\n\t\t\tthis.send(`|nametaken|${name}|Your assertion is stale. This usually means that the clock on the server computer is incorrect. If this is your server, please set the clock to the correct time.`);\r\n\t\t\treturn null;\r\n\t\t}\r\n\r\n\t\tconst success = await Verifier.verify(tokenData, tokenSig);\r\n\t\tif (!success) {\r\n\t\t\tMonitor.warn(`verify failed: ${token}`);\r\n\t\t\tMonitor.warn(`challenge was: ${challenge}`);\r\n\t\t\tthis.send(`|nametaken|${name}|Your verification signature was invalid.`);\r\n\t\t\treturn null;\r\n\t\t}\r\n\r\n\t\t// future-proofing\r\n\t\tthis.s1 = tokenDataSplit[5];\r\n\t\tthis.s2 = tokenDataSplit[6];\r\n\t\tthis.s3 = tokenDataSplit[7];\r\n\r\n\t\treturn userType;\r\n\t}\r\n\t/**\r\n\t * Do a rename, passing and validating a login token.\r\n\t *\r\n\t * @param name The name you want\r\n\t * @param token Signed assertion returned from login server\r\n\t * @param newlyRegistered Make sure this account will identify as registered\r\n\t * @param connection The connection asking for the rename\r\n\t */\r\n\tasync rename(name: string, token: string, newlyRegistered: boolean, connection: Connection) {\r\n\t\tlet userid = toID(name);\r\n\t\tif (userid !== this.id) {\r\n\t\t\tfor (const roomid of this.games) {\r\n\t\t\t\tconst room = Rooms.get(roomid);\r\n\t\t\t\tif (!room?.game || room.game.ended) {\r\n\t\t\t\t\tthis.games.delete(roomid);\r\n\t\t\t\t\tconsole.log(`desynced roomgame ${roomid} renaming ${this.id} -> ${userid}`);\r\n\t\t\t\t\tcontinue;\r\n\t\t\t\t}\r\n\t\t\t\tif (room.game.allowRenames || !this.named) continue;\r\n\t\t\t\tthis.popup(`You can't change your name right now because you're in ${room.game.title}, which doesn't allow renaming.`);\r\n\t\t\t\treturn false;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif (!name) name = '';\r\n\t\tif (!/[a-zA-Z]/.test(name)) {\r\n\t\t\t// technically it's not \"taken\", but if your client doesn't warn you\r\n\t\t\t// before it gets to this stage it's your own fault for getting a\r\n\t\t\t// bad error message\r\n\t\t\tthis.send(`|nametaken||Your name must contain at least one letter.`);\r\n\t\t\treturn false;\r\n\t\t}\r\n\r\n\t\tif (userid.length > 18) {\r\n\t\t\tthis.send(`|nametaken||Your name must be 18 characters or shorter.`);\r\n\t\t\treturn false;\r\n\t\t}\r\n\t\tname = Chat.namefilter(name, this);\r\n\t\tif (userid !== toID(name)) {\r\n\t\t\tif (name) {\r\n\t\t\t\tname = userid;\r\n\t\t\t} else {\r\n\t\t\t\tuserid = '';\r\n\t\t\t}\r\n\t\t}\r\n\t\tif (this.registered) newlyRegistered = false;\r\n\r\n\t\tif (!userid) {\r\n\t\t\tthis.send(`|nametaken||Your name contains a banned word.`);\r\n\t\t\treturn false;\r\n\t\t} else {\r\n\t\t\tif (userid === this.id && !newlyRegistered) {\r\n\t\t\t\treturn this.forceRename(name, this.registered);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tconst userType = await this.validateToken(token, name, userid, connection);\r\n\t\tif (userType === null) return;\r\n\t\tif (userType === '1') newlyRegistered = false;\r\n\r\n\t\tif (!this.trusted && userType === '1') { // userType '1' means unregistered\r\n\t\t\tconst elapsed = Date.now() - this.lastNewNameTime;\r\n\t\t\tif (elapsed < NAMECHANGE_THROTTLE && !Config.nothrottle) {\r\n\t\t\t\tif (this.newNames >= NAMES_PER_THROTTLE) {\r\n\t\t\t\t\tthis.send(\r\n\t\t\t\t\t\t`|nametaken|${name}|You must wait ${Chat.toDurationString(NAMECHANGE_THROTTLE - elapsed)} more\r\n\t\t\t\t\t\tseconds before using another unregistered name.`\r\n\t\t\t\t\t);\r\n\t\t\t\t\treturn false;\r\n\t\t\t\t}\r\n\t\t\t\tthis.newNames++;\r\n\t\t\t} else {\r\n\t\t\t\tthis.lastNewNameTime = Date.now();\r\n\t\t\t\tthis.newNames = 1;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tthis.handleRename(name, userid, newlyRegistered, userType);\r\n\t}\r\n\r\n\thandleRename(name: string, userid: ID, newlyRegistered: boolean, userType: string) {\r\n\t\tconst registered = (userType !== '1');\r\n\r\n\t\tconst conflictUser = users.get(userid);\r\n\t\tif (conflictUser) {\r\n\t\t\t// unregistered users can only merge in limited situations\r\n\t\t\tlet canMerge = registered && conflictUser.registered;\r\n\t\t\tif (\r\n\t\t\t\t!registered && !conflictUser.registered && conflictUser.latestIp === this.latestIp &&\r\n\t\t\t\t!conflictUser.connected\r\n\t\t\t) {\r\n\t\t\t\tcanMerge = true;\r\n\t\t\t}\r\n\t\t\tif (!canMerge) {\r\n\t\t\t\tif (registered && !conflictUser.registered) {\r\n\t\t\t\t\t// user has just registered; don't merge just to be safe\r\n\t\t\t\t\tif (conflictUser !== this) conflictUser.resetName();\r\n\t\t\t\t} else {\r\n\t\t\t\t\tthis.send(`|nametaken|${name}|Someone is already using the name \"${conflictUser.name}\".`);\r\n\t\t\t\t\treturn false;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// user types:\r\n\t\t// 1: unregistered user\r\n\t\t// 2: registered user\r\n\t\t// 3: Pokemon Showdown system operator\r\n\t\t// 4: autoconfirmed\r\n\t\t// 5: permalocked\r\n\t\t// 6: permabanned\r\n\t\tif (registered) {\r\n\t\t\tif (userType === '3') {\r\n\t\t\t\tthis.isSysop = true;\r\n\t\t\t\tthis.isStaff = true;\r\n\t\t\t\tthis.trusted = userid;\r\n\t\t\t\tthis.autoconfirmed = userid;\r\n\t\t\t} else if (userType === '4') {\r\n\t\t\t\tthis.autoconfirmed = userid;\r\n\t\t\t} else if (userType === '5') {\r\n\t\t\t\tthis.permalocked = userid;\r\n\t\t\t\tvoid Punishments.lock(this, Date.now() + PERMALOCK_CACHE_TIME, userid, true, `Permalocked as ${name}`, true);\r\n\t\t\t} else if (userType === '6') {\r\n\t\t\t\tvoid Punishments.lock(this, Date.now() + PERMALOCK_CACHE_TIME, userid, true, `Permabanned as ${name}`, true);\r\n\t\t\t\tthis.disconnectAll();\r\n\t\t\t}\r\n\t\t}\r\n\t\tif (Users.isTrusted(userid)) {\r\n\t\t\tthis.trusted = userid;\r\n\t\t\tthis.autoconfirmed = userid;\r\n\t\t}\r\n\t\tif (this.trusted) {\r\n\t\t\tthis.locked = null;\r\n\t\t\tthis.namelocked = null;\r\n\t\t\tthis.permalocked = null;\r\n\t\t\tthis.semilocked = null;\r\n\t\t\tthis.destroyPunishmentTimer();\r\n\t\t}\r\n\r\n\t\tthis.isPublicBot = Users.isPublicBot(userid);\r\n\r\n\t\tChat.runHandlers('onRename', this, this.id, userid);\r\n\t\tlet user = users.get(userid);\r\n\t\tconst possibleUser = Users.get(userid);\r\n\t\tif (possibleUser?.namelocked) {\r\n\t\t\t// allows namelocked users to be merged\r\n\t\t\tuser = possibleUser;\r\n\t\t}\r\n\t\tif (user && user !== this) {\r\n\t\t\t// This user already exists; let's merge\r\n\t\t\tuser.merge(this);\r\n\r\n\t\t\tUsers.merge(user, this);\r\n\t\t\tfor (const id of this.previousIDs) {\r\n\t\t\t\tif (!user.previousIDs.includes(id)) user.previousIDs.push(id);\r\n\t\t\t}\r\n\t\t\tif (this.named && !user.previousIDs.includes(this.id)) user.previousIDs.push(this.id);\r\n\t\t\tthis.destroy();\r\n\r\n\t\t\tPunishments.checkName(user, userid, registered);\r\n\r\n\t\t\tRooms.global.checkAutojoin(user);\r\n\t\t\tRooms.global.joinOldBattles(this);\r\n\t\t\tChat.loginfilter(user, this, userType);\r\n\t\t\treturn true;\r\n\t\t}\r\n\r\n\t\tPunishments.checkName(this, userid, registered);\r\n\t\tif (this.namelocked) {\r\n\t\t\tChat.loginfilter(this, null, userType);\r\n\t\t\treturn false;\r\n\t\t}\r\n\r\n\t\t// rename success\r\n\t\tif (!this.forceRename(name, registered)) {\r\n\t\t\treturn false;\r\n\t\t}\r\n\t\tRooms.global.checkAutojoin(this);\r\n\t\tRooms.global.joinOldBattles(this);\r\n\t\tChat.loginfilter(this, null, userType);\r\n\t\treturn true;\r\n\t}\r\n\tforceRename(name: string, registered: boolean, isForceRenamed = false) {\r\n\t\t// skip the login server\r\n\t\tconst userid = toID(name);\r\n\r\n\t\tif (users.has(userid) && users.get(userid) !== this) {\r\n\t\t\treturn false;\r\n\t\t}\r\n\r\n\t\tconst oldname = this.name;\r\n\t\tconst oldid = this.id;\r\n\t\tif (userid !== this.id) {\r\n\t\t\tthis.cancelReady();\r\n\r\n\t\t\tif (!Users.move(this, userid)) {\r\n\t\t\t\treturn false;\r\n\t\t\t}\r\n\r\n\t\t\t// MMR is different for each userid\r\n\t\t\tthis.mmrCache = {};\r\n\r\n\t\t\tthis.updateGroup(registered);\r\n\t\t} else if (registered) {\r\n\t\t\tthis.updateGroup(registered);\r\n\t\t}\r\n\r\n\t\tif (this.named && oldid !== userid && !this.previousIDs.includes(oldid)) this.previousIDs.push(oldid);\r\n\t\tthis.name = name;\r\n\r\n\t\tconst joining = !this.named;\r\n\t\tthis.named = !userid.startsWith('guest') || !!this.namelocked;\r\n\r\n\t\tif (isForceRenamed) this.userMessage = '';\r\n\r\n\t\tfor (const connection of this.connections) {\r\n\t\t\t// console.log('' + name + ' renaming: socket ' + i + ' of ' + this.connections.length);\r\n\t\t\tconnection.send(this.getUpdateuserText());\r\n\t\t}\r\n\t\tfor (const roomid of this.games) {\r\n\t\t\tconst room = Rooms.get(roomid);\r\n\t\t\tif (!room) {\r\n\t\t\t\tMonitor.warn(`while renaming, room ${roomid} expired for user ${this.id} in rooms ${[...this.inRooms]} and games ${[...this.games]}`);\r\n\t\t\t\tthis.games.delete(roomid);\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\t\t\tif (!room.game) {\r\n\t\t\t\tMonitor.warn(`game desync for user ${this.id} in room ${room.roomid}`);\r\n\t\t\t\tthis.games.delete(roomid);\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\t\t\troom.game.onRename(this, oldid, joining, isForceRenamed);\r\n\t\t}\r\n\t\tfor (const roomid of this.inRooms) {\r\n\t\t\tRooms.get(roomid)!.onRename(this, oldid, joining);\r\n\t\t}\r\n\t\tif (isForceRenamed) this.trackRename = oldname;\r\n\t\treturn true;\r\n\t}\r\n\tgetUpdateuserText() {\r\n\t\tconst named = this.named ? 1 : 0;\r\n\t\tconst settings = {\r\n\t\t\t...this.settings,\r\n\t\t\t// Battle privacy state needs to be propagated in addition to regular settings so that the\r\n\t\t\t// 'Ban spectators' checkbox on the client can be kept in sync (and disable privacy correctly)\r\n\t\t\thiddenNextBattle: this.battleSettings.hidden,\r\n\t\t\tinviteOnlyNextBattle: this.battleSettings.inviteOnly,\r\n\t\t\tlanguage: this.language,\r\n\t\t};\r\n\t\treturn `|updateuser|${this.getIdentityWithStatus()}|${named}|${this.avatar}|${JSON.stringify(settings)}`;\r\n\t}\r\n\tupdate() {\r\n\t\tthis.send(this.getUpdateuserText());\r\n\t}\r\n\t/**\r\n\t * If Alice logs into Bob's account, and Bob is currently logged into PS,\r\n\t * their connections will be merged, so that both `Connection`s are attached\r\n\t * to the Alice `User`.\r\n\t *\r\n\t * In this function, `this` is Bob, and `oldUser` is Alice.\r\n\t *\r\n\t * This is a pretty routine thing: If Alice opens PS, then opens PS again in\r\n\t * a new tab, PS will first create a Guest `User`, then automatically log in\r\n\t * and merge that Guest `User` into the Alice `User` from the first tab.\r\n\t */\r\n\tmerge(oldUser: User) {\r\n\t\toldUser.cancelReady();\r\n\t\tfor (const roomid of oldUser.inRooms) {\r\n\t\t\tRooms.get(roomid)!.onLeave(oldUser);\r\n\t\t}\r\n\r\n\t\tconst oldLocked = this.locked;\r\n\t\tconst oldSemilocked = this.semilocked;\r\n\r\n\t\tif (!oldUser.semilocked) this.semilocked = null;\r\n\r\n\t\t// If either user is unlocked and neither is locked by name, remove the lock.\r\n\t\t// Otherwise, keep any locks that were by name.\r\n\t\tif (\r\n\t\t\t(!oldUser.locked || !this.locked) &&\r\n\t\t\toldUser.locked !== oldUser.id &&\r\n\t\t\tthis.locked !== this.id &&\r\n\t\t\t// Only unlock if no previous names are locked\r\n\t\t\t!oldUser.previousIDs.some(id => !!Punishments.hasPunishType(id, 'LOCK'))\r\n\t\t) {\r\n\t\t\tthis.locked = null;\r\n\t\t\tthis.destroyPunishmentTimer();\r\n\t\t} else if (this.locked !== this.id) {\r\n\t\t\tthis.locked = oldUser.locked;\r\n\t\t}\r\n\t\tif (oldUser.autoconfirmed) this.autoconfirmed = oldUser.autoconfirmed;\r\n\r\n\t\tthis.updateGroup(this.registered, true);\r\n\t\tif (oldLocked !== this.locked || oldSemilocked !== this.semilocked) this.updateIdentity();\r\n\r\n\t\t// We only propagate the 'busy' statusType through merging - merging is\r\n\t\t// active enough that the user should no longer be in the 'idle' state.\r\n\t\t// Doing this before merging connections ensures the updateuser message\r\n\t\t// shows the correct idle state.\r\n\t\tconst isBusy = this.statusType === 'busy' || oldUser.statusType === 'busy';\r\n\t\tthis.setStatusType(isBusy ? 'busy' : 'online');\r\n\r\n\t\tfor (const connection of oldUser.connections) {\r\n\t\t\tthis.mergeConnection(connection);\r\n\t\t}\r\n\t\toldUser.inRooms.clear();\r\n\t\toldUser.connections = [];\r\n\r\n\t\tif (oldUser.chatQueue) {\r\n\t\t\tif (!this.chatQueue) this.chatQueue = [];\r\n\t\t\tthis.chatQueue.push(...oldUser.chatQueue);\r\n\t\t\toldUser.clearChatQueue();\r\n\t\t\tif (!this.chatQueueTimeout) this.startChatQueue();\r\n\t\t}\r\n\r\n\t\tthis.s1 = oldUser.s1;\r\n\t\tthis.s2 = oldUser.s2;\r\n\t\tthis.s3 = oldUser.s3;\r\n\r\n\t\t// merge IPs\r\n\t\tfor (const ip of oldUser.ips) {\r\n\t\t\tif (!this.ips.includes(ip)) this.ips.push(ip);\r\n\t\t}\r\n\r\n\t\tif (oldUser.isSysop) {\r\n\t\t\tthis.isSysop = true;\r\n\t\t\toldUser.isSysop = false;\r\n\t\t}\r\n\r\n\t\toldUser.ips = [];\r\n\t\tthis.latestIp = oldUser.latestIp;\r\n\t\tthis.latestHost = oldUser.latestHost;\r\n\t\tthis.latestHostType = oldUser.latestHostType;\r\n\t\tthis.userMessage = oldUser.userMessage || this.userMessage || '';\r\n\r\n\t\toldUser.markDisconnected();\r\n\t}\r\n\tmergeConnection(connection: Connection) {\r\n\t\t// the connection has changed name to this user's username, and so is\r\n\t\t// being merged into this account\r\n\t\tif (!this.connected) {\r\n\t\t\tthis.connected = true;\r\n\t\t\tUsers.onlineCount++;\r\n\t\t}\r\n\t\tif (connection.connectedAt > this.lastConnected) {\r\n\t\t\tthis.lastConnected = connection.connectedAt;\r\n\t\t}\r\n\t\tthis.connections.push(connection);\r\n\r\n\t\t// console.log('' + this.name + ' merging: connection ' + connection.socket.id);\r\n\t\tconnection.send(this.getUpdateuserText());\r\n\t\tconnection.user = this;\r\n\t\tfor (const roomid of connection.inRooms) {\r\n\t\t\tconst room = Rooms.get(roomid)!;\r\n\t\t\tif (!this.inRooms.has(roomid)) {\r\n\t\t\t\tif (Punishments.checkNameInRoom(this, room.roomid)) {\r\n\t\t\t\t\t// the connection was in a room that this user is banned from\r\n\t\t\t\t\tconnection.sendTo(room.roomid, `|deinit`);\r\n\t\t\t\t\tconnection.leaveRoom(room);\r\n\t\t\t\t\tcontinue;\r\n\t\t\t\t}\r\n\t\t\t\troom.onJoin(this, connection);\r\n\t\t\t\tthis.inRooms.add(roomid);\r\n\t\t\t}\r\n\t\t\tif (room.game && room.game.onUpdateConnection) {\r\n\t\t\t\t// Yes, this is intentionally supposed to call onConnect twice\r\n\t\t\t\t// during a normal login. Override onUpdateConnection if you\r\n\t\t\t\t// don't want this behavior.\r\n\t\t\t\troom.game.onUpdateConnection(this, connection);\r\n\t\t\t}\r\n\t\t}\r\n\t\tthis.updateReady(connection);\r\n\t}\r\n\tdebugData() {\r\n\t\tlet str = `${this.tempGroup}${this.name} (${this.id})`;\r\n\t\tfor (const [i, connection] of this.connections.entries()) {\r\n\t\t\tstr += ` socket${i}[`;\r\n\t\t\tstr += [...connection.inRooms].join(`, `);\r\n\t\t\tstr += `]`;\r\n\t\t}\r\n\t\tif (!this.connected) str += ` (DISCONNECTED)`;\r\n\t\treturn str;\r\n\t}\r\n\t/**\r\n\t * Updates several group-related attributes for the user, namely:\r\n\t * User#group, User#registered, User#isStaff, User#trusted\r\n\t *\r\n\t * Note that unlike the others, User#trusted isn't reset every\r\n\t * name change.\r\n\t */\r\n\tupdateGroup(registered: boolean, isMerge?: boolean) {\r\n\t\tif (!registered) {\r\n\t\t\tthis.registered = false;\r\n\t\t\tthis.tempGroup = Users.Auth.defaultSymbol();\r\n\t\t\tthis.isStaff = false;\r\n\t\t\treturn;\r\n\t\t}\r\n\t\tthis.registered = true;\r\n\t\tif (!isMerge) this.tempGroup = globalAuth.get(this.id);\r\n\r\n\t\tUsers.Avatars?.handleLogin(this);\r\n\r\n\t\tconst groupInfo = Config.groups[this.tempGroup];\r\n\t\tthis.isStaff = !!(groupInfo && (groupInfo.lock || groupInfo.root));\r\n\t\tif (!this.isStaff) {\r\n\t\t\tconst rank = Rooms.get('staff')?.auth.getDirect(this.id);\r\n\t\t\tthis.isStaff = !!(rank && rank !== '*' && rank !== Users.Auth.defaultSymbol());\r\n\t\t}\r\n\t\tif (this.trusted) {\r\n\t\t\tif (this.locked && this.permalocked) {\r\n\t\t\t\tMonitor.log(`[CrisisMonitor] Trusted user '${this.id}' is ${this.permalocked !== this.id ? `an alt of permalocked user '${this.permalocked}'` : `a permalocked user`}, and was automatically demoted from ${this.distrust()}.`);\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\t\t\tthis.locked = null;\r\n\t\t\tthis.namelocked = null;\r\n\t\t\tthis.destroyPunishmentTimer();\r\n\t\t}\r\n\t\tif (this.autoconfirmed && this.semilocked) {\r\n\t\t\tif (this.semilocked.startsWith('#sharedip')) {\r\n\t\t\t\tthis.semilocked = null;\r\n\t\t\t} else if (this.semilocked === '#dnsbl') {\r\n\t\t\t\tthis.popup(`You are locked because someone using your IP has spammed/hacked other websites. This usually means either you're using a proxy, you're in a country where other people commonly hack, or you have a virus on your computer that's spamming websites.`);\r\n\t\t\t\tthis.semilocked = '#dnsbl.' as PunishType;\r\n\t\t\t}\r\n\t\t}\r\n\t\tif (this.settings.blockPMs && this.can('lock') && !this.can('bypassall')) this.settings.blockPMs = false;\r\n\t}\r\n\t/**\r\n\t * Set a user's group. Pass (' ', true) to force trusted\r\n\t * status without giving the user a group.\r\n\t */\r\n\tsetGroup(group: GroupSymbol, forceTrusted = false) {\r\n\t\tif (!group) throw new Error(`Falsy value passed to setGroup`);\r\n\t\tthis.tempGroup = group;\r\n\t\tconst groupInfo = Config.groups[this.tempGroup];\r\n\t\tthis.isStaff = !!(groupInfo && (groupInfo.lock || groupInfo.root));\r\n\t\tif (!this.isStaff) {\r\n\t\t\tconst rank = Rooms.get('staff')?.auth.getDirect(this.id);\r\n\t\t\tthis.isStaff = !!(rank && rank !== '*' && rank !== Users.Auth.defaultSymbol());\r\n\t\t}\r\n\t\tRooms.global.checkAutojoin(this);\r\n\t\tif (this.registered) {\r\n\t\t\tif (forceTrusted || this.tempGroup !== Users.Auth.defaultSymbol()) {\r\n\t\t\t\tglobalAuth.set(this.id, this.tempGroup);\r\n\t\t\t\tthis.trusted = this.id;\r\n\t\t\t\tthis.autoconfirmed = this.id;\r\n\t\t\t} else {\r\n\t\t\t\tglobalAuth.delete(this.id);\r\n\t\t\t\tthis.trusted = '';\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t/**\r\n\t * Demotes a user from anything that grants trusted status.\r\n\t * Returns an array describing what the user was demoted from.\r\n\t */\r\n\tdistrust() {\r\n\t\tif (!this.trusted) return;\r\n\t\tconst userid = this.trusted;\r\n\t\tconst removed = [];\r\n\t\tconst globalGroup = globalAuth.get(userid);\r\n\t\tif (globalGroup && globalGroup !== ' ') {\r\n\t\t\tremoved.push(globalAuth.get(userid));\r\n\t\t}\r\n\t\tfor (const room of Rooms.global.chatRooms) {\r\n\t\t\tif (!room.settings.isPrivate && room.auth.isStaff(userid)) {\r\n\t\t\t\tlet oldGroup = room.auth.getDirect(userid) as string;\r\n\t\t\t\tif (oldGroup === ' ') {\r\n\t\t\t\t\toldGroup = 'whitelist in ';\r\n\t\t\t\t} else {\r\n\t\t\t\t\troom.auth.set(userid, '+');\r\n\t\t\t\t}\r\n\t\t\t\tremoved.push(`${oldGroup}${room.roomid}`);\r\n\t\t\t}\r\n\t\t}\r\n\t\tthis.trusted = '';\r\n\t\tglobalAuth.set(userid, Users.Auth.defaultSymbol());\r\n\t\treturn removed;\r\n\t}\r\n\tmarkDisconnected() {\r\n\t\tif (!this.connected) return;\r\n\t\tChat.runHandlers('onDisconnect', this);\r\n\t\tthis.connected = false;\r\n\t\tUsers.onlineCount--;\r\n\t\tthis.lastDisconnected = Date.now();\r\n\t\tif (!this.registered) {\r\n\t\t\t// for \"safety\"\r\n\t\t\tthis.tempGroup = Users.Auth.defaultSymbol();\r\n\t\t\tthis.isSysop = false; // should never happen\r\n\t\t\tthis.isStaff = false;\r\n\t\t\t// This isn't strictly necessary since we don't reuse User objects\r\n\t\t\t// for PS, but just in case.\r\n\t\t\t// We're not resetting .trusted/.autoconfirmed so those accounts\r\n\t\t\t// can still be locked after logout.\r\n\t\t}\r\n\t\t// NOTE: can't do a this.update(...) at this point because we're no longer connected.\r\n\t}\r\n\tonDisconnect(connection: Connection) {\r\n\t\t// slightly safer to do this here so that we can do this before Conn#user is nulled.\r\n\t\tif (connection.openPages) {\r\n\t\t\tfor (const page of connection.openPages) {\r\n\t\t\t\tChat.handleRoomClose(page as RoomID, this, connection);\r\n\t\t\t}\r\n\t\t}\r\n\t\tfor (const [i, connected] of this.connections.entries()) {\r\n\t\t\tif (connected === connection) {\r\n\t\t\t\tthis.connections.splice(i, 1);\r\n\t\t\t\t// console.log('DISCONNECT: ' + this.id);\r\n\t\t\t\tif (!this.connections.length) {\r\n\t\t\t\t\tthis.markDisconnected();\r\n\t\t\t\t}\r\n\t\t\t\tfor (const roomid of connection.inRooms) {\r\n\t\t\t\t\tthis.leaveRoom(Rooms.get(roomid)!, connection);\r\n\t\t\t\t}\r\n\t\t\t\tbreak;\r\n\t\t\t}\r\n\t\t}\r\n\t\tif (!this.connections.length) {\r\n\t\t\tfor (const roomid of this.inRooms) {\r\n\t\t\t\t// should never happen.\r\n\t\t\t\tMonitor.debug(`!! room miscount: ${roomid} not left`);\r\n\t\t\t\tRooms.get(roomid)!.onLeave(this);\r\n\t\t\t}\r\n\t\t\t// cleanup\r\n\t\t\tthis.inRooms.clear();\r\n\t\t\tif (!this.named && !this.previousIDs.length) {\r\n\t\t\t\t// user never chose a name (and therefore never talked/battled)\r\n\t\t\t\t// there's no need to keep track of this user, so we can\r\n\t\t\t\t// immediately deallocate\r\n\t\t\t\tthis.destroy();\r\n\t\t\t} else {\r\n\t\t\t\tthis.cancelReady();\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\tdisconnectAll() {\r\n\t\t// Disconnects a user from the server\r\n\t\tthis.clearChatQueue();\r\n\t\tlet connection = null;\r\n\t\tthis.markDisconnected();\r\n\t\tfor (let i = this.connections.length - 1; i >= 0; i--) {\r\n\t\t\t// console.log('DESTROY: ' + this.id);\r\n\t\t\tconnection = this.connections[i];\r\n\t\t\tfor (const roomid of connection.inRooms) {\r\n\t\t\t\tthis.leaveRoom(Rooms.get(roomid)!, connection);\r\n\t\t\t}\r\n\t\t\tconnection.destroy();\r\n\t\t}\r\n\t\tif (this.connections.length) {\r\n\t\t\t// should never happen\r\n\t\t\tthrow new Error(`Failed to drop all connections for ${this.id}`);\r\n\t\t}\r\n\t\tfor (const roomid of this.inRooms) {\r\n\t\t\t// should never happen.\r\n\t\t\tthrow new Error(`Room miscount: ${roomid} not left for ${this.id}`);\r\n\t\t}\r\n\t\tthis.inRooms.clear();\r\n\t}\r\n\t/**\r\n\t * If this user is included in the returned list of\r\n\t * alts (i.e. when forPunishment is true), they will always be the first element of that list.\r\n\t */\r\n\tgetAltUsers(includeTrusted = false, forPunishment = false) {\r\n\t\tlet alts = findUsers([this.getLastId()], this.ips, {includeTrusted, forPunishment});\r\n\t\talts = alts.filter(user => user !== this);\r\n\t\tif (forPunishment) alts.unshift(this);\r\n\t\treturn alts;\r\n\t}\r\n\tgetLastName() {\r\n\t\tif (this.named) return this.name;\r\n\t\tconst lastName = this.previousIDs.length ? this.previousIDs[this.previousIDs.length - 1] : this.name;\r\n\t\treturn `[${lastName}]`;\r\n\t}\r\n\tgetLastId() {\r\n\t\tif (this.named) return this.id;\r\n\t\treturn (this.previousIDs.length ? this.previousIDs[this.previousIDs.length - 1] : this.id);\r\n\t}\r\n\tasync tryJoinRoom(roomid: RoomID | Room, connection: Connection) {\r\n\t\troomid = roomid && (roomid as Room).roomid ? (roomid as Room).roomid : roomid as RoomID;\r\n\t\tconst room = Rooms.search(roomid);\r\n\t\tif (!room && roomid.startsWith('view-')) {\r\n\t\t\treturn Chat.resolvePage(roomid, this, connection);\r\n\t\t}\r\n\t\tif (!room?.checkModjoin(this)) {\r\n\t\t\tif (!this.named) {\r\n\t\t\t\treturn Rooms.RETRY_AFTER_LOGIN;\r\n\t\t\t} else {\r\n\t\t\t\tif (room) {\r\n\t\t\t\t\tconnection.sendTo(roomid, `|noinit|joinfailed|The room \"${roomid}\" is invite-only, and you haven't been invited.`);\r\n\t\t\t\t} else {\r\n\t\t\t\t\tconnection.sendTo(roomid, `|noinit|nonexistent|The room \"${roomid}\" does not exist.`);\r\n\t\t\t\t}\r\n\t\t\t\treturn false;\r\n\t\t\t}\r\n\t\t}\r\n\t\tif ((room as GameRoom).tour) {\r\n\t\t\tconst errorMessage = (room as GameRoom).tour!.onBattleJoin(room as GameRoom, this);\r\n\t\t\tif (errorMessage) {\r\n\t\t\t\tconnection.sendTo(roomid, `|noinit|joinfailed|${errorMessage}`);\r\n\t\t\t\treturn false;\r\n\t\t\t}\r\n\t\t}\r\n\t\tif (room.settings.isPrivate) {\r\n\t\t\tif (!this.named) {\r\n\t\t\t\treturn Rooms.RETRY_AFTER_LOGIN;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif (!this.can('bypassall') && Punishments.isRoomBanned(this, room.roomid)) {\r\n\t\t\tconnection.sendTo(roomid, `|noinit|joinfailed|You are banned from the room \"${roomid}\".`);\r\n\t\t\treturn false;\r\n\t\t}\r\n\r\n\t\tif (room.roomid.startsWith('groupchat-') && !room.parent) {\r\n\t\t\tconst groupchatbanned = Punishments.isGroupchatBanned(this);\r\n\t\t\tif (groupchatbanned) {\r\n\t\t\t\tconst expireText = Punishments.checkPunishmentExpiration(groupchatbanned);\r\n\t\t\t\tconnection.sendTo(roomid, `|noinit|joinfailed|You are banned from using groupchats${expireText}.`);\r\n\t\t\t\treturn false;\r\n\t\t\t}\r\n\t\t\tPunishments.monitorGroupchatJoin(room, this);\r\n\t\t}\r\n\r\n\t\tif (Rooms.aliases.get(roomid) === room.roomid) {\r\n\t\t\tconnection.send(`>${roomid}\\n|deinit`);\r\n\t\t}\r\n\r\n\t\tthis.joinRoom(room, connection);\r\n\t\treturn true;\r\n\t}\r\n\tjoinRoom(roomid: RoomID | Room, connection: Connection | null = null) {\r\n\t\tconst room = Rooms.get(roomid);\r\n\t\tif (!room) throw new Error(`Room not found: ${roomid}`);\r\n\t\tif (!connection) {\r\n\t\t\tfor (const curConnection of this.connections) {\r\n\t\t\t\tthis.joinRoom(room, curConnection);\r\n\t\t\t}\r\n\t\t\treturn;\r\n\t\t}\r\n\t\tif (!connection.inRooms.has(room.roomid)) {\r\n\t\t\tif (!this.inRooms.has(room.roomid)) {\r\n\t\t\t\tthis.inRooms.add(room.roomid);\r\n\t\t\t\troom.onJoin(this, connection);\r\n\t\t\t}\r\n\t\t\tconnection.joinRoom(room);\r\n\t\t\troom.onConnect(this, connection);\r\n\t\t}\r\n\t}\r\n\tleaveRoom(room: Room | string, connection: Connection | null = null) {\r\n\t\troom = Rooms.get(room)!;\r\n\t\tif (!this.inRooms.has(room.roomid)) {\r\n\t\t\treturn false;\r\n\t\t}\r\n\t\tfor (const curConnection of this.connections) {\r\n\t\t\tif (connection && curConnection !== connection) continue;\r\n\t\t\tif (curConnection.inRooms.has(room.roomid)) {\r\n\t\t\t\tcurConnection.sendTo(room.roomid, `|deinit`);\r\n\t\t\t\tcurConnection.leaveRoom(room);\r\n\t\t\t}\r\n\t\t\tif (connection) break;\r\n\t\t}\r\n\r\n\t\tlet stillInRoom = false;\r\n\t\tif (connection) {\r\n\t\t\t// @ts-ignore TypeScript inferring wrong type for room\r\n\t\t\tstillInRoom = this.connections.some(conn => conn.inRooms.has(room.roomid));\r\n\t\t}\r\n\t\tif (!stillInRoom) {\r\n\t\t\troom.onLeave(this);\r\n\t\t\tthis.inRooms.delete(room.roomid);\r\n\t\t}\r\n\t}\r\n\r\n\tcancelReady() {\r\n\t\t// setting variables because this can't be short-circuited\r\n\t\tconst searchesCancelled = Ladders.cancelSearches(this);\r\n\t\tconst challengesCancelled = Ladders.challenges.clearFor(this.id, 'they changed their username');\r\n\t\tif (searchesCancelled || challengesCancelled) {\r\n\t\t\tthis.popup(`Your searches and challenges have been cancelled because you changed your username.`);\r\n\t\t}\r\n\t\t// cancel tour challenges\r\n\t\t// no need for a popup because users can't change their name while in a tournament anyway\r\n\t\tfor (const roomid of this.games) {\r\n\t\t\tconst room = Rooms.get(roomid);\r\n\t\t\t// @ts-ignore Tournaments aren't TS'd yet\r\n\t\t\tif (room.game && room.game.cancelChallenge) room.game.cancelChallenge(this);\r\n\t\t}\r\n\t}\r\n\tupdateReady(connection: Connection | null = null) {\r\n\t\tLadders.updateSearch(this, connection);\r\n\t\tLadders.challenges.updateFor(connection || this);\r\n\t}\r\n\tupdateSearch(connection: Connection | null = null) {\r\n\t\tLadders.updateSearch(this, connection);\r\n\t}\r\n\t/**\r\n\t * Moves the user's connections in a given room to another room.\r\n\t * This function's main use case is for when a room is renamed.\r\n\t */\r\n\tmoveConnections(oldRoomID: RoomID, newRoomID: RoomID) {\r\n\t\tthis.inRooms.delete(oldRoomID);\r\n\t\tthis.inRooms.add(newRoomID);\r\n\t\tfor (const connection of this.connections) {\r\n\t\t\tconnection.inRooms.delete(oldRoomID);\r\n\t\t\tconnection.inRooms.add(newRoomID);\r\n\t\t\tSockets.roomRemove(connection.worker, oldRoomID, connection.socketid);\r\n\t\t\tSockets.roomAdd(connection.worker, newRoomID, connection.socketid);\r\n\t\t}\r\n\t}\r\n\t/**\r\n\t * The user says message in room.\r\n\t * Returns false if the rest of the user's messages should be discarded.\r\n\t */\r\n\tchat(message: string, room: Room | null, connection: Connection) {\r\n\t\tconst now = Date.now();\r\n\t\tconst noThrottle = this.hasSysopAccess() || Config.nothrottle;\r\n\r\n\t\tif (message.startsWith('/cmd userdetails') || message.startsWith('>> ') || noThrottle) {\r\n\t\t\t// certain commands are exempt from the queue\r\n\t\t\tMonitor.activeIp = connection.ip;\r\n\t\t\tChat.parse(message, room, this, connection);\r\n\t\t\tMonitor.activeIp = null;\r\n\t\t\tif (noThrottle) return;\r\n\t\t\treturn false; // but end the loop here\r\n\t\t}\r\n\r\n\t\tconst throttleDelay = this.isPublicBot ? THROTTLE_DELAY_PUBLIC_BOT : this.trusted ? THROTTLE_DELAY_TRUSTED :\r\n\t\t\tTHROTTLE_DELAY;\r\n\r\n\t\tif (this.chatQueueTimeout) {\r\n\t\t\tif (!this.chatQueue) this.chatQueue = []; // this should never happen\r\n\t\t\tif (this.chatQueue.length >= THROTTLE_BUFFER_LIMIT - 1) {\r\n\t\t\t\tconnection.sendTo(\r\n\t\t\t\t\troom,\r\n\t\t\t\t\t`|raw|Your message was not sent because you've been typing too quickly.`\r\n\t\t\t\t);\r\n\t\t\t\treturn false;\r\n\t\t\t} else {\r\n\t\t\t\tthis.chatQueue.push([message, room ? room.roomid : '', connection]);\r\n\t\t\t}\r\n\t\t} else if (now < this.lastChatMessage + throttleDelay) {\r\n\t\t\tthis.chatQueue = [[message, room ? room.roomid : '', connection]];\r\n\t\t\tthis.startChatQueue(throttleDelay - (now - this.lastChatMessage));\r\n\t\t} else {\r\n\t\t\tthis.lastChatMessage = now;\r\n\t\t\tMonitor.activeIp = connection.ip;\r\n\t\t\tChat.parse(message, room, this, connection);\r\n\t\t\tMonitor.activeIp = null;\r\n\t\t}\r\n\t}\r\n\tstartChatQueue(delay: number | null = null) {\r\n\t\tif (delay === null) {\r\n\t\t\tdelay = (this.isPublicBot ? THROTTLE_DELAY_PUBLIC_BOT : this.trusted ? THROTTLE_DELAY_TRUSTED :\r\n\t\t\t\tTHROTTLE_DELAY) - (Date.now() - this.lastChatMessage);\r\n\t\t}\r\n\r\n\t\tthis.chatQueueTimeout = setTimeout(\r\n\t\t\t() => this.processChatQueue(),\r\n\t\t\tdelay\r\n\t\t);\r\n\t}\r\n\tclearChatQueue() {\r\n\t\tthis.chatQueue = null;\r\n\t\tif (this.chatQueueTimeout) {\r\n\t\t\tclearTimeout(this.chatQueueTimeout);\r\n\t\t\tthis.chatQueueTimeout = null;\r\n\t\t}\r\n\t}\r\n\tprocessChatQueue(): void {\r\n\t\tthis.chatQueueTimeout = null;\r\n\t\tif (!this.chatQueue) return;\r\n\t\tconst queueElement = this.chatQueue.shift();\r\n\t\tif (!queueElement) {\r\n\t\t\tthis.chatQueue = null;\r\n\t\t\treturn;\r\n\t\t}\r\n\t\tconst [message, roomid, connection] = queueElement;\r\n\t\tif (!connection.user) {\r\n\t\t\t// connection disconnected, chat queue should not be big enough\r\n\t\t\t// for recursion to be an issue, also didn't ES6 spec tail\r\n\t\t\t// recursion at some point?\r\n\t\t\treturn this.processChatQueue();\r\n\t\t}\r\n\r\n\t\tthis.lastChatMessage = new Date().getTime();\r\n\r\n\t\tconst room = Rooms.get(roomid);\r\n\t\tif (room || !roomid) {\r\n\t\t\tMonitor.activeIp = connection.ip;\r\n\t\t\tChat.parse(message, room, this, connection);\r\n\t\t\tMonitor.activeIp = null;\r\n\t\t} else {\r\n\t\t\t// room no longer exists; do nothing\r\n\t\t}\r\n\r\n\t\tconst throttleDelay = this.isPublicBot ? THROTTLE_DELAY_PUBLIC_BOT : this.trusted ? THROTTLE_DELAY_TRUSTED :\r\n\t\t\tTHROTTLE_DELAY;\r\n\r\n\t\tif (this.chatQueue.length) {\r\n\t\t\tthis.chatQueueTimeout = setTimeout(() => this.processChatQueue(), throttleDelay);\r\n\t\t} else {\r\n\t\t\tthis.chatQueue = null;\r\n\t\t}\r\n\t}\r\n\tsetStatusType(type: StatusType) {\r\n\t\tif (type === this.statusType) return;\r\n\t\tthis.statusType = type;\r\n\t\tthis.updateIdentity();\r\n\t\tthis.update();\r\n\t}\r\n\tsetUserMessage(message: string) {\r\n\t\tif (message === this.userMessage) return;\r\n\t\tthis.userMessage = message;\r\n\t\tthis.updateIdentity();\r\n\t}\r\n\tclearStatus(type: StatusType = this.statusType) {\r\n\t\tthis.statusType = type;\r\n\t\tthis.userMessage = '';\r\n\t\tthis.updateIdentity();\r\n\t}\r\n\tgetAccountStatusString() {\r\n\t\treturn this.trusted === this.id ? `[trusted]` :\r\n\t\t\tthis.autoconfirmed === this.id ? `[ac]` :\r\n\t\t\tthis.registered ? `[registered]` :\r\n\t\t\t``;\r\n\t}\r\n\tdestroy() {\r\n\t\t// deallocate user\r\n\t\tfor (const roomid of this.games) {\r\n\t\t\tconst room = Rooms.get(roomid);\r\n\t\t\tif (!room) {\r\n\t\t\t\tMonitor.warn(`while deallocating, room ${roomid} did not exist for ${this.id} in rooms ${[...this.inRooms]} and games ${[...this.games]}`);\r\n\t\t\t\tthis.games.delete(roomid);\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\t\t\tconst game = room.game;\r\n\t\t\tif (!game) {\r\n\t\t\t\tMonitor.warn(`while deallocating, room ${roomid} did not have a game for ${this.id} in rooms ${[...this.inRooms]} and games ${[...this.games]}`);\r\n\t\t\t\tthis.games.delete(roomid);\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\t\t\tif (game.ended) continue;\r\n\t\t\tif (game.forfeit) game.forfeit(this);\r\n\t\t}\r\n\t\tthis.clearChatQueue();\r\n\t\tthis.destroyPunishmentTimer();\r\n\t\tUsers.delete(this);\r\n\t}\r\n\tdestroyPunishmentTimer() {\r\n\t\tif (this.punishmentTimer) {\r\n\t\t\tclearTimeout(this.punishmentTimer);\r\n\t\t\tthis.punishmentTimer = null;\r\n\t\t}\r\n\t}\r\n\ttoString() {\r\n\t\treturn this.id;\r\n\t}\r\n}\r\n\r\n/*********************************************************\r\n * Inactive user pruning\r\n *********************************************************/\r\n\r\nfunction pruneInactive(threshold: number) {\r\n\tconst now = Date.now();\r\n\tfor (const user of users.values()) {\r\n\t\tif (user.statusType === 'online') {\r\n\t\t\t// check if we should set status to idle\r\n\t\t\tconst awayTimer = user.can('lock') ? STAFF_IDLE_TIMER : IDLE_TIMER;\r\n\t\t\tconst bypass = !user.can('bypassall') && (\r\n\t\t\t\tuser.can('bypassafktimer') ||\r\n\t\t\t\tArray.from(user.inRooms).some(room => user.can('bypassafktimer', null, Rooms.get(room)!))\r\n\t\t\t);\r\n\t\t\tif (!bypass && !user.connections.some(connection => now - connection.lastActiveTime < awayTimer)) {\r\n\t\t\t\tuser.setStatusType('idle');\r\n\t\t\t}\r\n\t\t}\r\n\t\tif (!user.connected && (now - user.lastDisconnected) > threshold) {\r\n\t\t\tuser.destroy();\r\n\t\t}\r\n\t\tif (!user.can('addhtml')) {\r\n\t\t\tfor (const connection of user.connections) {\r\n\t\t\t\tif (now - connection.lastActiveTime > CONNECTION_EXPIRY_TIME) {\r\n\t\t\t\t\tconnection.destroy();\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}\r\n\r\nfunction logGhostConnections(threshold: number): Promise {\r\n\tconst buffer = [];\r\n\tfor (const connection of connections.values()) {\r\n\t\t// If the connection's been around for at least a week and it doesn't\r\n\t\t// use raw WebSockets (which doesn't have any kind of keepalive or\r\n\t\t// timeouts on it), log it.\r\n\t\tif (connection.protocol !== 'websocket-raw' && connection.connectedAt <= Date.now() - threshold) {\r\n\t\t\tconst timestamp = Chat.toTimestamp(new Date(connection.connectedAt));\r\n\t\t\tconst now = Chat.toTimestamp(new Date());\r\n\t\t\tconst log = `Connection ${connection.id} from ${connection.ip} with protocol \"${connection.protocol}\" has been around since ${timestamp} (currently ${now}).`;\r\n\t\t\tbuffer.push(log);\r\n\t\t}\r\n\t}\r\n\treturn buffer.length ?\r\n\t\tFS(`logs/ghosts-${process.pid}.log`).append(buffer.join('\\r\\n') + '\\r\\n') :\r\n\t\tPromise.resolve();\r\n}\r\n\r\n/*********************************************************\r\n * Routing\r\n *********************************************************/\r\n\r\nfunction socketConnect(\r\n\tworker: ProcessManager.StreamWorker,\r\n\tworkerid: number,\r\n\tsocketid: string,\r\n\tip: string,\r\n\tprotocol: string\r\n) {\r\n\tconst id = '' + workerid + '-' + socketid;\r\n\tconst connection = new Connection(id, worker, socketid, null, ip, protocol);\r\n\tconnections.set(id, connection);\r\n\r\n\tconst banned = Punishments.checkIpBanned(connection);\r\n\tif (banned) {\r\n\t\treturn connection.destroy();\r\n\t}\r\n\t// Emergency mode connections logging\r\n\tif (Config.emergency) {\r\n\t\tvoid FS('logs/cons.emergency.log').append('[' + ip + ']\\n');\r\n\t}\r\n\r\n\tconst user = new User(connection);\r\n\tconnection.user = user;\r\n\tvoid Punishments.checkIp(user, connection);\r\n\t// Generate 1024-bit challenge string.\r\n\trequire('crypto').randomBytes(128, (err: Error | null, buffer: Buffer) => {\r\n\t\tif (err) {\r\n\t\t\t// It's not clear what sort of condition could cause this.\r\n\t\t\t// For now, we'll basically assume it can't happen.\r\n\t\t\tMonitor.crashlog(err, 'randomBytes');\r\n\t\t\t// This is pretty crude, but it's the easiest way to deal\r\n\t\t\t// with this case, which should be impossible anyway.\r\n\t\t\tuser.disconnectAll();\r\n\t\t} else if (connection.user) {\t// if user is still connected\r\n\t\t\tconnection.challenge = buffer.toString('hex');\r\n\t\t\t// console.log('JOIN: ' + connection.user.name + ' [' + connection.challenge.substr(0, 15) + '] [' + socket.id + ']');\r\n\t\t\tconst keyid = Config.loginserverpublickeyid || 0;\r\n\t\t\tconnection.sendTo(null, `|challstr|${keyid}|${connection.challenge}`);\r\n\t\t}\r\n\t});\r\n\r\n\tRooms.global.handleConnect(user, connection);\r\n}\r\nfunction socketDisconnect(worker: ProcessManager.StreamWorker, workerid: number, socketid: string) {\r\n\tconst id = '' + workerid + '-' + socketid;\r\n\r\n\tconst connection = connections.get(id);\r\n\tif (!connection) return;\r\n\tconnection.onDisconnect();\r\n}\r\nfunction socketDisconnectAll(worker: ProcessManager.StreamWorker, workerid: number) {\r\n\tfor (const connection of connections.values()) {\r\n\t\tif (connection.worker === worker) {\r\n\t\t\tconnection.onDisconnect();\r\n\t\t}\r\n\t}\r\n}\r\nfunction socketReceive(worker: ProcessManager.StreamWorker, workerid: number, socketid: string, message: string) {\r\n\tconst id = `${workerid}-${socketid}`;\r\n\r\n\tconst connection = connections.get(id);\r\n\tif (!connection) return;\r\n\tconnection.lastActiveTime = Date.now();\r\n\r\n\t// Due to a bug in SockJS or Faye, if an exception propagates out of\r\n\t// the `data` event handler, the user will be disconnected on the next\r\n\t// `data` event. To prevent this, we log exceptions and prevent them\r\n\t// from propagating out of this function.\r\n\r\n\t// drop legacy JSON messages\r\n\tif (message.startsWith('{')) return;\r\n\r\n\tconst pipeIndex = message.indexOf('|');\r\n\tif (pipeIndex < 0) {\r\n\t\t// drop invalid messages without a pipe character\r\n\t\tconnection.popup(`Invalid message; messages should be in the format \\`ROOMID|MESSAGE\\`. See https://github.com/smogon/pokemon-showdown/blob/master/PROTOCOL.md`);\r\n\t\treturn;\r\n\t}\r\n\r\n\tconst user = connection.user;\r\n\tif (!user) return;\r\n\r\n\t// LEGACY: In the past, an empty room ID would default to Lobby,\r\n\t// but that is no longer supported\r\n\tconst roomId = message.slice(0, pipeIndex) || '';\r\n\tmessage = message.slice(pipeIndex + 1);\r\n\r\n\tconst room = Rooms.get(roomId) || null;\r\n\tconst multilineMessage = Chat.multiLinePattern.test(message);\r\n\tif (multilineMessage) {\r\n\t\tuser.chat(multilineMessage, room, connection);\r\n\t\treturn;\r\n\t}\r\n\r\n\tconst lines = message.split('\\n');\r\n\tif (!lines[lines.length - 1]) lines.pop();\r\n\t// eslint-disable-next-line @typescript-eslint/prefer-optional-chain\r\n\tconst maxLineCount = (\r\n\t\tuser.can('bypassall') ? THROTTLE_MULTILINE_WARN_ADMIN :\r\n\t\t(user.isStaff || (room && room.auth.isStaff(user.id))) ?\r\n\t\t\tTHROTTLE_MULTILINE_WARN_STAFF : THROTTLE_MULTILINE_WARN\r\n\t);\r\n\tif (lines.length > maxLineCount && !Config.nothrottle) {\r\n\t\tconnection.popup(`You're sending too many lines at once. Try using a paste service like [[Pastebin]].`);\r\n\t\treturn;\r\n\t}\r\n\t// Emergency logging\r\n\tif (Config.emergency) {\r\n\t\tvoid FS('logs/emergency.log').append(`[${user} (${connection.ip})] ${roomId}|${message}\\n`);\r\n\t}\r\n\r\n\tfor (const line of lines) {\r\n\t\tif (user.chat(line, room, connection) === false) break;\r\n\t}\r\n}\r\n\r\nconst users = new Map();\r\nconst prevUsers = new Map();\r\nlet numUsers = 0;\r\n\r\nexport const Users = {\r\n\tdelete: deleteUser,\r\n\tmove,\r\n\tadd,\r\n\tmerge,\r\n\tusers,\r\n\tprevUsers,\r\n\tonlineCount: 0,\r\n\tget: getUser,\r\n\tgetExact: getExactUser,\r\n\tfindUsers,\r\n\tAuth,\r\n\tAvatars: null as typeof import('./chat-commands/avatars').Avatars | null,\r\n\tglobalAuth,\r\n\tisUsernameKnown,\r\n\tisUsername,\r\n\tisTrusted,\r\n\tisPublicBot,\r\n\tSECTIONLEADER_SYMBOL,\r\n\tPLAYER_SYMBOL,\r\n\tHOST_SYMBOL,\r\n\tconnections,\r\n\tUser,\r\n\tConnection,\r\n\tsocketDisconnect,\r\n\tsocketDisconnectAll,\r\n\tsocketReceive,\r\n\tpruneInactive,\r\n\tpruneInactiveTimer: setInterval(() => {\r\n\t\tpruneInactive(Config.inactiveuserthreshold || 60 * MINUTES);\r\n\t}, 30 * MINUTES),\r\n\tlogGhostConnections,\r\n\tlogGhostConnectionsTimer: setInterval(() => {\r\n\t\tvoid logGhostConnections(7 * 24 * 60 * MINUTES);\r\n\t}, 7 * 24 * 60 * MINUTES),\r\n\tsocketConnect,\r\n};\r\n"], "mappings": "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", "names": [] }