/chat-commands.js
JavaScript | 2134 lines | 1890 code | 181 blank | 63 comment | 424 complexity | b230d2c5713942189b1ead1f84151dd4 MD5 | raw file
1/* to reload chat commands: 2 3>> for (var i in require.cache) delete require.cache[i];parseCommand = require('./chat-commands.js').parseCommand;'' 4 5*/ 6 7var crypto = require('crypto'); 8 9/** 10 * `parseCommand`. This is the function most of you are interested in, 11 * apparently. 12 * 13 * `message` is exactly what the user typed in. 14 * If the user typed in a command, `cmd` and `target` are the command (with "/" 15 * omitted) and command target. Otherwise, they're both the empty string. 16 * 17 * For instance, say a user types in "/foo": 18 * cmd === "/foo", target === "", message === "/foo bar baz" 19 * 20 * Or, say a user types in "/foo bar baz": 21 * cmd === "foo", target === "bar baz", message === "/foo bar baz" 22 * 23 * Or, say a user types in "!foo bar baz": 24 * cmd === "!foo", target === "bar baz", message === "!foo bar baz" 25 * 26 * Or, say a user types in "foo bar baz": 27 * cmd === "", target === "", message === "foo bar baz" 28 * 29 * `user` and `socket` are the user and socket that sent the message, 30 * and `room` is the room that sent the message. 31 * 32 * Deal with the message however you wish: 33 * return; will output the message normally: "user: message" 34 * return false; will supress the message output. 35 * returning a string will replace the message with that string, 36 * then output it normally. 37 * 38 */ 39 40var modlog = modlog || fs.createWriteStream('logs/modlog.txt', {flags:'a+'}); 41var updateServerLock = false; 42 43function parseCommandLocal(user, cmd, target, room, socket, message) { 44 if (!room) return; 45 cmd = cmd.toLowerCase(); 46 switch (cmd) { 47 case 'cmd': 48 var spaceIndex = target.indexOf(' '); 49 var cmd = target; 50 if (spaceIndex > 0) { 51 cmd = target.substr(0, spaceIndex); 52 target = target.substr(spaceIndex+1); 53 } else { 54 target = ''; 55 } 56 if (cmd === 'userdetails') { 57 var targetUser = Users.get(target); 58 if (!targetUser || !room) return false; 59 var roomList = {}; 60 for (var i in targetUser.roomCount) { 61 if (i==='lobby') continue; 62 var targetRoom = Rooms.get(i); 63 if (!targetRoom) continue; 64 var roomData = {}; 65 if (targetRoom.battle) { 66 var battle = targetRoom.battle; 67 roomData.p1 = battle.p1?' '+battle.p1:''; 68 roomData.p2 = battle.p2?' '+battle.p2:''; 69 } 70 roomList[i] = roomData; 71 } 72 var userdetails = { 73 command: 'userdetails', 74 userid: targetUser.userid, 75 avatar: targetUser.avatar, 76 rooms: roomList, 77 room: room.id 78 }; 79 if (user.can('ip', targetUser)) { 80 userdetails.ip = targetUser.ip; 81 } 82 emit(socket, 'command', userdetails); 83 } else if (cmd === 'roomlist') { 84 if (!room || !room.getRoomList) return false; 85 emit(socket, 'command', { 86 command: 'roomlist', 87 rooms: room.getRoomList(true), 88 room: room.id 89 }); 90 } 91 return false; 92 break; 93 94 case 'me': 95 case 'mee': 96 if (canTalk(user, room)) return true; 97 break; 98 99 case '!birkal': 100 case 'birkal': 101 if (canTalk(user, room) && user.can('broadcast') && room.id === 'lobby') { 102 if (cmd === '!birkal') { 103 room.add('|c|'+user.getIdentity()+'|!birkal '+target, true); 104 } 105 room.logEntry(user.name + ' used /birkal ' + target); 106 room.add('|c| Birkal|/me '+target, true); 107 return false; 108 } 109 break; 110 111 case 'namelock': 112 case 'nl': 113 if(!target) { 114 return false; 115 } 116 var targets = splitTarget(target); 117 var targetUser = targets[0]; 118 var targetName = targets[1] || (targetUser && targetUser.name); 119 if (!user.can('namelock', targetUser)) { 120 emit(socket, 'console', '/namelock - access denied.'); 121 return false; 122 } else if (targetUser && targetName) { 123 var oldname = targetUser.name; 124 var targetId = toUserid(targetName); 125 var userOfName = Users.users[targetId]; 126 var isAlt = false; 127 if (userOfName) { 128 for(var altName in userOfName.getAlts()) { 129 var altUser = Users.users[toUserid(altName)]; 130 if (!altUser) continue; 131 if (targetId === altUser.userid) { 132 isAlt = true; 133 break; 134 } 135 for (var prevName in altUser.prevNames) { 136 if (targetId === toUserid(prevName)) { 137 isAlt = true; 138 break; 139 } 140 } 141 if (isAlt) break; 142 } 143 } 144 if (!userOfName || oldname === targetName || isAlt) { 145 targetUser.nameLock(targetName, true); 146 } 147 if (targetUser.nameLocked()) { 148 logModCommand(room,user.name+" name-locked "+oldname+" to "+targetName+"."); 149 return false; 150 } 151 emit(socket, 'console', oldname+" can't be name-locked to "+targetName+"."); 152 } else { 153 emit(socket, 'console', "User "+targets[2]+" not found."); 154 } 155 return false; 156 break; 157 case 'nameunlock': 158 case 'unnamelock': 159 case 'nul': 160 case 'unl': 161 if(!user.can('namelock') || !target) { 162 return false; 163 } 164 var removed = false; 165 for (var i in nameLockedIps) { 166 if (nameLockedIps[i] === target) { 167 delete nameLockedIps[i]; 168 removed = true; 169 } 170 } 171 if (removed) { 172 var targetUser = Users.get(target); 173 if (targetUser) { 174 rooms.lobby.sendIdentity(targetUser); 175 } 176 logModCommand(room,user.name+" unlocked the name of "+target+"."); 177 } else { 178 emit(socket, 'console', target+" not found."); 179 } 180 return false; 181 break; 182 183 case 'forfeit': 184 case 'concede': 185 case 'surrender': 186 if (!room.battle) { 187 emit(socket, 'console', "There's nothing to forfeit here."); 188 return false; 189 } 190 if (!room.forfeit(user)) { 191 emit(socket, 'console', "You can't forfeit this battle."); 192 } 193 return false; 194 break; 195 196 case 'register': 197 emit(socket, 'console', 'You must win a rated battle to register.'); 198 return false; 199 break; 200 201 case 'avatar': 202 if (!target) return parseCommand(user, 'avatars', '', room, socket); 203 var parts = target.split(','); 204 var avatar = parseInt(parts[0]); 205 if (!avatar || avatar > 294 || avatar < 1) { 206 if (!parts[1]) { 207 emit(socket, 'console', 'Invalid avatar.'); 208 } 209 return false; 210 } 211 212 user.avatar = avatar; 213 if (!parts[1]) { 214 emit(socket, 'console', 'Avatar changed to:'); 215 emit(socket, 'console', {rawMessage: '<img src="/sprites/trainers/'+avatar+'.png" alt="" width="80" height="80" />'}); 216 } 217 218 return false; 219 break; 220 221 case 'rooms': 222 var targetUser = user; 223 if (target) targetUser = Users.get(target); 224 if (!targetUser) { 225 emit(socket, 'console', 'User '+target+' not found.'); 226 } else { 227 var output = ""; 228 var first = true; 229 for (var i in targetUser.roomCount) { 230 if (!first) output += ' | '; 231 first = false; 232 233 output += '<a href="/'+i+'" room="'+i+'">'+i+'</a>'; 234 } 235 if (!output) { 236 emit(socket, 'console', ""+targetUser.name+" is offline."); 237 } else { 238 emit(socket, 'console', {rawMessage: ""+targetUser.name+" is in: "+output}); 239 } 240 } 241 return false; 242 break; 243 244 case 'altcheck': 245 case 'alt': 246 case 'alts': 247 case 'getalts': 248 if (!target) return parseCommand(user, '?', cmd, room, socket); 249 var targetUser = Users.get(target); 250 if (!targetUser) { 251 emit(socket, 'console', 'User '+target+' not found.'); 252 return false; 253 } 254 if (!user.can('alts', targetUser)) { 255 emit(socket, 'console', '/alts - Access denied.'); 256 return false; 257 } 258 259 var alts = targetUser.getAlts(); 260 261 emit(socket, 'console', 'User: '+targetUser.name); 262 263 if (!user.can('alts', targetUser.getHighestRankedAlt())) { 264 return false; 265 } 266 267 var output = ''; 268 for (var i in targetUser.prevNames) { 269 if (output) output += ", "; 270 output += targetUser.prevNames[i]; 271 } 272 if (output) emit(socket, 'console', 'Previous names: '+output); 273 274 for (var j=0; j<alts.length; j++) { 275 var targetAlt = Users.get(alts[j]); 276 if (!targetAlt.named && !targetAlt.connected) continue; 277 278 emit(socket, 'console', 'Alt: '+targetAlt.name); 279 output = ''; 280 for (var i in targetAlt.prevNames) { 281 if (output) output += ", "; 282 output += targetAlt.prevNames[i]; 283 } 284 if (output) emit(socket, 'console', 'Previous names: '+output); 285 } 286 return false; 287 break; 288 289 case 'whois': 290 var targetUser = user; 291 if (target) { 292 targetUser = Users.get(target); 293 } 294 if (!targetUser) { 295 emit(socket, 'console', 'User '+target+' not found.'); 296 } else { 297 emit(socket, 'console', 'User: '+targetUser.name); 298 if (config.groups[targetUser.group] && config.groups[targetUser.group].name) { 299 emit(socket, 'console', 'Group: ' + config.groups[targetUser.group].name + ' (' + targetUser.group + ')'); 300 } 301 if (!targetUser.authenticated) { 302 emit(socket, 'console', '(Unregistered)'); 303 } 304 if (user.can('ip', targetUser)) { 305 emit(socket, 'console', 'IP: '+targetUser.ip); 306 } 307 var output = 'In rooms: '; 308 var first = true; 309 for (var i in targetUser.roomCount) { 310 if (!first) output += ' | '; 311 first = false; 312 313 output += '<a href="/'+i+'" room="'+i+'">'+i+'</a>'; 314 } 315 emit(socket, 'console', {rawMessage: output}); 316 } 317 return false; 318 break; 319 320 case 'ban': 321 case 'b': 322 if (!target) return parseCommand(user, '?', cmd, room, socket); 323 var targets = splitTarget(target); 324 var targetUser = targets[0]; 325 if (!targetUser) { 326 emit(socket, 'console', 'User '+targets[2]+' not found.'); 327 return false; 328 } 329 if (!user.can('ban', targetUser)) { 330 emit(socket, 'console', '/ban - Access denied.'); 331 return false; 332 } 333 334 logModCommand(room,""+targetUser.name+" was banned by "+user.name+"." + (targets[1] ? " (" + targets[1] + ")" : "")); 335 targetUser.emit('message', user.name+' has banned you. If you feel that your banning was unjustified you can <a href="http://www.smogon.com/forums/announcement.php?f=126&a=204" target="_blank">appeal the ban</a>. '+targets[1]); 336 var alts = targetUser.getAlts(); 337 if (alts.length) logModCommand(room,""+targetUser.name+"'s alts were also banned: "+alts.join(", ")); 338 339 targetUser.ban(); 340 return false; 341 break; 342 343 case 'banredirect': 344 case 'br': 345 emit(socket, 'console', '/banredirect - This command is obsolete and has been removed.'); 346 return false; 347 break; 348 349 case 'redirect': 350 case 'redir': 351 emit(socket, 'console', '/redirect - This command is obsolete and has been removed.'); 352 return false; 353 break; 354 355 case 'kick': 356 case 'k': 357 // TODO: /kick will be removed in due course. 358 if (!target) return parseCommand(user, '?', cmd, room, socket); 359 var targets = splitTarget(target); 360 var targetUser = targets[0]; 361 if (!targetUser || !targetUser.connected) { 362 emit(socket, 'console', 'User '+targets[2]+' not found.'); 363 return false; 364 } 365 if (!user.can('redirect', targetUser)) { 366 emit(socket, 'console', '/redirect - Access denied.'); 367 return false; 368 } 369 370 logModCommand(room,''+targetUser.name+' was kicked to the Rules page by '+user.name+'' + (targets[1] ? " (" + targets[1] + ")" : "")); 371 targetUser.emit('console', {evalRulesRedirect: 1}); 372 return false; 373 break; 374 375 case 'unban': 376 if (!target) return parseCommand(user, '?', cmd, room, socket); 377 if (!user.can('ban')) { 378 emit(socket, 'console', '/unban - Access denied.'); 379 return false; 380 } 381 382 var targetid = toUserid(target); 383 var success = false; 384 385 for (var ip in bannedIps) { 386 if (bannedIps[ip] === targetid) { 387 delete bannedIps[ip]; 388 success = true; 389 } 390 } 391 if (success) { 392 logModCommand(room,''+target+' was unbanned by '+user.name+'.'); 393 } else { 394 emit(socket, 'console', 'User '+target+' is not banned.'); 395 } 396 return false; 397 break; 398 399 case 'unbanall': 400 if (!user.can('ban')) { 401 emit(socket, 'console', '/unbanall - Access denied.'); 402 return false; 403 } 404 logModCommand(room,'All bans and ip mutes have been lifted by '+user.name+'.'); 405 bannedIps = {}; 406 mutedIps = {}; 407 return false; 408 break; 409 410 case 'reply': 411 case 'r': 412 if (!target) return parseCommand(user, '?', cmd, room, socket); 413 if (!user.lastPM) { 414 emit(socket, 'console', 'No one has PMed you yet.'); 415 return false; 416 } 417 return parseCommand(user, 'msg', ''+(user.lastPM||'')+', '+target, room, socket); 418 break; 419 420 case 'msg': 421 case 'pm': 422 case 'whisper': 423 case 'w': 424 if (!target) return parseCommand(user, '?', cmd, room, socket); 425 var targets = splitTarget(target); 426 var targetUser = targets[0]; 427 if (!targets[1]) { 428 emit(socket, 'console', 'You forgot the comma.'); 429 return parseCommand(user, '?', cmd, room, socket); 430 } 431 if (!targets[0] || !targetUser.connected) { 432 if (target.indexOf(' ')) { 433 emit(socket, 'console', 'User '+targets[2]+' not found. Did you forget a comma?'); 434 } else { 435 emit(socket, 'console', 'User '+targets[2]+' not found. Did you misspell their name?'); 436 } 437 return parseCommand(user, '?', cmd, room, socket); 438 } 439 // temporarily disable this because blarajan 440 /* if (user.muted && !targetUser.can('mute', user)) { 441 emit(socket, 'console', 'You can only private message members of the Moderation Team (users marked by %, @, &, or ~) when muted.'); 442 return false; 443 } */ 444 445 if (!user.named) { 446 emit(socket, 'console', 'You must choose a name before you can send private messages.'); 447 return false; 448 } 449 450 var message = { 451 name: user.getIdentity(), 452 pm: targetUser.getIdentity(), 453 message: targets[1] 454 }; 455 user.emit('console', message); 456 targets[0].emit('console', message); 457 targets[0].lastPM = user.userid; 458 user.lastPM = targets[0].userid; 459 return false; 460 break; 461 462 case 'ip': 463 case 'getip': 464 if (!target) { 465 emit(socket, 'console', 'Your IP is: '+user.ip); 466 return false; 467 } 468 var targetUser = Users.get(target); 469 if (!targetUser) { 470 emit(socket, 'console', 'User '+target+' not found.'); 471 return false; 472 } 473 if (!user.can('ip', targetUser)) { 474 emit(socket, 'console', '/ip - Access denied.'); 475 return false; 476 } 477 emit(socket, 'console', 'User '+targetUser.name+' has IP: '+targetUser.ip); 478 return false; 479 break; 480 481 case 'mute': 482 case 'm': 483 if (!target) return parseCommand(user, '?', cmd, room, socket); 484 var targets = splitTarget(target); 485 var targetUser = targets[0]; 486 if (!targetUser) { 487 emit(socket, 'console', 'User '+targets[2]+' not found.'); 488 return false; 489 } 490 if (!user.can('mute', targetUser)) { 491 emit(socket, 'console', '/mute - Access denied.'); 492 return false; 493 } 494 495 logModCommand(room,''+targetUser.name+' was muted by '+user.name+'.' + (targets[1] ? " (" + targets[1] + ")" : "")); 496 targetUser.emit('message', user.name+' has muted you. '+targets[1]); 497 var alts = targetUser.getAlts(); 498 if (alts.length) logModCommand(room,""+targetUser.name+"'s alts were also muted: "+alts.join(", ")); 499 500 targetUser.muted = true; 501 rooms.lobby.sendIdentity(targetUser); 502 for (var i=0; i<alts.length; i++) { 503 var targetAlt = Users.get(alts[i]); 504 if (targetAlt) { 505 targetAlt.muted = true; 506 rooms.lobby.sendIdentity(targetAlt); 507 } 508 } 509 510 return false; 511 break; 512 513 case 'ipmute': 514 if (!target) return parseCommand(user, '?', cmd, room, socket); 515 var targetUser = Users.get(target); 516 if (!targetUser) { 517 emit(socket, 'console', 'User '+target+' not found.'); 518 return false; 519 } 520 if (!user.can('mute', targetUser)) { 521 emit(socket, 'console', '/ipmute - Access denied.'); 522 return false; 523 } 524 525 logModCommand(room,''+targetUser.name+"'s IP was muted by "+user.name+'.'); 526 var alts = targetUser.getAlts(); 527 if (alts.length) logModCommand(room,""+targetUser.name+"'s alts were also muted: "+alts.join(", ")); 528 529 targetUser.muted = true; 530 rooms.lobby.sendIdentity(targetUser); 531 mutedIps[targetUser.ip] = targetUser.userid; 532 for (var i=0; i<alts.length; i++) { 533 var targetAlt = Users.get(alts[i]); 534 if (targetAlt) { 535 targetAlt.muted = true; 536 rooms.lobby.sendIdentity(targetAlt); 537 } 538 } 539 540 return false; 541 break; 542 543 case 'unmute': 544 case 'um': 545 if (!target) return parseCommand(user, '?', cmd, room, socket); 546 var targetid = toUserid(target); 547 var targetUser = Users.get(target); 548 if (!targetUser) { 549 emit(socket, 'console', 'User '+target+' not found.'); 550 return false; 551 } 552 if (!user.can('mute', targetUser)) { 553 emit(socket, 'console', '/unmute - Access denied.'); 554 return false; 555 } 556 557 var success = false; 558 559 for (var ip in mutedIps) { 560 if (mutedIps[ip] === targetid) { 561 delete mutedIps[ip]; 562 success = true; 563 } 564 } 565 566 if (success) { 567 logModCommand(room,''+(targetUser?targetUser.name:target)+"'s IP was unmuted by "+user.name+'.'); 568 } 569 570 targetUser.muted = false; 571 rooms.lobby.sendIdentity(targetUser); 572 logModCommand(room,''+targetUser.name+' was unmuted by '+user.name+'.'); 573 return false; 574 break; 575 576 case 'promote': 577 case 'demote': 578 if (!target) return parseCommand(user, '?', cmd, room, socket); 579 var targets = splitTarget(target, true); 580 var targetUser = targets[0]; 581 var userid = toUserid(targets[2]); 582 583 var currentGroup = ' '; 584 if (targetUser) { 585 currentGroup = targetUser.group; 586 } else if (Users.usergroups[userid]) { 587 currentGroup = Users.usergroups[userid].substr(0,1); 588 } 589 var name = targetUser ? targetUser.name : targets[2]; 590 591 var nextGroup = targets[1] ? targets[1] : Users.getNextGroupSymbol(currentGroup, cmd === 'demote'); 592 if (targets[1] === 'deauth') nextGroup = config.groupsranking[0]; 593 if (!config.groups[nextGroup]) { 594 emit(socket, 'console', 'Group \'' + nextGroup + '\' does not exist.'); 595 return false; 596 } 597 if (!user.checkPromotePermission(currentGroup, nextGroup)) { 598 emit(socket, 'console', '/promote - Access denied.'); 599 return false; 600 } 601 602 var isDemotion = (config.groups[nextGroup].rank < config.groups[currentGroup].rank); 603 if (!Users.setOfflineGroup(name, nextGroup)) { 604 emit(socket, 'console', '/promote - WARNING: This user is offline and could be unregistered. Use /forcepromote if you\'re sure you want to risk it.'); 605 return false; 606 } 607 var groupName = (config.groups[nextGroup].name || nextGroup || '').trim() || 'a regular user'; 608 var entry = ''+name+' was '+(isDemotion?'demoted':'promoted')+' to ' + groupName + ' by '+user.name+'.'; 609 logModCommand(room, entry, isDemotion); 610 if (isDemotion) { 611 rooms.lobby.logEntry(entry); 612 emit(socket, 'console', 'You demoted ' + name + ' to ' + groupName + '.'); 613 if (targetUser) { 614 targetUser.emit('console', 'You were demoted to ' + groupName + ' by ' + user.name + '.'); 615 } 616 } 617 rooms.lobby.sendIdentity(targetUser); 618 return false; 619 break; 620 621 case 'forcepromote': 622 // warning: never document this command in /help 623 if (!user.can('forcepromote')) { 624 emit(socket, 'console', '/forcepromote - Access denied.'); 625 return false; 626 } 627 var targets = splitTarget(target, true); 628 var name = targets[2]; 629 var nextGroup = targets[1] ? targets[1] : Users.getNextGroupSymbol(' ', false); 630 631 if (!Users.setOfflineGroup(name, nextGroup, true)) { 632 emit(socket, 'console', '/forcepromote - Don\'t forcepromote unless you have to.'); 633 return false; 634 } 635 var groupName = config.groups[nextGroup].name || nextGroup || ''; 636 logModCommand(room,''+name+' was promoted to ' + (groupName.trim()) + ' by '+user.name+'.'); 637 return false; 638 break; 639 640 case 'deauth': 641 return parseCommand(user, 'demote', target+', deauth', room, socket); 642 break; 643 644 case 'modchat': 645 if (!target) { 646 emit(socket, 'console', 'Moderated chat is currently set to: '+config.modchat); 647 return false; 648 } 649 if (!user.can('modchat')) { 650 emit(socket, 'console', '/modchat - Access denied.'); 651 return false; 652 } 653 654 target = target.toLowerCase(); 655 switch (target) { 656 case 'on': 657 case 'true': 658 case 'yes': 659 config.modchat = true; 660 break; 661 case 'off': 662 case 'false': 663 case 'no': 664 config.modchat = false; 665 break; 666 default: 667 if (!config.groups[target]) { 668 emit(socket, 'console', 'That moderated chat setting is unrecognized.'); 669 return false; 670 } 671 if (config.groupsranking.indexOf(target) > 1 && !user.can('modchatall')) { 672 emit(socket, 'console', '/modchat - Access denied for setting higher than ' + config.groupsranking[1] + '.'); 673 return false; 674 } 675 config.modchat = target; 676 break; 677 } 678 if (config.modchat === true) { 679 room.addRaw('<div class="broadcast-red"><b>Moderated chat was enabled!</b><br />Only registered users can talk.</div>'); 680 } else if (!config.modchat) { 681 room.addRaw('<div class="broadcast-blue"><b>Moderated chat was disabled!</b><br />Anyone may talk now.</div>'); 682 } else { 683 var modchat = sanitize(config.modchat); 684 room.addRaw('<div class="broadcast-red"><b>Moderated chat was set to '+modchat+'!</b><br />Only users of rank '+modchat+' and higher can talk.</div>'); 685 } 686 logModCommand(room,user.name+' set modchat to '+config.modchat,true); 687 return false; 688 break; 689 690 case 'declare': 691 if (!target) return parseCommand(user, '?', cmd, room, socket); 692 if (!user.can('declare')) { 693 emit(socket, 'console', '/declare - Access denied.'); 694 return false; 695 } 696 room.addRaw('<div class="broadcast-blue"><b>'+target+'</b></div>'); 697 logModCommand(room,user.name+' declared '+target,true); 698 return false; 699 break; 700 701 case 'announce': 702 case 'wall': 703 if (!target) return parseCommand(user, '?', cmd, room, socket); 704 if (!user.can('announce')) { 705 emit(socket, 'console', '/announce - Access denied.'); 706 return false; 707 } 708 return '/announce '+target; 709 break; 710 711 case 'hotpatch': 712 if (!target) return parseCommand(user, '?', cmd, room, socket); 713 if (!user.can('hotpatch')) { 714 emit(socket, 'console', '/hotpatch - Access denied.'); 715 return false; 716 } 717 718 if (target === 'all') { 719 for (var i in require.cache) delete require.cache[i]; 720 Tools = require('./tools.js'); 721 722 parseCommand = require('./chat-commands.js').parseCommand; 723 724 sim = require('./battles.js'); 725 BattlePokemon = sim.BattlePokemon; 726 BattleSide = sim.BattleSide; 727 Battle = sim.Battle; 728 emit(socket, 'console', 'The game engine has been hot-patched.'); 729 return false; 730 } else if (target === 'data') { 731 for (var i in require.cache) delete require.cache[i]; 732 Tools = require('./tools.js'); 733 emit(socket, 'console', 'Game resources have been hot-patched.'); 734 return false; 735 } else if (target === 'chat') { 736 for (var i in require.cache) delete require.cache[i]; 737 parseCommand = require('./chat-commands.js').parseCommand; 738 emit(socket, 'console', 'Chat commands have been hot-patched.'); 739 return false; 740 } 741 emit(socket, 'console', 'Your hot-patch command was unrecognized.'); 742 return false; 743 break; 744 745 case 'savelearnsets': 746 if (user.can('hotpatch')) { 747 emit(socket, 'console', '/savelearnsets - Access denied.'); 748 return false; 749 } 750 fs.writeFile('data/learnsets.js', 'exports.BattleLearnsets = '+JSON.stringify(BattleLearnsets)+";\n"); 751 emit(socket, 'console', 'learnsets.js saved.'); 752 return false; 753 break; 754 755 case 'rating': 756 case 'ranking': 757 case 'rank': 758 case 'ladder': 759 emit(socket, 'console', 'You are using an old version of Pokemon Showdown. Please reload the page.'); 760 return false; 761 break; 762 763 case 'nick': 764 if (!target) return parseCommand(user, '?', cmd, room, socket); 765 user.rename(target); 766 return false; 767 break; 768 769 case 'disableladder': 770 if (!user.can('disableladder')) { 771 emit(socket, 'console', '/disableladder - Access denied.'); 772 return false; 773 } 774 if (LoginServer.disabled) { 775 emit(socket, 'console', '/disableladder - Ladder is already disabled.'); 776 return false; 777 } 778 LoginServer.disabled = true; 779 logModCommand(room, 'The ladder was disabled by ' + user.name + '.', true); 780 room.addRaw('<div class="broadcast-red"><b>Due to high server load, the ladder has been temporarily disabled</b><br />Rated games will no longer update the ladder. It will be back momentarily.</div>'); 781 return false; 782 break; 783 case 'enableladder': 784 if (!user.can('disableladder')) { 785 emit(socket, 'console', '/enable - Access denied.'); 786 return false; 787 } 788 if (!LoginServer.disabled) { 789 emit(socket, 'console', '/enable - Ladder is already enabled.'); 790 return false; 791 } 792 LoginServer.disabled = false; 793 logModCommand(room, 'The ladder was enabled by ' + user.name + '.', true); 794 room.addRaw('<div class="broadcast-green"><b>The ladder is now back.</b><br />Rated games will update the ladder now.</div>'); 795 return false; 796 break; 797 798 case 'savereplay': 799 if (!room || !room.battle) return false; 800 var data = room.log.join("\n"); 801 var datahash = crypto.createHash('md5').update(data.replace(/[^(\x20-\x7F)]+/g,'')).digest('hex'); 802 803 LoginServer.request('prepreplay', { 804 id: room.id.substr(7), 805 loghash: datahash, 806 p1: room.p1.name, 807 p2: room.p2.name, 808 format: room.format 809 }, function(success) { 810 emit(socket, 'command', { 811 command: 'savereplay', 812 log: data, 813 room: 'lobby', 814 id: room.id.substr(7) 815 }); 816 }); 817 return false; 818 break; 819 820 case 'trn': 821 var commaIndex = target.indexOf(','); 822 var targetName = target; 823 var targetAuth = false; 824 var targetToken = ''; 825 if (commaIndex >= 0) { 826 targetName = target.substr(0,commaIndex); 827 target = target.substr(commaIndex+1); 828 commaIndex = target.indexOf(','); 829 targetAuth = target; 830 if (commaIndex >= 0) { 831 targetAuth = !!parseInt(target.substr(0,commaIndex),10); 832 targetToken = target.substr(commaIndex+1); 833 } 834 } 835 user.rename(targetName, targetToken, targetAuth, socket); 836 return false; 837 break; 838 839 case 'forcerename': 840 case 'fr': 841 if (!target) return parseCommand(user, '?', cmd, room, socket); 842 var targets = splitTarget(target); 843 var targetUser = targets[0]; 844 if (!targetUser) { 845 emit(socket, 'console', 'User '+targets[2]+' not found.'); 846 return false; 847 } 848 if (!user.can('forcerename', targetUser)) { 849 emit(socket, 'console', '/forcerename - Access denied.'); 850 return false; 851 } 852 853 if (targetUser.userid === toUserid(targets[2])) { 854 var entry = ''+targetUser.name+' was forced to choose a new name by '+user.name+'.' + (targets[1] ? " (" + targets[1] + ")" : ""); 855 logModCommand(room, entry, true); 856 rooms.lobby.sendAuth(entry); 857 if (room.id !== 'lobby') { 858 room.add(entry); 859 } else { 860 room.logEntry(entry); 861 } 862 targetUser.resetName(); 863 targetUser.emit('nameTaken', {reason: user.name+" has forced you to change your name. "+targets[1]}); 864 } else { 865 emit(socket, 'console', "User "+targetUser.name+" is no longer using that name."); 866 } 867 return false; 868 break; 869 870 case 'forcerenameto': 871 case 'frt': 872 if (!target) return parseCommand(user, '?', cmd, room, socket); 873 var targets = splitTarget(target); 874 var targetUser = targets[0]; 875 if (!targetUser) { 876 emit(socket, 'console', 'User '+targets[2]+' not found.'); 877 return false; 878 } 879 if (!targets[1]) { 880 emit(socket, 'console', 'No new name was specified.'); 881 return false; 882 } 883 if (!user.can('forcerenameto', targetUser)) { 884 emit(socket, 'console', '/forcerenameto - Access denied.'); 885 return false; 886 } 887 888 if (targetUser.userid === toUserid(targets[2])) { 889 var entry = ''+targetUser.name+' was forcibly renamed to '+targets[1]+' by '+user.name+'.'; 890 logModCommand(room, entry, true); 891 rooms.lobby.sendAuth(entry); 892 if (room.id !== 'lobby') { 893 room.add(entry); 894 } else { 895 room.logEntry(entry); 896 } 897 targetUser.forceRename(targets[1]); 898 } else { 899 emit(socket, 'console', "User "+targetUser.name+" is no longer using that name."); 900 } 901 return false; 902 break; 903 904 // INFORMATIONAL COMMANDS 905 906 case 'data': 907 case '!data': 908 case 'stats': 909 case '!stats': 910 case 'dex': 911 case '!dex': 912 case 'pokedex': 913 case '!pokedex': 914 showOrBroadcastStart(user, cmd, room, socket, message); 915 var dataMessages = getDataMessage(target); 916 for (var i=0; i<dataMessages.length; i++) { 917 if (cmd.substr(0,1) !== '!') { 918 sendData(socket, '>'+room.id+'\n'+dataMessages[i]); 919 } else if (user.can('broadcast') && canTalk(user, room)) { 920 room.add(dataMessages[i]); 921 } 922 } 923 return false; 924 break; 925 926 case 'learnset': 927 case '!learnset': 928 case 'learn': 929 case '!learn': 930 case 'learnall': 931 case '!learnall': 932 case 'learn5': 933 case '!learn5': 934 var lsetData = {set:{}}; 935 var targets = target.split(','); 936 if (!targets[1]) return parseCommand(user, 'help', 'learn', room, socket); 937 var template = Tools.getTemplate(targets[0]); 938 var move = {}; 939 var problem; 940 var all = (cmd.substr(cmd.length-3) === 'all'); 941 942 if (cmd === 'learn5' || cmd === '!learn5') lsetData.set.level = 5; 943 944 showOrBroadcastStart(user, cmd, room, socket, message); 945 946 if (!template.exists) { 947 showOrBroadcast(user, cmd, room, socket, 948 'Pokemon "'+template.id+'" not found.'); 949 return false; 950 } 951 952 for (var i=1, len=targets.length; i<len; i++) { 953 move = Tools.getMove(targets[i]); 954 if (!move.exists) { 955 showOrBroadcast(user, cmd, room, socket, 956 'Move "'+move.id+'" not found.'); 957 return false; 958 } 959 problem = Tools.checkLearnset(move, template, lsetData); 960 if (problem) break; 961 } 962 var buffer = ''+template.name+(problem?" <span class=\"message-learn-cannotlearn\">can't</span> learn ":" <span class=\"message-learn-canlearn\">can</span> learn ")+(targets.length>2?"these moves":move.name); 963 if (!problem) { 964 var sourceNames = {E:"egg",S:"event",D:"dream world"}; 965 if (lsetData.sources || lsetData.sourcesBefore) buffer += " only when obtained from:<ul class=\"message-learn-list\">"; 966 if (lsetData.sources) { 967 var sources = lsetData.sources.sort(); 968 var prevSource; 969 var prevSourceType; 970 for (var i=0, len=sources.length; i<len; i++) { 971 var source = sources[i]; 972 if (source.substr(0,2) === prevSourceType) { 973 if (prevSourceCount < 0) buffer += ": "+source.substr(2); 974 else if (all || prevSourceCount < 3) buffer += ', '+source.substr(2); 975 else if (prevSourceCount == 3) buffer += ', ...'; 976 prevSourceCount++; 977 continue; 978 } 979 prevSourceType = source.substr(0,2); 980 prevSourceCount = source.substr(2)?0:-1; 981 buffer += "<li>gen "+source.substr(0,1)+" "+sourceNames[source.substr(1,1)]; 982 if (prevSourceType === '5E' && template.maleOnlyDreamWorld) buffer += " (cannot have DW ability)"; 983 if (source.substr(2)) buffer += ": "+source.substr(2); 984 } 985 } 986 if (lsetData.sourcesBefore) buffer += "<li>any generation before "+(lsetData.sourcesBefore+1); 987 buffer += "</ul>"; 988 } 989 showOrBroadcast(user, cmd, room, socket, 990 buffer); 991 return false; 992 break; 993 994 case 'uptime': 995 case '!uptime': 996 var uptime = process.uptime(); 997 var uptimeText; 998 if (uptime > 24*60*60) { 999 var uptimeDays = Math.floor(uptime/(24*60*60)); 1000 uptimeText = ''+uptimeDays+' '+(uptimeDays == 1 ? 'day' : 'days'); 1001 var uptimeHours = Math.floor(uptime/(60*60)) - uptimeDays*24; 1002 if (uptimeHours) uptimeText += ', '+uptimeHours+' '+(uptimeHours == 1 ? 'hour' : 'hours'); 1003 } else { 1004 uptimeText = uptime.seconds().duration(); 1005 } 1006 showOrBroadcastStart(user, cmd, room, socket, message); 1007 showOrBroadcast(user, cmd, room, socket, 1008 '<div class="infobox">' + 1009 'Uptime: <b>'+uptimeText+'</b>'+ 1010 '</div>'); 1011 return false; 1012 break; 1013 1014 case 'groups': 1015 case '!groups': 1016 showOrBroadcastStart(user, cmd, room, socket, message); 1017 showOrBroadcast(user, cmd, room, socket, 1018 '<div class="infobox">' + 1019 '+ <b>Voice</b> - They can use ! commands like !groups, and talk during moderated chat<br />' + 1020 '% <b>Driver</b> - The above, and they can also mute users and run tournaments<br />' + 1021 '@ <b>Moderator</b> - The above, and they can ban users and check for alts<br />' + 1022 '& <b>Leader</b> - The above, and they can promote moderators and force ties<br />'+ 1023 '~ <b>Administrator</b> - They can do anything, like change what this message says'+ 1024 '</div>'); 1025 return false; 1026 break; 1027 1028 case 'opensource': 1029 case '!opensource': 1030 showOrBroadcastStart(user, cmd, room, socket, message); 1031 showOrBroadcast(user, cmd, room, socket, 1032 '<div class="infobox">Pokemon Showdown is open source:<br />- Language: JavaScript<br />- <a href="https://github.com/Zarel/Pokemon-Showdown/commits/master" target="_blank">What\'s new?</a><br />- <a href="https://github.com/Zarel/Pokemon-Showdown" target="_blank">Server source code</a><br />- <a href="https://github.com/Zarel/Pokemon-Showdown-Client" target="_blank">Client source code</a></div>'); 1033 return false; 1034 break; 1035 1036 case 'avatars': 1037 case '!avatars': 1038 showOrBroadcastStart(user, cmd, room, socket, message); 1039 showOrBroadcast(user, cmd, room, socket, 1040 '<div class="infobox">Want a custom avatar?<br />- <a href="/sprites/trainers/" target="_blank">How to change your avatar</a></div>'); 1041 return false; 1042 break; 1043 1044 case 'intro': 1045 case 'introduction': 1046 case '!intro': 1047 case '!introduction': 1048 showOrBroadcastStart(user, cmd, room, socket, message); 1049 showOrBroadcast(user, cmd, room, socket, 1050 '<div class="infobox">New to competitive pokemon?<br />' + 1051 '- <a href="http://www.smogon.com/dp/articles/intro_comp_pokemon" target="_blank">An introduction to competitive pokemon</a><br />' + 1052 '- <a href="http://www.smogon.com/bw/articles/bw_tiers" target="_blank">What do "OU", "UU", etc mean?</a><br />' + 1053 '- <a href="http://www.smogon.com/bw/banlist/" target="_blank">What are the rules for each format? What is "Sleep Clause"?</a>' + 1054 '</div>'); 1055 return false; 1056 break; 1057 1058 case 'calc': 1059 case '!calc': 1060 case 'calculator': 1061 case '!calculator': 1062 showOrBroadcastStart(user, cmd, room, socket, message); 1063 showOrBroadcast(user, cmd , room , socket, 1064 '<div class="infobox">Pokemon Showdown! damage calculator. (Courtesy of Honko)<br />' + 1065 '- <a href="http://pokemonshowdown.com/damagecalc/" target="_blank">Damage Calculator</a><br />' + 1066 '</div>'); 1067 return false; 1068 break; 1069 1070 case 'cap': 1071 case '!cap': 1072 showOrBroadcastStart(user, cmd, room, socket, message); 1073 showOrBroadcast(user, cmd, room, socket, 1074 '<div class="infobox">An introduction to the Create-A-Pokemon project:<br />' + 1075 '- <a href="http://www.smogon.com/cap/" target="_blank">CAP project website and description</a><br />' + 1076 '- <a href="http://www.smogon.com/forums/showthread.php?t=48782" target="_blank">What Pokemon have been made?</a><br />' + 1077 '- <a href="http://www.smogon.com/forums/showthread.php?t=3464513" target="_blank">Talk about the metagame here</a><br />' + 1078 '- <a href="http://www.smogon.com/forums/showthread.php?t=3466826" target="_blank">Practice BW CAP teams</a>' + 1079 '</div>'); 1080 return false; 1081 break; 1082 1083 case 'om': 1084 case 'othermetas': 1085 case '!om': 1086 case '!othermetas': 1087 showOrBroadcastStart(user, cmd, room, socket, message); 1088 showOrBroadcast(user, cmd, room, socket, 1089 '<div class="infobox">Information on the Other Metagames:<br />' + 1090 '- <a href="http://www.smogon.com/forums/showthread.php?t=3463764" target="_blank">Balanced Hackmons</a><br />' + 1091 '- <a href="http://www.smogon.com/forums/showthread.php?t=3471810" target="_blank">Dream World OU</a><br />' + 1092 '- <a href="http://www.smogon.com/forums/showthread.php?t=3467120" target="_blank">Glitchmons</a><br />' + 1093 '- <a href="http://www.smogon.com/sim/seasonal" target="_blank">Seasonal: Spring Forward</a><br />' + 1094 '- <a href="http://www.smogon.com/forums/showthread.php?t=3476469" target="_blank">Smogon Doubles</a><br />' + 1095 '- <a href="http://www.smogon.com/forums/showthread.php?t=3471161" target="_blank">VGC 2013</a>' + 1096 '</div>'); 1097 return false; 1098 break; 1099 1100 case 'rules': 1101 case 'rule': 1102 case '!rules': 1103 case '!rule': 1104 showOrBroadcastStart(user, cmd, room, socket, message); 1105 showOrBroadcast(user, cmd, room, socket, 1106 '<div class="infobox">Please follow the rules:<br />' + 1107 '- <a href="http://pokemonshowdown.com/rules" target="_blank">Rules</a><br />' + 1108 '</div>'); 1109 return false; 1110 break; 1111 1112 case 'faq': 1113 case '!faq': 1114 target = target.toLowerCase(); 1115 var buffer = '<div class="infobox">'; 1116 var matched = false; 1117 if (!target || target === 'all') { 1118 matched = true; 1119 buffer += '<a href="http://www.smogon.com/sim/faq" target="_blank">Frequently Asked Questions</a><br />'; 1120 } 1121 if (target === 'all' || target === 'deviation') { 1122 matched = true; 1123 buffer += '<a href="http://www.smogon.com/sim/faq#deviation" target="_blank">Why did this user gain or lose so many points?</a><br />'; 1124 } 1125 if (target === 'all' || target === 'doubles' || target === 'triples' || target === 'rotation') { 1126 matched = true; 1127 buffer += '<a href="http://www.smogon.com/sim/faq#doubles" target="_blank">Can I play doubles/triples/rotation battles here?</a><br />'; 1128 } 1129 if (target === 'all' || target === 'randomcap') { 1130 matched = true; 1131 buffer += '<a href="http://www.smogon.com/sim/faq#randomcap" target="_blank">What is this fakemon and what is it doing in my random battle?</a><br />'; 1132 } 1133 if (target === 'all' || target === 'restarts') { 1134 matched = true; 1135 buffer += '<a href="http://www.smogon.com/sim/faq#restarts" target="_blank">Why is the server restarting?</a><br />'; 1136 } 1137 if (target === 'all' || target === 'staff') { 1138 matched = true; 1139 buffer += '<a href="http://www.smogon.com/sim/staff_faq" target="_blank">Staff FAQ</a><br />'; 1140 } 1141 if (!matched) { 1142 emit(socket, 'console', 'The FAQ entry "'+target+'" was not found. Try /faq for general help.'); 1143 return false; 1144 } 1145 buffer += '</div>'; 1146 showOrBroadcastStart(user, cmd, room, socket, message); 1147 showOrBroadcast(user, cmd, room, socket, buffer); 1148 return false; 1149 break; 1150 1151 case 'banlists': 1152 case 'tiers': 1153 case '!banlists': 1154 case '!tiers': 1155 showOrBroadcastStart(user, cmd, room, socket, message); 1156 showOrBroadcast(user, cmd, room, socket, 1157 '<div class="infobox">Smogon tiers:<br />' + 1158 '- <a href="http://www.smogon.com/bw/banlist/" target="_blank">The banlists for each tier</a><br />' + 1159 '- <a href="http://www.smogon.com/bw/tiers/uber" target="_blank">Uber Pokemon</a><br />' + 1160 '- <a href="http://www.smogon.com/bw/tiers/ou" target="_blank">Overused Pokemon</a><br />' + 1161 '- <a href="http://www.smogon.com/bw/tiers/uu" target="_blank">Underused Pokemon</a><br />' + 1162 '- <a href="http://www.smogon.com/bw/tiers/ru" target="_blank">Rarelyused Pokemon</a><br />' + 1163 '- <a href="http://www.smogon.com/bw/tiers/nu" target="_blank">Neverused Pokemon</a><br />' + 1164 '- <a href="http://www.smogon.com/bw/tiers/lc" target="_blank">Little Cup Pokemon</a><br />' + 1165 '</div>'); 1166 return false; 1167 break; 1168 1169 case 'analysis': 1170 case '!analysis': 1171 case 'strategy': 1172 case '!strategy': 1173 case 'smogdex': 1174 case '!smogdex': 1175 var targets = target.split(','); 1176 var pokemon = Tools.getTemplate(targets[0]); 1177 var item = Tools.getItem(targets[0]); 1178 var move = Tools.getMove(targets[0]); 1179 var ability = Tools.getAbility(targets[0]); 1180 var atLeastOne = false; 1181 var generation = (targets[1] || "bw").trim().toLowerCase(); 1182 var genNumber = 5; 1183 1184 showOrBroadcastStart(user, cmd, room, socket, message); 1185 1186 if (generation === "bw" || generation === "bw2" || generation === "5" || generation === "five") { 1187 generation = "bw"; 1188 } else if (generation === "dp" || generation === "dpp" || generation === "4" || generation === "four") { 1189 generation = "dp"; 1190 genNumber = 4; 1191 } else if (generation === "adv" || generation === "rse" || generation === "rs" || generation === "3" || generation === "three") { 1192 generation = "rs"; 1193 genNumber = 3; 1194 } else if (generation === "gsc" || generation === "gs" || generation === "2" || generation === "two") { 1195 generation = "gs"; 1196 genNumber = 2; 1197 } else if(generation === "rby" || generation === "rb" || generation === "1" || generation === "one") { 1198 generation = "rb"; 1199 genNumber = 1; 1200 } else { 1201 generation = "bw"; 1202 } 1203 1204 // Pokemon 1205 if (pokemon.exists) { 1206 atLeastOne = true; 1207 if (genNumber < pokemon.gen) { 1208 showOrBroadcast(user, cmd, room, socket, pokemon.name+' did not exist in '+generation.toUpperCase()+'!'); 1209 return false; 1210 } 1211 if (pokemon.tier === 'G4CAP' || pokemon.tier === 'G5CAP') { 1212 generation = "cap"; 1213 } 1214 1215 var poke = pokemon.name.toLowerCase(); 1216 if (poke === 'nidoranm') poke = 'nidoran-m'; 1217 if (poke === 'nidoranf') poke = 'nidoran-f'; 1218 if (poke === 'farfetch\'d') poke = 'farfetchd'; 1219 if (poke === 'mr. mime') poke = 'mr_mime'; 1220 if (poke === 'mime jr.') poke = 'mime_jr'; 1221 if (poke === 'deoxys-attack' || poke === 'deoxys-defense' || poke === 'deoxys-speed' || poke === 'kyurem-black' || poke === 'kyurem-white') poke = poke.substr(0,8); 1222 if (poke === 'wormadam-trash') poke = 'wormadam-s'; 1223 if (poke === 'wormadam-sandy') poke = 'wormadam-g'; 1224 if (poke === 'rotom-wash' || poke === 'rotom-frost' || poke === 'rotom-heat') poke = poke.substr(0,7); 1225 if (poke === 'rotom-mow') poke = 'rotom-c'; 1226 if (poke === 'rotom-fan') poke = 'rotom-s'; 1227 if (poke === 'giratina-origin' || poke === 'tornadus-therian' || poke === 'landorus-therian') poke = poke.substr(0,10); 1228 if (poke === 'shaymin-sky') poke = 'shaymin-s'; 1229 if (poke === 'arceus') poke = 'arceus-normal'; 1230 if (poke === 'thundurus-therian') poke = 'thundurus-t'; 1231 1232 showOrBroadcast(user, cmd, room, socket, 1233 '<a href="http://www.smogon.com/'+generation+'/pokemon/'+poke+'" target="_blank">'+generation.toUpperCase()+' '+pokemon.name+' analysis</a>, brought to you by <a href="http://www.smogon.com" target="_blank">Smogon University</a>'); 1234 } 1235 1236 // Item 1237 if (item.exists && genNumber > 1) { 1238 atLeastOne = true; 1239 var itemName = item.name.toLowerCase().replace(' ', '_'); 1240 showOrBroadcast(user, cmd, room, socket, 1241 '<a href="http://www.smogon.com/'+generation+'/items/'+itemName+'" target="_blank">'+generation.toUpperCase()+' '+item.name+' item analysis</a>, brought to you by <a href="http://www.smogon.com" target="_blank">Smogon University</a>'); 1242 } 1243 1244 // Ability 1245 if (ability.exists && genNumber > 2) { 1246 atLeastOne = true; 1247 var abilityName = ability.name.toLowerCase().replace(' ', '_'); 1248 showOrBroadcast(user, cmd, room, socket, 1249 '<a href="http://www.smogon.com/'+generation+'/abilities/'+abilityName+'" target="_blank">'+generation.toUpperCase()+' '+ability.name+' ability analysis</a>, brought to you by <a href="http://www.smogon.com" target="_blank">Smogon University</a>'); 1250 } 1251 1252 // Move 1253 if (move.exists) { 1254 atLeastOne = true; 1255 var moveName = move.name.toLowerCase().replace(' ', '_'); 1256 showOrBroadcast(user, cmd, room, socket, 1257 '<a href="http://www.smogon.com/'+generation+'/moves/'+moveName+'" target="_blank">'+generation.toUpperCase()+' '+move.name+' move analysis</a>, brought to you by <a href="http://www.smogon.com" target="_blank">Smogon University</a>'); 1258 } 1259 1260 if (!atLeastOne) { 1261 showOrBroadcast(user, cmd, room, socket, 'Pokemon, item, move, or ability not found for generation ' + generation.toUpperCase() + '.'); 1262 return false; 1263 } 1264 1265 return false; 1266 break; 1267 1268 case 'join': 1269 var targetRoom = Rooms.get(target); 1270 if (!targetRoom) { 1271 emit(socket, 'console', "The room '"+target+"' does not exist."); 1272 return false; 1273 } 1274 if (!user.joinRoom(targetRoom, socket)) { 1275 emit(socket, 'console', "The room '"+target+"' could not be joined (most likely, you're already in it)."); 1276 return false; 1277 } 1278 return false; 1279 break; 1280 1281 case 'leave': 1282 case 'part': 1283 if (room.id === 'lobby') return false; 1284 1285 user.leaveRoom(room, socket); 1286 return false; 1287 break; 1288 1289 // Battle commands 1290 1291 case 'reset': 1292 case 'restart': 1293 emit(socket, 'console', 'This functionality is no longer available.'); 1294 return false; 1295 break; 1296 1297 case 'move': 1298 case 'attack': 1299 case 'mv': 1300 if (!room.decision) { emit(socket, 'console', 'You can only do this in battle rooms.'); return false; } 1301 1302 room.decision(user, 'choose', 'move '+target); 1303 return false; 1304 break; 1305 1306 case 'switch': 1307 case 'sw': 1308 if (!room.decision) { emit(socket, 'console', 'You can only do this in battle rooms.'); return false; } 1309 1310 room.decision(user, 'choose', 'switch '+parseInt(target,10)); 1311 return false; 1312 break; 1313 1314 case 'choose': 1315 if (!room.decision) { emit(socket, 'console', 'You can only do this in battle rooms.'); return false; } 1316 1317 room.decision(user, 'choose', target); 1318 return false; 1319 break; 1320 1321 case 'undo': 1322 if (!room.decision) { emit(socket, 'console', 'You can only do this in battle rooms.'); return false; } 1323 1324 room.decision(user, 'undo', target); 1325 return false; 1326 break; 1327 1328 case 'team': 1329 if (!room.decision) { emit(socket, 'console', 'You can only do this in battle rooms.'); return false; } 1330 1331 room.decision(user, 'choose', 'team '+target); 1332 return false; 1333 break; 1334 1335 case 'search': 1336 case 'cancelsearch': 1337 if (!room.searchBattle) { emit(socket, 'console', 'You can only do this in lobby rooms.'); return false; } 1338 1339 if (target) { 1340 room.searchBattle(user, target); 1341 } else { 1342 room.cancelSearch(user); 1343 } 1344 return false; 1345 break; 1346 1347 case 'challenge': 1348 case 'chall': 1349 var targets = splitTarget(target); 1350 var targetUser = targets[0]; 1351 target = targets[1]; 1352 if (!targetUser || !targetUser.connected) { 1353 emit(socket, 'message', "The user '"+targets[2]+"' was not found."); 1354 return false; 1355 } 1356 if (targetUser.blockChallenges && !user.can('bypassblocks', targetUser)) { 1357 emit(socket, 'message', "The user '"+targets[2]+"' is not accepting challenges right now."); 1358 return false; 1359 } 1360 if (typeof target !== 'string') target = 'customgame'; 1361 var problems = Tools.validateTeam(user.team, target); 1362 if (problems) { 1363 emit(socket, 'message', "Your team was rejected for the following reasons:\n\n- "+problems.join("\n- ")); 1364 return false; 1365 } 1366 user.makeChallenge(targetUser, target); 1367 return false; 1368 break; 1369 1370 case 'away': 1371 case 'idle': 1372 case 'blockchallenges': 1373 user.blockChallenges = true; 1374 emit(socket, 'console', 'You are now blocking all incoming challenge requests.'); 1375 return false; 1376 break; 1377 1378 case 'back': 1379 case 'allowchallenges': 1380 user.blockChallenges = false; 1381 emit(socket, 'console', 'You are available for challenges from now on.'); 1382 return false; 1383 break; 1384 1385 case 'cancelchallenge': 1386 case 'cchall': 1387 user.cancelChallengeTo(target); 1388 return false; 1389 break; 1390 1391 case 'accept': 1392 var userid = toUserid(target); 1393 var format = 'debugmode'; 1394 if (user.challengesFrom[userid]) format = user.challengesFrom[userid].format; 1395 var problems = Tools.validateTeam(user.team, format); 1396 if (problems) { 1397 emit(socket, 'message', "Your team was rejected for the following reasons:\n\n- "+problems.join("\n- ")); 1398 return false; 1399 } 1400 user.acceptChallengeFrom(userid); 1401 return false; 1402 break; 1403 1404 case 'reject': 1405 user.rejectChallengeFrom(toUserid(target)); 1406 return false; 1407 break; 1408 1409 case 'saveteam': 1410 case 'utm': 1411 try { 1412 user.team = JSON.parse(target); 1413 user.emit('update', {team: 'saved', room: 'teambuilder'}); 1414 } catch (e) { 1415 emit(socket, 'console', 'Not a valid team.'); 1416 } 1417 return false; 1418 break; 1419 1420 case 'joinbattle': 1421 case 'partbattle': 1422 if (!room.joinBattle) { emit(socket, 'console', 'You can only do this in battle rooms.'); return false; } 1423 1424 room.joinBattle(user); 1425 return false; 1426 break; 1427 1428 case 'leavebattle': 1429 if (!room.leaveBattle) { emit(socket, 'console', 'You can only do this in battle rooms.'); return false; } 1430 1431 room.leaveBattle(user); 1432 return false; 1433 break; 1434 1435 case 'kickinactive': 1436 if (room.requestKickInactive) { 1437 room.requestKickInactive(user); 1438 } else { 1439 emit(socket, 'console', 'You can only kick inactive players from inside a room.'); 1440 } 1441 return false; 1442 break; 1443 1444 case 'timer': 1445 target = toId(target); 1446 if (room.requestKickInactive) { 1447 if (target === 'off' || target === 'stop') { 1448 room.stopKickInactive(user, user.can('timer')); 1449 } else if (target === 'on' || !target) { 1450 room.requestKickInactive(user, user.can('timer')); 1451 } else { 1452 emit(socket, 'console', "'"+target+"' is not a recognized timer state."); 1453 } 1454 } else { 1455 emit(socket, 'console', 'You can only set the timer from inside a room.'); 1456 } 1457 return false; 1458 break; 1459 break; 1460 1461 case 'lobbychat': 1462 target = toId(target); 1463 if (target === 'off') { 1464 user.blockLobbyChat = true; 1465 emit(socket, 'console', 'You are now blocking lobby chat.'); 1466 } else { 1467 user.blockLobbyChat = false; 1468 emit(socket, 'console', 'You are now receiving lobby chat.'); 1469 } 1470 return false; 1471 break; 1472 break; 1473 1474 case 'a': 1475 if (user.can('battlemessage')) { 1476 // secret sysop command 1477 room.battle.add(target); 1478 return false; 1479 } 1480 break; 1481 1482 // Admin commands 1483 1484 case 'forcewin': 1485 case 'forcetie': 1486 if (!user.can('forcewin') || !room.battle) { 1487 emit(socket, 'console', '/forcewin - Access denied.'); 1488 return false; 1489 } 1490 1491 room.battle.endType = 'forced'; 1492 if (!target) { 1493 room.battle.tie(); 1494 logModCommand(room,user.name+' forced a tie.',true); 1495 return false; 1496 } 1497 target = Users.get(target); 1498 if (target) target = target.userid; 1499 else target = ''; 1500 1501 if (target) { 1502 room.battle.win(target); 1503 logModCommand(room,user.name+' forced a win for '+target+'.',true); 1504 } 1505 1506 return false; 1507 break; 1508 1509 case 'potd': 1510 if (!user.can('potd')) { 1511 emit(socket, 'console', '/potd - Access denied.'); 1512 return false; 1513 } 1514 1515 config.potd = target; 1516 Simulator.eval('config.potd = \''+toId(target)+'\''); 1517 if (target) { 1518 rooms.lobby.addRaw('<div class="broadcast-blue"><b>The Pokemon of the Day is now '+target+'!</b><br />This Pokemon will be guaranteed to show up in random battles.</div>'); 1519 logModCommand(room, 'The Pokemon of the Day was changed to '+target+' by '+user.name+'.', true); 1520 } else { 1521 rooms.lobby.addRaw('<div class="broadcast-blue"><b>The Pokemon of the Day was removed!</b><br />No pokemon will be guaranteed in random battles.</div>'); 1522 logModCommand(room, 'The Pokemon of the Day was removed by '+user.name+'.', true); 1523 } 1524 return false; 1525 break; 1526 1527 case 'lockdown': 1528 if (!user.can('lockdown')) { 1529 emit(socket, 'console', '/lockdown - Access denied.'); 1530 return false; 1531 } 1532 1533 lockdown = true; 1534 for (var id in rooms) { 1535 rooms[id].addRaw('<div class="broadcast-red"><b>The server is restarting soon.</b><br />Please finish your battles quickly. No new battles can be started until the server resets in a few minutes.</div>'); 1536 if (rooms[id].requestKickInactive) rooms[id].requestKickInactive(user, true); 1537 } 1538 1539 rooms.lobby.logEntry(user.name + ' used /lockdown'); 1540 1541 return false; 1542 break; 1543 1544 case 'endlockdown': 1545 if (!user.can('lockdown')) { 1546 emit(socket, 'console', '/endlockdown - Access denied.'); 1547 return false; 1548 } 1549 1550 lockdown = false; 1551 for (var id in rooms) { 1552 rooms[id].addRaw('<div class="broadcast-green"><b>The server shutdown was canceled.</b></div>'); 1553 } 1554 1555 rooms.lobby.logEntry(user.name + ' used /endlockdown'); 1556 1557 return false; 1558 break; 1559 1560 case 'kill': 1561 if (!user.can('lockdown')) { 1562 emit(socket, 'console', '/lockdown - Access denied.'); 1563 return false; 1564 } 1565 1566 if (!lockdown) { 1567 emit(socket, 'console', 'For safety reasons, /kill can only be used during lockdown.'); 1568 return false; 1569 } 1570 1571 if (updateServerLock) { 1572 emit(socket, 'console', 'Wait for /updateserver to finish before using /kill.'); 1573 return false; 1574 } 1575 1576 rooms.lobby.destroyLog(function() { 1577 rooms.lobby.logEntry(user.name + ' used /kill'); 1578 }, function() { 1579 process.exit(); 1580 }); 1581 1582 // Just in the case the above never terminates, kill the process 1583 // after 10 seconds. 1584 setTimeout(function() { 1585 process.exit(); 1586 }, 10000); 1587 return false; 1588 break; 1589 1590 case 'loadbanlist': 1591 if (!user.can('declare')) { 1592 emit(socket, 'console', '/loadbanlist - Access denied.'); 1593 return false; 1594 } 1595 1596 emit(socket, 'console', 'loading'); 1597 fs.readFile('config/ipbans.txt', function (err, data) { 1598 if (err) return; 1599 data = (''+data).split("\n"); 1600 for (var i=0; i<data.length; i++) { 1601 if (data[i]) bannedIps[data[i]] = '#ipban'; 1602 } 1603 emit(socket, 'console', 'banned '+i+' ips'); 1604 }); 1605 return false; 1606 break; 1607 1608 case 'refreshpage': 1609 if (!user.can('hotpatch')) { 1610 emit(socket, 'console', '/refreshpage - Access denied.'); 1611 return false; 1612 } 1613 rooms.lobby.send('|refresh|'); 1614 rooms.lobby.logEntry(user.name + ' used /refreshpage'); 1615 return false; 1616 break; 1617 1618 case 'updateserver': 1619 if (!user.can('console')) { 1620 emit(socket, 'console', '/updateserver - Access denied.'); 1621 return false; 1622 } 1623 1624 if (updateServerLock) { 1625 emit(socket, 'console', '/updateserver - Another update is already in progress.'); 1626 return false; 1627 } 1628 1629 updateServerLock = true; 1630 1631 var logQueue = []; 1632 logQueue.push(user.name + ' used /updateserver'); 1633 1634 var exec = require('child_process').exec; 1635 exec('git diff-index --quiet HEAD --', function(error) { 1636 var cmd = 'git pull --rebase'; 1637 if (error) { 1638 if (error.code === 1) { 1639 // The working directory or index have local changes. 1640 cmd = 'git stash;' + cmd + ';git stash pop'; 1641 } else { 1642 // The most likely case here is that the user does not have 1643 // `git` on the PATH (which would be error.code === 127). 1644 user.emit('console', '' + error); 1645 logQueue.push('' + error); 1646 logQueue.forEach(rooms.lobby.logEntry); 1647 updateServerLock = false; 1648 return; 1649 } 1650 } 1651 var entry = 'Running `' + cmd + '`'; 1652 user.emit('console', entry); 1653 logQueue.push(entry); 1654 exec(cmd, function(error, stdout, stderr) { 1655 ('' + stdout + stderr).split('\n').forEach(function(s) { 1656 user.emit('console', s); 1657 logQueue.push(s); 1658 }); 1659 logQueue.forEach(rooms.lobby.logEntry); 1660 updateServerLock = false; 1661 }); 1662 }); 1663 return false; 1664 break; 1665 1666 case 'crashfixed': 1667 if (!lockdown) { 1668 emit(socket, 'console', '/crashfixed - There is no active crash.'); 1669 return false; 1670 } 1671 if (!user.can('hotpatch')) { 1672 emit(socket, 'console', '/crashfixed - Access denied.'); 1673 return false; 1674 } 1675 1676 lockdown = false; 1677 config.modchat = false; 1678 rooms.lobby.addRaw('<div class="broadcast-green"><b>We fixed the crash without restarting the server!</b><br />You may resume talking in the lobby and starting new battles.</div>'); 1679 rooms.lobby.logEntry(user.name + ' used /crashfixed'); 1680 return false; 1681 break; 1682 case 'crashnoted': 1683 case 'crashlogged': 1684 if (!lockdown) { 1685 emit(socket, 'console', '/crashnoted - There is no active crash.'); 1686 return false; 1687 } 1688 if (!user.can('declare')) { 1689 emit(socket, 'console', '/crashnoted - Access denied.'); 1690 return false; 1691 } 1692 1693 lockdown = false; 1694 config.modchat = false; 1695 rooms.lobby.addRaw('<div class="broadcast-green"><b>We have logged the crash and are working on fixing it!</b><br />You may resume talking in the lobby and starting new battles.</div>'); 1696 rooms.lobby.logEntry(user.name + ' used /crashnoted'); 1697 return false; 1698 break; 1699 case 'modlog': 1700 if (!user.can('modlog')) { 1701 emit(socket, 'console', '/modlog - Access denied.'); 1702 return false; 1703 } 1704 var lines = parseInt(target || 15, 10); 1705 if (lines > 100) lines = 100; 1706 var filename = 'logs/modlog.txt'; 1707 var command = 'tail -'+lines+' '+filename; 1708 var grepLimit = 100; 1709 if (!lines || lines < 0) { // searching for a word instead 1710 if (target.match(/^["'].+["']$/)) target = target.substring(1,target.length-1); 1711 command = "awk '{print NR,$0}' "+filename+" | sort -nr | cut -d' ' -f2- | grep -m"+grepLimit+" -i '"+target.replace(/\\/g,'\\\\\\\\').replace(/["'`]/g,'\'\\$&\'').replace(/[\{\}\[\]\(\)\$\^\.\?\+\-\*]/g,'[$&]')+"'"; 1712 } 1713 1714 require('child_process').exec(command, function(error, stdout, stderr) { 1715 if (error && stderr) { 1716 emit(socket, 'console', '/modlog errored, tell Zarel or bmelts.'); 1717 console.log('/modlog error: '+error); 1718 return false; 1719 } 1720 if (lines) { 1721 if (!stdout) { 1722 emit(socket, 'console', 'The modlog is empty. (Weird.)'); 1723 } else { 1724 emit(socket, 'message', 'Displaying the last '+lines+' lines of the Moderator Log:\n\n'+sanitize(stdout)); 1725 } 1726 } else { 1727 if (!stdout) { 1728 emit(socket, 'console', 'No moderator actions containing "'+target+'" were found.'); 1729 } else { 1730 emit(socket, 'message', 'Displaying the last '+grepLimit+' logged actions containing "'+target+'":\n\n'+sanitize(stdout)); 1731 } 1732 } 1733 }); 1734 return false; 1735 break; 1736 case 'banword': 1737 case 'bw': 1738 if (!user.can('declare')) { 1739 emit(socket, 'console', '/banword - Access denied.'); 1740 return false; 1741 } 1742 target = toId(target); 1743 if (!target) { 1744 emit(socket, 'console', 'Specify a word or phrase to ban.'); 1745 return false; 1746 } 1747 Users.addBannedWord(target); 1748 emit(socket, 'console', 'Added \"'+target+'\" to the list of banned words.'); 1749 return false; 1750 break; 1751 case 'unbanword': 1752 case 'ubw': 1753 if (!user.can('declare')) { 1754 emit(socket, 'console', '/unbanword - Access denied.'); 1755 return false; 1756 } 1757 target = toId(target); 1758 if (!target) { 1759 emit(socket, 'console', 'Specify a word or phrase to unban.'); 1760 return false; 1761 } 1762 Users.removeBannedWord(target); 1763 emit(socket, 'console', 'Removed \"'+target+'\" from the list of banned words.'); 1764 return false; 1765 break; 1766 case 'help': 1767 case 'commands': 1768 case 'h': 1769 case '?': 1770 target = target.toLowerCase(); 1771 var matched = false; 1772 if (target === 'all' || target === 'msg' || target === 'pm' || cmd === 'whisper' || cmd === 'w') { 1773 matched = true; 1774 emit(socket, 'console', '/msg OR /whisper OR /w [username], [message] - Send a private message.'); 1775 } 1776 if (target === 'all' || target === 'r' || target === 'reply') { 1777 matched = true; 1778 emit(socket, 'console', '/reply OR /r [message] - Send a private message to the last person you received a message from, or sent a message to.'); 1779 } 1780 if (target === 'all' || target === 'getip' || target === 'ip') { 1781 matched = true; 1782 emit(socket, 'console', '/ip - Get your own IP address.'); 1783 emit(socket, 'console', '/ip [username] - Get a user\'s IP address. Requires: @ & ~'); 1784 } 1785 if (target === 'all' || target === 'rating' || target === 'ranking' || target === 'rank' || target === 'ladder') { 1786 matched = true; 1787 emit(socket, 'console', '/rating - Get your own rating.'); 1788 emit(socket, 'console', '/rating [username] - Get user\'s rating.'); 1789 } 1790 if (target === 'all' || target === 'nick') { 1791 matched = true; 1792 emit(socket, 'console', '/nick [new username] - Change your username.'); 1793 } 1794 if (target === 'all' || target === 'avatar') { 1795 matched = true; 1796 emit(socket, 'console', '/avatar [new avatar number] - Change your trainer sprite.'); 1797 } 1798 if (target === 'all' || target === 'rooms') { 1799 matched = true; 1800 emit(socket, 'console', '/rooms [username] - Show what rooms a user is in.'); 1801 } 1802 if (target === 'all' || target === 'whois') { 1803 matched = true; 1804 emit(socket, 'console', '/whois [username] - Get details on a username: group, and rooms.'); 1805 } 1806 if (target === 'all' || target === 'data') { 1807 matched = true; 1808 emit(socket, 'console', '/data [pokemon/item/move/ability] - Get details on this pokemon/item/move/ability.'); 1809 emit(socket, 'console', '!data [pokemon/item/move/ability] - Show everyone these details. Requires: + % @ & ~'); 1810 } 1811 if (target === "all" || target === 'analysis') { 1812 matched = true; 1813 emit(socket, 'console', '/analysis [pokemon], [generation] - Links to the Smogon University analysis for this Pokemon in the given generation.'); 1814 emit(socket, 'console', '!analysis [pokemon], [generation] - Shows everyone this link. Requires: + % @ & ~'); 1815 } 1816 if (target === 'all' || target === 'groups') { 1817 matched = true; 1818 emit(socket, 'console', '/groups - Explains what the + % @ & next to people\'s names mean.'); 1819 emit(socket, 'console', '!groups - Show everyone that information. Requires: + % @ & ~'); 1820 } 1821 if (target === 'all' || target === 'opensource') { 1822 matched = true; 1823 emit(socket, 'console', '/opensource - Links to PS\'s source code repository.'); 1824 emit(socket, 'console', '!opensource - Show everyone that information. Requires: + % @ & ~'); 1825 } 1826 if (target === 'all' || target === 'avatars') { 1827 matched = true; 1828 emit(socket, 'console', '/avatars - Explains how to change avatars.'); 1829 emit(socket, 'console', '!avatars - Show everyone that information. Requires: + % @ & ~'); 1830 } 1831 if (target === 'all' || target === 'intro') { 1832 matched = true; 1833 emit(socket, 'console', '/intro - Provides an introduction to competitive pokemon.'); 1834 emit(socket, 'console', '!intro - Show everyone that information. Requires: + % @ & ~'); 1835 } 1836 if (target === 'all' || target === 'cap') { 1837 matched = true; 1838 emit(socket, 'console', '/cap - Provides an introduction to the Create-A-Pokemon project.'); 1839 emit(socket, 'console', '!cap - Show everyone that information. Requires: + % @ & ~'); 1840 } 1841 if (target === 'all' || target === 'om') { 1842 matched = true; 1843 emit(socket, 'console', '/om - Provides links to information on the Other Metagames.'); 1844 emit(socket, 'console', '!om - Show everyone that information. Requires: + % @ & ~'); 1845 } 1846 if (target === 'all' || target === 'learn' || target === 'learnset' || target === 'learnall') { 1847 matched = true; 1848 emit(socket, 'console', '/learn [pokemon], [move, move, ...] - Displays how a Pokemon can learn the given moves, if it can at all.') 1849 emit(socket, 'console', '!learn [pokemon], [move, move, ...] - Show everyone that information. Requires: + % @ & ~') 1850 } 1851 if (target === 'all' || target === 'calc' || target === 'caclulator') { 1852 matched = true; 1853 emit(socket, 'console', '/calc - Provides a link to a damage calculator'); 1854 emit(socket, 'console', '!calc - Shows everyone a link to a damage calculator. Requires: + % @ & ~'); 1855 } 1856 if (target === 'all' || target === 'blockchallenges' || target === 'away' || target === 'idle') { 1857 matched = true; 1858 emit(socket, 'console', '/away - Blocks challenges so no one can challenge you.'); 1859 } 1860 if (target === 'all' || target === 'allowchallenges' || target === 'back') { 1861 matched = true; 1862 emit(socket, 'console', '/back - Unlocks challenges so you can be challenged again.'); 1863 } 1864 if (target === 'all' || target === 'faq') { 1865 matched = true; 1866 emit(socket, 'console', '/faq [theme] - Provides a link to the FAQ. Add deviation, doubles, randomcap, restart, or staff for a link to these questions. Add all for all of them.'); 1867 emit(socket, 'console', '!faq [theme] - Shows everyone a link to the FAQ. Add deviation, doubles, randomcap, restart, or staff for a link to these questions. Add all for all of them. Requires: + % @ & ~'); 1868 } 1869 if (target === 'all' || target === 'highlight') { 1870 matched = true; 1871 emit(socket, 'console', 'Set up highlights:'); 1872 emit(socket, 'console', '/highlight add, word - add a new word to the highlight list.'); 1873 emit(socket, 'console', '/highlight list - list all words that currently highlight you.'); 1874 emit(socket, 'console', '/highlight delete, word - delete a word from the highlight list.'); 1875 emit(socket, 'console', '/highlight delete - clear the highlight list'); 1876 } 1877 if (target === 'timestamps') { 1878 matched = true; 1879 emit(socket, 'console', 'Set your timestamps preference:'); 1880 emit(socket, 'console', '/timestamps [all|lobby|pms], [minutes|seconds|off]'); 1881 emit(socket, 'console', 'all - change all timestamps preferences, lobby - change only lobby chat preferences, pms - change only PM preferences'); 1882 emit(socket, 'console', 'off - set timestamps off, minutes - show timestamps of the form [hh:mm], seconds - show timestamps of the form [hh:mm:ss]'); 1883 } 1884 if (target === '%' || target === 'altcheck' || target === 'alt' || target === 'alts' || target === 'getalts') { 1885 matched = true; 1886 emit(socket, 'console', '/alts OR /altcheck OR /alt OR /getalts [username] - Get a user\'s alts. Requires: @ & ~'); 1887 } 1888 if (target === '%' || target === 'forcerename' || target === 'fr') { 1889 matched = true; 1890 emit(socket, 'console', '/forcerename OR /fr [username], [reason] - Forcibly change a user\'s name and shows them the [reason]. Requires: @ & ~'); 1891 } 1892 if (target === '@' || target === 'ban' || target === 'b') { 1893 matched = true; 1894 emit(socket, 'console', '/ban OR /b [username], [reason] - Kick user from all rooms and ban user\'s IP address with reason. Requires: @ & ~'); 1895 } 1896 if (target === "@" || target === 'kick' || target === 'k') { 1897 matched = true; 1898 emit(socket, 'console', '/kick OR /k [username] - Quickly kicks a user by redirecting them to the Pokemon Showdown Rules page. Requires: @ & ~'); 1899 } 1900 if (target === '@' || target === 'unban') { 1901 matched = true; 1902 emit(socket, 'console', '/unban [username] - Unban a user. Requires: @ & ~'); 1903 } 1904 if (target === '@' || target === 'unbanall') { 1905 matched = true; 1906 emit(socket, 'console', '/unbanall - Unban all IP addresses. Requires: @ & ~'); 1907 } 1908 if (target === '@' || target === 'modlog') { 1909 matched = true; 1910 emit(socket, 'console', '/modlog [n] - If n is a number or omitted, display the last n lines of the moderator log. Defaults to 15. If n is not a number, search the moderator log for "n". Requires: @ & ~'); 1911 } 1912 if (target === '%' || target === 'mute' || target === 'm') { 1913 matched = true; 1914 emit(socket, 'console', '/mute OR /m [username], [reason] - Mute user with reason. Requires: % @ & ~'); 1915 } 1916 if (target === '%' || target === 'unmute') { 1917 matched = true; 1918 emit(socket, 'console', '/unmute [username] - Remove mute from user. Requires: % @ & ~'); 1919 } 1920 if (target === '&' || target === 'promote') { 1921 matched = true; 1922 emit(socket, 'console', '/promote [username], [group] - Promotes the user to the specified group or next ranked group. Requires: & ~'); 1923 } 1924 if (target === '&' || target === 'demote') { 1925 matched = true; 1926 emit(socket, 'console', '/demote [username], [group] - Demotes the user to the specified group or previous ranked group. Requires: & ~'); 1927 } 1928 if (target === '&' || target === 'namelock' || target === 'nl') { 1929 matched === true; 1930 emit(socket, 'console', '/namelock OR /nl [username] - Disallowes the used from changing their names. Requires: & ~'); 1931 } 1932 if (target === '&' || target === 'unnamelock') { 1933 matched === true; 1934 emit(socket, 'console', '/unnamelock - Removes name lock from user. Requres: & ~'); 1935 } 1936 if (target === '&' || target === 'forcerenameto' || target === 'frt') { 1937 matched = true; 1938 emit(socket, 'console', '/forcerenameto OR /frt [username] - Force a user to choose a new name. Requires: & ~'); 1939 emit(socket, 'console', '/forcerenameto OR /frt [username], [new name] - Forcibly change a user\'s name to [new name]. Requires: & ~'); 1940 } 1941 if (target === '&' || target === 'forcetie') { 1942 matched === true; 1943 emit(socket, 'console', '/forcetie - Forces the current match to tie. Requires: & ~'); 1944 } 1945 if (target === '&' || target === 'declare' ) { 1946 matched = true; 1947 emit(socket, 'console', '/declare [message] - Anonymously announces a message. Requires: & ~'); 1948 } 1949 if (target === '&' || target === 'potd' ) { 1950 matched = true; 1951 emit(socket, 'console', '/potd [pokemon] - Sets the Random Battle Pokemon of the Day. Requires: & ~'); 1952 } 1953 if (target === '%' || target === 'announce' || target === 'wall' ) { 1954 matched = true; 1955 emit(socket, 'console', '/announce OR /wall [message] - Makes an announcement. Requires: % @ & ~'); 1956 } 1957 if (target === '@' || target === 'modchat') { 1958 matched = true; 1959 emit(socket, 'console', '/modchat [on/off/+/%/@/&/~] - Set the level of moderated chat. Requires: @ & ~'); 1960 } 1961 if (target === '~' || target === 'hotpatch') { 1962 matched = true; 1963 emit(socket, 'console', 'Hot-patching the game engine allows you to update parts of Showdown without interrupting currently-running battles. Requires: ~'); 1964 emit(socket, 'console', 'Hot-patching has greater memory requirements than restarting.'); 1965 emit(socket, 'console', '/hotpatch all - reload the game engine, data, and chat commands'); 1966 emit(socket, 'console', '/hotpatch data - reload the game data (abilities, moves...)'); 1967 emit(socket, 'console', '/hotpatch chat - reload chat-commands.js'); 1968 } 1969 if (target === '~' || target === 'lockdown') { 1970 matched = true; 1971 emit(socket, 'console', '/lockdown - locks down the server, which prevents new battles from starting so that the server can eventually be restarted. Requires: ~'); 1972 } 1973 if (target === '~' || target === 'kill') { 1974 matched = true; 1975 emit(socket, 'console', '/kill - kills the server. Can\'t be done unless the server is in lockdown state. Requires: ~'); 1976 } 1977 if (target === 'all' || target === 'help' || target === 'h' || target === '?' || target === 'commands') { 1978 matched = true; 1979 emit(socket, 'console', '/help OR /h OR /? - Gives you help.'); 1980 } 1981 if (!target) { 1982 emit(socket, 'console', 'COMMANDS: /msg, /reply, /ip, /rating, /nick, /avatar, /rooms, /whois, /help, /away, /back, /timestamps'); 1983 emit(socket, 'console', 'INFORMATIONAL COMMANDS: /data, /groups, /opensource, /avatars, /faq, /rules, /intro, /tiers, /othermetas, /learn, /analysis, /calc (replace / with ! to broadcast. (Requires: + % @ & ~))'); 1984 emit(socket, 'console', 'For details on all commands, use /help all'); 1985 if (user.group !== config.groupsranking[0]) { 1986 emit(socket, 'console', 'DRIVER COMMANDS: /mute, /unmute, /announce, /forcerename, /alts') 1987 emit(socket, 'console', 'MODERATOR COMMANDS: /ban, /unban, /unbanall, /ip, /modlog, /redirect, /kick'); 1988 emit(socket, 'console', 'LEADER COMMANDS: /promote, /demote, /forcerenameto, /namelock, /nameunlock, /forcewin, /forcetie, /declare'); 1989 emit(socket, 'console', 'For details on all moderator commands, use /help @'); 1990 } 1991 emit(socket, 'console', 'For details of a specific command, use something like: /help data'); 1992 } else if (!matched) { 1993 emit(socket, 'console', 'The command "/'+target+'" was not found. Try /help for general help'); 1994 } 1995 return false; 1996 break; 1997 1998 default: 1999 // Check for mod/demod/admin/deadmin/etc depending on the group ids 2000 for (var g in config.groups) { 2001 if (cmd === config.groups[g].id) { 2002 return parseCommand(user, 'promote', toUserid(target) + ',' + g, room, socket); 2003 } else if (cmd === 'de' + config.groups[g].id || cmd === 'un' + config.groups[g].id) { 2004 var nextGroup = config.groupsranking[config.groupsranking.indexOf(g) - 1]; 2005 if (!nextGroup) nextGroup = config.groupsranking[0]; 2006 return parseCommand(user, 'demote', toUserid(target) + ',' + nextGroup, room, socket); 2007 } 2008 } 2009 } 2010 2011 if (message.substr(0,1) === '/' && cmd) { 2012 // To guard against command typos, we now emit an error message 2013 emit(socket, 'console', 'The command "/'+cmd+'" was unrecognized. To send a message starting with "/'+cmd+'", type "//'+cmd+'".'); 2014 return false; 2015 } 2016 2017 // chat moderation 2018 if (!canTalk(user, room, socket)) { 2019 return false; 2020 } 2021 2022 if (message.match(/\bnimp\.org\b/)) { 2023 // spam site 2024 // todo: custom banlists 2025 return false; 2026 } 2027 2028 // remove zalgo 2029 message = message.replace(/[\u0300-\u036f]{3,}/g,''); 2030 2031 return message; 2032} 2033 2034/** 2035 * Can this user talk? 2036 * Pass the corresponding socket to give the user an error, if not 2037 */ 2038function canTalk(user, room, socket) { 2039 if (!user.named) return false; 2040 if (user.muted) { 2041 if (socket) emit(socket, 'console', 'You are muted.'); 2042 return false; 2043 } 2044 if (user.blockLobbyChat) { 2045 if (socket) emit(socket, 'console', "You can't send messages while blocking lobby chat."); 2046 return false; 2047 } 2048 if (config.modchat && room.id === 'lobby') { 2049 if (config.modchat === 'crash') { 2050 if (!user.can('ignorelimits')) { 2051 if (socket) emit(socket, 'console', 'Because the server has crashed, you cannot speak in lobby chat.'); 2052 return false; 2053 } 2054 } else { 2055 if (!user.authenticated && config.modchat === true) { 2056 if (socket) emit(socket, 'console', 'Because moderated chat is set, you must be registered to speak in lobby chat. To register, simply win a rated battle by clicking the look for battle button'); 2057 return false; 2058 } else if (config.groupsranking.indexOf(user.group) < config.groupsranking.indexOf(config.modchat)) { 2059 var groupName = config.groups[config.modchat].name; 2060 if (!groupName) groupName = config.modchat; 2061 if (socket) emit(socket, 'console', 'Because moderated chat is set, you must be of rank ' + groupName +' or higher to speak in lobby chat.'); 2062 return false; 2063 } 2064 } 2065 } 2066 return true; 2067} 2068 2069function showOrBroadcastStart(user, cmd, room, socket, message) { 2070 if (cmd.substr(0,1) === '!') { 2071 if (!user.can('broadcast') || user.muted) { 2072 emit(socket, 'console', "You need to be voiced to broadcast this command's information."); 2073 emit(socket, 'console', "To see it for yourself, use: /"+message.substr(1)); 2074 } else if (canTalk(user, room, socket)) { 2075 room.add('|c|'+user.getIdentity()+'|'+message); 2076 } 2077 } 2078} 2079 2080function showOrBroadcast(user, cmd, room, socket, rawMessage) { 2081 if (cmd.substr(0,1) !== '!') { 2082 sendData(socket, '>'+room.id+'\n|raw|'+rawMessage); 2083 } else if (user.can('broadcast') && canTalk(user, room)) { 2084 room.addRaw(rawMessage); 2085 } 2086} 2087 2088function getDataMessage(target) { 2089 var pokemon = Tools.getTemplate(target); 2090 var item = Tools.getItem(target); 2091 var move = Tools.getMove(target); 2092 var ability = Tools.getAbility(target); 2093 var atLeastOne = false; 2094 var response = []; 2095 if (pokemon.exists) { 2096 response.push('|c|~|/data-pokemon '+pokemon.name); 2097 atLeastOne = true; 2098 } 2099 if (ability.exists) { 2100 response.push('|c|~|/data-ability '+ability.name); 2101 atLeastOne = true; 2102 } 2103 if (item.exists) { 2104 response.push('|c|~|/data-item '+item.name); 2105 atLeastOne = true; 2106 } 2107 if (move.exists) { 2108 response.push('|c|~|/data-move '+move.name); 2109 atLeastOne = true; 2110 } 2111 if (!atLeastOne) { 2112 response.push("||No pokemon, item, move, or ability named '"+target+"' was found. (Check your spelling?)"); 2113 } 2114 return response; 2115} 2116 2117function splitTarget(target, exactName) { 2118 var commaIndex = target.indexOf(','); 2119 if (commaIndex < 0) { 2120 return [Users.get(target, exactName), '', target]; 2121 } 2122 var targetUser = Users.get(target.substr(0, commaIndex), exactName); 2123 if (!targetUser) { 2124 targetUser = null; 2125 } 2126 return [targetUser, target.substr(commaIndex+1).trim(), target.substr(0, commaIndex)]; 2127} 2128 2129function logModCommand(room, result, noBroadcast) { 2130 if (!noBroadcast) room.add(result); 2131 modlog.write('['+(new Date().toJSON())+'] ('+room.id+') '+result+'\n'); 2132} 2133 2134exports.parseCommand = parseCommandLocal;