Spaces:
Paused
Paused
| ; | |
| var __defProp = Object.defineProperty; | |
| var __getOwnPropDesc = Object.getOwnPropertyDescriptor; | |
| var __getOwnPropNames = Object.getOwnPropertyNames; | |
| var __hasOwnProp = Object.prototype.hasOwnProperty; | |
| var __export = (target, all) => { | |
| for (var name in all) | |
| __defProp(target, name, { get: all[name], enumerable: true }); | |
| }; | |
| var __copyProps = (to, from, except, desc) => { | |
| if (from && typeof from === "object" || typeof from === "function") { | |
| for (let key of __getOwnPropNames(from)) | |
| if (!__hasOwnProp.call(to, key) && key !== except) | |
| __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); | |
| } | |
| return to; | |
| }; | |
| var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); | |
| var core_exports = {}; | |
| __export(core_exports, { | |
| commands: () => commands, | |
| crqHandlers: () => crqHandlers, | |
| loginfilter: () => loginfilter, | |
| pages: () => pages | |
| }); | |
| module.exports = __toCommonJS(core_exports); | |
| var import_lib = require("../../lib"); | |
| /** | |
| * Core commands | |
| * Pokemon Showdown - http://pokemonshowdown.com/ | |
| * | |
| * These are core commands - basic commands required for Pokemon Showdown | |
| * to run. A lot of these are sent by the client. | |
| * | |
| * System commands should not be modified, added, or removed. If you'd | |
| * like to modify or add commands, add or edit files in chat-plugins/ | |
| * | |
| * For the API, see chat-plugins/COMMANDS.md | |
| * | |
| * @license MIT | |
| */ | |
| const crqHandlers = { | |
| userdetails(target, user, trustable) { | |
| if (target.length > 18) { | |
| return null; | |
| } | |
| const targetUser = Users.get(target); | |
| if (!trustable || !targetUser) { | |
| return { | |
| id: target, | |
| userid: toID(target), | |
| name: target, | |
| rooms: false | |
| }; | |
| } | |
| let roomList = {}; | |
| for (const roomid of targetUser.inRooms) { | |
| const targetRoom = Rooms.get(roomid); | |
| if (!targetRoom) | |
| continue; | |
| const roomData = {}; | |
| if (targetRoom.settings.isPrivate) { | |
| if (!user.inRooms.has(roomid) && !user.games.has(roomid)) | |
| continue; | |
| roomData.isPrivate = true; | |
| } | |
| if (targetRoom.battle) { | |
| if (targetUser.settings.hideBattlesFromTrainerCard && user.id !== targetUser.id && !user.can("lock")) | |
| continue; | |
| const battle = targetRoom.battle; | |
| roomData.p1 = battle.p1 ? " " + battle.p1.name : ""; | |
| roomData.p2 = battle.p2 ? " " + battle.p2.name : ""; | |
| } | |
| let roomidWithAuth = roomid; | |
| if (targetRoom.auth.has(targetUser.id)) { | |
| roomidWithAuth = targetRoom.auth.getDirect(targetUser.id) + roomid; | |
| } | |
| roomList[roomidWithAuth] = roomData; | |
| } | |
| if (!targetUser.connected) | |
| roomList = false; | |
| let group = targetUser.tempGroup; | |
| if (targetUser.locked) | |
| group = Config.punishgroups?.locked?.symbol ?? "\u203D"; | |
| if (targetUser.namelocked) | |
| group = Config.punishgroups?.namelocked?.symbol ?? "\u2716"; | |
| const sectionleader = Users.globalAuth.sectionLeaders.has(targetUser.id); | |
| return { | |
| id: target, | |
| userid: targetUser.id, | |
| name: targetUser.name, | |
| avatar: targetUser.avatar, | |
| group, | |
| customgroup: sectionleader ? "Section Leader" : void 0, | |
| autoconfirmed: targetUser.autoconfirmed ? true : void 0, | |
| status: targetUser.getStatus() || void 0, | |
| rooms: roomList, | |
| friended: user.friends?.has(targetUser.id) || void 0 | |
| }; | |
| }, | |
| roomlist(target, user, trustable) { | |
| if (!trustable) | |
| return false; | |
| return { rooms: Rooms.global.getBattles(target) }; | |
| }, | |
| rooms(target, user, trustable) { | |
| if (!trustable) | |
| return false; | |
| return Rooms.global.getRooms(user); | |
| }, | |
| laddertop(target, user, trustable) { | |
| if (!trustable) | |
| return false; | |
| const [format, prefix] = target.split(",").map((x) => x.trim()); | |
| return Ladders(toID(format)).getTop(prefix); | |
| }, | |
| roominfo(target, user, trustable) { | |
| if (!trustable) | |
| return false; | |
| if (target.length > 225) { | |
| return null; | |
| } | |
| const targetRoom = Rooms.get(target); | |
| if (!targetRoom || targetRoom.settings.isPrivate && !user.inRooms.has(targetRoom.roomid) && !user.games.has(targetRoom.roomid)) { | |
| const roominfo2 = { id: target, error: "not found or access denied" }; | |
| return roominfo2; | |
| } | |
| let visibility; | |
| if (targetRoom.settings.isPrivate) { | |
| visibility = targetRoom.settings.isPrivate === "hidden" ? "hidden" : "secret"; | |
| } else { | |
| visibility = "public"; | |
| } | |
| const roominfo = { | |
| id: target, | |
| roomid: targetRoom.roomid, | |
| title: targetRoom.title, | |
| type: targetRoom.type, | |
| visibility, | |
| modchat: targetRoom.settings.modchat, | |
| modjoin: targetRoom.settings.modjoin, | |
| auth: {}, | |
| users: [] | |
| }; | |
| for (const [id, rank] of targetRoom.auth) { | |
| if (!roominfo.auth[rank]) | |
| roominfo.auth[rank] = []; | |
| roominfo.auth[rank].push(id); | |
| } | |
| for (const userid in targetRoom.users) { | |
| const curUser = targetRoom.users[userid]; | |
| if (!curUser.named) | |
| continue; | |
| const userinfo = curUser.getIdentity(targetRoom); | |
| roominfo.users.push(userinfo); | |
| } | |
| return roominfo; | |
| }, | |
| fullformat(target, user, trustable) { | |
| if (!trustable) | |
| return false; | |
| if (target.length > 225) { | |
| return null; | |
| } | |
| const targetRoom = Rooms.get(target); | |
| if (!targetRoom?.battle?.playerTable[user.id]) { | |
| return null; | |
| } | |
| return targetRoom.battle.format; | |
| }, | |
| cmdsearch(target, user, trustable) { | |
| if (target.length > 40) | |
| return null; | |
| const cmdPrefix = target.charAt(0); | |
| if (!["/", "!"].includes(cmdPrefix)) | |
| return null; | |
| target = toID(target.slice(1)); | |
| const results = []; | |
| for (const command of Chat.allCommands()) { | |
| if (cmdPrefix === "!" && !command.broadcastable) | |
| continue; | |
| const req = command.requiredPermission; | |
| if (!!req && !(command.hasRoomPermissions ? !!this.room && user.can(req, null, this.room) : user.can(req))) { | |
| continue; | |
| } | |
| const cmds = [ | |
| command.fullCmd, | |
| ...command.aliases.map((x) => command.fullCmd.replace(command.cmd, `${x}`)) | |
| ]; | |
| for (const cmd of cmds) { | |
| if (toID(cmd).startsWith(target)) { | |
| results.push(cmdPrefix + cmd); | |
| break; | |
| } | |
| } | |
| if (results.length >= 20) | |
| break; | |
| } | |
| return results; | |
| } | |
| }; | |
| const commands = { | |
| version(target, room, user) { | |
| if (!this.runBroadcast()) | |
| return; | |
| const version = Chat.packageData.version; | |
| this.sendReplyBox(this.tr`Server version: <b>${version}</b>`); | |
| }, | |
| versionhelp: [ | |
| `/version - Get the current server version.` | |
| ], | |
| userlist(target, room, user) { | |
| room = this.requireRoom(); | |
| const userList = []; | |
| for (const id in room.users) { | |
| const curUser = Users.get(room.users[id]); | |
| if (!curUser?.named) | |
| continue; | |
| userList.push(import_lib.Utils.escapeHTML(curUser.getIdentity(room))); | |
| } | |
| let output = `There ${Chat.plural(userList, "are", "is")} <strong style="color:#24678d">${Chat.count(userList, "</strong> users")} in this room:<br />`; | |
| output += userList.join(`, `); | |
| this.sendReplyBox(output); | |
| }, | |
| userlisthelp: [`/userlist - Displays a list of users who are currently in the room.`], | |
| mee: "me", | |
| me(target, room, user) { | |
| if (this.cmd === "mee" && /[A-Z-a-z0-9/]/.test(target.charAt(0))) { | |
| return this.errorReply(this.tr`/mee - must not start with a letter or number`); | |
| } | |
| target = this.checkChat(`/${this.cmd} ${target || ""}`); | |
| if (this.message.startsWith(`/ME`)) { | |
| const uppercaseIdentity = user.getIdentity(room).toUpperCase(); | |
| if (this.pmTarget) { | |
| const msg = `|pm|${uppercaseIdentity}|${this.pmTarget.getIdentity()}|${target}`; | |
| user.send(msg); | |
| if (this.pmTarget !== user) | |
| this.pmTarget.send(msg); | |
| } else { | |
| this.add(`|c|${uppercaseIdentity}|${target}`); | |
| } | |
| return; | |
| } | |
| return target; | |
| }, | |
| mehelp: [`/me [action] - Adds the given [action] into chat, attributed to the user.`], | |
| shrug(target) { | |
| target = target ? " " + target + " " : ""; | |
| if (target.startsWith(" /me")) | |
| target = target.slice(1); | |
| return this.checkChat(target + "\xAF\\_(\u30C4)_/\xAF"); | |
| }, | |
| shrughelp: ["/shrug [message] - Sends the given message, if any, appended with \xAF\\_(\u30C4)_/\xAF"], | |
| tableflip(target) { | |
| target = target ? " " + target + " " : ""; | |
| if (target.startsWith(" /me")) | |
| target = target.slice(1); | |
| return this.checkChat(target + "(\u256F\xB0\u25A1\xB0\uFF09\u256F\uFE35 \u253B\u2501\u253B"); | |
| }, | |
| tablefliphelp: ["/tableflip [message] - Sends the given message, if any, appended with (\u256F\xB0\u25A1\xB0\uFF09\u256F\uFE35 \u253B\u2501\u253B"], | |
| tableunflip(target) { | |
| target = target ? " " + target + " " : ""; | |
| if (target.startsWith(" /me")) | |
| target = target.slice(1); | |
| return this.checkChat(target + "\u252C\u2500\u2500\u252C\u25E1\uFF89(\xB0 -\xB0\uFF89)"); | |
| }, | |
| tableunfliphelp: ["/tableunflip [message] - Sends the given message, if any, appended with \u252C\u2500\u2500\u252C\u25E1\uFF89(\xB0 -\xB0\uFF89)"], | |
| "battle!": "battle", | |
| battle(target, room, user, connection, cmd) { | |
| if (cmd === "battle") { | |
| return this.sendReply(this.tr`What?! How are you not more excited to battle?! Try /battle! to show me you're ready.`); | |
| } | |
| if (!target) | |
| target = "randombattle"; | |
| return this.parse(`/search ${target}`); | |
| }, | |
| battlehelp: [ | |
| `/battle! [format] - Starts a battle in the given [format].`, | |
| `If none is given, defaults to current generation random battle.` | |
| ], | |
| signout: "logout", | |
| logout(target, room, user) { | |
| user.resetName(); | |
| }, | |
| logouthelp: [`/logout - Logs you out and ends your session.`], | |
| noreply(target, room, user) { | |
| if (!target.startsWith("/")) | |
| return this.parse("/help noreply"); | |
| return this.parse(target, { isQuiet: true }); | |
| }, | |
| noreplyhelp: [`/noreply [command] - Runs the command without displaying the response.`], | |
| async linksmogon(target, room, user) { | |
| if (Config.smogonauth && !Users.globalAuth.atLeast(user, Config.smogonauth)) { | |
| throw new Chat.ErrorMessage("Access denied."); | |
| } | |
| if (!user.registered) { | |
| throw new Chat.ErrorMessage( | |
| "You must be registered in order to use this command. If you just registered, please refresh and try again." | |
| ); | |
| } | |
| this.sendReply("Linking..."); | |
| const response = await LoginServer.request("smogon/validate", { | |
| username: user.id | |
| }); | |
| const name = response[0]?.signed_username; | |
| if (response[1] || !name) { | |
| throw new Chat.ErrorMessage("Error while verifying username: " + (response[1]?.message || "malformed name received")); | |
| } | |
| const link = `https://www.smogon.com/tools/connect-ps-account/${user.id}/${name}`; | |
| user.send(`|openpage|${link}`); | |
| this.sendReply(`|html|If the page failed to open, you may link your Smogon and PS accounts by clicking <a href="${link}">this link</a>.`); | |
| }, | |
| async msgroom(target, room, user, connection) { | |
| const [targetId, message] = import_lib.Utils.splitFirst(target, ",").map((i) => i.trim()); | |
| if (!targetId || !message) { | |
| return this.parse(`/help msgroom`); | |
| } | |
| this.checkRecursion(); | |
| const targetRoom = Rooms.search(targetId.trim()); | |
| if (!targetRoom) | |
| return this.errorReply(`Room not found.`); | |
| if (message.trim().startsWith("/msgroom ")) { | |
| return this.errorReply(`Please do not nest /msgroom inside itself.`); | |
| } | |
| const subcontext = new Chat.CommandContext({ room: targetRoom, message, user, connection }); | |
| await subcontext.parse(); | |
| }, | |
| msgroomhelp: [`/msgroom [room], [command] - Runs the [command] in the given [room].`], | |
| r: "reply", | |
| reply(target, room, user) { | |
| if (!target) | |
| return this.parse("/help reply"); | |
| if (!user.lastPM) { | |
| return this.errorReply(this.tr`No one has PMed you yet.`); | |
| } | |
| return this.parse(`/msg ${user.lastPM || ""}, ${target}`); | |
| }, | |
| replyhelp: [`/reply OR /r [message] - Send a message to the last user you got a message from, or sent a message to.`], | |
| pm: "msg", | |
| whisper: "msg", | |
| w: "msg", | |
| msg(target, room, user, connection) { | |
| if (!target) | |
| return this.parse("/help msg"); | |
| if (!target.includes(",")) { | |
| this.errorReply(this.tr`You forgot the comma.`); | |
| return this.parse("/help msg"); | |
| } | |
| this.checkRecursion(); | |
| const { targetUser, targetUsername, rest: message } = this.splitUser(target); | |
| if (targetUsername === "~") { | |
| this.pmTarget = null; | |
| this.room = null; | |
| } else if (!targetUser) { | |
| if (Chat.PrivateMessages.offlineIsEnabled) { | |
| if (user.lastCommand === "pm") { | |
| return this.parse(`/offlinemsg ${targetUsername},${message}`); | |
| } | |
| user.lastCommand = "pm"; | |
| return this.errorReply( | |
| this.tr`User ${targetUsername} is offline. Send the message again to confirm. If you are using /msg, use /offlinemsg instead.` | |
| ); | |
| } | |
| let error = this.tr`User ${targetUsername} not found. Did you misspell their name?`; | |
| error = `|pm|${this.user.getIdentity()}| ${targetUsername}|/error ${error}`; | |
| connection.send(error); | |
| return; | |
| } else { | |
| this.pmTarget = targetUser; | |
| this.room = null; | |
| } | |
| if (targetUser && !targetUser.connected) { | |
| if (Chat.PrivateMessages.offlineIsEnabled) { | |
| if (user.lastCommand === "pm") { | |
| return this.parse(`/offlinemsg ${targetUser.getLastId()},${message}`); | |
| } | |
| user.lastCommand = "pm"; | |
| return this.errorReply( | |
| this.tr`User ${targetUsername} is offline. Send the message again to confirm. If you are using /msg, use /offlinemsg instead.` | |
| ); | |
| } | |
| return this.errorReply(`${targetUsername} is offline.`); | |
| } | |
| return this.parse(message); | |
| }, | |
| msghelp: [`/msg OR /whisper OR /w [username], [message] - Send a private message.`], | |
| offlinepm: "offlinemsg", | |
| opm: "offlinemsg", | |
| offlinewhisper: "offlinemsg", | |
| ofw: "offlinemsg", | |
| async offlinemsg(target, room, user) { | |
| target = target.trim(); | |
| if (!target) | |
| return this.parse("/help offlinemsg"); | |
| if (!Chat.PrivateMessages.offlineIsEnabled) { | |
| return this.errorReply(`Offline private messages have been disabled.`); | |
| } | |
| let [username, message] = import_lib.Utils.splitFirst(target, ",").map((i) => i.trim()); | |
| const userid = toID(username); | |
| Chat.PrivateMessages.checkCanPM(user, userid); | |
| if (!userid || !message) { | |
| return this.parse("/help offlinemsg"); | |
| } | |
| if (Chat.parseCommand(message)) { | |
| return this.errorReply(`You cannot send commands in offline PMs.`); | |
| } | |
| if (userid === user.id) { | |
| return this.errorReply(`You cannot send offline PMs to yourself.`); | |
| } else if (userid.startsWith("guest")) { | |
| return this.errorReply("You cannot send offline PMs to guests."); | |
| } | |
| if (Users.get(userid)?.connected) { | |
| this.sendReply(`That user is online, so a normal PM is being sent.`); | |
| return this.parse(`/pm ${userid}, ${message}`); | |
| } | |
| if (userid.length > 18) { | |
| throw new Chat.ErrorMessage(`Invalid userid. Must be <=18 characters in length.`); | |
| } | |
| message = this.checkChat(message); | |
| if (!message) | |
| return; | |
| await Chat.PrivateMessages.sendOffline(userid, user, message, this); | |
| }, | |
| offlinemsghelp: [ | |
| `/offlinemsg [username], [message] - Sends a message to the offline [username], to be received when they log in.` | |
| ], | |
| receivedpms: "offlinepms", | |
| offlinepms() { | |
| return this.parse(`/j view-receivedpms`); | |
| }, | |
| offlinepmshelp: [ | |
| `/offlinepms - View your recently received offline PMs.` | |
| ], | |
| inv: "invite", | |
| invite(target, room, user) { | |
| if (!target) | |
| return this.parse("/help invite"); | |
| const pmTarget = this.pmTarget; | |
| if (!pmTarget) { | |
| const users = target.split(",").map((part) => part.trim()); | |
| let targetRoom2; | |
| if (users.length > 1 && Rooms.search(users[users.length - 1])) { | |
| targetRoom2 = users.pop(); | |
| } else { | |
| targetRoom2 = room; | |
| } | |
| if (users.length > 1 && !user.trusted) { | |
| return this.errorReply("You do not have permission to mass-invite users."); | |
| } | |
| if (users.length > 10) { | |
| return this.errorReply("You cannot invite more than 10 users at once."); | |
| } | |
| for (const toInvite of users) { | |
| this.parse(`/pm ${toInvite}, /invite ${targetRoom2}`); | |
| } | |
| return; | |
| } | |
| const targetRoom = Rooms.search(target); | |
| if (!targetRoom) | |
| return this.errorReply(this.tr`The room "${target}" was not found.`); | |
| const invitesBlocked = pmTarget.settings.blockInvites; | |
| if (invitesBlocked) { | |
| if (invitesBlocked === true ? !user.can("lock") : !Users.globalAuth.atLeast(user, invitesBlocked)) { | |
| Chat.maybeNotifyBlocked("invite", pmTarget, user); | |
| return this.errorReply(`This user is currently blocking room invites.`); | |
| } | |
| } | |
| if (!targetRoom.checkModjoin(pmTarget)) { | |
| this.room = targetRoom; | |
| this.parse(`/roomvoice ${pmTarget.name}`); | |
| if (!targetRoom.checkModjoin(pmTarget)) { | |
| return this.errorReply(this.tr`You do not have permission to invite people into this room.`); | |
| } | |
| } | |
| if (pmTarget.id in targetRoom.users) { | |
| return this.errorReply(this.tr`This user is already in "${targetRoom.title}".`); | |
| } | |
| return this.checkChat(`/invite ${targetRoom.roomid}`); | |
| }, | |
| invitehelp: [ | |
| `/invite [username] - Invites the player [username] to join the room you sent the command to.`, | |
| `/invite [comma-separated usernames] - Invites multiple users to join the room you sent the command to. Requires trusted`, | |
| `/invite [username], [roomname] - Invites the player [username] to join the room [roomname].`, | |
| `(in a PM) /invite [roomname] - Invites the player you're PMing to join the room [roomname].` | |
| ], | |
| blockpm: "blockpms", | |
| ignorepms: "blockpms", | |
| ignorepm: "blockpms", | |
| blockofflinepms: "blockpms", | |
| async blockpms(target, room, user, connection, cmd) { | |
| target = target.toLowerCase().trim(); | |
| if (target === "ac") | |
| target = "autoconfirmed"; | |
| const isOffline = cmd.includes("offline"); | |
| const msg = isOffline ? `offline ` : ``; | |
| if (!isOffline && user.settings.blockPMs === (target || true)) { | |
| return this.errorReply(this.tr`You are already blocking ${msg}private messages! To unblock, use /unblockpms`); | |
| } | |
| if (Users.Auth.isAuthLevel(target)) { | |
| if (!isOffline) | |
| user.settings.blockPMs = target; | |
| this.sendReply(this.tr`You are now blocking ${msg}private messages, except from staff and ${target}.`); | |
| } else if (target === "autoconfirmed" || target === "trusted" || target === "unlocked") { | |
| if (!isOffline) | |
| user.settings.blockPMs = target; | |
| target = this.tr(target); | |
| this.sendReply(this.tr`You are now blocking ${msg}private messages, except from staff and ${target} users.`); | |
| } else if (target === "friends") { | |
| if (!isOffline) | |
| user.settings.blockPMs = target; | |
| this.sendReply(this.tr`You are now blocking ${msg}private messages, except from staff and friends.`); | |
| } else { | |
| if (!isOffline) | |
| user.settings.blockPMs = true; | |
| this.sendReply(this.tr`You are now blocking ${msg}private messages, except from staff.`); | |
| } | |
| if (isOffline) { | |
| let saveValue = target; | |
| if (!saveValue) | |
| saveValue = "none"; | |
| if (["unlocked", "autoconfirmed"].includes(saveValue)) { | |
| saveValue = null; | |
| } | |
| await Chat.PrivateMessages.setViewOnly(user, saveValue); | |
| } | |
| user.update(); | |
| return true; | |
| }, | |
| blockpmshelp: [ | |
| `/blockpms - Blocks private messages except from staff. Unblock them with /unblockpms.`, | |
| `/blockpms [unlocked/ac/trusted/+/friends] - Blocks private messages except from staff and the specified group.` | |
| ], | |
| unblockpm: "unblockpms", | |
| unignorepms: "unblockpms", | |
| unignorepm: "unblockpms", | |
| unblockofflinepms: "unblockpms", | |
| async unblockpms(target, room, user, connection, cmd) { | |
| const isOffline = cmd.includes("offline"); | |
| const msg = isOffline ? "offline " : ""; | |
| if (isOffline ? !await Chat.PrivateMessages.getSettings(user.id) : !user.settings.blockPMs) { | |
| return this.errorReply(this.tr`You are not blocking ${msg}private messages! To block, use /blockpms`); | |
| } | |
| if (isOffline) { | |
| await Chat.PrivateMessages.deleteSettings(user.id); | |
| } else { | |
| user.settings.blockPMs = false; | |
| } | |
| user.update(); | |
| return this.sendReply(this.tr`You are no longer blocking ${msg}private messages.`); | |
| }, | |
| unblockpmshelp: [ | |
| `/unblockpms - Unblocks private messages. Block them with /blockpms.`, | |
| `/unblockofflinepms - Unblocks offline private messages. Block them with /blockofflinepms.` | |
| ], | |
| unblockinvites: "blockinvites", | |
| blockinvites(target, room, user, connection, cmd) { | |
| const unblock = cmd.includes("unblock"); | |
| if (unblock) { | |
| if (!user.settings.blockInvites) { | |
| return this.errorReply(`You are not blocking room invites! To block, use /blockinvites.`); | |
| } | |
| user.settings.blockInvites = false; | |
| this.sendReply(`You are no longer blocking room invites.`); | |
| } else { | |
| if (toID(target) === "ac") | |
| target = "autoconfirmed"; | |
| if (user.settings.blockInvites === (target || true)) { | |
| return this.errorReply("You are already blocking room invites - to unblock, use /unblockinvites"); | |
| } | |
| if (target in Config.groups) { | |
| user.settings.blockInvites = target; | |
| this.sendReply(this.tr`You are now blocking room invites, except from staff and ${target}.`); | |
| } else if (target === "autoconfirmed" || target === "trusted" || target === "unlocked") { | |
| user.settings.blockInvites = target; | |
| this.sendReply(this.tr`You are now blocking room invites, except from staff and ${target} users.`); | |
| } else { | |
| user.settings.blockInvites = true; | |
| this.sendReply(this.tr`You are now blocking room invites, except from staff.`); | |
| } | |
| } | |
| return user.update(); | |
| }, | |
| blockinviteshelp: [ | |
| `/blockinvites [rank] - Allows only users with the given [rank] to invite you to rooms.`, | |
| `Valid settings: autoconfirmed, trusted, unlocked, +, %, @, ~.`, | |
| `/unblockinvites - Allows anyone to invite you to rooms.` | |
| ], | |
| status(target, room, user, connection, cmd) { | |
| if (user.locked || user.semilocked) { | |
| return this.errorReply(this.tr`Your status cannot be updated while you are locked or semilocked.`); | |
| } | |
| if (!target) | |
| return this.parse("/help status"); | |
| const maxLength = 70; | |
| if (target.length > maxLength) { | |
| return this.errorReply(this.tr`Your status is too long; it must be under ${maxLength} characters.`); | |
| } | |
| target = this.statusfilter(target); | |
| if (!target) | |
| return this.errorReply(this.tr`Your status contains a banned word.`); | |
| user.setUserMessage(target); | |
| this.sendReply(this.tr`Your status has been set to: ${target}.`); | |
| }, | |
| statushelp: [ | |
| `/status [note] - Sets a short note as your status, visible when users click your username.`, | |
| `Use /clearstatus to clear your status message.` | |
| ], | |
| donotdisturb: "busy", | |
| dnd: "busy", | |
| busy(target, room, user, connection, cmd) { | |
| if (target) { | |
| this.errorReply(this.tr`Setting status messages in /busy is no longer supported. Set a status using /status.`); | |
| } | |
| user.setStatusType("busy"); | |
| const isDND = ["dnd", "donotdisturb"].includes(cmd); | |
| if (isDND) { | |
| this.parse("/blockpms +"); | |
| this.parse("/blockchallenges"); | |
| user.settings.doNotDisturb = true; | |
| } | |
| this.sendReply(this.tr`You are now marked as busy.`); | |
| }, | |
| busyhelp: [ | |
| `/busy OR /donotdisturb - Marks you as busy.`, | |
| `Use /donotdisturb to also block private messages and challenges.`, | |
| `Use /back to mark yourself as back.` | |
| ], | |
| idle: "away", | |
| afk: "away", | |
| brb: "away", | |
| away(target, room, user, connection, cmd) { | |
| if (target) { | |
| this.errorReply(this.tr`Setting status messages in /away is no longer supported. Set a status using /status.`); | |
| } | |
| user.setStatusType("idle"); | |
| this.sendReply(this.tr`You are now marked as away. Send a message or use /back to indicate you are back.`); | |
| }, | |
| awayhelp: [`/away - Marks you as away. Send a message or use /back to indicate you are back.`], | |
| cs: "clearstatus", | |
| clearstatus(target, room, user) { | |
| if (target) | |
| return this.parse(`/forceclearstatus ${target}`); | |
| if (!user.userMessage) | |
| return this.sendReply(this.tr`You don't have a status message set.`); | |
| user.setUserMessage(""); | |
| return this.sendReply(this.tr`You have cleared your status message.`); | |
| }, | |
| clearstatushelp: [ | |
| `/clearstatus - Clears your status message.`, | |
| `/clearstatus user, reason - Clears another person's status message. Requires: % @ ~` | |
| ], | |
| unaway: "back", | |
| unafk: "back", | |
| back(target, room, user) { | |
| if (user.statusType === "online") | |
| return this.errorReply(this.tr`You are already marked as back.`); | |
| const statusType = user.statusType; | |
| user.setStatusType("online"); | |
| if (user.settings.doNotDisturb) { | |
| this.parse("/unblockpms"); | |
| this.parse("/unblockchallenges"); | |
| user.settings.doNotDisturb = false; | |
| } | |
| if (statusType) { | |
| return this.sendReply(this.tr`You are no longer marked as busy.`); | |
| } | |
| return this.sendReply(this.tr`You have cleared your status message.`); | |
| }, | |
| backhelp: [`/back - Marks you as back if you are away.`], | |
| async rank(target, room, user) { | |
| if (!target) | |
| target = user.name; | |
| const values = await Ladders.visualizeAll(target); | |
| let buffer = `<div class="ladder"><table>`; | |
| buffer += import_lib.Utils.html`<tr><td colspan="8">User: <strong>${target}</strong></td></tr>`; | |
| const ratings = values.join(``); | |
| if (!ratings) { | |
| buffer += `<tr><td colspan="8"><em>${this.tr`This user has not played any ladder games yet.`}</em></td></tr>`; | |
| } else { | |
| buffer += `<tr><th>${this.tr`Format`}</th><th><abbr title="Elo rating">Elo</abbr></th><th>${this.tr`W`}</th><th>${this.tr`L`}</th><th>${this.tr`Total`}</th>`; | |
| buffer += ratings; | |
| } | |
| buffer += `</table></div>`; | |
| this.sendReply(`|raw|${buffer}`); | |
| }, | |
| rankhelp: [ | |
| `/rank [user] - Shows all ladder ranks for the given [user].`, | |
| `If no user is given, it defaults to the user of the command.` | |
| ], | |
| showrank: "hiderank", | |
| hiderank(target, room, user, connection, cmd) { | |
| const userGroup = Users.Auth.getGroup(Users.globalAuth.get(user.id)); | |
| if (!userGroup["hiderank"] || !user.registered) { | |
| return this.errorReply(`/hiderank - Access denied.`); | |
| } | |
| const isShow = cmd === "showrank"; | |
| const group = isShow ? Users.globalAuth.get(user.id) : target.trim() || Users.Auth.defaultSymbol(); | |
| if (user.tempGroup === group) { | |
| return this.errorReply(this.tr`You already have the temporary symbol '${group}'.`); | |
| } | |
| if (!Users.Auth.isValidSymbol(group) || !(group in Config.groups)) { | |
| return this.errorReply(this.tr`You must specify a valid group symbol.`); | |
| } | |
| if (!isShow && Config.groups[group].rank > Config.groups[user.tempGroup].rank) { | |
| return this.errorReply(this.tr`You may only set a temporary symbol below your current rank.`); | |
| } | |
| user.tempGroup = group; | |
| user.updateIdentity(); | |
| this.sendReply(`|c|~|${this.tr`Your temporary group symbol is now`} \`\`${user.tempGroup}\`\`.`); | |
| }, | |
| showrankhelp: "hiderankhelp", | |
| hiderankhelp: [ | |
| `/hiderank [rank] - Displays your global rank as the given [rank].`, | |
| `/showrank - Displays your true global rank instead of the rank you're hidden as.` | |
| ], | |
| language(target, room, user) { | |
| if (!target) { | |
| const language2 = Chat.languages.get(user.language || "english"); | |
| return this.sendReply(this.tr`Currently, you're viewing Pokémon Showdown in ${language2}.`); | |
| } | |
| const languageID = toID(target); | |
| if (!Chat.languages.has(languageID)) { | |
| const languages = [...Chat.languages.values()].join(", "); | |
| return this.errorReply(this.tr`Valid languages are: ${languages}`); | |
| } | |
| user.language = languageID; | |
| user.update(); | |
| const languageName = Chat.languages.get(languageID); | |
| const langRoom = Rooms.search(languageName || ""); | |
| let language = languageName; | |
| if (langRoom) { | |
| language = `<a href="/${langRoom.roomid}">${languageName}</a>`; | |
| } | |
| return this.sendReply( | |
| `|html|` + this.tr`Pokémon Showdown will now be displayed in ${language} (except in language rooms).` | |
| ); | |
| }, | |
| languagehelp: [ | |
| `/language - View your current language setting.`, | |
| `/language [language] - Changes the language Pok\xE9mon Showdown will be displayed to you in.`, | |
| `Note that rooms can set their own language, which will override this setting.` | |
| ], | |
| updatesettings(target, room, user) { | |
| const settings = {}; | |
| try { | |
| const raw = JSON.parse(target); | |
| if (typeof raw !== "object" || Array.isArray(raw) || !raw) { | |
| this.errorReply(this.tr`/updatesettings expects JSON encoded object.`); | |
| } | |
| if (typeof raw.language === "string") | |
| this.parse(`/noreply /language ${raw.language}`); | |
| for (const setting in user.settings) { | |
| if (setting in raw) { | |
| if (setting === "blockPMs" && Users.Auth.isAuthLevel(raw[setting])) { | |
| settings[setting] = raw[setting]; | |
| } else { | |
| settings[setting] = !!raw[setting]; | |
| } | |
| } | |
| } | |
| Object.assign(user.settings, settings); | |
| user.update(); | |
| } catch { | |
| this.errorReply(this.tr`Unable to parse settings in /updatesettings!`); | |
| } | |
| }, | |
| updatesettingshelp: [ | |
| `/updatesettings [settings] - Update your settings to match the given JSON settings blob.` | |
| ], | |
| /********************************************************* | |
| * Battle management commands | |
| *********************************************************/ | |
| allowexportinputlog(target, room, user) { | |
| room = this.requireRoom(); | |
| const battle = room.battle; | |
| if (!battle) { | |
| return this.errorReply(this.tr`Must be in a battle.`); | |
| } | |
| const targetUser = Users.getExact(target); | |
| if (!targetUser) { | |
| return this.errorReply(this.tr`User ${target} not found.`); | |
| } | |
| if (!battle.playerTable[user.id]) { | |
| return this.errorReply(this.tr`Must be a player in this battle.`); | |
| } | |
| if (!battle.allowExtraction[targetUser.id]) { | |
| return this.errorReply(this.tr`${targetUser.name} has not requested extraction.`); | |
| } | |
| if (battle.allowExtraction[targetUser.id].has(user.id)) { | |
| return this.errorReply(this.tr`You have already consented to extraction with ${targetUser.name}.`); | |
| } | |
| battle.allowExtraction[targetUser.id].add(user.id); | |
| this.addModAction(room.tr`${user.name} consents to sharing battle team and choices with ${targetUser.name}.`); | |
| if (!battle.inputLog) | |
| return this.errorReply(this.tr`No input log found.`); | |
| if (Object.keys(battle.playerTable).length === battle.allowExtraction[targetUser.id].size) { | |
| this.addModAction(room.tr`${targetUser.name} has extracted the battle input log.`); | |
| const inputLog = battle.inputLog.map(import_lib.Utils.escapeHTML).join(`<br />`); | |
| targetUser.sendTo( | |
| room, | |
| `|html|<div class="chat"><code style="white-space: pre-wrap; overflow-wrap: break-word; display: block">${inputLog}</code></div>` | |
| ); | |
| } | |
| }, | |
| allowexportinputloghelp: [ | |
| `/allowexportinputlog [user] - Consents to sharing teams and choices from the current battle with the specified user.` | |
| ], | |
| requestinputlog: "exportinputlog", | |
| exportinputlog(target, room, user) { | |
| room = this.requireRoom(); | |
| const battle = room.battle; | |
| if (!battle) { | |
| return this.errorReply(this.tr`This command only works in battle rooms.`); | |
| } | |
| if (!battle.inputLog) { | |
| this.errorReply(this.tr`This command only works when the battle has ended - if the battle has stalled, use /offertie.`); | |
| if (user.can("forcewin")) | |
| this.errorReply(this.tr`Alternatively, you can end the battle with /forcetie.`); | |
| return; | |
| } | |
| this.checkCan("exportinputlog", null, room); | |
| if (user.can("forcewin") || Dex.formats.get(battle.format).team) { | |
| if (!battle.inputLog) | |
| return this.errorReply(this.tr`No input log found.`); | |
| this.addModAction(room.tr`${user.name} has extracted the battle input log.`); | |
| const inputLog = battle.inputLog.map(import_lib.Utils.escapeHTML).join(`<br />`); | |
| user.sendTo( | |
| room, | |
| `|html|<div class="chat"><code style="white-space: pre-wrap; overflow-wrap: break-word; display: block">${inputLog}</code></div>` | |
| ); | |
| } else if (!battle.allowExtraction[user.id]) { | |
| battle.allowExtraction[user.id] = /* @__PURE__ */ new Set(); | |
| for (const id in battle.playerTable) { | |
| const playerUser = Users.get(id); | |
| if (!playerUser) | |
| continue; | |
| if (playerUser.id === user.id) { | |
| battle.allowExtraction[user.id].add(user.id); | |
| } else { | |
| playerUser.sendTo( | |
| room, | |
| import_lib.Utils.html`|html|${user.name} wants to extract the battle input log. <button name="send" value="/allowexportinputlog ${user.id}">Share your team and choices with "${user.name}"</button>` | |
| ); | |
| } | |
| } | |
| this.addModAction(room.tr`${user.name} wants to extract the battle input log.`); | |
| } else { | |
| let logExported = true; | |
| for (const id in battle.playerTable) { | |
| const playerUser = Users.get(id); | |
| if (!playerUser || battle.allowExtraction[user.id].has(playerUser.id)) | |
| continue; | |
| logExported = false; | |
| playerUser.sendTo( | |
| room, | |
| import_lib.Utils.html`|html|${user.name} wants to extract the battle input log. <button name="send" value="/allowexportinputlog ${user.id}">Share your team and choices with "${user.name}"</button>` | |
| ); | |
| } | |
| if (logExported) | |
| return this.errorReply(this.tr`You already extracted the battle input log.`); | |
| this.sendReply(this.tr`Battle input log re-requested.`); | |
| } | |
| }, | |
| exportinputloghelp: [`/exportinputlog - Asks players in a battle for permission to export an inputlog. Requires: ~`], | |
| importinputlog(target, room, user, connection) { | |
| this.checkCan("importinputlog"); | |
| const formatIndex = target.indexOf(`"formatid":"`); | |
| const nextQuoteIndex = target.indexOf(`"`, formatIndex + 12); | |
| if (formatIndex < 0 || nextQuoteIndex < 0) | |
| return this.errorReply(this.tr`Invalid input log.`); | |
| target = target.replace(/\r/g, ""); | |
| if ((` | |
| ` + target).includes(` | |
| >eval `) && !user.hasConsoleAccess(connection)) { | |
| return this.errorReply(this.tr`Your input log contains untrusted code - you must have console access to use it.`); | |
| } | |
| const formatid = target.slice(formatIndex + 12, nextQuoteIndex); | |
| const battleRoom = Rooms.createBattle({ format: formatid, players: [], inputLog: target }); | |
| if (!battleRoom) | |
| return; | |
| battleRoom.auth.set(user.id, Users.HOST_SYMBOL); | |
| this.parse(`/join ${battleRoom.roomid}`); | |
| setTimeout(() => { | |
| battleRoom.add(`|uhtmlchange|invites|<div class="broadcast broadcast-blue"><strong>This is an imported replay</strong><br />Players will need to be manually added with <code>/invitebattle</code> or <code>/restoreplayers</code></div>`); | |
| battleRoom.add(`|uhtml|invites|`).update(); | |
| battleRoom.battle.sendInviteForm(user); | |
| }, 500); | |
| }, | |
| importinputloghelp: [`/importinputlog [inputlog] - Starts a battle with a given inputlog. Requires: + % @ ~`], | |
| showteam: "showset", | |
| async showset(target, room, user, connection, cmd) { | |
| this.checkChat(); | |
| const showAll = cmd === "showteam"; | |
| const hideStats = toID(target) === "hidestats"; | |
| room = this.requireRoom(); | |
| const battle = room.battle; | |
| if (!showAll && !target) | |
| return this.parse(`/help showset`); | |
| if (!battle) | |
| return this.errorReply(this.tr`This command can only be used in a battle.`); | |
| let team = await battle.getTeam(user); | |
| if (!team) | |
| return this.errorReply(this.tr`You are not a player and don't have a team.`); | |
| if (!showAll) { | |
| const parsed = parseInt(target); | |
| if (isNaN(parsed)) { | |
| const id = toID(target); | |
| const matchedSet = team.find((set) => toID(set.name) === id || toID(set.species) === id); | |
| if (!matchedSet) | |
| return this.errorReply(this.tr`You don't have a Pokémon matching "${target}" in your team.`); | |
| team = [matchedSet]; | |
| } else { | |
| const setIndex = parsed - 1; | |
| const indexedSet = team[setIndex]; | |
| if (!indexedSet) { | |
| return this.errorReply(this.tr`You don't have a Pokémon #${parsed} on your team - your team only has ${team.length} Pokémon.`); | |
| } | |
| team = [indexedSet]; | |
| } | |
| } | |
| let resultString = import_lib.Utils.escapeHTML(Teams.export(team, { hideStats })); | |
| if (showAll) { | |
| resultString = `<details><summary>${this.tr`View team`}</summary>${resultString}</details>`; | |
| } | |
| this.runBroadcast(true); | |
| return this.sendReplyBox(resultString); | |
| }, | |
| showsethelp: [ | |
| `!showteam - show the team you're using in the current battle (must be used in a battle you're a player in).`, | |
| `!showteam hidestats - show the team you're using in the current battle, without displaying any stat-related information.`, | |
| `!showset [number] - shows the set of the pokemon corresponding to that number (in original Team Preview order, not necessarily current order)` | |
| ], | |
| confirmready(target, room, user) { | |
| const game = this.requireGame(Rooms.BestOfGame); | |
| game.confirmReady(user); | |
| }, | |
| acceptopenteamsheets(target, room, user, connection, cmd) { | |
| room = this.requireRoom(); | |
| const battle = room.battle; | |
| if (!battle) | |
| return this.errorReply(this.tr`Must be in a battle room.`); | |
| const player = battle.playerTable[user.id]; | |
| if (!player) { | |
| return this.errorReply(this.tr`Must be a player to agree to open team sheets.`); | |
| } | |
| const format = Dex.formats.get(battle.options.format); | |
| if (!Dex.formats.getRuleTable(format).has("openteamsheets")) { | |
| return this.errorReply(this.tr`This format does not allow requesting open team sheets. You can both manually agree to it by using !showteam hidestats.`); | |
| } | |
| if (battle.turn > 0) { | |
| return this.errorReply(this.tr`You cannot agree to open team sheets after Team Preview. Each player can still show their own sheet by using this command: !showteam hidestats`); | |
| } | |
| if (battle.players.some((curPlayer) => curPlayer.wantsOpenTeamSheets === false)) { | |
| return this.errorReply(this.tr`An opponent has already rejected open team sheets.`); | |
| } | |
| if (player.wantsOpenTeamSheets !== null) { | |
| return this.errorReply(this.tr`You have already made your decision about agreeing to open team sheets.`); | |
| } | |
| player.wantsOpenTeamSheets = true; | |
| player.sendRoom(import_lib.Utils.html`|uhtmlchange|otsrequest|`); | |
| this.add(this.tr`${user.name} has agreed to open team sheets.`); | |
| if (battle.players.every((curPlayer) => curPlayer.wantsOpenTeamSheets)) { | |
| void battle.stream.write(">show-openteamsheets"); | |
| } | |
| }, | |
| acceptopenteamsheetshelp: [`/acceptopenteamsheets - Agrees to an open team sheet opportunity during Team Preview, where all information on a team except stats is shared with the opponent. Requires: \u2606`], | |
| rejectopenteamsheets(target, room, user) { | |
| room = this.requireRoom(); | |
| const battle = room.battle; | |
| if (!battle) | |
| return this.errorReply(this.tr`Must be in a battle room.`); | |
| const player = battle.playerTable[user.id]; | |
| if (!player) { | |
| return this.errorReply(this.tr`Must be a player to reject open team sheets.`); | |
| } | |
| const format = Dex.formats.get(battle.options.format); | |
| if (!Dex.formats.getRuleTable(format).has("openteamsheets")) { | |
| return this.errorReply(this.tr`This format does not allow requesting open team sheets.`); | |
| } | |
| if (battle.turn > 0) { | |
| return this.errorReply(this.tr`You cannot reject open team sheets after Team Preview.`); | |
| } | |
| if (player.wantsOpenTeamSheets !== null) { | |
| return this.errorReply(this.tr`You have already made your decision about agreeing to open team sheets.`); | |
| } | |
| player.wantsOpenTeamSheets = false; | |
| for (const otherPlayer of battle.players) { | |
| otherPlayer.sendRoom(import_lib.Utils.html`|uhtmlchange|otsrequest|`); | |
| } | |
| return this.add(this.tr`${user.name} rejected open team sheets.`); | |
| }, | |
| rejectopenteamsheetshelp: [`/rejectopenteamsheetshelp - Rejects an open team sheet opportunity during Team Preview, where all information on a team except stats is shared with the opponent. Requires: \u2606`], | |
| acceptdraw: "offertie", | |
| accepttie: "offertie", | |
| offerdraw: "offertie", | |
| requesttie: "offertie", | |
| offertie(target, room, user, connection, cmd) { | |
| room = this.requireRoom(); | |
| const battle = room.battle; | |
| if (!battle) | |
| return this.errorReply(this.tr`Must be in a battle room.`); | |
| if (!Config.allowrequestingties) { | |
| return this.errorReply(this.tr`This server does not allow offering ties.`); | |
| } | |
| if (room.tour) { | |
| return this.errorReply(this.tr`You can't offer ties in tournaments.`); | |
| } | |
| if (battle.turn < 100) { | |
| return this.errorReply(this.tr`It's too early to tie, please play until turn 100.`); | |
| } | |
| this.checkCan("roomvoice", null, room); | |
| if (cmd === "accepttie" && !battle.players.some((player2) => player2.wantsTie)) { | |
| return this.errorReply(this.tr`No other player is requesting a tie right now. It was probably canceled.`); | |
| } | |
| const player = battle.playerTable[user.id]; | |
| if (!battle.players.some((curPlayer) => curPlayer.wantsTie)) { | |
| this.add(this.tr`${user.name} is offering a tie.`); | |
| room.update(); | |
| for (const otherPlayer of battle.players) { | |
| if (otherPlayer !== player) { | |
| otherPlayer.sendRoom( | |
| import_lib.Utils.html`|uhtml|offertie|<button class="button" name="send" value="/accepttie"><strong>${this.tr`Accept tie`}</strong></button> <button class="button" name="send" value="/rejecttie">${this.tr`Reject`}</button>` | |
| ); | |
| } else { | |
| player.wantsTie = true; | |
| } | |
| } | |
| } else { | |
| if (!player) { | |
| return this.errorReply(this.tr`Must be a player to accept ties.`); | |
| } | |
| if (!player.wantsTie) { | |
| player.wantsTie = true; | |
| } else { | |
| return this.errorReply(this.tr`You have already agreed to a tie.`); | |
| } | |
| player.sendRoom(import_lib.Utils.html`|uhtmlchange|offertie|`); | |
| this.add(this.tr`${user.name} accepted the tie.`); | |
| if (battle.players.every((curPlayer) => curPlayer.wantsTie)) { | |
| if (battle.players.length > 2) { | |
| this.add(this.tr`All players have accepted the tie.`); | |
| } | |
| battle.tie(); | |
| } | |
| } | |
| }, | |
| offertiehelp: [`/offertie - Offers a tie to all players in a battle; if all accept, it ties. Can only be used after 100+ turns have passed. Requires: \u2606 @ # ~`], | |
| rejectdraw: "rejecttie", | |
| rejecttie(target, room, user) { | |
| room = this.requireRoom(); | |
| const battle = room.battle; | |
| if (!battle) | |
| return this.errorReply(this.tr`Must be in a battle room.`); | |
| const player = battle.playerTable[user.id]; | |
| if (!player) { | |
| return this.errorReply(this.tr`Must be a player to reject ties.`); | |
| } | |
| if (!battle.players.some((curPlayer) => curPlayer.wantsTie)) { | |
| return this.errorReply(this.tr`No other player is requesting a tie right now. It was probably canceled.`); | |
| } | |
| if (player.wantsTie) | |
| player.wantsTie = false; | |
| for (const otherPlayer of battle.players) { | |
| otherPlayer.sendRoom(import_lib.Utils.html`|uhtmlchange|offertie|`); | |
| } | |
| return this.add(this.tr`${user.name} rejected the tie.`); | |
| }, | |
| rejecttiehelp: [`/rejecttie - Rejects a tie offered by another player in a battle.`], | |
| inputlog() { | |
| this.parse(`/help exportinputlog`); | |
| this.parse(`/help importinputlog`); | |
| }, | |
| /********************************************************* | |
| * Battle commands | |
| *********************************************************/ | |
| forfeit(target, room, user) { | |
| room = this.requireRoom(); | |
| if (!room.game) | |
| return this.errorReply(this.tr`This room doesn't have an active game.`); | |
| if (!room.game.forfeit) { | |
| return this.errorReply(this.tr`This kind of game can't be forfeited.`); | |
| } | |
| room.game.forfeit(user); | |
| }, | |
| forfeithelp: [ | |
| `/forfeit - Forfeits your currently active game, if it supports that.` | |
| ], | |
| guess: "choose", | |
| choose(target, room, user) { | |
| room = this.requireRoom(); | |
| if (!room.game) | |
| return this.errorReply(this.tr`This room doesn't have an active game.`); | |
| if (!room.game.choose) | |
| return this.errorReply(this.tr`This game doesn't support /choose`); | |
| if (room.game.checkChat) | |
| this.checkChat(); | |
| room.game.choose(user, target); | |
| }, | |
| choosehelp: [ | |
| `/choose [text] - Make a choice for the currently active game.` | |
| ], | |
| mv: "move", | |
| attack: "move", | |
| move(target, room, user) { | |
| this.parse(`/choose move ${target}`); | |
| }, | |
| movehelp: [ | |
| `/move [move] - Make a move for the current game.` | |
| ], | |
| sw: "switch", | |
| switch(target, room, user) { | |
| this.parse(`/choose switch ${target}`); | |
| }, | |
| switchhelp: [ | |
| `/switch [pokemon] - Make a switch for the current game.` | |
| ], | |
| team(target, room, user) { | |
| this.parse(`/choose team ${target}`); | |
| }, | |
| teamhelp: [ | |
| `/team [pokemon] - Change your team for the current game.` | |
| ], | |
| undo(target, room, user) { | |
| room = this.requireRoom(); | |
| if (!room.game) | |
| return this.errorReply(this.tr`This room doesn't have an active game.`); | |
| if (!room.game.undo) | |
| return this.errorReply(this.tr`This game doesn't support /undo`); | |
| room.game.undo(user, target); | |
| }, | |
| undohelp: [ | |
| `/undo - Reverts the last move of the player in the current game, if it supports it.` | |
| ], | |
| uploadreplay: "savereplay", | |
| async savereplay(target, room, user, connection) { | |
| if (!room?.battle) { | |
| return this.errorReply(this.tr`You can only save replays for battles.`); | |
| } | |
| const options = target === "forpunishment" || target === "silent" ? target : void 0; | |
| await room.uploadReplay(user, connection, options); | |
| }, | |
| savereplayhelp: [`/savereplay - Saves the replay for the current battle.`], | |
| hidereplay(target, room, user, connection) { | |
| if (!room?.battle) | |
| return this.errorReply(`Must be used in a battle.`); | |
| this.checkCan("joinbattle", null, room); | |
| if (room.tour?.forcePublic) { | |
| return this.errorReply(this.tr`This battle can't have hidden replays, because the tournament is set to be forced public.`); | |
| } | |
| if (room.hideReplay) | |
| return this.errorReply(this.tr`The replay for this battle is already set to hidden.`); | |
| room.hideReplay = true; | |
| if (room.battle.replaySaved) | |
| this.parse("/savereplay"); | |
| this.addModAction(room.tr`${user.name} hid the replay of this battle.`); | |
| }, | |
| hidereplayhelp: [`/hidereplay - Hides the replay of the current battle. Requires: ${Users.PLAYER_SYMBOL} ~`], | |
| addplayer: "invitebattle", | |
| invitebattle(target, room, user, connection) { | |
| room = this.requireRoom(); | |
| if (!room.battle) | |
| return this.errorReply(this.tr`You can only do this in battle rooms.`); | |
| if (room.rated) | |
| return this.errorReply(this.tr`You can only add a Player to unrated battles.`); | |
| this.checkCan("joinbattle", null, room); | |
| const { targetUser, targetUsername: name, rest: slot } = this.splitUser(target, { exactName: true }); | |
| if (slot !== "p1" && slot !== "p2" && slot !== "p3" && slot !== "p4") { | |
| this.errorReply(this.tr`Player must be set to "p1" or "p2", not "${slot}".`); | |
| return this.parse("/help addplayer"); | |
| } | |
| const battle = room.battle; | |
| const player = battle[slot]; | |
| if (!player) { | |
| return this.errorReply(`This battle does not support having players in ${slot}`); | |
| } | |
| if (!targetUser) { | |
| battle.sendInviteForm(connection); | |
| return this.errorReply(this.tr`User ${name} not found.`); | |
| } | |
| if (player.id) { | |
| battle.sendInviteForm(connection); | |
| return this.errorReply(this.tr`This room already has a player in slot ${slot}.`); | |
| } | |
| if (player.invite) { | |
| battle.sendInviteForm(connection); | |
| return this.errorReply(`Someone else (${player.invite}) has already been invited to be ${slot}!`); | |
| } | |
| if (targetUser.id in battle.playerTable) { | |
| battle.sendInviteForm(connection); | |
| return this.errorReply(this.tr`${targetUser.name} is already a player in this battle.`); | |
| } | |
| if (targetUser.settings.blockChallenges && !user.can("bypassblocks", targetUser)) { | |
| battle.sendInviteForm(connection); | |
| Chat.maybeNotifyBlocked("challenge", targetUser, user); | |
| return this.errorReply(this.tr`The user '${targetUser.name}' is not accepting challenges right now.`); | |
| } | |
| if (!targetUser.inRooms.has(room.roomid) || !player.hasTeam) { | |
| player.invite = targetUser.id; | |
| const playerNames = battle.players.map((p) => p.id && p.name).filter(Boolean).join(", "); | |
| const ready = player.hasTeam ? battle.format : new Ladders.BattleReady(user.id, battle.format, user.battleSettings); | |
| Ladders.challenges.add( | |
| new Ladders.BattleInvite(user.id, targetUser.id, ready, { | |
| acceptCommand: `/acceptbattle ${user.id}`, | |
| message: `You're invited to join a battle (with ${playerNames})`, | |
| roomid: room.roomid | |
| }) | |
| ); | |
| battle.sendInviteForm(battle.invitesFull() ? true : connection); | |
| return this.add(`||Invite sent to ${targetUser.name}!`); | |
| } | |
| room.auth.set(targetUser.id, Users.PLAYER_SYMBOL); | |
| const success = battle.joinGame(targetUser, slot); | |
| if (!success) { | |
| room.auth.delete(targetUser.id); | |
| return; | |
| } | |
| if (!battle.started) | |
| battle.sendInviteForm(connection); | |
| }, | |
| invitebattlehelp: [ | |
| `/addplayer [username], [p1|p2|p3|p4] - Invites the player to join your current battle.` | |
| ], | |
| async acceptbattle(target, room, user, connection) { | |
| const chall = Ladders.challenges.resolveAcceptCommand(this); | |
| const targetRoom = Rooms.get(chall.roomid); | |
| if (!targetRoom) | |
| return this.errorReply(`Room ${chall.roomid} not found`); | |
| const battle = targetRoom.battle; | |
| const player = battle.players.find((maybe) => maybe.invite === user.id); | |
| if (!player) { | |
| return this.errorReply(`You haven't been invited to that battle.`); | |
| } | |
| const slot = player.slot; | |
| if (player.id) { | |
| throw new Error(`Player ${player.slot} in ${chall.roomid} should not have both 'id' and 'invite'`); | |
| } | |
| let playerOpts = void 0; | |
| if (!player.hasTeam) { | |
| const ladder = Ladders(battle.format); | |
| const ready = await ladder.prepBattle(connection, "challenge"); | |
| if (!ready) | |
| return; | |
| playerOpts = ready.settings; | |
| } | |
| const fromUser = Ladders.challenges.accept(this); | |
| this.pmTarget = fromUser; | |
| this.sendChatMessage(`/text You accepted the battle invite`); | |
| this.parse(`/join ${targetRoom.roomid}`); | |
| battle.joinGame(user, slot, playerOpts); | |
| }, | |
| acceptbattlehelp: [`/acceptbattle - Accept an invite from someone to join a battle.`], | |
| uninvitebattle(target, room, user, connection) { | |
| room = this.requireRoom(); | |
| this.checkCan("joinbattle", null, room); | |
| if (!room.battle) | |
| return this.errorReply(this.tr`You can only do this in battle rooms.`); | |
| const invitesFull = room.battle.invitesFull(); | |
| const challenges = Ladders.challenges.get(target); | |
| if (!challenges) | |
| throw new Chat.ErrorMessage(`User ${target} is not currently invited to the battle`); | |
| for (const challenge of challenges) { | |
| if (challenge.to === target && challenge.roomid === room.roomid) { | |
| Ladders.challenges.remove(challenge); | |
| Ladders.challenges.send(challenge.from, target, `/text The battle invite was changed to someone else; sorry!`); | |
| } | |
| } | |
| room.battle.sendInviteForm(invitesFull ? true : connection); | |
| }, | |
| uninvitebattlehelp: [ | |
| `/uninvitebattle [username] - Revokes an invite from a user to join a battle.`, | |
| `Requires: ${Users.PLAYER_SYMBOL} ~` | |
| ], | |
| restoreplayers(target, room, user) { | |
| room = this.requireRoom(); | |
| if (!room.battle) | |
| return this.errorReply(this.tr`You can only do this in battle rooms.`); | |
| if (room.rated) | |
| return this.errorReply(this.tr`You can only add a Player to unrated battles.`); | |
| let didSomething = false; | |
| for (const player of room.battle.players) { | |
| if (!player.id && player.name !== `Player ${player.num}`) { | |
| this.parse(`/invitebattle ${player.name}, ${player.slot}`); | |
| didSomething = true; | |
| } | |
| } | |
| if (!didSomething) { | |
| return this.errorReply(this.tr`Players could not be restored (maybe this battle already has two players?).`); | |
| } | |
| }, | |
| restoreplayershelp: [ | |
| `/restoreplayers - Restore previous players in an imported input log.` | |
| ], | |
| joinbattle: "joingame", | |
| joingame(target, room, user) { | |
| room = this.requireRoom(); | |
| if (!room.game) | |
| return this.errorReply(this.tr`This room doesn't have an active game.`); | |
| if (!room.game.joinGame) | |
| return this.errorReply(this.tr`This game doesn't support /joingame`); | |
| room.game.joinGame(user, target); | |
| }, | |
| joingamehelp: [`/joingame [username] - Join the game being played in the current room.`], | |
| leavebattle: "leavegame", | |
| partbattle: "leavegame", | |
| leavegame(target, room, user) { | |
| room = this.requireRoom(); | |
| if (!room.game) | |
| return this.errorReply(this.tr`This room doesn't have an active game.`); | |
| if (!room.game.leaveGame) | |
| return this.errorReply(this.tr`This game doesn't support /leavegame`); | |
| room.game.leaveGame(user); | |
| }, | |
| leavegamehelp: [`/leavegame - Leave the current game.`], | |
| kickbattle: "kickgame", | |
| kickgame(target, room, user) { | |
| room = this.requireRoom(); | |
| if (!room.battle) | |
| return this.errorReply(this.tr`You can only do this in battle rooms.`); | |
| if (room.battle.challengeType === "tour" || room.battle.rated) { | |
| return this.errorReply(this.tr`You can only do this in unrated non-tour battles.`); | |
| } | |
| const { targetUser, rest: reason } = this.requireUser(target, { allowOffline: true }); | |
| this.checkCan("kick", targetUser, room); | |
| if (room.battle.leaveGame(targetUser)) { | |
| const displayReason = reason ? ` (${reason})` : ``; | |
| this.addModAction(room.tr`${targetUser.name} was kicked from a battle by ${user.name}.${displayReason}`); | |
| this.modlog("KICKBATTLE", targetUser, reason, { noip: 1, noalts: 1 }); | |
| } else { | |
| this.errorReply("/kickbattle - User isn't in battle."); | |
| } | |
| }, | |
| kickbattlehelp: [`/kickbattle [username], [reason] - Kicks a user from a battle with reason. Requires: % @ ~`], | |
| kickinactive(target, room, user) { | |
| this.parse(`/timer on`); | |
| }, | |
| kickinactivehelp: [ | |
| `/kickinactive - Activates the inactive timer, if the game supports it.` | |
| ], | |
| timer(target, room, user) { | |
| target = toID(target); | |
| room = this.requireRoom(); | |
| if (!room.game?.timer) { | |
| return this.errorReply(this.tr`You can only set the timer from inside a battle room.`); | |
| } | |
| const timer = room.game.timer; | |
| if (!timer.timerRequesters) { | |
| return this.sendReply(this.tr`This game's timer is managed by a different command.`); | |
| } | |
| if (!target) { | |
| if (!timer.timerRequesters.size) { | |
| return this.sendReply(this.tr`The game timer is OFF.`); | |
| } | |
| const requester = [...timer.timerRequesters].join(", "); | |
| return this.sendReply(this.tr`The game timer is ON (requested by ${requester})`); | |
| } | |
| const force = user.can("timer", null, room); | |
| if (!force && !room.game.playerTable[user.id]) { | |
| return this.errorReply(this.tr`Access denied.`); | |
| } | |
| if (this.meansNo(target) || target === "stop") { | |
| if (timer.timerRequesters.size) { | |
| timer.stop(force ? void 0 : user); | |
| if (force) { | |
| room.send(`|inactiveoff|${room.tr`Timer was turned off by staff. Please do not turn it back on until our staff say it's okay.`}`); | |
| } | |
| } else { | |
| this.errorReply(this.tr`The timer is already off.`); | |
| } | |
| } else if (this.meansYes(target) || target === "start") { | |
| timer.start(user); | |
| } else { | |
| this.errorReply(this.tr`"${target}" is not a recognized timer state.`); | |
| } | |
| }, | |
| timerhelp: [ | |
| `/timer [start|stop] - Starts or stops the game timer. Requires: ${Users.PLAYER_SYMBOL} % @ ~` | |
| ], | |
| autotimer: "forcetimer", | |
| forcetimer(target, room, user) { | |
| room = this.requireRoom(); | |
| target = toID(target); | |
| this.checkCan("autotimer"); | |
| if (this.meansNo(target) || target === "stop") { | |
| Config.forcetimer = false; | |
| this.addModAction(room.tr`Forcetimer is now OFF: The timer is now opt-in. (set by ${user.name})`); | |
| } else if (this.meansYes(target) || target === "start" || !target) { | |
| Config.forcetimer = true; | |
| this.addModAction(room.tr`Forcetimer is now ON: All battles will be timed. (set by ${user.name})`); | |
| } else { | |
| this.errorReply(this.tr`'${target}' is not a recognized forcetimer setting.`); | |
| } | |
| }, | |
| forcetimerhelp: [ | |
| `/forcetimer [start|stop] - Forces all battles to have the inactive timer enabled. Requires: ~` | |
| ], | |
| forcetie: "forcewin", | |
| forcewin(target, room, user) { | |
| room = this.requireRoom(); | |
| this.checkCan("forcewin"); | |
| if (!room.battle && !(room.game && typeof room.game.tie === "function" && typeof room.game.win === "function")) { | |
| this.errorReply("/forcewin - This is not a battle room."); | |
| return false; | |
| } | |
| if (room.battle) | |
| room.battle.endType = "forced"; | |
| if (!target) { | |
| room.game.tie(); | |
| this.modlog("FORCETIE"); | |
| return false; | |
| } | |
| const targetUser = Users.getExact(target); | |
| if (!targetUser) | |
| return this.errorReply(this.tr`User '${target}' not found.`); | |
| room.game.win(targetUser); | |
| this.modlog("FORCEWIN", targetUser.id); | |
| }, | |
| forcewinhelp: [ | |
| `/forcetie - Forces the current match to end in a tie. Requires: ~`, | |
| `/forcewin [user] - Forces the current match to end in a win for a user. Requires: ~` | |
| ], | |
| /********************************************************* | |
| * Challenging and searching commands | |
| *********************************************************/ | |
| async search(target, room, user, connection) { | |
| if (target) { | |
| if (Config.laddermodchat && !Users.globalAuth.atLeast(user, Config.laddermodchat)) { | |
| const groupName = Config.groups[Config.laddermodchat].name || Config.laddermodchat; | |
| this.popupReply(this.tr`This server requires you to be rank ${groupName} or higher to search for a battle.`); | |
| return false; | |
| } | |
| const ladder = Ladders(target); | |
| if (!user.registered && Config.forceregisterelo && await ladder.getRating(user.id) >= Config.forceregisterelo) { | |
| user.send( | |
| import_lib.Utils.html`|popup||html|${this.tr`Since you have reached ${Config.forceregisterelo} ELO in ${target}, you must register your account to continue playing that format on ladder.`}<p style="text-align: center"><button name="register" value="${user.id}"><b>${this.tr`Register`}</b></button></p>` | |
| ); | |
| return false; | |
| } | |
| Chat.runHandlers("onLadderSearch", user, connection, ladder.formatid); | |
| return ladder.searchBattle(user, connection); | |
| } | |
| return Ladders.cancelSearches(user); | |
| }, | |
| searchhelp: [ | |
| `/search [format] - Searches for a battle in the specified format.` | |
| ], | |
| cancelsearch(target, room, user) { | |
| if (target) { | |
| Ladders(toID(target)).cancelSearch(user); | |
| } else { | |
| Ladders.cancelSearches(user); | |
| } | |
| }, | |
| cancelsearchhelp: [ | |
| `/cancelsearch [format] - Cancels a search for a battle in the specified format.`, | |
| `If no format is given, cancels searches for all formats.` | |
| ], | |
| chall: "challenge", | |
| challenge(target, room, user, connection) { | |
| const { targetUser, targetUsername, rest: formatName } = this.splitUser(target); | |
| if (!targetUser?.connected) { | |
| return this.popupReply(this.tr`The user '${targetUsername}' was not found.`); | |
| } | |
| if (user.locked && !targetUser.locked) { | |
| return this.popupReply(this.tr`You are locked and cannot challenge unlocked users. If this user is your friend, ask them to challenge you instead.`); | |
| } | |
| if (Punishments.isBattleBanned(user)) { | |
| return this.popupReply(this.tr`You are banned from battling and cannot challenge users.`); | |
| } | |
| if (!user.named) { | |
| return this.popupReply(this.tr`You must choose a username before you challenge someone.`); | |
| } | |
| if (Config.pmmodchat && !user.hasSysopAccess() && !Users.globalAuth.atLeast(user, Config.pmmodchat)) { | |
| const groupName = Config.groups[Config.pmmodchat].name || Config.pmmodchat; | |
| this.popupReply(this.tr`This server requires you to be rank ${groupName} or higher to challenge users.`); | |
| return false; | |
| } | |
| return Ladders(formatName).makeChallenge(connection, targetUser); | |
| }, | |
| challengehelp: [ | |
| `/challenge [user], [format] - Challenges the given [user] to a battle in the given [format].` | |
| ], | |
| bch: "blockchallenges", | |
| blockchall: "blockchallenges", | |
| blockchalls: "blockchallenges", | |
| blockchallenges(target, room, user) { | |
| if (toID(target) === "ac") | |
| target = "autoconfirmed"; | |
| if (user.settings.blockChallenges === (target || true)) { | |
| return this.errorReply(this.tr`You are already blocking challenges!`); | |
| } | |
| if (Users.Auth.isAuthLevel(target)) { | |
| user.settings.blockChallenges = target; | |
| this.sendReply(this.tr`You are now blocking challenges, except from staff and ${target}.`); | |
| } else if (target === "autoconfirmed" || target === "trusted" || target === "unlocked" || target === "friends") { | |
| user.settings.blockChallenges = target; | |
| if (target === "friends") | |
| target = "friended"; | |
| target = this.tr(target); | |
| this.sendReply(this.tr`You are now blocking challenges, except from staff and ${target} users.`); | |
| } else { | |
| user.settings.blockChallenges = true; | |
| this.sendReply(this.tr`You are now blocking all incoming challenge requests.`); | |
| } | |
| user.update(); | |
| }, | |
| blockchallengeshelp: [ | |
| `/blockchallenges - Blocks challenges so no one can challenge you. Unblock them with /unblockchallenges.` | |
| ], | |
| unbch: "allowchallenges", | |
| unblockchall: "allowchallenges", | |
| unblockchalls: "allowchallenges", | |
| unblockchallenges: "allowchallenges", | |
| allowchallenges(target, room, user) { | |
| if (!user.settings.blockChallenges) | |
| return this.errorReply(this.tr`You are already available for challenges!`); | |
| user.settings.blockChallenges = false; | |
| user.update(); | |
| this.sendReply(this.tr`You are available for challenges from now on.`); | |
| }, | |
| allowchallengeshelp: [ | |
| `/unblockchallenges - Unblocks challenges so you can be challenged again. Block them with /blockchallenges.` | |
| ], | |
| cchall: "cancelchallenge", | |
| cancelchallenge(target, room, user, connection) { | |
| const { targetUser, targetUsername, rest } = this.splitUser(target); | |
| if (rest) | |
| return this.popupReply(this.tr`This command does not support specifying multiple users`); | |
| this.pmTarget = targetUser || this.pmTarget; | |
| if (!this.pmTarget) | |
| return this.popupReply(this.tr`User "${targetUsername}" not found.`); | |
| const chall = Ladders.challenges.search(user.id, this.pmTarget.id); | |
| if (!chall || chall.from !== user.id) { | |
| connection.popup(`You are not challenging ${this.pmTarget.name}. Maybe they accepted/rejected before you cancelled?`); | |
| return false; | |
| } | |
| this.sendChatMessage(`/log ${user.name} cancelled the challenge.`); | |
| return Ladders.challenges.remove(chall); | |
| }, | |
| cancelchallengehelp: [ | |
| `/cancelchallenge [user] - Cancels a pending challenge to the given [user].` | |
| ], | |
| async accept(target, room, user, connection) { | |
| const { targetUser, targetUsername, rest } = this.splitUser(target); | |
| if (rest) | |
| return this.popupReply(this.tr`This command does not support specifying multiple users`); | |
| this.pmTarget = targetUser || this.pmTarget; | |
| if (!this.pmTarget) | |
| return this.popupReply(this.tr`User "${targetUsername}" not found.`); | |
| const chall = Ladders.challenges.search(user.id, this.pmTarget.id); | |
| if (!chall || chall.to !== user.id) { | |
| connection.popup(`${this.pmTarget.id} is not challenging you. Maybe they cancelled before you accepted?`); | |
| return false; | |
| } | |
| if (chall.acceptCommand) { | |
| return this.parse(chall.acceptCommand); | |
| } | |
| const gameRoom = await Ladders.acceptChallenge(connection, chall); | |
| if (!gameRoom) | |
| return false; | |
| this.sendChatMessage(import_lib.Utils.html`/nonotify ${user.name} accepted the challenge, starting «<a href="/${gameRoom.roomid}">${gameRoom.roomid}</a>»`); | |
| return true; | |
| }, | |
| accepthelp: [`/accept [user] - Accepts a challenge from the given user.`], | |
| reject(target, room, user, connection) { | |
| const { targetUser, targetUsername, rest } = this.splitUser(target); | |
| if (rest) | |
| return this.popupReply(this.tr`This command does not support specifying multiple users`); | |
| this.pmTarget = targetUser || this.pmTarget; | |
| if (!this.pmTarget) | |
| return this.popupReply(this.tr`User "${targetUsername}" not found.`); | |
| const chall = Ladders.challenges.search(user.id, this.pmTarget.id); | |
| if (!chall || chall.to !== user.id) { | |
| connection.popup(`${this.pmTarget.id} is not challenging you. Maybe they cancelled before you rejected?`); | |
| return false; | |
| } | |
| this.sendChatMessage(`/nonotify ${user.name} rejected the challenge.`); | |
| return Ladders.challenges.remove(chall, false); | |
| }, | |
| rejecthelp: [`/reject [user] - Rejects a challenge from the given user.`], | |
| saveteam: "useteam", | |
| utm: "useteam", | |
| useteam(target, room, user) { | |
| user.battleSettings.team = target; | |
| }, | |
| useteamhelp: [`/useteam [packed team] - Sets your team for your next battles to the given [team].`], | |
| vtm(target, room, user, connection) { | |
| if (Monitor.countPrepBattle(connection.ip, connection)) { | |
| return; | |
| } | |
| if (!target) | |
| return this.errorReply(this.tr`Provide a valid format.`); | |
| const originalFormat = Dex.formats.get(target); | |
| const format = originalFormat.effectType === "Format" ? originalFormat : Dex.formats.get("Anything Goes"); | |
| if (format.effectType !== "Format") | |
| return this.popupReply(this.tr`Please provide a valid format.`); | |
| return TeamValidatorAsync.get(format.id).validateTeam(user.battleSettings.team, { user: user.id }).then((result) => { | |
| const matchMessage = originalFormat === format ? "" : this.tr`The format '${originalFormat.name}' was not found.`; | |
| if (result.startsWith("1")) { | |
| connection.popup(`${matchMessage ? matchMessage + "\n\n" : ""}${this.tr`Your team is valid for ${format.name}.`}`); | |
| } else { | |
| connection.popup(`${matchMessage ? matchMessage + "\n\n" : ""}${this.tr`Your team was rejected for the following reasons:`} | |
| - ${result.slice(1).replace(/\n/g, "\n- ")}`); | |
| } | |
| }); | |
| }, | |
| vtmhelp: [`/vtm [format] - Validates your current team (set with /utm).`], | |
| hbtc: "hidebattlesfromtrainercard", | |
| sbtc: "hidebattlesfromtrainercard", | |
| showbattlesinusercard: "hidebattlesfromtrainercard", | |
| hidebattlesfromusercard: "hidebattlesfromtrainercard", | |
| showbattlesintrainercard: "hidebattlesfromtrainercard", | |
| hidebattlesfromtrainercard(target, room, user, connection, cmd) { | |
| const shouldHide = cmd.includes("hide") || cmd === "hbtc"; | |
| user.settings.hideBattlesFromTrainerCard = shouldHide; | |
| user.update(); | |
| if (shouldHide) { | |
| this.sendReply(this.tr`Battles are now hidden (except to staff) in your trainer card.`); | |
| } else { | |
| this.sendReply(this.tr`Battles are now visible in your trainer card.`); | |
| } | |
| }, | |
| hidebattlesfromtrainercardhelp: [ | |
| `/hidebattlesfromtrainercard OR /hbtc - Hides your battles in your trainer card.`, | |
| `/showbattlesintrainercard OR /sbtc - Displays your battles in your trainer card.` | |
| ], | |
| /********************************************************* | |
| * Low-level | |
| *********************************************************/ | |
| cmd: "crq", | |
| query: "crq", | |
| async crq(target, room, user, connection) { | |
| const trustable = !Config.emergency || user.named && user.registered; | |
| let cmd; | |
| [cmd, target] = import_lib.Utils.splitFirst(target, " "); | |
| const handler = Chat.crqHandlers[cmd]; | |
| if (!handler) | |
| return connection.send(`|queryresponse|${cmd}|null`); | |
| let data = handler.call(this, target, user, trustable); | |
| if (data?.then) | |
| data = await data; | |
| connection.send(`|queryresponse|${cmd}|${JSON.stringify(data)}`); | |
| }, | |
| trn(target, room, user, connection) { | |
| if (target === user.name) | |
| return false; | |
| const [name, registeredString, token] = import_lib.Utils.splitFirst(target, ",", 2); | |
| const registered = !!parseInt(registeredString); | |
| return user.rename(name, token || "", registered, connection); | |
| }, | |
| trnhelp: [ | |
| `/trn [username], [registered], [token] - Finishes a rename to the [username] with a given [token].` | |
| ], | |
| /********************************************************* | |
| * Help commands | |
| *********************************************************/ | |
| commands: "help", | |
| h: "help", | |
| "?": "help", | |
| man: "help", | |
| help(target, room, user) { | |
| if (!this.runBroadcast()) | |
| return; | |
| target = target.toLowerCase().trim(); | |
| if (target.startsWith("/") || target.startsWith("!")) | |
| target = target.slice(1); | |
| if (!target) { | |
| const broadcastMsg = this.tr`(replace / with ! to broadcast. Broadcasting requires: + % @ # ~)`; | |
| this.sendReply(`${this.tr`COMMANDS`}: /report, /msg, /reply, /logout, /challenge, /search, /rating, /whois, /user, /join, /leave, /userauth, /roomauth`); | |
| this.sendReply(`${this.tr`BATTLE ROOM COMMANDS`}: /savereplay, /hideroom, /inviteonly, /invite, /timer, /forfeit`); | |
| this.sendReply(`${this.tr`OPTION COMMANDS`}: /nick, /avatar, /ignore, /status, /away, /busy, /back, /timestamps, /highlight, /showjoins, /hidejoins, /blockchallenges, /blockpms`); | |
| this.sendReply(`${this.tr`INFORMATIONAL/RESOURCE COMMANDS`}: /groups, /faq, /rules, /intro, /formatshelp, /othermetas, /analysis, /punishments, /calc, /git, /cap, /roomhelp, /roomfaq ${broadcastMsg}`); | |
| this.sendReply(`${this.tr`DATA COMMANDS`}: /data, /dexsearch, /movesearch, /itemsearch, /learn, /statcalc, /effectiveness, /weakness, /coverage, /randommove, /randompokemon ${broadcastMsg}`); | |
| if (user.tempGroup !== Users.Auth.defaultSymbol()) { | |
| this.sendReply(`${this.tr`DRIVER COMMANDS`}: /warn, /mute, /hourmute, /unmute, /alts, /forcerename, /modlog, /modnote, /modchat, /lock, /weeklock, /unlock, /announce`); | |
| this.sendReply(`${this.tr`MODERATOR COMMANDS`}: /globalban, /unglobalban, /ip, /markshared, /unlockip`); | |
| this.sendReply(`${this.tr`ADMIN COMMANDS`}: /declare, /forcetie, /forcewin, /promote, /demote, /banip, /host, /ipsearch`); | |
| } | |
| this.sendReply(this.tr`For an overview of room commands, use /roomhelp`); | |
| this.sendReply(this.tr`For details of a specific command, use something like: /help data`); | |
| return; | |
| } | |
| const getHelp = (namespace, cmds) => { | |
| const [cmd, ...subCmds] = cmds; | |
| if (subCmds.length) { | |
| const subNamespace = namespace[cmd]; | |
| if (typeof subNamespace === "object" && !Array.isArray(subNamespace)) { | |
| if (getHelp(subNamespace, subCmds)) { | |
| return true; | |
| } | |
| } | |
| } | |
| const curHandler = namespace[cmd]; | |
| let isPrivate = curHandler?.isPrivate; | |
| let requiredPerm = curHandler?.requiredPermission || "lock"; | |
| let help = namespace[`${cmd}help`]; | |
| if (typeof help === "string") { | |
| help = namespace[help]; | |
| } | |
| if (!help && typeof namespace[cmd] === "string") { | |
| help = namespace[`${namespace[cmd]}help`]; | |
| } | |
| if (!help && namespace !== Chat.commands && namespace["help"]) { | |
| help = namespace["help"]; | |
| } | |
| const isNamespace = typeof Chat.commands[cmd] === "object"; | |
| if ((namespace !== Chat.commands || isNamespace) && !isPrivate) { | |
| if (isNamespace) | |
| namespace = Chat.commands[cmd]; | |
| for (const k in namespace) { | |
| const cur = namespace[k]; | |
| if (typeof cur === "function" && cur.isPrivate) { | |
| isPrivate = true; | |
| requiredPerm = cur.requiredPermission || "lock"; | |
| } | |
| } | |
| } | |
| if (isPrivate && !user.can(requiredPerm)) { | |
| throw new Chat.ErrorMessage(this.tr`The command '/${target}' does not exist.`); | |
| } | |
| if (typeof help === "function") { | |
| this.run(help); | |
| return true; | |
| } | |
| if (Array.isArray(help)) { | |
| this.sendReply(help.map((line) => this.tr(line)).join("\n")); | |
| return true; | |
| } | |
| if (!curHandler) { | |
| for (const g in Config.groups) { | |
| const groupid = Config.groups[g].id; | |
| if (new RegExp(`(global)?(un|de)?${groupid}`).test(cmd)) { | |
| return this.parse(`/help promote`); | |
| } | |
| if (new RegExp(`room(un|de)?${groupid}`).test(cmd)) { | |
| return this.parse(`/help roompromote`); | |
| } | |
| } | |
| throw new Chat.ErrorMessage(this.tr`The command '/${target}' does not exist.`); | |
| } | |
| if (cmd.endsWith("help")) { | |
| this.sendReply(this.tr`'/${target}' is a help command.`); | |
| return true; | |
| } | |
| return false; | |
| }; | |
| if (!getHelp(Chat.commands, target.split(" "))) { | |
| throw new Chat.ErrorMessage(this.tr`Could not find help for '/${target}'. Try /help for general help.`); | |
| } | |
| }, | |
| helphelp: [ | |
| `/help OR /h OR /? - Gives you help.`, | |
| `/help [command] - Gives you help for a specific command.` | |
| ] | |
| }; | |
| const pages = { | |
| receivedpms(query, user) { | |
| this.title = "[Received PMs]"; | |
| if (!Chat.PrivateMessages.offlineIsEnabled) { | |
| return this.errorReply(`Offline PMs are presently disabled.`); | |
| } | |
| return Chat.PrivateMessages.renderReceived(user); | |
| } | |
| }; | |
| process.nextTick(() => { | |
| Chat.multiLinePattern.register( | |
| ">>>? ", | |
| "/(?:room|staff)intro ", | |
| "/(?:staff)?topic ", | |
| "/(?:add|widen)datacenters ", | |
| "/bash ", | |
| "!code ", | |
| "/code ", | |
| "/modnote ", | |
| "/mn ", | |
| "/eval", | |
| "!eval", | |
| "/evalbattle", | |
| "/evalsql", | |
| ">>sql", | |
| "/importinputlog " | |
| ); | |
| }); | |
| const loginfilter = (user) => { | |
| if (!Chat.PrivateMessages.checkCanUse(user, { isLogin: true, forceBool: true })) | |
| return; | |
| void Chat.PrivateMessages.sendReceived(user); | |
| }; | |
| //# sourceMappingURL=core.js.map | |