';
self.add('|raw|'+buffer);
} catch(e) {}
}), 'text');
return false;
case 'buttonban':
var reason = prompt('Why do you wish to ban this user?');
if (reason === null) return false;
if (reason === false) reason = '';
this.send('/ban ' + target + ', ' + reason);
return false;
case 'buttonmute':
var reason = prompt('Why do you wish to mute this user?');
if (reason === null) return false;
if (reason === false) reason = '';
this.send('/mute ' + target + ', ' + reason);
return false;
case 'buttonunmute':
this.send('/unmute ' + target);
return false;
case 'buttonkick':
var reason = prompt('Why do you wish to kick this user?');
if (reason === null) return false;
if (reason === false) reason = '';
this.send('/kick ' + target + ', ' + reason);
return false;
case 'avatar':
var parts = target.split(',');
var avatar = parseInt(parts[0], 10);
if (avatar) {
Tools.prefs('avatar', avatar);
}
return text; // Send the /avatar command through to the server.
}
return text;
}
});
var ChatRoom = this.ChatRoom = ConsoleRoom.extend({
minWidth: 320,
maxWidth: 1024,
isSideRoom: true,
initialize: function() {
var buf = '
Connecting...
';
this.$el.addClass('ps-room-light').html(buf);
this.$chatAdd = this.$('.chat-log-add');
this.$chatFrame = this.$('.chat-log');
this.$chat = this.$('.inner');
this.$chatbox = null;
this.users = {};
this.userCount = {};
this.$joinLeave = null;
this.joinLeave = {
'join': [],
'leave': []
};
this.$userList = this.$('.userlist');
this.userList = new UserList({
el: this.$userList,
room: this
});
},
updateLayout: function() {
if (this.$el.width() >= 570) {
this.$userList.show();
this.$chatFrame.addClass('hasuserlist');
this.$chatAdd.addClass('hasuserlist');
} else {
this.$userList.hide();
this.$chatFrame.removeClass('hasuserlist');
this.$chatAdd.removeClass('hasuserlist');
}
this.$chatFrame.scrollTop(this.$chat.height());
},
show: function() {
Room.prototype.show.apply(this, arguments);
this.updateLayout();
},
join: function() {
app.send('/join '+this.id);
},
leave: function() {
app.send('/leave '+this.id);
},
receive: function(data) {
this.add(data);
},
add: function(log) {
if (typeof log === 'string') log = log.split('\n');
var autoscroll = false;
if (this.$chatFrame.scrollTop() + 60 >= this.$chat.height() - this.$chatFrame.height()) {
autoscroll = true;
}
var userlist = '';
for (var i = 0; i < log.length; i++) {
if (log[i].substr(0,7) === '|users|') {
userlist = log[i];
} else {
this.addRow(log[i]);
}
}
if (userlist) this.addRow(userlist);
if (autoscroll) {
this.$chatFrame.scrollTop(this.$chat.height());
}
var $children = this.$chat.children();
if ($children.length > 900) {
$children.slice(0,100).remove();
}
},
addPM: function(user, message, pm) {
var autoscroll = false;
if (this.$chatFrame.scrollTop() + 60 >= this.$chat.height() - this.$chatFrame.height()) {
autoscroll = true;
}
this.addChat(user, message, pm);
if (autoscroll) {
this.$chatFrame.scrollTop(this.$chat.height());
}
},
addRow: function(line) {
var name, name2, room, action, silent, oldid;
if (line && typeof line === 'string') {
if (line.substr(0,1) !== '|') line = '||'+line;
var row = line.substr(1).split('|');
switch (row[0]) {
case 'init':
// ignore (handled elsewhere)
break;
case 'title':
this.title = row[1];
break;
case 'c':
case 'chat':
if (/[a-zA-Z0-9]/.test(row[1].charAt(0))) row[1] = ' '+row[1];
this.addChat(row[1], row.slice(2).join('|'));
break;
case 'tc':
if (/[a-zA-Z0-9]/.test(row[2].charAt(0))) row[2] = ' '+row[2];
this.addChat(row[2], row.slice(3).join('|'), false, row[1]);
break;
case 'b':
case 'B':
var id = row[1];
name = row[2];
name2 = row[3];
silent = (row[0] === 'B');
var matches = ChatRoom.parseBattleID(id);
if (!matches) {
return; // bogus room ID could be used to inject JavaScript
}
var format = Tools.escapeFormat(matches ? matches[1] : '');
if (silent && !Tools.prefs('showbattles')) return;
this.addJoinLeave();
var battletype = 'Battle';
if (format) {
battletype = format + ' battle';
if (format === 'Random Battle') battletype = 'Random Battle';
}
this.$chat.append('
');
break;
case 'j':
case 'join':
case 'J':
this.addJoinLeave('join', row[1], null, row[0] === 'J');
break;
case 'l':
case 'leave':
case 'L':
this.addJoinLeave('leave', row[1], null, row[0] === 'L');
break;
case 'n':
case 'name':
case 'N':
this.addJoinLeave('rename', row[1], row[2], true);
break;
case 'refresh':
// refresh the page
document.location.reload(true);
break;
case 'users':
this.parseUserList(row[1]);
break;
case 'usercount':
if (this.id === 'lobby') {
this.userCount.globalUsers = parseInt(row[1], 10);
this.userList.updateUserCount();
}
break;
case 'formats':
// deprecated; please send formats to the global room
app.parseFormats(row);
break;
case 'raw':
case 'html':
this.$chat.append('
');
break;
case 'unlink':
// note: this message has global effects, but it's handled here
// so that it can be included in the scrollback buffer.
$('.message-link-' + toId(row[1])).each(function() {
$(this).replaceWith($(this).html());
});
break;
case '':
this.$chat.append('
' + Tools.escapeHTML(row.slice(1).join('|')) + '
');
break;
default:
this.$chat.append('
|' + Tools.escapeHTML(row.join('|')) + '
');
break;
}
}
},
parseUserList: function(userList) {
this.userCount = {};
this.users = {};
var commaIndex = userList.indexOf(',');
if (commaIndex >= 0) {
this.userCount.users = parseInt(userList.substr(0,commaIndex),10);
var users = userList.substr(commaIndex+1).split(',');
for (var i=0,len=users.length; iLoading...');
this.$joinLeave = this.$chat.children().last();
}
this.joinLeave[action].push(name);
var message = '';
if (this.joinLeave['join'].length) {
var preList = this.joinLeave['join'];
var list = [];
var named = {};
for (var j = 0; j < preList.length; j++) {
if (!named[preList[j]]) list.push(preList[j]);
named[preList[j]] = true;
}
for (var j = 0; j < list.length; j++) {
if (j >= 5) {
message += ', and ' + (list.length - 5) + ' others';
break;
}
if (j > 0) {
if (j == 1 && list.length == 2) {
message += ' and ';
} else if (j == list.length - 1) {
message += ', and ';
} else {
message += ', ';
}
}
message += Tools.escapeHTML(list[j]);
}
message += ' joined';
}
if (this.joinLeave['leave'].length) {
if (this.joinLeave['join'].length) {
message += '; ';
}
var preList = this.joinLeave['leave'];
var list = [];
var named = {};
for (var j = 0; j < preList.length; j++) {
if (!named[preList[j]]) list.push(preList[j]);
named[preList[j]] = true;
}
for (var j = 0; j < list.length; j++) {
if (j >= 5) {
message += ', and ' + (list.length - 5) + ' others';
break;
}
if (j > 0) {
if (j == 1 && list.length == 2) {
message += ' and ';
} else if (j == list.length - 1) {
message += ', and ';
} else {
message += ', ';
}
}
message += Tools.escapeHTML(list[j]);
}
message += ' left ';
}
this.$joinLeave.html('' + message + '');
},
addChat: function(name, message, pm, deltatime) {
var userid = toUserid(name);
var color = hashColor(userid);
if (app.ignore[userid] && (name.charAt(0) === ' ' || name.charAt(0) === '+')) return;
// Add this user to the list of people who have spoken recently.
this.markUserActive(userid);
this.$joinLeave = null;
this.joinLeave = {
'join': [],
'leave': []
};
var clickableName = '' + Tools.escapeHTML(name.substr(1)) + '';
if (!pm) {
// PMs already notify in the main menu; no need to make them notify again
var isHighlighted = this.getHighlight(message);
if (isHighlighted) {
this.notifyOnce("Mentioned by "+name, "\""+message+"\"", 'highlight');
}
}
var highlight = isHighlighted ? ' highlighted' : '';
var chatDiv = '
');
} else {
// Normal chat message.
if (message.substr(0,2) === '//') message = message.substr(1);
outputChat();
Storage.logChat(this.id, ''+name+': '+message);
}
}
}, {
getTimestamp: function(section, deltatime) {
var pref = Tools.prefs('timestamps') || {};
var sectionPref = ((section === 'pms') ? pref.pms : pref.lobby) || 'off';
if ((sectionPref === 'off') || (sectionPref === undefined)) return '';
if (!deltatime || isNaN(deltatime)) {
var date = new Date();
} else {
var date = new Date(Date.now() - deltatime * 1000);
}
var components = [ date.getHours(), date.getMinutes() ];
if (sectionPref === 'seconds') {
components.push(date.getSeconds());
}
return '[' + components.map(
function(x) { return (x < 10) ? '0' + x : x; }
).join(':') + '] ';
},
parseBattleID: function(id) {
if (id.lastIndexOf('-') > 6) {
return id.match(/^battle\-([a-z0-9]*)\-?[0-9]*$/);
}
return id.match(/^battle\-([a-z0-9]*[a-z])[0-9]*$/);
}
});
// user list
var UserList = this.UserList = Backbone.View.extend({
initialize: function(options) {
this.room = options.room;
},
construct: function() {
var buf = '';
buf += '
' + (this.room.userCount.users || '0') + ' users
';
var users = [];
if (this.room.users) {
var self = this;
users = Object.keys(this.room.users).sort(function(a, b) {
return self.comparator(a, b);
});
}
for (var i=0, len=users.length; i(' + this.room.userCount.guests + ' guest' + (this.room.userCount.guests == 1 ? '' : 's') + ')';
}
this.$el.html(buf);
},
ranks: {
'#': 2,
'~': 2,
'&': 2,
'@': 1,
'%': 1,
'+': 1,
' ': 0,
'!': 0,
'‽': 0
},
rankOrder: {
'#': 1,
'~': 2,
'&': 3,
'@': 4,
'%': 5,
'+': 6,
' ': 7,
'!': 8,
'‽': 9
},
updateUserCount: function() {
var users = Math.max(this.room.userCount.users || 0, this.room.userCount.globalUsers || 0);
$('#' + this.room.id + '-usercount-users').html('' + users);
},
updateCurrentUser: function() {
$('.userlist > .cur').attr('class', ''); // this doesn't need to be namespaced
$('#' + this.room.id + '-userlist-user-' + me.userForm).attr('class', 'cur');
},
add: function(userid) {
$('#' + this.room.id + '-userlist-user-' + userid).remove();
var users = this.$el.children();
// Determine where to insert the user using a binary search.
var left = 0;
var right = users.length - 1;
while (right >= left) {
var mid = Math.floor((right - left) / 2 + left);
var cmp = this.elemComparator(users[mid], userid);
if (cmp < 0) {
left = mid + 1;
} else if (cmp > 0) {
right = mid - 1;
} else {
// The user is already in the list.
return;
}
}
$(this.constructItem(userid)).insertAfter($(users[right]));
},
remove: function(userid) {
$('#' + this.room.id + '-userlist-user-' + userid).remove();
},
buttonOnClick: function(userid) {
if (app.user.get('named')) {
return selfR.formChallenge(userid);
}
return selfR.formRename();
},
constructItem: function(userid) {
var name = this.room.users[userid];
var text = '';
// Sanitising the `userid` here is probably unnecessary, because
// IDs can't contain anything dangerous.
text += '
';
text += '';
text += '
';
return text;
},
elemComparator: function(elem, userid) {
// look at the part of the `id` after the roomid
var id = elem.id.substr(this.room.id.length + 1);
switch (id) {
case 'userlist-users':
return -1; // `elem` comes first
case 'userlist-empty':
case 'userlist-unregistered':
case 'userlist-guests':
return 1; // `userid` comes first
}
// extract the portion of the `id` after 'userlist-user-'
var elemuserid = id.substr(14);
return this.comparator(elemuserid, userid);
},
comparator: function(a, b) {
if (a === b) return 0;
var aRank = (this.rankOrder[this.room.users[a] ? this.room.users[a].substr(0, 1) : ' '] || 5);
var bRank = (this.rankOrder[this.room.users[b] ? this.room.users[b].substr(0, 1) : ' '] || 5);
if (aRank !== bRank) return aRank - bRank;
return (a > b ? 1 : -1);
},
getNoNamedUsersOnline: function() {
return '