';
}
buffer += '';
self.add('|raw|' + buffer);
}), 'text');
return false;
case 'buttonban':
if (this.checkBroadcast(cmd, text)) return false;
var self = this;
app.addPopupPrompt("Why do you wish to ban this user?", "Ban user", function (reason) {
self.send('/ban ' + toName(target) + ', ' + (reason || ''));
});
return false;
case 'buttonmute':
if (this.checkBroadcast(cmd, text)) return false;
var self = this;
app.addPopupPrompt("Why do you wish to mute this user?", "Mute user", function (reason) {
self.send('/mute ' + toName(target) + ', ' + (reason || ''));
});
return false;
case 'buttonunmute':
if (this.checkBroadcast(cmd, text)) return false;
this.send('/unmute ' + target);
return false;
case 'buttonkick':
case 'buttonwarn':
if (this.checkBroadcast(cmd, text)) return false;
var self = this;
app.addPopupPrompt("Why do you wish to warn this user?", "Warn user", function (reason) {
self.send('/warn ' + toName(target) + ', ' + (reason || ''));
});
return false;
case 'joim':
case 'join':
case 'j':
if (this.checkBroadcast(cmd, text)) return false;
if (noSpace) return text;
if (app.rooms[target]) {
app.focusRoom(target);
return false;
}
var roomid = toID(target);
if (app.rooms[roomid]) {
app.focusRoom(roomid);
return false;
}
return text; // Send the /join command through to the server.
case 'part':
case 'leave':
if (this.checkBroadcast(cmd, text)) return false;
if (this.requestLeave && !this.requestLeave()) return false;
return text;
case 'avatar':
if (this.checkBroadcast(cmd, text)) return false;
var parts = target.split(',');
var avatar = parts[0].toLowerCase().replace(/[^a-z0-9-]+/g, '');
// Replace avatar number with name before sending it to the server, only the client knows what to do with the numbers
if (window.BattleAvatarNumbers && Object.prototype.hasOwnProperty.call(window.BattleAvatarNumbers, avatar)) {
avatar = window.BattleAvatarNumbers[avatar];
}
Storage.prefs('avatar', avatar);
return '/avatar ' + avatar; // Send the command through to the server.
case 'afd':
if (this.checkBroadcast(cmd, text)) return false;
var cleanedTarget = toID(target);
if (cleanedTarget === 'off' || cleanedTarget === 'disable') {
Storage.prefs('afd', false);
if (typeof BattleTextNotAFD !== 'undefined') BattleText = BattleTextNotAFD;
this.add('April Fools\' day mode disabled.');
} else {
Storage.prefs('afd', true);
if (typeof BattleTextAFD !== 'undefined') BattleText = BattleTextAFD;
this.add('April Fools\' day mode enabled.');
}
for (var roomid in app.rooms) {
var battle = app.rooms[roomid] && app.rooms[roomid].battle;
if (!battle) continue;
battle.resetToCurrentTurn();
}
return false;
// documentation of client commands
case 'help':
case 'h':
switch (toID(target)) {
case 'chal':
case 'chall':
case 'challenge':
this.add('/challenge - Open a prompt to challenge a user to a battle.');
this.add('/challenge [user] - Challenge the user [user] to a battle.');
this.add('/challenge [user], [format] - Challenge the user [user] to a battle in the specified [format].');
this.add('/challenge [user], [format] @@@ [rules] - Challenge the user [user] to a battle with custom rules.');
this.add('[rules] can be a comma-separated list of: [added rule], ![removed rule], -[banned thing], *[restricted thing], +[unbanned/unrestricted thing]');
this.add('/battlerules - Detailed information on what can go in [rules].');
return false;
case 'accept':
this.add('/accept - Accept a challenge if only one is pending.');
this.add('/accept [user] - Accept a challenge from the specified user.');
return false;
case 'reject':
this.add('/reject - Reject a challenge if only one is pending.');
this.add('/reject [user] - Reject a challenge from the specified user.');
return false;
case 'user':
case 'open':
this.add('/user [user] - Open a popup containing the user [user]\'s avatar, name, rank, and chatroom list.');
return false;
case 'news':
this.add('/news - Opens a popup containing the news.');
return false;
case 'ignore':
case 'unignore':
this.add('/ignore [user] - Ignore all messages from the user [user].');
this.add('/unignore [user] - Remove the user [user] from your ignore list.');
this.add('/ignorelist - List all the users that you currently ignore.');
this.add('/clearignore - Remove all users on your ignore list.');
this.add('Note that staff messages cannot be ignored.');
return false;
case 'nick':
this.add('/nick [new username] - Change your username.');
return false;
case 'clear':
this.add('/clear - Clear the room\'s chat log.');
return false;
case 'showdebug':
case 'hidedebug':
this.add('/showdebug - Receive debug messages from battle events.');
this.add('/hidedebug - Ignore debug messages from battle events.');
return false;
case 'showjoins':
case 'hidejoins':
this.add('/showjoins [room] - Receive users\' join/leave messages. Optionally for only specified room.');
this.add('/hidejoins [room] - Ignore users\' join/leave messages. Optionally for only specified room.');
return false;
case 'showbattles':
case 'hidebattles':
this.add('/showbattles - Receive links to new battles in Lobby.');
this.add('/hidebattles - Ignore links to new battles in Lobby.');
return false;
case 'unpackhidden':
case 'packhidden':
this.add('/unpackhidden - Suppress hiding locked or banned users\' chat messages after the fact.');
this.add('/packhidden - Hide locked or banned users\' chat messages after the fact.');
this.add('Hidden messages from a user can be restored by clicking the button underneath their lock/ban reason.');
return false;
case 'timestamps':
this.add('Set your timestamps preference:');
this.add('/timestamps [all|lobby|pms], [minutes|seconds|off]');
this.add('all - Change all timestamps preferences, lobby - Change only lobby chat preferences, pms - Change only PM preferences.');
this.add('off - Set timestamps off, minutes - Show timestamps of the form [hh:mm], seconds - Show timestamps of the form [hh:mm:ss].');
return false;
case 'highlight':
case 'hl':
this.add('Set up highlights:');
this.add('/highlight add [word 1], [word 2], [...] - Add the provided list of words to your highlight list.');
this.add('/highlight roomadd [word 1], [word 2], [...] - Add the provided list of words to the highlight list of whichever room you used the command in.');
this.add('/highlight list - List all words that currently highlight you.');
this.add('/highlight roomlist - List all words that currently highlight you in whichever room you used the command in.');
this.add('/highlight delete [word 1], [word 2], [...] - Delete the provided list of words from your entire highlight list.');
this.add('/highlight roomdelete [word 1], [word 2], [...] - Delete the provided list of words from the highlight list of whichever room you used the command in.');
this.add('/highlight clear - Clear your global highlight list.');
this.add('/highlight roomclear - Clear the highlight list of whichever room you used the command in.');
this.add('/highlight clearall - Clear your entire highlight list (all rooms and globally).');
return false;
case 'rank':
case 'ranking':
case 'rating':
case 'ladder':
this.add('/rating - Get your own rating.');
this.add('/rating [username] - Get user [username]\'s rating.');
return false;
case 'afd':
this.add('/afd - Enable April Fools\' Day sprites.');
this.add('/afd disable - Disable April Fools\' Day sprites.');
return false;
}
}
return text;
},
challengeData: {},
challengeUserdetails: function (data) {
app.off('response:userdetails', this.challengeUserdetails);
if (!data) return;
if (data.rooms === false) {
this.add('This player does not exist or is not online.');
return;
}
app.focusRoom('');
// if foe has changed name, challengeData.userid will be wrong, so defer to data
var name = data.name || data.userid;
if (/^[a-z0-9]/i.test(name)) name = ' ' + name;
app.rooms[''].challenge(name, this.challengeData.format, this.challengeData.team);
},
showOtherFormats: function (d, target) {
var autoscroll = (this.$chatFrame.scrollTop() + 60 >= this.$chat.height() - this.$chatFrame.height());
var $target = $(target);
var $table = $target.closest('table');
$table.find('tr.hidden').show();
$table.find('tr.no-matches').remove();
$target.closest('tr').remove();
if (autoscroll) {
this.$chatFrame.scrollTop(this.$chat.height());
}
},
destroy: function (alreadyLeft) {
app.user.off('change', this.updateUser, this);
Room.prototype.destroy.call(this, alreadyLeft);
}
}, {
toggleFormatChar: function (textbox, formatChar) {
if (!textbox.setSelectionRange) return false;
var value = textbox.value;
var start = textbox.selectionStart;
var end = textbox.selectionEnd;
// make sure start and end aren't midway through the syntax
if (value.charAt(start) === formatChar && value.charAt(start - 1) === formatChar &&
value.charAt(start - 2) !== formatChar) {
start++;
}
if (value.charAt(end) === formatChar && value.charAt(end - 1) === formatChar &&
value.charAt(end - 2) !== formatChar) {
end--;
}
// wrap in doubled format char
var wrap = formatChar + formatChar;
value = value.substr(0, start) + wrap + value.substr(start, end - start) + wrap + value.substr(end);
start += 2;
end += 2;
// prevent nesting
var nesting = wrap + wrap;
if (value.substr(start - 4, 4) === nesting) {
value = value.substr(0, start - 4) + value.substr(start);
start -= 4;
end -= 4;
} else if (start !== end && value.substr(start - 2, 4) === nesting) {
value = value.substr(0, start - 2) + value.substr(start + 2);
start -= 2;
end -= 4;
}
if (value.substr(end, 4) === nesting) {
value = value.substr(0, end) + value.substr(end + 4);
} else if (start !== end && value.substr(end - 2, 4) === nesting) {
value = value.substr(0, end - 2) + value.substr(end + 2);
end -= 2;
}
textbox.value = value;
textbox.setSelectionRange(start, end);
return true;
}
});
var ChatRoom = this.ChatRoom = ConsoleRoom.extend({
minWidth: 320,
minMainWidth: 580,
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.$tournamentWrapper = this.$('.tournament-wrapper');
this.tournamentBox = 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');
this.$tournamentWrapper.addClass('hasuserlist');
} else {
this.userList.hide();
this.$chatFrame.removeClass('hasuserlist');
this.$chatAdd.removeClass('hasuserlist');
this.$tournamentWrapper.removeClass('hasuserlist');
}
this.$chatFrame.scrollTop(this.$chat.height());
if (this.tournamentBox) this.tournamentBox.updateLayout();
},
show: function () {
Room.prototype.show.apply(this, arguments);
this.updateLayout();
},
join: function () {
app.send('/join ' + this.id);
},
leave: function () {
app.send('/noreply /leave ' + this.id);
app.updateAutojoin();
},
requestLeave: function (e) {
if (app.rooms[''].games && app.rooms[''].games[this.id]) {
app.addPopup(ForfeitPopup, { room: this, sourceEl: e && e.currentTarget, gameType: (this.id.substring(0, 5) === 'help-' ? 'help' : 'game') });
return false;
} else if (Dex.prefs('leavePopupRoom')) {
app.addPopup(ForfeitPopup, { room: this, sourceEl: e && e.currentTarget, gameType: 'room' });
return false;
}
return true;
},
receive: function (data) {
this.add(data);
},
getUserGroup: function (userid) {
return (app.rooms[this.id].users[userid] || { group: ' ' }).group;
},
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;
}
if (!(message.substr(0, 4) === '/raw' || message.substr(0, 5) === '/html' || message.substr(0, 6) === '/uhtml' || message.substr(0, 12) === '/uhtmlchange')) this.addChat(user, message, pm);
if (autoscroll) {
this.$chatFrame.scrollTop(this.$chat.height());
}
if (!app.focused) app.playNotificationSound();
},
addRow: function (line) {
var name, name2, silent;
if (line && typeof line === 'string') {
if (line.charAt(0) !== '|') line = '||' + line;
var row = line.substr(1).split('|');
switch (row[0]) {
case 'init':
// ignore (handled elsewhere)
break;
case 'title':
this.title = row[1];
app.roomTitleChanged(this);
app.topbar.updateTabbar();
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 ':':
this.timeOffset = ~~(Date.now() / 1000) - (parseInt(row[1], 10) || 0);
break;
case 'c:':
if (/[a-zA-Z0-9]/.test(row[2].charAt(0))) row[2] = ' ' + row[2];
var msgTime = this.timeOffset + (parseInt(row[1], 10) || 0);
this.addChat(row[2], row.slice(3).join('|'), false, msgTime);
break;
case 'tc':
if (/[a-zA-Z0-9]/.test(row[2].charAt(0))) row[2] = ' ' + row[2];
var msgTime = row[1] ? ~~(Date.now() / 1000) - (parseInt(row[1], 10) || 0) : 0;
this.addChat(row[2], row.slice(3).join('|'), false, msgTime);
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 = BattleLog.escapeFormat(matches[1]);
if (silent && !Dex.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 '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 'uhtml':
case 'uhtmlchange':
var $elements = this.$chat.find('div.uhtml-' + toID(row[1]));
var html = row.slice(2).join('|');
if (!html) {
$elements.remove();
} else if (!$elements.length) {
if (row[0] === 'uhtmlchange') {
this.$chat.prepend('
');
}
break;
case 'unlink':
// |unlink| is deprecated in favor of |hidelines|
// note: this message has global effects, but it's handled here
// so that it can be included in the scrollback buffer.
if (Dex.prefs('nounlink')) return;
var user = toID(row[2]) || toID(row[1]);
var $messages = $('.chatmessage-' + user);
if (!$messages.length) break;
$messages.find('a').contents().unwrap();
if (row[2]) {
// there used to be a condition for
// row[1] === 'roomhide'
// but it's now always applied
$messages = this.$chat.find('.chatmessage-' + user);
if (!$messages.length) break;
var lineCount = parseInt(row[3], 10) || 0;
if (lineCount) $messages = $messages.slice(-lineCount);
$messages.hide().addClass('revealed').find('button').parent().remove();
this.$chat.children().last().append(' ');
}
break;
case 'hidelines':
if (Dex.prefs('nounlink')) return;
var user = toID(row[2]);
var $messages = $('.chatmessage-' + user);
if (!$messages.length) break;
$messages.find('a').contents().unwrap();
if (row[1] !== 'unlink') {
$messages = this.$chat.find('.chatmessage-' + user);
if (!$messages.length) break;
var lineCount = parseInt(row[3], 10) || 0;
if (lineCount) $messages = $messages.slice(-lineCount);
$messages.hide().addClass('revealed').find('button').parent().remove();
var staffGroups = Object.keys(Config.groups).filter(function (group) {
return ['staff', 'leadership'].includes(Config.groups[group].type);
});
if (row[1] === 'hide' || staffGroups.includes(this.getUserGroup(app.user.get('userid')))) {
this.$chat.children().last().append(' ');
}
}
break;
case 'tournament':
case 'tournaments':
if (Dex.prefs('tournaments') === 'hide') {
if (row[1] === 'create') {
this.$chat.append('
' + BattleLog.escapeFormat(row[2]) + ' ' + BattleLog.escapeHTML(row[3]) + ' tournament created (and hidden because you have tournaments disabled).
');
} else if (row[1] === 'start') {
this.$chat.append('
Tournament started.
');
} else if (row[1] === 'forceend') {
this.$chat.append('
Tournament force-ended.
');
} else if (row[1] === 'end') {
this.$chat.append('
Tournament ended.
');
}
break;
}
if (!this.tournamentBox) this.tournamentBox = new TournamentBox(this, this.$tournamentWrapper);
if (!this.tournamentBox.parseMessage(row.slice(1), row[0] === 'tournaments')) break;
// fallthrough in case of unparsed message
case '':
this.$chat.append('
');
break;
}
}
},
toggleMessages: function (user, button) {
var $messages = this.$('.chatmessage-' + user + '.revealed');
var $button = $(button);
if (!$messages.is(':hidden')) {
$messages.hide();
$button.html('(' + ($messages.length) + ' line' + ($messages.length !== 1 ? 's' : '') + ' from ' + user + ' hidden)');
} else {
$button.html('(Hide ' + ($messages.length) + ' line' + ($messages.length !== 1 ? 's' : '') + ' from ' + user + ')');
$messages.show();
}
},
tournamentButton: function (val, button) {
if (this.tournamentBox) this.tournamentBox[$(button).data('type')](val, button);
},
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; i < len; i++) {
if (users[i]) {
var user = BattleTextParser.parseNameParts(users[i]);
this.users[toUserid(user.name)] = user;
}
}
} else {
this.userCount.users = parseInt(userList, 10);
this.userCount.guests = this.userCount.users;
}
this.userList.construct();
},
addJoinLeave: function (action, name, oldid, silent) {
if (!action) {
this.$joinLeave = null;
this.joinLeave = {
'join': [],
'leave': []
};
return;
}
var user = BattleTextParser.parseNameParts(name);
var userid = toUserid(user.name);
if (action === 'join') {
if (oldid) delete this.users[toUserid(oldid)];
if (!this.users[userid]) this.userCount.users++;
this.users[userid] = user;
this.userList.add(userid);
this.userList.updateUserCount();
this.userList.updateNoUsersOnline();
} else if (action === 'leave') {
if (this.users[userid]) this.userCount.users--;
delete this.users[userid];
this.userList.remove(userid);
this.userList.updateUserCount();
this.userList.updateNoUsersOnline();
} else if (action === 'rename') {
if (oldid) delete this.users[toUserid(oldid)];
this.users[userid] = user;
this.userList.remove(oldid);
this.userList.add(userid);
return;
}
var allShowjoins = Dex.prefs('showjoins') || {};
var showjoins = allShowjoins[Config.server.id];
if (silent && (!showjoins || (!showjoins['global'] && !showjoins[this.id]) || showjoins[this.id] === 0)) {
return;
}
if (!this.$joinLeave) {
this.$chat.append('
Loading...
');
this.$joinLeave = this.$chat.children().last();
}
var formattedUser = user.group + user.name;
if (action === 'join' && this.joinLeave['leave'].includes(formattedUser)) {
this.joinLeave['leave'].splice(this.joinLeave['leave'].indexOf(formattedUser), 1);
} else {
this.joinLeave[action].push(formattedUser);
}
var message = '';
if (this.joinLeave['join'].length) {
message += this.displayJoinLeaves(this.joinLeave['join'], 'joined');
}
if (this.joinLeave['leave'].length) {
if (this.joinLeave['join'].length) message += '; ';
message += this.displayJoinLeaves(this.joinLeave['leave'], 'left') + ' ';
}
this.$joinLeave.html('' + message + '');
},
displayJoinLeaves: function (preList, action) {
var message = '';
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 += BattleLog.escapeHTML(list[j]);
}
return message + ' ' + action;
},
addChat: function (name, message, pm, msgTime) {
var userid = toUserid(name);
var speakerHasAuth = !" +\u2606".includes(name.charAt(0));
var user = (this.users && this.users[app.user.get('userid')]) || {};
var readerHasAuth = !" +\u2606\u203D\u2716!".includes(user.group || ' ');
if (app.ignore[userid] && !speakerHasAuth && !readerHasAuth) {
if (!app.ignoreNotified) {
this.$chat.append(
'
A message from ' + BattleLog.escapeHTML(name) + ' was ignored. (to unignore use /unignore)
'
);
app.ignoreNotified = true;
}
return;
}
// Add this user to the list of people who have spoken recently.
this.markUserActive(userid);
this.$joinLeave = null;
this.joinLeave = {
'join': [],
'leave': []
};
if (pm) {
var pmuserid = toUserid(pm);
var oName = pmuserid === app.user.get('userid') ? name : pm;
var clickableName = '' + BattleLog.escapeHTML(name.substr(1)) + '';
this.$chat.append(
'
'
);
return; // PMs independently notify in the main menu; no need to make them notify again with `inchatpm`.
}
var lastMessageDates = Dex.prefs('logtimes') || (Storage.prefs('logtimes', {}), Dex.prefs('logtimes'));
if (!lastMessageDates[Config.server.id]) lastMessageDates[Config.server.id] = {};
var lastMessageDate = lastMessageDates[Config.server.id][this.id] || 0;
// because the time offset to the server can vary slightly, subtract it to not have it affect comparisons between dates
var serverMsgTime = msgTime - (this.timeOffset || 0);
var mayNotify = serverMsgTime > lastMessageDate && userid !== app.user.get('userid');
if (app.focused && (this === app.curSideRoom || this === app.curRoom)) {
this.lastMessageDate = 0;
lastMessageDates[Config.server.id][this.id] = serverMsgTime;
Storage.prefs.save();
} else {
// To be saved on focus
this.lastMessageDate = Math.max(this.lastMessageDate || 0, serverMsgTime);
}
var isHighlighted = userid !== app.user.get('userid') && this.getHighlight(message);
var parsedMessage = MainMenuRoom.parseChatMessage(message, name, ChatRoom.getTimestamp('chat', msgTime), isHighlighted, this.$chat, true);
if (typeof parsedMessage.challenge === 'string') {
this.$chat.append('
The server sent a challenge but this isn\'t a PM window!
');
return;
}
if (typeof parsedMessage === 'object' && 'noNotify' in parsedMessage) {
mayNotify = mayNotify && !parsedMessage.noNotify;
parsedMessage = parsedMessage.message;
}
if (!$.isArray(parsedMessage)) parsedMessage = [parsedMessage];
for (var i = 0; i < parsedMessage.length; i++) {
if (!parsedMessage[i]) continue;
this.$chat.append(parsedMessage[i]);
}
if (mayNotify && isHighlighted) {
app.playNotificationSound();
var $lastMessage = this.$chat.children().last();
var notifyTitle = "Mentioned by " + name + (this.id === 'lobby' ? '' : " in " + this.title);
var notifyText = $lastMessage.html().indexOf('') >= 0 ? '(spoiler)' : $lastMessage.children().last().text();
this.notifyOnce(notifyTitle, "\"" + notifyText + "\"", 'highlight');
} else if (mayNotify && this.id.substr(0, 5) === 'help-') {
this.notifyOnce("Help message from " + name, "\"" + message + "\"", 'pm');
} else if (mayNotify && name !== '~') { // |c:|~| prefixes a system message
this.subtleNotifyOnce();
}
if (message.slice(0, 4) === '/me ' || message.slice(0, 5) === '/mee') {
Storage.logChat(this.id, '* ' + name + (message.slice(0, 4) === '/me ' ? ' ' : '') + message);
} else if (message.slice(0, 5) === '/log ') {
Storage.logChat(this.id, '' + message.slice(5));
} else {
Storage.logChat(this.id, '' + name + ': ' + message);
}
},
destroy: function (alreadyLeft) {
if (this.tournamentBox) {
app.user.off('saveteams', this.tournamentBox.updateTeams, this.tournamentBox);
}
ConsoleRoom.prototype.destroy.call(this, alreadyLeft);
}
}, {
getTimestamp: function (section, msgTime) {
var pref = Dex.prefs('timestamps') || {};
var sectionPref = ((section === 'pms') ? pref.pms : pref.lobby) || 'off';
if ((sectionPref === 'off') || (sectionPref === undefined)) return '';
var date = (msgTime && !isNaN(msgTime) ? new Date(msgTime * 1000) : new Date());
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;
},
events: {
'click .userlist-count': 'toggleUserlist'
},
construct: function () {
var plural = this.room.userCount.users === 1 ? ' user' : ' users';
var buf = '';
var usersString = "" + (this.room.userCount.users || '0') + plural;
buf += '
';
buf += '' + usersString + '
';
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; i < users.length; i++) {
var userid = users[i];
buf += this.constructItem(userid);
}
if (!users.length) {
buf += this.getNoNamedUsersOnline();
}
if (this.room.userCount.guests) {
buf += '
';
}
this.$el.html(buf);
},
toggleUserlist: function (e) {
e.preventDefault();
e.stopPropagation();
if (this.$el.hasClass('userlist-minimized')) {
this.$el.removeClass('userlist-minimized');
this.$el.addClass('userlist-maximized');
} else if (this.$el.hasClass('userlist-maximized')) {
this.$el.removeClass('userlist-maximized');
this.$el.addClass('userlist-minimized');
}
},
show: function () {
this.$el.removeClass('userlist-minimized');
this.$el.removeClass('userlist-maximized');
},
hide: function () {
this.$el.scrollTop(0);
this.$el.removeClass('userlist-maximized');
this.$el.addClass('userlist-minimized');
},
updateUserCount: function () {
var users = Math.max(this.room.userCount.users || 0, this.room.userCount.globalUsers || 0);
$('#' + this.room.id + '-usercount-users').html('' + users + (users === 1 ? ' user' : ' users'));
},
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();
},
constructItem: function (userid) {
var user = 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 aUser = this.room.users[a] || { group: Config.defaultGroup, away: false };
var bUser = this.room.users[b] || { group: Config.defaultGroup, away: false };
var aRank = (
Config.groups[aUser.group || ' '] ||
{ order: (Config.defaultOrder || 10006.5) }
).order;
var bRank = (
Config.groups[bUser.group || ' '] ||
{ order: (Config.defaultOrder || 10006.5) }
).order;
if (aRank !== bRank) return aRank - bRank;
if ((aUser.away ? 1 : 0) !== (bUser.away ? 1 : 0)) return (aUser.away ? 1 : 0) - (bUser.away ? 1 : 0);
return (a > b ? 1 : -1);
},
getNoNamedUsersOnline: function () {
return '
Only guests
';
},
updateNoUsersOnline: function () {
var elem = $('#' + this.room.id + '-userlist-empty');
if ($("[id^=" + this.room.id + "-userlist-user-]").length === 0) {
if (elem.length === 0) {
var guests = $('#' + this.room.id + '-userlist-guests');
if (guests.length === 0) {
this.$el.append($(this.getNoNamedUsersOnline()));
} else {
guests.before($(this.getNoNamedUsersOnline()));
}
}
} else {
elem.remove();
}
}
});
}).call(this, jQuery);
function ChatHistory() {
this.lines = [];
this.index = 0;
}
ChatHistory.prototype.push = function (line) {
var duplicate = this.lines.indexOf(line);
if (duplicate >= 0) this.lines.splice(duplicate, 1);
if (this.lines.length > 100) this.lines.splice(0, 20);
this.lines.push(line);
this.index = this.lines.length;
};
ChatHistory.prototype.up = function (line) { // Ensure index !== 0 first!
if (line !== '') this.lines[this.index] = line;
return this.lines[--this.index];
};
ChatHistory.prototype.down = function (line) {
if (line !== '') this.lines[this.index] = line;
if (this.index === this.lines.length) return '';
if (++this.index === this.lines.length) return '';
return this.lines[this.index];
};