/** * Command parser * Pokemon Showdown - http://pokemonshowdown.com/ * * This is the command parser. Call it with CommandParser.parse * (scroll down to its definition for details) * * Individual commands are put in: * commands.js - "core" commands that shouldn't be modified * config/commands.js - other commands that can be safely modified * * The command API is (mostly) documented in config/commands.js * * @license MIT license */ /* To reload chat commands: /hotpatch chat */ const MAX_MESSAGE_LENGTH = 300; const BROADCAST_COOLDOWN = 20 * 1000; const MESSAGE_COOLDOWN = 5 * 60 * 1000; const MAX_PARSE_RECURSION = 10; var fs = require('fs'); /********************************************************* * Load command files *********************************************************/ var commands = exports.commands = require('./commands.js').commands; var customCommands = require('./config/commands.js'); if (customCommands && customCommands.commands) { Object.merge(commands, customCommands.commands); } // Install plug-in commands fs.readdirSync('./chat-plugins').forEach(function (file) { if (file.substr(-3) === '.js') Object.merge(commands, require('./chat-plugins/' + file).commands); }); /********************************************************* * Parser *********************************************************/ var modlog = exports.modlog = {lobby: fs.createWriteStream('logs/modlog/modlog_lobby.txt', {flags:'a+'}), battle: fs.createWriteStream('logs/modlog/modlog_battle.txt', {flags:'a+'})}; /** * Can this user talk? * Shows an error message if not. */ function canTalk(user, room, connection, message) { if (!user.named) { connection.popup("You must choose a name before you can talk."); return false; } if (room && user.locked) { connection.sendTo(room, "You are locked from talking in chat."); return false; } if (room && user.mutedRooms[room.id]) { connection.sendTo(room, "You are muted and cannot talk in this room."); return false; } if (room && room.modchat) { if (room.modchat === 'crash') { if (!user.can('ignorelimits')) { connection.sendTo(room, "Because the server has crashed, you cannot speak in lobby chat."); return false; } } else { var userGroup = user.group; if (room.auth) { if (room.auth[user.userid]) { userGroup = room.auth[user.userid]; } else if (room.isPrivate === true) { userGroup = ' '; } } if (!user.autoconfirmed && (room.auth && room.auth[user.userid] || user.group) === ' ' && room.modchat === 'autoconfirmed') { connection.sendTo(room, "Because moderated chat is set, your account must be at least one week old and you must have won at least one ladder game to speak in this room."); return false; } else if (Config.groupsranking.indexOf(userGroup) < Config.groupsranking.indexOf(room.modchat)) { var groupName = Config.groups[room.modchat].name || room.modchat; connection.sendTo(room, "Because moderated chat is set, you must be of rank " + groupName + " or higher to speak in this room."); return false; } } } if (room && !(user.userid in room.users)) { connection.popup("You can't send a message to this room without being in it."); return false; } if (typeof message === 'string') { if (!message) { connection.popup("Your message can't be blank."); return false; } if (message.length > MAX_MESSAGE_LENGTH && !user.can('ignorelimits')) { connection.popup("Your message is too long:\n\n" + message); return false; } // remove zalgo message = message.replace(/[\u0300-\u036f\u0483-\u0489\u064b-\u065f\u0670\u0E31\u0E34-\u0E3A\u0E47-\u0E4E]{3,}/g, ''); if (room && room.id === 'lobby') { var normalized = message.trim(); if ((normalized === user.lastMessage) && ((Date.now() - user.lastMessageTime) < MESSAGE_COOLDOWN)) { connection.popup("You can't send the same message again so soon."); return false; } user.lastMessage = message; user.lastMessageTime = Date.now(); } if (Config.chatfilter) { return Config.chatfilter(message, user, room, connection); } return message; } return true; } /** * Command parser * * Usage: * CommandParser.parse(message, room, user, connection) * * message - the message the user is trying to say * room - the room the user is trying to say it in * user - the user that sent the message * connection - the connection the user sent the message from * * Returns the message the user should say, or a falsy value which * means "don't say anything" * * Examples: * CommandParser.parse("/join lobby", room, user, connection) * will make the user join the lobby, and return false. * * CommandParser.parse("Hi, guys!", room, user, connection) * will return "Hi, guys!" if the user isn't muted, or * if he's muted, will warn him that he's muted, and * return false. */ var parse = exports.parse = function (message, room, user, connection, levelsDeep) { var cmd = '', target = ''; if (!message || !message.trim().length) return; if (!levelsDeep) { levelsDeep = 0; } else { if (levelsDeep > MAX_PARSE_RECURSION) { return connection.sendTo(room, "Error: Too much recursion"); } } if (message.substr(0, 3) === '>> ') { // multiline eval message = '/eval ' + message.substr(3); } else if (message.substr(0, 4) === '>>> ') { // multiline eval message = '/evalbattle ' + message.substr(4); } if (message.charAt(0) === '/' && message.charAt(1) !== '/') { var spaceIndex = message.indexOf(' '); if (spaceIndex > 0) { cmd = message.substr(1, spaceIndex - 1); target = message.substr(spaceIndex + 1); } else { cmd = message.substr(1); target = ''; } } else if (message.charAt(0) === '!') { var spaceIndex = message.indexOf(' '); if (spaceIndex > 0) { cmd = message.substr(0, spaceIndex); target = message.substr(spaceIndex + 1); } else { cmd = message; target = ''; } } cmd = cmd.toLowerCase(); var broadcast = false; if (cmd.charAt(0) === '!') { broadcast = true; cmd = cmd.substr(1); } var namespaces = []; var currentCommands = commands; var originalMessage = message; var commandHandler; do { commandHandler = currentCommands[cmd]; if (typeof commandHandler === 'string') { // in case someone messed up, don't loop commandHandler = currentCommands[commandHandler]; } if (commandHandler && typeof commandHandler === 'object') { namespaces.push(cmd); var newCmd = target; var newTarget = ''; var spaceIndex = target.indexOf(' '); if (spaceIndex > 0) { newCmd = target.substr(0, spaceIndex); newTarget = target.substr(spaceIndex + 1); } newCmd = newCmd.toLowerCase(); var newMessage = message.replace(cmd + (target ? ' ' : ''), ''); cmd = newCmd; target = newTarget; message = newMessage; currentCommands = commandHandler; } } while (commandHandler && typeof commandHandler === 'object'); if (!commandHandler && currentCommands.default) { commandHandler = currentCommands.default; if (typeof commandHandler === 'string') { commandHandler = currentCommands[commandHandler]; } } var fullCmd = namespaces.concat(cmd).join(' '); if (commandHandler) { var context = { sendReply: function (data) { if (this.broadcasting) { room.add(data, true); } else { connection.sendTo(room, data); } }, sendReplyBox: function (html) { this.sendReply('|raw|