Skip to content

Instantly share code, notes, and snippets.

@Xyphis12
Last active August 29, 2015 13:56
Show Gist options
  • Save Xyphis12/8894189 to your computer and use it in GitHub Desktop.
Save Xyphis12/8894189 to your computer and use it in GitHub Desktop.
/**
* System commands
* Pokemon Showdown - http://pokemonshowdown.com/
*
* These are system commands - commands required for Pokemon Showdown
* to run. A lot of these are sent by the client.
*
* If you'd like to modify commands, please go to config/commands.js,
* which also teaches you how to use commands.
*
* @license MIT license
*/
var crypto = require('crypto');
const MAX_REASON_LENGTH = 300;
var commands = exports.commands = {
// Friends lists, from frost
friends: function(target, room, user, connection) {
var data = fs.readFileSync('config/friends.csv','utf8')
var match = false;
var friends = '';
var row = (''+data).split("\n");
for (var i = 0; i < row.length; i++) {
if (!row[i]) continue;
var parts = row[i].split(",");
var userid = toUserid(parts[0]);
if (user.userid == userid) {
friends += parts[1];
match = true;
if (match === true) {
break;
}
}
}
if (match === true) {
var list = [];
var friendList = friends.split(' ');
for (var i = 0; i < friendList.length; i++) {
if(Users.get(friendList[i])) {
if(Users.get(friendList[i]).connected) {
list.push(friendList[i]);
}
}
}
if (list[0] === undefined) {
return this.sendReply('You have no online friends.');
}
var buttons = '';
for (var i = 0; i < list.length; i++) {
buttons = buttons + '<button name = "openUser" value = "' + Users.get(list[i]).userid + '">' + Users.get(list[i]).name + '</button>';
}
this.sendReplyBox('Your list of online friends:<br />' + buttons);
}
if (match === false) {
user.send('You have no friends to show.');
}
},
addfriend: function(target, room, user, connection) {
if(!target) return this.parse('/help addfriend');
target = this.splitTarget(target);
var targetUser = this.targetUser;
if (!targetUser) {
return this.sendReply('User '+this.targetUsername+' not found.');
}
if (targetUser.userid === user.userid) {
return this.sendReply('Are you really trying to friend yourself?');
}
var data = fs.readFileSync('config/friends.csv','utf8')
var match = false;
var line = '';
var row = (''+data).split("\n");
for (var i = row.length; i > -1; i--) {
if (!row[i]) continue;
var parts = row[i].split(",");
var userid = toUserid(parts[0]);
if (user.userid == userid) {
match = true;
}
if (match === true) {
line = line + row[i];
var individuals = parts[1].split(" ");
for (var i = 0; i < individuals.length; i++) {
if (individuals[i] === targetUser.userid) {
return connection.send('This user is already in your friends list.');
}
}
break;
}
}
if (match === true) {
var re = new RegExp(line,"g");
fs.readFile('config/friends.csv', 'utf8', function (err,data) {
if (err) {
return console.log(err);
}
var result = data.replace(re, line +' '+targetUser.userid);
fs.writeFile('config/friends.csv', result, 'utf8', function (err) {
if (err) return console.log(err);
});
});
} else {
var log = fs.createWriteStream('config/friends.csv', {'flags': 'a'});
log.write("\n"+user.userid+','+targetUser.userid);
}
this.sendReply(targetUser.name + ' was added to your friends list.');
targetUser.send(user.name + ' has added you to their friends list.');
},
removefriend: function(target, room, user, connection) {
if(!target) return this.parse('/help removefriend');
var noCaps = target.toLowerCase();
var idFormat = toUserid(target);
var data = fs.readFileSync('config/friends.csv','utf8')
var match = false;
var line = '';
var row = (''+data).split("\n");
for (var i = row.length; i > -1; i--) {
if (!row[i]) continue;
var parts = row[i].split(",");
var userid = toUserid(parts[0]);
if (user.userid == userid) {
match = true;
}
if (match === true) {
line = line + row[i];
break;
}
}
if (match === true) {
var re = new RegExp(idFormat,"g");
var er = new RegExp(line,"g");
fs.readFile('config/friends.csv', 'utf8', function (err,data) {
if (err) {
return console.log(err);
}
var result = line.replace(re, '');
var replace = data.replace(er, result);
fs.writeFile('config/friends.csv', replace, 'utf8', function (err) {
if (err) return console.log(err);
});
});
} else {
return this.sendReply('This user doesn\'t appear to be in your friends. Make sure you spelled their username right.');
}
this.sendReply(idFormat + ' was removed from your friends list.');
if(Users.get(target).connected) {
Users.get(target).send(user.name + ' has removed you from their friends list.');
}
},
version: function(target, room, user) {
if (!this.canBroadcast()) return;
this.sendReplyBox('Server version: <b>'+CommandParser.package.version+'</b> <small>(<a href="http://pokemonshowdown.com/versions#' + CommandParser.serverVersion + '">' + CommandParser.serverVersion.substr(0,10) + '</a>)</small>');
},
me: function(target, room, user, connection) {
// By default, /me allows a blank message
if (target) target = this.canTalk(target);
if (!target) return;
return '/me ' + target;
},
mee: function(target, room, user, connection) {
// By default, /mee allows a blank message
if (target) target = this.canTalk(target);
if (!target) return;
return '/mee ' + target;
},
avatar: function(target, room, user) {
if (!target) return this.parse('/avatars');
var parts = target.split(',');
var avatar = parseInt(parts[0]);
if (!avatar || avatar > 294 || avatar < 1) {
if (!parts[1]) {
this.sendReply("Invalid avatar.");
}
return false;
}
user.avatar = avatar;
if (!parts[1]) {
this.sendReply("Avatar changed to:\n" +
'|raw|<img src="//play.pokemonshowdown.com/sprites/trainers/'+avatar+'.png" alt="" width="80" height="80" />');
}
},
logout: function(target, room, user) {
user.resetName();
},
r: 'reply',
reply: function(target, room, user) {
if (!target) return this.parse('/help reply');
if (!user.lastPM) {
return this.sendReply('No one has PMed you yet.');
}
return this.parse('/msg '+(user.lastPM||'')+', '+target);
},
pm: 'msg',
whisper: 'msg',
w: 'msg',
msg: function(target, room, user) {
if (!target) return this.parse('/help msg');
target = this.splitTarget(target);
var targetUser = this.targetUser;
if (!target) {
this.sendReply('You forgot the comma.');
return this.parse('/help msg');
}
if (!targetUser || !targetUser.connected) {
if (targetUser && !targetUser.connected) {
this.popupReply('User '+this.targetUsername+' is offline.');
} else if (!target) {
this.popupReply('User '+this.targetUsername+' not found. Did you forget a comma?');
} else {
this.popupReply('User '+this.targetUsername+' not found. Did you misspell their name?');
}
return this.parse('/help msg');
}
if (config.pmmodchat) {
var userGroup = user.group;
if (config.groupsranking.indexOf(userGroup) < config.groupsranking.indexOf(config.pmmodchat)) {
var groupName = config.groups[config.pmmodchat].name;
if (!groupName) groupName = config.pmmodchat;
this.popupReply('Because moderated chat is set, you must be of rank ' + groupName +' or higher to PM users.');
return false;
}
}
if (user.locked && !targetUser.can('lock', user)) {
return this.popupReply('You can only private message members of the moderation team (users marked by %, @, &, or ~) when locked.');
}
if (targetUser.locked && !user.can('lock', targetUser)) {
return this.popupReply('This user is locked and cannot PM.');
}
if (targetUser.ignorePMs && !user.can('lock')) {
if (!targetUser.can('lock')) {
return this.popupReply('This user is blocking Private Messages right now.');
} else if (targetUser.can('hotpatch')) {
return this.popupReply('This admin is too busy to answer Private Messages right now. Please contact a different staff member.');
}
}
target = this.canTalk(target, null);
if (!target) return false;
var message = '|pm|'+user.getIdentity()+'|'+targetUser.getIdentity()+'|'+target;
user.send(message);
if (targetUser !== user) targetUser.send(message);
targetUser.lastPM = user.userid;
user.lastPM = targetUser.userid;
},
blockpm: 'ignorepms',
blockpms: 'ignorepms',
ignorepm: 'ignorepms',
ignorepms: function(target, room, user) {
if (user.ignorePMs) return this.sendReply('You are already blocking Private Messages!');
if (user.can('lock') && !user.can('hotpatch')) return this.sendReply('You are not allowed to block Private Messages.');
user.ignorePMs = true;
return this.sendReply('You are now blocking Private Messages.');
},
unblockpm: 'unignorepms',
unblockpms: 'unignorepms',
unignorepm: 'unignorepms',
unignorepms: function(target, room, user) {
if (!user.ignorePMs) return this.sendReply('You are not blocking Private Messages!');
user.ignorePMs = false;
return this.sendReply('You are no longer blocking Private Messages.');
},
makechatroom: function(target, room, user) {
if (!this.can('makeroom')) return;
var id = toId(target);
if (!id) return this.parse('/help makechatroom');
if (Rooms.rooms[id]) {
return this.sendReply("The room '"+target+"' already exists.");
}
if (Rooms.global.addChatRoom(target)) {
return this.sendReply("The room '"+target+"' was created.");
}
return this.sendReply("An error occurred while trying to create the room '"+target+"'.");
},
deregisterchatroom: function(target, room, user) {
if (!this.can('makeroom')) return;
var id = toId(target);
if (!id) return this.parse('/help deregisterchatroom');
var targetRoom = Rooms.get(id);
if (!targetRoom) return this.sendReply("The room '"+id+"' doesn't exist.");
target = targetRoom.title || targetRoom.id;
if (Rooms.global.deregisterChatRoom(id)) {
this.sendReply("The room '"+target+"' was deregistered.");
this.sendReply("It will be deleted as of the next server restart.");
return;
}
return this.sendReply("The room '"+target+"' isn't registered.");
},
privateroom: function(target, room, user) {
if (!this.can('privateroom')) return;
if (target === 'off') {
delete room.isPrivate;
this.addModCommand(user.name+' made this room public.');
if (room.chatRoomData) {
delete room.chatRoomData.isPrivate;
Rooms.global.writeChatRoomData();
}
} else {
room.isPrivate = true;
this.addModCommand(user.name+' made this room private.');
if (room.chatRoomData) {
room.chatRoomData.isPrivate = true;
Rooms.global.writeChatRoomData();
}
}
},
officialchatroom: 'officialroom',
officialroom: function(target, room, user) {
if (!this.can('makeroom')) return;
if (!room.chatRoomData) {
return this.sendReply("/officialroom - This room can't be made official");
}
if (target === 'off') {
delete room.isOfficial;
this.addModCommand(user.name+' made this chat room unofficial.');
delete room.chatRoomData.isOfficial;
Rooms.global.writeChatRoomData();
} else {
room.isOfficial = true;
this.addModCommand(user.name+' made this chat room official.');
room.chatRoomData.isOfficial = true;
Rooms.global.writeChatRoomData();
}
},
roomowner: function(target, room, user) {
if (!room.chatRoomData) {
return this.sendReply("/roomowner - This room isn't designed for per-room moderation to be added");
}
var target = this.splitTarget(target, true);
var targetUser = this.targetUser;
if (!targetUser) return this.sendReply("User '"+this.targetUsername+"' is not online.");
if (!this.can('makeroom', targetUser, room)) return false;
if (!room.auth) room.auth = room.chatRoomData.auth = {};
var name = targetUser.name;
room.auth[targetUser.userid] = '#';
this.addModCommand(''+name+' was appointed Room Owner by '+user.name+'.');
room.onUpdateIdentity(targetUser);
Rooms.global.writeChatRoomData();
},
roomdeowner: 'deroomowner',
deroomowner: function(target, room, user) {
if (!room.auth) {
return this.sendReply("/roomdeowner - This room isn't designed for per-room moderation");
}
var target = this.splitTarget(target, true);
var targetUser = this.targetUser;
var name = this.targetUsername;
var userid = toId(name);
if (!userid || userid === '') return this.sendReply("User '"+name+"' does not exist.");
if (room.auth[userid] !== '#') return this.sendReply("User '"+name+"' is not a room owner.");
if (!this.can('makeroom', null, room)) return false;
delete room.auth[userid];
this.sendReply('('+name+' is no longer Room Owner.)');
if (targetUser) targetUser.updateIdentity();
if (room.chatRoomData) {
Rooms.global.writeChatRoomData();
}
},
roomdesc: function(target, room, user) {
if (!target) {
if (!this.canBroadcast()) return;
var re = /(https?:\/\/(([-\w\.]+)+(:\d+)?(\/([\w/_\.]*(\?\S+)?)?)?))/g;
if (!room.desc) return this.sendReply("This room does not have a description set.");
this.sendReplyBox('The room description is: '+room.desc.replace(re, "<a href=\"$1\">$1</a>"));
return;
}
if (!this.can('roommod', null, room)) return false;
if (target.length > 80) {
return this.sendReply('Error: Room description is too long (must be at most 80 characters).');
}
room.desc = target;
this.sendReply('(The room description is now: '+target+')');
if (room.chatRoomData) {
room.chatRoomData.desc = room.desc;
Rooms.global.writeChatRoomData();
}
},
roomdemote: 'roompromote',
roompromote: function(target, room, user, connection, cmd) {
if (!room.auth) {
this.sendReply("/roompromote - This room isn't designed for per-room moderation");
return this.sendReply("Before setting room mods, you need to set it up with /roomowner");
}
if (!target) return this.parse('/help roompromote');
var target = this.splitTarget(target, true);
var targetUser = this.targetUser;
var userid = toUserid(this.targetUsername);
var name = targetUser ? targetUser.name : this.targetUsername;
if (!userid) {
if (target && config.groups[target]) {
var groupid = config.groups[target].id;
return this.sendReply("/room"+groupid+" [username] - Promote a user to "+groupid+" in this room only");
}
return this.parse("/help roompromote");
}
var currentGroup = (room.auth[userid] || ' ');
if (!targetUser && !room.auth[userid]) {
return this.sendReply("User '"+this.targetUsername+"' is offline and unauthed, and so can't be promoted.");
}
var nextGroup = target || Users.getNextGroupSymbol(currentGroup, cmd === 'roomdemote', true);
if (target === 'deauth') nextGroup = config.groupsranking[0];
if (!config.groups[nextGroup]) {
return this.sendReply('Group \'' + nextGroup + '\' does not exist.');
}
if (config.groups[nextGroup].globalonly) {
return this.sendReply('Group \'room' + config.groups[nextGroup].id + '\' does not exist as a room rank.');
}
if (currentGroup !== ' ' && !user.can('room'+config.groups[currentGroup].id, null, room)) {
return this.sendReply('/' + cmd + ' - Access denied for promoting from '+config.groups[currentGroup].name+'.');
}
if (nextGroup !== ' ' && !user.can('room'+config.groups[nextGroup].id, null, room)) {
return this.sendReply('/' + cmd + ' - Access denied for promoting to '+config.groups[nextGroup].name+'.');
}
if (currentGroup === nextGroup) {
return this.sendReply("User '"+this.targetUsername+"' is already a "+(config.groups[nextGroup].name || 'regular user')+" in this room.");
}
if (config.groups[nextGroup].globalonly) {
return this.sendReply("The rank of "+config.groups[nextGroup].name+" is global-only and can't be room-promoted to.");
}
var isDemotion = (config.groups[nextGroup].rank < config.groups[currentGroup].rank);
var groupName = (config.groups[nextGroup].name || nextGroup || '').trim() || 'a regular user';
if (nextGroup === ' ') {
delete room.auth[userid];
} else {
room.auth[userid] = nextGroup;
}
if (isDemotion) {
this.privateModCommand('('+name+' was appointed to Room ' + groupName + ' by '+user.name+'.)');
if (targetUser) {
targetUser.popup('You were appointed to Room ' + groupName + ' by ' + user.name + '.');
}
} else {
this.addModCommand(''+name+' was appointed to Room ' + groupName + ' by '+user.name+'.');
}
if (targetUser) {
targetUser.updateIdentity();
}
if (room.chatRoomData) {
Rooms.global.writeChatRoomData();
}
},
autojoin: function(target, room, user, connection) {
Rooms.global.autojoinRooms(user, connection)
},
join: function(target, room, user, connection) {
if (!target) return false;
var targetRoom = Rooms.get(target) || Rooms.get(toId(target));
if (!targetRoom) {
if (target === 'lobby') return connection.sendTo(target, "|noinit|nonexistent|");
return connection.sendTo(target, "|noinit|nonexistent|The room '"+target+"' does not exist.");
}
if (targetRoom.isPrivate && !user.named) {
return connection.sendTo(target, "|noinit|namerequired|You must have a name in order to join the room '"+target+"'.");
}
if (!user.joinRoom(targetRoom || room, connection)) {
return connection.sendTo(target, "|noinit|joinfailed|The room '"+target+"' could not be joined.");
}
// Join messages
fs.readFile('config/joinmessages.json', 'utf8', function (err, data) {
if (err) {
console.log('Error: ' + err);
return;
};
if (data) {
data = JSON.parse(data);
msg = data.target.toLowerCase();
return connection.sendTo(target.toLowerCase(),msg);
}
}
);
},
rb: 'roomban',
roomban: function(target, room, user, connection) {
if (!target) return this.parse('/help roomban');
target = this.splitTarget(target, true);
var targetUser = this.targetUser;
var name = this.targetUsername;
var userid = toId(name);
if (!userid || !targetUser) return this.sendReply("User '" + name + "' does not exist.");
if (!this.can('ban', targetUser, room)) return false;
if (!Rooms.rooms[room.id].users[userid] && room.isPrivate) {
return this.sendReply('User ' + this.targetUsername + ' is not in the room ' + room.id + '.');
}
if (!room.bannedUsers || !room.bannedIps) {
return this.sendReply('Room bans are not meant to be used in room ' + room.id + '.');
}
room.bannedUsers[userid] = true;
for (var ip in targetUser.ips) {
room.bannedIps[ip] = true;
}
targetUser.popup(user.name+" has banned you from the room " + room.id + "." + (target ? " (" + target + ")" : ""));
this.addModCommand(""+targetUser.name+" was banned from room " + room.id + " by "+user.name+"." + (target ? " (" + target + ")" : ""));
var alts = targetUser.getAlts();
if (alts.length) {
this.addModCommand(""+targetUser.name+"'s alts were also banned from room " + room.id + ": "+alts.join(", "));
for (var i = 0; i < alts.length; ++i) {
var altId = toId(alts[i]);
this.add('|unlink|' + altId);
room.bannedUsers[altId] = true;
}
}
this.add('|unlink|' + targetUser.userid);
targetUser.leaveRoom(room.id);
},
roomunban: function(target, room, user, connection) {
if (!target) return this.parse('/help roomunban');
target = this.splitTarget(target, true);
var targetUser = this.targetUser;
var name = this.targetUsername;
var userid = toId(name);
if (!userid || !targetUser) return this.sendReply("User '"+name+"' does not exist.");
if (!this.can('ban', targetUser, room)) return false;
if (!room.bannedUsers || !room.bannedIps) {
return this.sendReply('Room bans are not meant to be used in room ' + room.id + '.');
}
if (room.bannedUsers[userid]) delete room.bannedUsers[userid];
for (var ip in targetUser.ips) {
if (room.bannedIps[ip]) delete room.bannedIps[ip];
}
targetUser.popup(user.name+" has unbanned you from the room " + room.id + ".");
this.addModCommand(""+targetUser.name+" was unbanned from room " + room.id + " by "+user.name+".");
var alts = targetUser.getAlts();
if (alts.length) {
this.addModCommand(""+targetUser.name+"'s alts were also unbanned from room " + room.id + ": "+alts.join(", "));
for (var i = 0; i < alts.length; ++i) {
var altId = toId(alts[i]);
if (room.bannedUsers[altId]) delete room.bannedUsers[altId];
}
}
},
roomauth: function(target, room, user, connection) {
if (!room.auth) return this.sendReply("/roomauth - This room isn't designed for per-room moderation and therefore has no auth list.");
var buffer = [];
for (var u in room.auth) {
buffer.push(room.auth[u] + u);
}
if (buffer.length > 0) {
buffer = buffer.join(', ');
} else {
buffer = 'This room has no auth.';
}
connection.popup(buffer);
},
leave: 'part',
part: function(target, room, user, connection) {
if (room.id === 'global') return false;
var targetRoom = Rooms.get(target);
if (target && !targetRoom) {
return this.sendReply("The room '"+target+"' does not exist.");
}
user.leaveRoom(targetRoom || room, connection);
},
/*********************************************************
* Moderating: Punishments
*********************************************************/
kick: 'warn',
k: 'warn',
warn: function(target, room, user) {
if (!target) return this.parse('/help warn');
target = this.splitTarget(target);
var targetUser = this.targetUser;
if (!targetUser || !targetUser.connected) {
return this.sendReply('User '+this.targetUsername+' not found.');
}
if (room.isPrivate && room.auth) {
return this.sendReply('You can\'t warn here: This is a privately-owned room not subject to global rules.');
}
if (target.length > MAX_REASON_LENGTH) {
return this.sendReply('The reason is too long. It cannot exceed ' + MAX_REASON_LENGTH + ' characters.');
}
if (!this.can('warn', targetUser, room)) return false;
this.addModCommand(''+targetUser.name+' was warned by '+user.name+'.' + (target ? " (" + target + ")" : ""));
targetUser.send('|c|~|/warn '+target);
},
redirect: 'redir',
redir: function (target, room, user, connection) {
if (!target) return this.parse('/help redirect');
target = this.splitTarget(target);
var targetUser = this.targetUser;
var targetRoom = Rooms.get(target) || Rooms.get(toId(target));
if (!targetRoom) {
return this.sendReply("The room '" + target + "' does not exist.");
}
if (!this.can('warn', targetUser, room) || !this.can('warn', targetUser, targetRoom)) return false;
if (!targetUser || !targetUser.connected) {
return this.sendReply('User '+this.targetUsername+' not found.');
}
if (Rooms.rooms[targetRoom.id].users[targetUser.userid]) {
return this.sendReply("User " + targetUser.name + " is already in the room " + target + "!");
}
if (!Rooms.rooms[room.id].users[targetUser.userid]) {
return this.sendReply('User '+this.targetUsername+' is not in the room ' + room.id + '.');
}
if (targetUser.joinRoom(target) === false) return this.sendReply('User "' + targetUser.name + '" could not be joined to room ' + target + '. They could be banned from the room.');
var roomName = (targetRoom.isPrivate)? 'a private room' : 'room ' + target;
this.addModCommand(targetUser.name + ' was redirected to ' + roomName + ' by ' + user.name + '.');
targetUser.leaveRoom(room);
},
m: 'mute',
mute: function(target, room, user) {
if (!target) return this.parse('/help mute');
target = this.splitTarget(target);
var targetUser = this.targetUser;
if (!targetUser) {
return this.sendReply('User '+this.targetUsername+' not found.');
}
if (target.length > MAX_REASON_LENGTH) {
return this.sendReply('The reason is too long. It cannot exceed ' + MAX_REASON_LENGTH + ' characters.');
}
if (!this.can('mute', targetUser, room)) return false;
if (targetUser.mutedRooms[room.id] || targetUser.locked || !targetUser.connected) {
var problem = ' but was already '+(!targetUser.connected ? 'offline' : targetUser.locked ? 'locked' : 'muted');
if (!target) {
return this.privateModCommand('('+targetUser.name+' would be muted by '+user.name+problem+'.)');
}
return this.addModCommand(''+targetUser.name+' would be muted by '+user.name+problem+'.' + (target ? " (" + target + ")" : ""));
}
targetUser.popup(user.name+' has muted you for 7 minutes. '+target);
this.addModCommand(''+targetUser.name+' was muted by '+user.name+' for 7 minutes.' + (target ? " (" + target + ")" : ""));
var alts = targetUser.getAlts();
if (alts.length) this.addModCommand(""+targetUser.name+"'s alts were also muted: "+alts.join(", "));
this.add('|unlink|' + targetUser.userid);
targetUser.mute(room.id, 7*60*1000);
},
hm: 'hourmute',
hourmute: function(target, room, user) {
if (!target) return this.parse('/help hourmute');
target = this.splitTarget(target);
var targetUser = this.targetUser;
if (!targetUser) {
return this.sendReply('User '+this.targetUsername+' not found.');
}
if (target.length > MAX_REASON_LENGTH) {
return this.sendReply('The reason is too long. It cannot exceed ' + MAX_REASON_LENGTH + ' characters.');
}
if (!this.can('mute', targetUser, room)) return false;
if (((targetUser.mutedRooms[room.id] && (targetUser.muteDuration[room.id]||0) >= 50*60*1000) || targetUser.locked) && !target) {
var problem = ' but was already '+(!targetUser.connected ? 'offline' : targetUser.locked ? 'locked' : 'muted');
return this.privateModCommand('('+targetUser.name+' would be muted by '+user.name+problem+'.)');
}
targetUser.popup(user.name+' has muted you for 60 minutes. '+target);
this.addModCommand(''+targetUser.name+' was muted by '+user.name+' for 60 minutes.' + (target ? " (" + target + ")" : ""));
var alts = targetUser.getAlts();
if (alts.length) this.addModCommand(""+targetUser.name+"'s alts were also muted: "+alts.join(", "));
this.add('|unlink|' + targetUser.userid);
targetUser.mute(room.id, 60*60*1000, true);
},
um: 'unmute',
unmute: function(target, room, user) {
if (!target) return this.parse('/help unmute');
var targetUser = Users.get(target);
if (!targetUser) {
return this.sendReply('User '+target+' not found.');
}
if (!this.can('mute', targetUser, room)) return false;
if (!targetUser.mutedRooms[room.id]) {
return this.sendReply(''+targetUser.name+' isn\'t muted.');
}
this.addModCommand(''+targetUser.name+' was unmuted by '+user.name+'.');
targetUser.unmute(room.id);
},
l: 'lock',
ipmute: 'lock',
lock: function(target, room, user) {
if (!target) return this.parse('/help lock');
target = this.splitTarget(target);
var targetUser = this.targetUser;
if (!targetUser) {
return this.sendReply('User '+this.targetUser+' not found.');
}
if (target.length > MAX_REASON_LENGTH) {
return this.sendReply('The reason is too long. It cannot exceed ' + MAX_REASON_LENGTH + ' characters.');
}
if (!user.can('lock', targetUser)) {
return this.sendReply('/lock - Access denied.');
}
if ((targetUser.locked || Users.checkBanned(targetUser.latestIp)) && !target) {
var problem = ' but was already '+(targetUser.locked ? 'locked' : 'banned');
return this.privateModCommand('('+targetUser.name+' would be locked by '+user.name+problem+'.)');
}
targetUser.popup(user.name+' has locked you from talking in chats, battles, and PMing regular users.\n\n'+target+'\n\nIf you feel that your lock was unjustified, you can still PM staff members (%, @, &, and ~) to discuss it.');
this.addModCommand(""+targetUser.name+" was locked from talking by "+user.name+"." + (target ? " (" + target + ")" : ""));
var alts = targetUser.getAlts();
if (alts.length) this.addModCommand(""+targetUser.name+"'s alts were also locked: "+alts.join(", "));
this.add('|unlink|' + targetUser.userid);
targetUser.lock();
},
unlock: function(target, room, user) {
if (!target) return this.parse('/help unlock');
if (!this.can('lock')) return false;
var unlocked = Users.unlock(target);
if (unlocked) {
var names = Object.keys(unlocked);
this.addModCommand('' + names.join(', ') + ' ' +
((names.length > 1) ? 'were' : 'was') +
' unlocked by ' + user.name + '.');
} else {
this.sendReply('User '+target+' is not locked.');
}
},
b: 'ban',
ban: function(target, room, user) {
if (!target) return this.parse('/help ban');
target = this.splitTarget(target);
var targetUser = this.targetUser;
if (!targetUser) {
return this.sendReply('User '+this.targetUsername+' not found.');
}
if (target.length > MAX_REASON_LENGTH) {
return this.sendReply('The reason is too long. It cannot exceed ' + MAX_REASON_LENGTH + ' characters.');
}
if (!this.can('ban', targetUser)) return false;
if (Users.checkBanned(targetUser.latestIp) && !target && !targetUser.connected) {
var problem = ' but was already banned';
return this.privateModCommand('('+targetUser.name+' would be banned by '+user.name+problem+'.)');
}
targetUser.popup(user.name+" has banned you." + (config.appealurl ? (" If you feel that your banning was unjustified you can appeal the ban:\n" + config.appealurl) : "") + "\n\n"+target);
this.addModCommand(""+targetUser.name+" was banned by "+user.name+"." + (target ? " (" + target + ")" : ""), ' ('+targetUser.latestIp+')');
var alts = targetUser.getAlts();
if (alts.length) {
this.addModCommand(""+targetUser.name+"'s alts were also banned: "+alts.join(", "));
for (var i = 0; i < alts.length; ++i) {
this.add('|unlink|' + toId(alts[i]));
}
}
this.add('|unlink|' + targetUser.userid);
targetUser.ban();
},
unban: function(target, room, user) {
if (!target) return this.parse('/help unban');
if (!user.can('ban')) {
return this.sendReply('/unban - Access denied.');
}
var name = Users.unban(target);
if (name) {
this.addModCommand(''+name+' was unbanned by '+user.name+'.');
} else {
this.sendReply('User '+target+' is not banned.');
}
},
unbanall: function(target, room, user) {
if (!user.can('ban')) {
return this.sendReply('/unbanall - Access denied.');
}
// we have to do this the hard way since it's no longer a global
for (var i in Users.bannedIps) {
delete Users.bannedIps[i];
}
for (var i in Users.lockedIps) {
delete Users.lockedIps[i];
}
this.addModCommand('All bans and locks have been lifted by '+user.name+'.');
},
banip: function(target, room, user) {
target = target.trim();
if (!target) {
return this.parse('/help banip');
}
if (!this.can('rangeban')) return false;
Users.bannedIps[target] = '#ipban';
this.addModCommand(user.name+' temporarily banned the '+(target.charAt(target.length-1)==='*'?'IP range':'IP')+': '+target);
},
unbanip: function(target, room, user) {
target = target.trim();
if (!target) {
return this.parse('/help unbanip');
}
if (!this.can('rangeban')) return false;
if (!Users.bannedIps[target]) {
return this.sendReply(''+target+' is not a banned IP or IP range.');
}
delete Users.bannedIps[target];
this.addModCommand(user.name+' unbanned the '+(target.charAt(target.length-1)==='*'?'IP range':'IP')+': '+target);
},
/*********************************************************
* Moderating: Other
*********************************************************/
modnote: function(target, room, user, connection, cmd) {
if (!target) return this.parse('/help note');
if (target.length > MAX_REASON_LENGTH) {
return this.sendReply('The note is too long. It cannot exceed ' + MAX_REASON_LENGTH + ' characters.');
}
if (!this.can('mute')) return false;
return this.privateModCommand('(' + user.name + ' notes: ' + target + ')');
},
demote: 'promote',
promote: function(target, room, user, connection, cmd) {
if (!target) return this.parse('/help promote');
var target = this.splitTarget(target, true);
var targetUser = this.targetUser;
var userid = toUserid(this.targetUsername);
var name = targetUser ? targetUser.name : this.targetUsername;
if (!userid) {
if (target && config.groups[target]) {
var groupid = config.groups[target].id;
return this.sendReply("/"+groupid+" [username] - Promote a user to "+groupid+" globally");
}
return this.parse("/help promote");
}
var currentGroup = ' ';
if (targetUser) {
currentGroup = targetUser.group;
} else if (Users.usergroups[userid]) {
currentGroup = Users.usergroups[userid].substr(0,1);
}
var nextGroup = target ? target : Users.getNextGroupSymbol(currentGroup, cmd === 'demote', true);
if (target === 'deauth') nextGroup = config.groupsranking[0];
if (!config.groups[nextGroup]) {
return this.sendReply('Group \'' + nextGroup + '\' does not exist.');
}
if (config.groups[nextGroup].roomonly) {
return this.sendReply('Group \'' + config.groups[nextGroup].id + '\' does not exist as a global rank.');
}
if (!user.canPromote(currentGroup, nextGroup)) {
return this.sendReply('/' + cmd + ' - Access denied.');
}
var isDemotion = (config.groups[nextGroup].rank < config.groups[currentGroup].rank);
if (!Users.setOfflineGroup(name, nextGroup)) {
return this.sendReply('/promote - WARNING: This user is offline and could be unregistered. Use /forcepromote if you\'re sure you want to risk it.');
}
var groupName = (config.groups[nextGroup].name || nextGroup || '').trim() || 'a regular user';
if (isDemotion) {
this.privateModCommand('('+name+' was demoted to ' + groupName + ' by '+user.name+'.)');
if (targetUser) {
targetUser.popup('You were demoted to ' + groupName + ' by ' + user.name + '.');
}
} else {
this.addModCommand(''+name+' was promoted to ' + groupName + ' by '+user.name+'.');
}
if (targetUser) {
targetUser.updateIdentity();
}
},
forcepromote: function(target, room, user) {
// warning: never document this command in /help
if (!this.can('forcepromote')) return false;
var target = this.splitTarget(target, true);
var name = this.targetUsername;
var nextGroup = target ? target : Users.getNextGroupSymbol(' ', false);
if (!Users.setOfflineGroup(name, nextGroup, true)) {
return this.sendReply('/forcepromote - Don\'t forcepromote unless you have to.');
}
var groupName = config.groups[nextGroup].name || nextGroup || '';
this.addModCommand(''+name+' was promoted to ' + (groupName.trim()) + ' by '+user.name+'.');
},
deauth: function(target, room, user) {
return this.parse('/demote '+target+', deauth');
},
modchat: function(target, room, user) {
if (!target) {
return this.sendReply('Moderated chat is currently set to: '+room.modchat);
}
if (!this.can('modchat', null, room)) return false;
if (room.modchat && room.modchat.length <= 1 && config.groupsranking.indexOf(room.modchat) > 1 && !user.can('modchatall', null, room)) {
return this.sendReply('/modchat - Access denied for removing a setting higher than ' + config.groupsranking[1] + '.');
}
target = target.toLowerCase();
switch (target) {
case 'on':
case 'true':
case 'yes':
case 'registered':
this.sendReply("Modchat registered is no longer available.");
return false;
break;
case 'off':
case 'false':
case 'no':
room.modchat = false;
break;
case 'ac':
case 'autoconfirmed':
room.modchat = 'autoconfirmed';
break;
case '*':
case 'player':
target = '\u2605';
// fallthrough
default:
if (!config.groups[target]) {
return this.parse('/help modchat');
}
if (config.groupsranking.indexOf(target) > 1 && !user.can('modchatall', null, room)) {
return this.sendReply('/modchat - Access denied for setting higher than ' + config.groupsranking[1] + '.');
}
room.modchat = target;
break;
}
if (room.modchat === true) {
this.add('|raw|<div class="broadcast-red"><b>Moderated chat was enabled!</b><br />Only registered users can talk.</div>');
} else if (!room.modchat) {
this.add('|raw|<div class="broadcast-blue"><b>Moderated chat was disabled!</b><br />Anyone may talk now.</div>');
} else {
var modchat = sanitize(room.modchat);
this.add('|raw|<div class="broadcast-red"><b>Moderated chat was set to '+modchat+'!</b><br />Only users of rank '+modchat+' and higher can talk.</div>');
}
this.logModCommand(user.name+' set modchat to '+room.modchat);
},
declare: function(target, room, user) {
if (!target) return this.parse('/help declare');
if (!this.can('declare', null, room)) return false;
if (!this.canTalk()) return;
this.add('|raw|<div class="broadcast-blue"><b>'+target+'</b></div>');
this.logModCommand(user.name+' declared '+target);
},
gdeclare: 'globaldeclare',
globaldeclare: function(target, room, user) {
if (!target) return this.parse('/help globaldeclare');
if (!this.can('gdeclare')) return false;
for (var id in Rooms.rooms) {
if (id !== 'global') Rooms.rooms[id].addRaw('<div class="broadcast-blue"><b>'+target+'</b></div>');
}
this.logModCommand(user.name+' globally declared '+target);
},
cdeclare: 'chatdeclare',
chatdeclare: function(target, room, user) {
if (!target) return this.parse('/help chatdeclare');
if (!this.can('gdeclare')) return false;
for (var id in Rooms.rooms) {
if (id !== 'global') if (Rooms.rooms[id].type !== 'battle') Rooms.rooms[id].addRaw('<div class="broadcast-blue"><b>'+target+'</b></div>');
}
this.logModCommand(user.name+' globally declared (chat level) '+target);
},
wall: 'announce',
announce: function(target, room, user) {
if (!target) return this.parse('/help announce');
if (!this.can('announce', null, room)) return false;
target = this.canTalk(target);
if (!target) return;
return '/announce '+target;
},
fr: 'forcerename',
forcerename: function(target, room, user) {
if (!target) return this.parse('/help forcerename');
target = this.splitTarget(target);
var targetUser = this.targetUser;
if (!targetUser) {
return this.sendReply('User '+this.targetUsername+' not found.');
}
if (!this.can('forcerename', targetUser)) return false;
if (targetUser.userid === toUserid(this.targetUser)) {
var entry = ''+targetUser.name+' was forced to choose a new name by '+user.name+'' + (target ? ": " + target + "" : "");
this.privateModCommand('(' + entry + ')');
targetUser.resetName();
targetUser.send('|nametaken||'+user.name+" has forced you to change your name. "+target);
} else {
this.sendReply("User "+targetUser.name+" is no longer using that name.");
}
},
frt: 'forcerenameto',
forcerenameto: function(target, room, user) {
if (!target) return this.parse('/help forcerenameto');
target = this.splitTarget(target);
var targetUser = this.targetUser;
if (!targetUser) {
return this.sendReply('User '+this.targetUsername+' not found.');
}
if (!target) {
return this.sendReply('No new name was specified.');
}
if (!this.can('forcerenameto', targetUser)) return false;
if (targetUser.userid === toUserid(this.targetUser)) {
var entry = ''+targetUser.name+' was forcibly renamed to '+target+' by '+user.name+'.';
this.privateModCommand('(' + entry + ')');
targetUser.forceRename(target, undefined, true);
} else {
this.sendReply("User "+targetUser.name+" is no longer using that name.");
}
},
modlog: function(target, room, user, connection) {
if (!this.can('modlog')) return false;
var lines = 0;
// Specific case for modlog command. Room can be indicated with a comma, lines go after the comma.
// Otherwise, the text is defaulted to text search in current room's modlog.
var roomId = room.id;
var roomLogs = {};
var fs = require('fs');
if (target.indexOf(',') > -1) {
var targets = target.split(',');
target = targets[1].trim();
roomId = toId(targets[0]) || room.id;
}
// Let's check the number of lines to retrieve or if it's a word instead
if (!target.match('[^0-9]')) {
lines = parseInt(target || 15, 10);
if (lines > 100) lines = 100;
}
var wordSearch = (!lines || lines < 0);
// Control if we really, really want to check all modlogs for a word.
var roomNames = '';
var filename = '';
var command = '';
if (roomId === 'all' && wordSearch) {
roomNames = 'all rooms';
// Get a list of all the rooms
var fileList = fs.readdirSync('logs/modlog');
for (var i=0; i<fileList.length; i++) {
filename += 'logs/modlog/' + fileList[i] + ' ';
}
} else {
roomId = room.id;
roomNames = 'the room ' + roomId;
filename = 'logs/modlog/modlog_' + roomId + '.txt';
}
// Seek for all input rooms for the lines or text
command = 'tail -' + lines + ' ' + filename;
var grepLimit = 100;
if (wordSearch) { // searching for a word instead
if (target.match(/^["'].+["']$/)) target = target.substring(1,target.length-1);
command = "awk '{print NR,$0}' " + filename + " | sort -nr | cut -d' ' -f2- | grep -m"+grepLimit+" -i '"+target.replace(/\\/g,'\\\\\\\\').replace(/["'`]/g,'\'\\$&\'').replace(/[\{\}\[\]\(\)\$\^\.\?\+\-\*]/g,'[$&]')+"'";
}
// Execute the file search to see modlog
require('child_process').exec(command, function(error, stdout, stderr) {
if (error && stderr) {
connection.popup('/modlog empty on ' + roomNames + ' or erred - modlog does not support Windows');
console.log('/modlog error: '+error);
return false;
}
if (lines) {
if (!stdout) {
connection.popup('The modlog is empty. (Weird.)');
} else {
connection.popup('Displaying the last '+lines+' lines of the Moderator Log of ' + roomNames + ':\n\n'+stdout);
}
} else {
if (!stdout) {
connection.popup('No moderator actions containing "'+target+'" were found on ' + roomNames + '.');
} else {
connection.popup('Displaying the last '+grepLimit+' logged actions containing "'+target+'" on ' + roomNames + ':\n\n'+stdout);
}
}
});
},
bw: 'banword',
banword: function(target, room, user) {
if (!this.can('declare')) return false;
target = toId(target);
if (!target) {
return this.sendReply('Specify a word or phrase to ban.');
}
Users.addBannedWord(target);
this.sendReply('Added \"'+target+'\" to the list of banned words.');
},
ubw: 'unbanword',
unbanword: function(target, room, user) {
if (!this.can('declare')) return false;
target = toId(target);
if (!target) {
return this.sendReply('Specify a word or phrase to unban.');
}
Users.removeBannedWord(target);
this.sendReply('Removed \"'+target+'\" from the list of banned words.');
},
/*********************************************************
* Server management commands
*********************************************************/
hotpatch: function(target, room, user) {
if (!target) return this.parse('/help hotpatch');
if (!this.can('hotpatch')) return false;
this.logEntry(user.name + ' used /hotpatch ' + target);
if (target === 'chat' || target === 'commands') {
try {
CommandParser.uncacheTree('./command-parser.js');
CommandParser = require('./command-parser.js');
return this.sendReply('Chat commands have been hot-patched.');
} catch (e) {
return this.sendReply('Something failed while trying to hotpatch chat: \n' + e.stack);
}
} else if (target === 'battles') {
Simulator.SimulatorProcess.respawn();
return this.sendReply('Battles have been hotpatched. Any battles started after now will use the new code; however, in-progress battles will continue to use the old code.');
} else if (target === 'formats') {
try {
// uncache the tools.js dependency tree
CommandParser.uncacheTree('./tools.js');
// reload tools.js
Tools = require('./tools.js'); // note: this will lock up the server for a few seconds
// rebuild the formats list
Rooms.global.formatListText = Rooms.global.getFormatListText();
// respawn validator processes
TeamValidator.ValidatorProcess.respawn();
// respawn simulator processes
Simulator.SimulatorProcess.respawn();
// broadcast the new formats list to clients
Rooms.global.send(Rooms.global.formatListText);
return this.sendReply('Formats have been hotpatched.');
} catch (e) {
return this.sendReply('Something failed while trying to hotpatch formats: \n' + e.stack);
}
} else if (target === 'learnsets') {
try {
// uncache the tools.js dependency tree
CommandParser.uncacheTree('./tools.js');
// reload tools.js
Tools = require('./tools.js'); // note: this will lock up the server for a few seconds
return this.sendReply('Learnsets have been hotpatched.');
} catch (e) {
return this.sendReply('Something failed while trying to hotpatch learnsets: \n' + e.stack);
}
}
this.sendReply('Your hot-patch command was unrecognized.');
},
savelearnsets: function(target, room, user) {
if (!this.can('hotpatch')) return false;
fs.writeFile('data/learnsets.js', 'exports.BattleLearnsets = '+JSON.stringify(BattleLearnsets)+";\n");
this.sendReply('learnsets.js saved.');
},
disableladder: function(target, room, user) {
if (!this.can('disableladder')) return false;
if (LoginServer.disabled) {
return this.sendReply('/disableladder - Ladder is already disabled.');
}
LoginServer.disabled = true;
this.logModCommand('The ladder was disabled by ' + user.name + '.');
this.add('|raw|<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>');
},
enableladder: function(target, room, user) {
if (!this.can('disableladder')) return false;
if (!LoginServer.disabled) {
return this.sendReply('/enable - Ladder is already enabled.');
}
LoginServer.disabled = false;
this.logModCommand('The ladder was enabled by ' + user.name + '.');
this.add('|raw|<div class="broadcast-green"><b>The ladder is now back.</b><br />Rated games will update the ladder now.</div>');
},
lockdown: function(target, room, user) {
if (!this.can('lockdown')) return false;
Rooms.global.lockdown = true;
for (var id in Rooms.rooms) {
if (id !== 'global') Rooms.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>');
if (Rooms.rooms[id].requestKickInactive && !Rooms.rooms[id].battle.ended) Rooms.rooms[id].requestKickInactive(user, true);
}
this.logEntry(user.name + ' used /lockdown');
},
endlockdown: function(target, room, user) {
if (!this.can('lockdown')) return false;
if (!Rooms.global.lockdown) {
return this.sendReply("We're not under lockdown right now.");
}
Rooms.global.lockdown = false;
for (var id in Rooms.rooms) {
if (id !== 'global') Rooms.rooms[id].addRaw('<div class="broadcast-green"><b>The server shutdown was canceled.</b></div>');
}
this.logEntry(user.name + ' used /endlockdown');
},
emergency: function(target, room, user) {
if (!this.can('lockdown')) return false;
if (config.emergency) {
return this.sendReply("We're already in emergency mode.");
}
config.emergency = true;
for (var id in Rooms.rooms) {
if (id !== 'global') Rooms.rooms[id].addRaw('<div class="broadcast-red">The server has entered emergency mode. Some features might be disabled or limited.</div>');
}
this.logEntry(user.name + ' used /emergency');
},
endemergency: function(target, room, user) {
if (!this.can('lockdown')) return false;
if (!config.emergency) {
return this.sendReply("We're not in emergency mode.");
}
config.emergency = false;
for (var id in Rooms.rooms) {
if (id !== 'global') Rooms.rooms[id].addRaw('<div class="broadcast-green"><b>The server is no longer in emergency mode.</b></div>');
}
this.logEntry(user.name + ' used /endemergency');
},
kill: function(target, room, user) {
if (!this.can('lockdown')) return false;
if (!Rooms.global.lockdown) {
return this.sendReply('For safety reasons, /kill can only be used during lockdown.');
}
if (CommandParser.updateServerLock) {
return this.sendReply('Wait for /updateserver to finish before using /kill.');
}
for (var i in Sockets.workers) {
Sockets.workers[i].kill();
}
room.destroyLog(function() {
room.logEntry(user.name + ' used /kill');
}, function() {
process.exit();
});
// Just in the case the above never terminates, kill the process
// after 10 seconds.
setTimeout(function() {
process.exit();
}, 10000);
},
loadbanlist: function(target, room, user, connection) {
if (!this.can('hotpatch')) return false;
connection.sendTo(room, 'Loading ipbans.txt...');
fs.readFile('config/ipbans.txt', function (err, data) {
if (err) return;
data = (''+data).split("\n");
var rangebans = [];
for (var i=0; i<data.length; i++) {
var line = data[i].split('#')[0].trim();
if (!line) continue;
if (line.indexOf('/') >= 0) {
rangebans.push(line);
} else if (line && !Users.bannedIps[line]) {
Users.bannedIps[line] = '#ipban';
}
}
Users.checkRangeBanned = Cidr.checker(rangebans);
connection.sendTo(room, 'ibans.txt has been reloaded.');
});
},
refreshpage: function(target, room, user) {
if (!this.can('hotpatch')) return false;
Rooms.global.send('|refresh|');
this.logEntry(user.name + ' used /refreshpage');
},
updateserver: function(target, room, user, connection) {
if (!user.hasConsoleAccess(connection)) {
return this.sendReply('/updateserver - Access denied.');
}
if (CommandParser.updateServerLock) {
return this.sendReply('/updateserver - Another update is already in progress.');
}
CommandParser.updateServerLock = true;
var logQueue = [];
logQueue.push(user.name + ' used /updateserver');
connection.sendTo(room, 'updating...');
var exec = require('child_process').exec;
exec('git diff-index --quiet HEAD --', function(error) {
var cmd = 'git pull --rebase';
if (error) {
if (error.code === 1) {
// The working directory or index have local changes.
cmd = 'git stash;' + cmd + ';git stash pop';
} else {
// The most likely case here is that the user does not have
// `git` on the PATH (which would be error.code === 127).
connection.sendTo(room, '' + error);
logQueue.push('' + error);
logQueue.forEach(function(line) {
room.logEntry(line);
});
CommandParser.updateServerLock = false;
return;
}
}
var entry = 'Running `' + cmd + '`';
connection.sendTo(room, entry);
logQueue.push(entry);
exec(cmd, function(error, stdout, stderr) {
('' + stdout + stderr).split('\n').forEach(function(s) {
connection.sendTo(room, s);
logQueue.push(s);
});
logQueue.forEach(function(line) {
room.logEntry(line);
});
CommandParser.updateServerLock = false;
});
});
},
crashfixed: function(target, room, user) {
if (!Rooms.global.lockdown) {
return this.sendReply('/crashfixed - There is no active crash.');
}
if (!this.can('hotpatch')) return false;
Rooms.global.lockdown = false;
if (Rooms.lobby) {
Rooms.lobby.modchat = false;
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>');
}
this.logEntry(user.name + ' used /crashfixed');
},
crashlogged: function(target, room, user) {
if (!Rooms.global.lockdown) {
return this.sendReply('/crashlogged - There is no active crash.');
}
if (!this.can('declare')) return false;
Rooms.global.lockdown = false;
if (Rooms.lobby) {
Rooms.lobby.modchat = false;
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>');
}
this.logEntry(user.name + ' used /crashlogged');
},
'memusage': 'memoryusage',
memoryusage: function(target) {
if (!this.can('hotpatch')) return false;
target = toId(target) || 'all';
if (target === 'all') {
this.sendReply('Loading memory usage, this might take a while.');
}
if (target === 'all' || target === 'rooms' || target === 'room') {
this.sendReply('Calcualting Room size...');
var roomSize = ResourceMonitor.sizeOfObject(Rooms);
this.sendReply("Rooms are using " + roomSize + " bytes of memory.");
}
if (target === 'all' || target === 'config') {
this.sendReply('Calculating config size...');
var configSize = ResourceMonitor.sizeOfObject(config);
this.sendReply("Config is using " + configSize + " bytes of memory.");
}
if (target === 'all' || target === 'resourcemonitor' || target === 'rm') {
this.sendReply('Calculating Resource Monitor size...');
var rmSize = ResourceMonitor.sizeOfObject(ResourceMonitor);
this.sendReply("The Resource Monitor is using " + rmSize + " bytes of memory.");
}
if (target === 'all' || target === 'cmdp' || target === 'cp' || target === 'commandparser') {
this.sendReply('Calculating Command Parser size...');
var cpSize = ResourceMonitor.sizeOfObject(CommandParser);
this.sendReply("Command Parser is using " + cpSize + " bytes of memory.");
}
if (target === 'all' || target === 'sim' || target === 'simulator') {
this.sendReply('Calculating Simulator size...');
var simSize = ResourceMonitor.sizeOfObject(Simulator);
this.sendReply("Simulator is using " + simSize + " bytes of memory.");
}
if (target === 'all' || target === 'users') {
this.sendReply('Calculating Users size...');
var usersSize = ResourceMonitor.sizeOfObject(Users);
this.sendReply("Users is using " + usersSize + " bytes of memory.");
}
if (target === 'all' || target === 'tools') {
this.sendReply('Calculating Tools size...');
var toolsSize = ResourceMonitor.sizeOfObject(Tools);
this.sendReply("Tools are using " + toolsSize + " bytes of memory.");
}
if (target === 'all' || target === 'v8') {
this.sendReply('Retrieving V8 memory usage...');
var o = process.memoryUsage();
this.sendReply(
'Resident set size: ' + o.rss + ', ' + o.heapUsed +' heap used of ' + o.heapTotal + ' total heap. '
+ (o.heapTotal - o.heapUsed) + ' heap left.'
);
delete o;
}
if (target === 'all') {
this.sendReply('Calculating Total size...');
var total = (roomSize + configSize + rmSize + appSize + cpSize + simSize + toolsSize + usersSize) || 0;
var units = ['bytes', 'K', 'M', 'G'];
var converted = total;
var unit = 0;
while (converted > 1024) {
converted /= 1024;
unit++;
}
converted = Math.round(converted);
this.sendReply("Total memory used: " + converted + units[unit] + " (" + total + " bytes).");
}
return;
},
bash: function(target, room, user, connection) {
if (!user.hasConsoleAccess(connection)) {
return this.sendReply('/bash - Access denied.');
}
var exec = require('child_process').exec;
exec(target, function(error, stdout, stderr) {
connection.sendTo(room, ('' + stdout + stderr));
});
},
eval: function(target, room, user, connection, cmd, message) {
if (!user.hasConsoleAccess(connection)) {
return this.sendReply("/eval - Access denied.");
}
if (!this.canBroadcast()) return;
if (!this.broadcasting) this.sendReply('||>> '+target);
try {
var battle = room.battle;
var me = user;
this.sendReply('||<< '+eval(target));
} catch (e) {
this.sendReply('||<< error: '+e.message);
var stack = '||'+(''+e.stack).replace(/\n/g,'\n||');
connection.sendTo(room, stack);
}
},
evalbattle: function(target, room, user, connection, cmd, message) {
if (!user.hasConsoleAccess(connection)) {
return this.sendReply("/evalbattle - Access denied.");
}
if (!this.canBroadcast()) return;
if (!room.battle) {
return this.sendReply("/evalbattle - This isn't a battle room.");
}
room.battle.send('eval', target.replace(/\n/g, '\f'));
},
/*********************************************************
* Battle commands
*********************************************************/
concede: 'forfeit',
surrender: 'forfeit',
forfeit: function(target, room, user) {
if (!room.battle) {
return this.sendReply("There's nothing to forfeit here.");
}
if (!room.forfeit(user)) {
return this.sendReply("You can't forfeit this battle.");
}
},
savereplay: function(target, room, user, connection) {
if (!room || !room.battle) return;
var logidx = 2; // spectator log (no exact HP)
if (room.battle.ended) {
// If the battle is finished when /savereplay is used, include
// exact HP in the replay log.
logidx = 3;
}
var data = room.getLog(logidx).join("\n");
var datahash = crypto.createHash('md5').update(data.replace(/[^(\x20-\x7F)]+/g,'')).digest('hex');
LoginServer.request('prepreplay', {
id: room.id.substr(7),
loghash: datahash,
p1: room.p1.name,
p2: room.p2.name,
format: room.format
}, function(success) {
connection.send('|queryresponse|savereplay|'+JSON.stringify({
log: data,
id: room.id.substr(7)
}));
});
},
mv: 'move',
attack: 'move',
move: function(target, room, user) {
if (!room.decision) return this.sendReply('You can only do this in battle rooms.');
room.decision(user, 'choose', 'move '+target);
},
sw: 'switch',
switch: function(target, room, user) {
if (!room.decision) return this.sendReply('You can only do this in battle rooms.');
room.decision(user, 'choose', 'switch '+parseInt(target,10));
},
choose: function(target, room, user) {
if (!room.decision) return this.sendReply('You can only do this in battle rooms.');
room.decision(user, 'choose', target);
},
undo: function(target, room, user) {
if (!room.decision) return this.sendReply('You can only do this in battle rooms.');
room.decision(user, 'undo', target);
},
team: function(target, room, user) {
if (!room.decision) return this.sendReply('You can only do this in battle rooms.');
room.decision(user, 'choose', 'team '+target);
},
joinbattle: function(target, room, user) {
if (!room.joinBattle) return this.sendReply('You can only do this in battle rooms.');
if (!user.can('joinbattle', null, room)) return this.popupReply("You must be a roomvoice to join a battle you didn't start. Ask a player to use /roomvoice on you to join this battle.");
room.joinBattle(user);
},
partbattle: 'leavebattle',
leavebattle: function(target, room, user) {
if (!room.leaveBattle) return this.sendReply('You can only do this in battle rooms.');
room.leaveBattle(user);
},
kickbattle: function(target, room, user) {
if (!room.leaveBattle) return this.sendReply('You can only do this in battle rooms.');
target = this.splitTarget(target);
var targetUser = this.targetUser;
if (!targetUser || !targetUser.connected) {
return this.sendReply('User '+this.targetUsername+' not found.');
}
if (!this.can('kick', targetUser)) return false;
if (room.leaveBattle(targetUser)) {
this.addModCommand(''+targetUser.name+' was kicked from a battle by '+user.name+'' + (target ? " (" + target + ")" : ""));
} else {
this.sendReply("/kickbattle - User isn\'t in battle.");
}
},
kickinactive: function(target, room, user) {
if (room.requestKickInactive) {
room.requestKickInactive(user);
} else {
this.sendReply('You can only kick inactive players from inside a room.');
}
},
timer: function(target, room, user) {
target = toId(target);
if (room.requestKickInactive) {
if (target === 'off' || target === 'stop') {
room.stopKickInactive(user, user.can('timer'));
} else if (target === 'on' || !target) {
room.requestKickInactive(user, user.can('timer'));
} else {
this.sendReply("'"+target+"' is not a recognized timer state.");
}
} else {
this.sendReply('You can only set the timer from inside a room.');
}
},
forcetie: 'forcewin',
forcewin: function(target, room, user) {
if (!this.can('forcewin')) return false;
if (!room.battle) {
this.sendReply('/forcewin - This is not a battle room.');
return false;
}
room.battle.endType = 'forced';
if (!target) {
room.battle.tie();
this.logModCommand(user.name+' forced a tie.');
return false;
}
target = Users.get(target);
if (target) target = target.userid;
else target = '';
if (target) {
room.battle.win(target);
this.logModCommand(user.name+' forced a win for '+target+'.');
}
},
/*********************************************************
* Challenging and searching commands
*********************************************************/
cancelsearch: 'search',
search: function(target, room, user) {
if (target) {
if (config.pmmodchat) {
var userGroup = user.group;
if (config.groupsranking.indexOf(userGroup) < config.groupsranking.indexOf(config.pmmodchat)) {
var groupName = config.groups[config.pmmodchat].name;
if (!groupName) groupName = config.pmmodchat;
this.popupReply('Because moderated chat is set, you must be of rank ' + groupName +' or higher to search for a battle.');
return false;
}
}
Rooms.global.searchBattle(user, target);
} else {
Rooms.global.cancelSearch(user);
}
},
chall: 'challenge',
challenge: function(target, room, user, connection) {
target = this.splitTarget(target);
var targetUser = this.targetUser;
if (!targetUser || !targetUser.connected) {
return this.popupReply("The user '"+this.targetUsername+"' was not found.");
}
if (targetUser.blockChallenges && !user.can('bypassblocks', targetUser)) {
return this.popupReply("The user '"+this.targetUsername+"' is not accepting challenges right now.");
}
if (config.pmmodchat) {
var userGroup = user.group;
if (config.groupsranking.indexOf(userGroup) < config.groupsranking.indexOf(config.pmmodchat)) {
var groupName = config.groups[config.pmmodchat].name;
if (!groupName) groupName = config.pmmodchat;
this.popupReply('Because moderated chat is set, you must be of rank ' + groupName +' or higher to challenge users.');
return false;
}
}
user.prepBattle(target, 'challenge', connection, function (result) {
if (result) user.makeChallenge(targetUser, target);
});
},
away: 'blockchallenges',
idle: 'blockchallenges',
blockchallenges: function(target, room, user) {
user.blockChallenges = true;
this.sendReply('You are now blocking all incoming challenge requests.');
},
back: 'allowchallenges',
allowchallenges: function(target, room, user) {
user.blockChallenges = false;
this.sendReply('You are available for challenges from now on.');
},
cchall: 'cancelChallenge',
cancelchallenge: function(target, room, user) {
user.cancelChallengeTo(target);
},
accept: function(target, room, user, connection) {
var userid = toUserid(target);
var format = '';
if (user.challengesFrom[userid]) format = user.challengesFrom[userid].format;
if (!format) {
this.popupReply(target+" cancelled their challenge before you could accept it.");
return false;
}
user.prepBattle(format, 'challenge', connection, function (result) {
if (result) user.acceptChallengeFrom(userid);
});
},
reject: function(target, room, user) {
user.rejectChallengeFrom(toUserid(target));
},
saveteam: 'useteam',
utm: 'useteam',
useteam: function(target, room, user) {
user.team = target;
},
/*********************************************************
* Low-level
*********************************************************/
cmd: 'query',
query: function(target, room, user, connection) {
// Avoid guest users to use the cmd errors to ease the app-layer attacks in emergency mode
var trustable = (!config.emergency || (user.named && user.authenticated));
if (config.emergency && ResourceMonitor.countCmd(connection.ip, user.name)) return false;
var spaceIndex = target.indexOf(' ');
var cmd = target;
if (spaceIndex > 0) {
cmd = target.substr(0, spaceIndex);
target = target.substr(spaceIndex+1);
} else {
target = '';
}
if (cmd === 'userdetails') {
var targetUser = Users.get(target);
if (!trustable || !targetUser) {
connection.send('|queryresponse|userdetails|'+JSON.stringify({
userid: toId(target),
rooms: false
}));
return false;
}
var roomList = {};
for (var i in targetUser.roomCount) {
if (i==='global') continue;
var targetRoom = Rooms.get(i);
if (!targetRoom || targetRoom.isPrivate) continue;
var roomData = {};
if (targetRoom.battle) {
var battle = targetRoom.battle;
roomData.p1 = battle.p1?' '+battle.p1:'';
roomData.p2 = battle.p2?' '+battle.p2:'';
}
roomList[i] = roomData;
}
if (!targetUser.roomCount['global']) roomList = false;
var userdetails = {
userid: targetUser.userid,
avatar: targetUser.avatar,
rooms: roomList
};
if (user.can('ip', targetUser)) {
var ips = Object.keys(targetUser.ips);
if (ips.length === 1) {
userdetails.ip = ips[0];
} else {
userdetails.ips = ips;
}
}
connection.send('|queryresponse|userdetails|'+JSON.stringify(userdetails));
} else if (cmd === 'roomlist') {
if (!trustable) return false;
connection.send('|queryresponse|roomlist|'+JSON.stringify({
rooms: Rooms.global.getRoomList(true)
}));
} else if (cmd === 'rooms') {
if (!trustable) return false;
connection.send('|queryresponse|rooms|'+JSON.stringify(
Rooms.global.getRooms()
));
}
},
trn: function(target, room, user, connection) {
var commaIndex = target.indexOf(',');
var targetName = target;
var targetAuth = false;
var targetToken = '';
if (commaIndex >= 0) {
targetName = target.substr(0,commaIndex);
target = target.substr(commaIndex+1);
commaIndex = target.indexOf(',');
targetAuth = target;
if (commaIndex >= 0) {
targetAuth = !!parseInt(target.substr(0,commaIndex),10);
targetToken = target.substr(commaIndex+1);
}
}
user.rename(targetName, targetToken, targetAuth, connection);
},
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment