1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-07-25 03:58:30 +00:00
FrankerFaceZ/src/ember/room.js

1202 lines
No EOL
32 KiB
JavaScript

var FFZ = window.FrankerFaceZ,
CSS = /\.([\w\-_]+)\s*?\{content:\s*?"([^"]+)";\s*?background-image:\s*?url\("([^"]+)"\);\s*?height:\s*?(\d+)px;\s*?width:\s*?(\d+)px;\s*?margin:([^;}]+);?([^}]*)\}/mg,
MOD_CSS = /[^\n}]*\.badges\s+\.moderator\s*{\s*background-image:\s*url\(\s*['"]([^'"]+)['"][^}]+(?:}|$)/,
GROUP_CHAT = /^_([^_]+)_\d+$/,
constants = require('../constants'),
utils = require('../utils'),
moderator_css = function(room) {
if ( ! room.moderator_badge )
return "";
return '.chat-line[data-room="' + room.id + '"] .badges .moderator:not(.ffz-badge-replacement) { background-image:url("' + room.moderator_badge + '") !important; }';
}
// --------------------
// Initialization
// --------------------
FFZ.prototype.setup_room = function() {
this.rooms = {};
this.log("Creating room style element.");
var s = this._room_style = document.createElement("style");
s.id = "ffz-room-css";
document.head.appendChild(s);
this.log("Hooking the Ember Room controller.");
// Responsive ban button.
var f = this,
RC = App.__container__.lookup('controller:room');
if ( RC ) {
var orig_ban = RC._actions.banUser,
orig_to = RC._actions.timeoutUser;
RC._actions.banUser = function(e) {
orig_ban.bind(this)(e);
this.get("model").clearMessages(e.user);
}
RC._actions.timeoutUser = function(e) {
orig_to.bind(this)(e);
this.get("model").clearMessages(e.user);
}
RC._actions.purgeUser = function(e) {
this.get("model.tmiRoom").sendMessage("/timeout " + e.user + " 1");
this.get("model").clearMessages(e.user);
}
}
this.log("Hooking the Ember Room model.");
var Room = App.__container__.resolve('model:room');
this._modify_room(Room);
// Modify all current instances of Room, as the changes to the base
// class won't be inherited automatically.
var instances = Room.instances;
for(var key in instances) {
if ( ! instances.hasOwnProperty(key) )
continue;
var inst = instances[key];
this.add_room(inst.id, inst);
this._modify_room(inst);
inst.ffzPatchTMI();
}
this.log("Hooking the Ember Room view.");
var RoomView = App.__container__.resolve('view:room');
this._modify_rview(RoomView);
// For some reason, this doesn't work unless we create an instance of the
// room view and then destroy it immediately.
try {
RoomView.create().destroy();
} catch(err) { }
// Modify all existing Room views.
for(var key in Ember.View.views) {
if ( ! Ember.View.views.hasOwnProperty(key) )
continue;
var view = Ember.View.views[key];
if ( !(view instanceof RoomView) )
continue;
this.log("Manually updating existing Room view.", view);
try {
view.ffzInit();
} catch(err) {
this.error("RoomView setup ffzInit: " + err);
}
}
}
// --------------------
// View Customization
// --------------------
FFZ.prototype._modify_rview = function(view) {
var f = this;
view.reopen({
didInsertElement: function() {
this._super();
try {
this.ffzInit();
} catch(err) {
f.error("RoomView didInsertElement: " + err);
}
},
willClearRender: function() {
try {
this.ffzTeardown();
} catch(err) {
f.error("RoomView willClearRender: " + err);
}
this._super();
},
ffzInit: function() {
f._roomv = this;
this.ffz_frozen = false;
if ( f.settings.chat_hover_pause )
this.ffzEnableFreeze();
if ( f.settings.room_status )
this.ffzUpdateStatus();
var controller = this.get('controller');
if ( controller ) {
controller.reopen({
submitButtonText: function() {
if ( this.get("model.isWhisperMessage") && this.get("model.isWhispersEnabled") )
return i18n("Whisper");
var wait = this.get("model.slowWait"),
msg = this.get("model.messageToSend") || "";
if ( (msg.charAt(0) === "/" && msg.substr(0, 4) !== "/me ") || !wait || !f.settings.room_status )
return i18n("Chat");
return utils.time_to_string(wait, false, false, true);
}.property("model.isWhisperMessage", "model.isWhispersEnabled", "model.slowWait")
});
Ember.propertyDidChange(controller, 'submitButtonText');
}
},
ffzTeardown: function() {
if ( f._roomv === this )
f._roomv = undefined;
this.ffzDisableFreeze();
},
ffzUpdateStatus: function() {
var room = this.get('controller.model'),
el = this.get('element'),
cont = el && el.querySelector('.chat-buttons-container');
if ( ! cont )
return f.log("no container");
var r9k_badge = cont.querySelector('#ffz-stat-r9k'),
sub_badge = cont.querySelector('#ffz-stat-sub'),
slow_badge = cont.querySelector('#ffz-stat-slow'),
banned_badge = cont.querySelector('#ffz-stat-banned'),
btn = cont.querySelector('button');
if ( f.has_bttv || ! f.settings.room_status ) {
if ( r9k_badge )
r9k_badge.parentElement.removeChild(r9k_badge);
if ( sub_badge )
sub_badge.parentElement.removeChild(sub_badge);
if ( slow_badge )
slow_badge.parentElement.removeChild(slow_badge);
if ( btn )
btn.classList.remove('ffz-waiting');
return;
}
if ( ! r9k_badge ) {
r9k_badge = document.createElement('span');
r9k_badge.className = 'ffz room-state stat float-right';
r9k_badge.id = 'ffz-stat-r9k';
r9k_badge.innerHTML = 'R9K';
r9k_badge.title = "This room is in R9K-mode.";
cont.appendChild(r9k_badge);
jQuery(r9k_badge).tipsy({gravity:"s", offset:15});
}
if ( ! sub_badge ) {
sub_badge = document.createElement('span');
sub_badge.className = 'ffz room-state stat float-right';
sub_badge.id = 'ffz-stat-sub';
sub_badge.innerHTML = 'SUB';
sub_badge.title = "This room is in subscribers-only mode.";
cont.appendChild(sub_badge);
jQuery(sub_badge).tipsy({gravity:"s", offset:15});
}
if ( ! slow_badge ) {
slow_badge = document.createElement('span');
slow_badge.className = 'ffz room-state stat float-right';
slow_badge.id = 'ffz-stat-slow';
slow_badge.innerHTML = 'SLOW';
slow_badge.title = "This room is in slow mode. You may send messages every 120 seconds.";
cont.appendChild(slow_badge);
jQuery(slow_badge).tipsy({gravity:"s", offset:15});
}
if ( ! banned_badge ) {
banned_badge = document.createElement('span');
banned_badge.className = 'ffz room-state stat float-right';
banned_badge.id = 'ffz-stat-banned';
banned_badge.innerHTML = 'BAN';
banned_badge.title = "You have been banned from talking in this room.";
cont.appendChild(banned_badge);
jQuery(banned_badge).tipsy({gravity:"s", offset:15});
}
r9k_badge.classList.toggle('hidden', !(room && room.get('r9kMode')));
sub_badge.classList.toggle('hidden', !(room && room.get('subsOnlyMode')));
slow_badge.classList.toggle('hidden', !(room && room.get('slowMode')));
slow_badge.title = "This room is in slow mode. You may send messages every " + utils.number_commas(room && room.get('slowValue')||120) + " seconds.";
banned_badge.classList.toggle('hidden', !(room && room.get('ffz_banned')));
if ( btn ) {
btn.classList.toggle('ffz-waiting', (room && room.get('slowWait') || 0));
btn.classList.toggle('ffz-banned', (room && room.get('ffz_banned')));
}
}.observes('controller.model'),
ffzEnableFreeze: function() {
var el = this.get('element'),
messages = el.querySelector('.chat-messages');
if ( ! messages )
return;
this._ffz_interval = setInterval(this.ffzPulse.bind(this), 200);
this._ffz_messages = messages;
this._ffz_mouse_move = this.ffzMouseMove.bind(this);
this._ffz_mouse_out = this.ffzMouseOut.bind(this);
messages.addEventListener('mousemove', this._ffz_mouse_move);
messages.addEventListener('mouseout', this._ffz_mouse_out);
document.addEventListener('mouseout', this._ffz_mouse_out);
},
ffzDisableFreeze: function() {
if ( this._ffz_interval ) {
clearInterval(this._ffz_interval);
this._ffz_interval = undefined;
}
this.ffzUnfreeze();
var messages = this._ffz_messages;
if ( ! messages )
return;
this._ffz_messages = undefined;
if ( this._ffz_mouse_move ) {
messages.removeEventListener('mousemove', this._ffz_mouse_move);
this._ffz_mouse_move = undefined;
}
if ( this._ffz_mouse_out ) {
messages.removeEventListener('mouseout', this._ffz_mouse_out);
this._ffz_mouse_out = undefined;
}
},
ffzPulse: function() {
if ( this.ffz_frozen ) {
var elapsed = Date.now() - this._ffz_last_move;
if ( elapsed > 750 )
this.ffzUnfreeze();
}
},
ffzUnfreeze: function() {
this.ffz_frozen = false;
this._ffz_last_move = 0;
this.ffzUnwarnPaused();
if ( this.get('stuckToBottom') )
this._scrollToBottom();
},
ffzMouseOut: function(event) {
this._ffz_outside = true;
var e = this;
setTimeout(function() {
if ( e._ffz_outside )
e.ffzUnfreeze();
}, 25);
},
ffzMouseMove: function(event) {
this._ffz_last_move = Date.now();
this._ffz_outside = false;
if ( event.screenX === this._ffz_last_screenx && event.screenY === this._ffz_last_screeny )
return;
this._ffz_last_screenx = event.screenX;
this._ffz_last_screeny = event.screenY;
if ( this.ffz_frozen )
return;
// Don't do it if we're over the bar itself.
if ( event.clientY >= (this._ffz_messages.getBoundingClientRect().bottom - 21) )
return;
this.ffz_frozen = true;
if ( this.get('stuckToBottom') ) {
this.set('controller.model.messageBufferSize', f.settings.scrollback_length + 150);
this.ffzWarnPaused();
}
},
_scrollToBottom: _.throttle(function() {
var e = this,
s = this._$chatMessagesScroller;
Ember.run.next(function() {
setTimeout(function() {
!e.ffz_frozen && s && s.length && (s.scrollTop(s[0].scrollHeight), e._setStuckToBottom(!0));
})
})
}, 200),
_setStuckToBottom: function(val) {
this.set("stuckToBottom", val);
this.get("controller.model") && this.set("controller.model.messageBufferSize", f.settings.scrollback_length + (val ? 0 : 150));
},
// Warnings~!
ffzWarnPaused: function() {
var el = this.get('element'),
warning = el && el.querySelector('.chat-interface .more-messages-indicator.ffz-freeze-indicator');
if ( ! el )
return;
if ( ! warning ) {
warning = document.createElement('div');
warning.className = 'more-messages-indicator ffz-freeze-indicator';
warning.innerHTML = '(Chat Paused Due to Mouse Movement)';
var cont = el.querySelector('.chat-interface');
if ( ! cont )
return;
cont.insertBefore(warning, cont.childNodes[0])
}
warning.classList.remove('hidden');
},
ffzUnwarnPaused: function() {
var el = this.get('element'),
warning = el && el.querySelector('.chat-interface .more-messages-indicator.ffz-freeze-indicator');
if ( warning )
warning.classList.add('hidden');
}
});
}
// --------------------
// Command System
// --------------------
FFZ.chat_commands = {};
FFZ.ffz_commands = {};
FFZ.prototype.room_message = function(room, text) {
var lines = text.split("\n");
if ( this.has_bttv ) {
for(var i=0; i < lines.length; i++)
BetterTTV.chat.handlers.onPrivmsg(room.id, {style: 'admin', date: new Date(), from: 'jtv', message: lines[i]});
} else {
for(var i=0; i < lines.length; i++)
room.room.addMessage({style: 'ffz admin', date: new Date(), from: 'FFZ', message: lines[i]});
}
}
FFZ.prototype.run_command = function(text, room_id) {
var room = this.rooms[room_id];
if ( ! room || ! room.room )
return false;
if ( ! text )
return;
var args = text.split(" "),
cmd = args.shift().substr(1).toLowerCase(),
command = FFZ.chat_commands[cmd],
output;
if ( ! command )
return false;
if ( command.hasOwnProperty('enabled') ) {
var val = command.enabled;
if ( typeof val == "function" ) {
try {
val = command.enabled.bind(this)(room, args);
} catch(err) {
this.error('command "' + cmd + '" enabled: ' + err);
val = false;
}
}
if ( ! val )
return false;
}
this.log("Received Command: " + cmd, args, true);
try {
output = command.bind(this)(room, args);
} catch(err) {
this.error('command "' + cmd + '" runner: ' + err);
output = "There was an error running the command.";
}
if ( output )
this.room_message(room, output);
return true;
}
FFZ.prototype.run_ffz_command = function(text, room_id) {
var room = this.rooms[room_id];
if ( ! room || !room.room )
return;
if ( ! text ) {
// Try to pop-up the menu.
var link = document.querySelector('a.ffz-ui-toggle');
if ( link )
return link.click();
text = "help";
}
var args = text.split(" "),
cmd = args.shift().toLowerCase();
this.log("Received Command: " + cmd, args, true);
var command = FFZ.ffz_commands[cmd], output;
if ( command ) {
try {
output = command.bind(this)(room, args);
} catch(err) {
this.log("Error Running Command - " + cmd + ": " + err, room);
output = "There was an error running the command.";
}
} else
output = 'There is no "' + cmd + '" command.';
if ( output )
this.room_message(room, output);
}
FFZ.ffz_commands.help = function(room, args) {
if ( args && args.length ) {
var command = FFZ.ffz_commands[args[0].toLowerCase()];
if ( ! command )
return 'There is no "' + args[0] + '" command.';
else if ( ! command.help )
return 'No help is available for the command "' + args[0] + '".';
else
return command.help;
}
var cmds = [];
for(var c in FFZ.ffz_commands)
FFZ.ffz_commands.hasOwnProperty(c) && cmds.push(c);
return "The available commands are: " + cmds.join(", ");
}
FFZ.ffz_commands.help.help = "Usage: /ffz help [command]\nList available commands, or show help for a specific command.";
// --------------------
// Room Management
// --------------------
FFZ.prototype.add_room = function(id, room) {
if ( this.rooms[id] )
return this.log("Tried to add existing room: " + id);
this.log("Adding Room: " + id);
// Create a basic data table for this room.
var data = this.rooms[id] = {id: id, room: room, menu_sets: [], sets: [], css: null, needs_history: false};
if ( this.follow_sets && this.follow_sets[id] ) {
data.extra_sets = this.follow_sets[id];
delete this.follow_sets[id];
for(var i=0; i < data.extra_sets.length; i++) {
var sid = data.extra_sets[i],
set = this.emote_sets && this.emote_sets[sid];
if ( set ) {
if ( set.users.indexOf(id) === -1 )
set.users.push(id);
continue;
}
this.load_set(sid, function(success, data) {
if ( success )
data.users.push(id);
});
}
}
// Let the server know where we are.
this.ws_send("sub", id);
// See if we need history?
if ( ! this.has_bttv && this.settings.chat_history && room && (room.get('messages.length') || 0) < 10 ) {
if ( ! this.ws_send("chat_history", [id,25], this._load_history.bind(this, id)) )
data.needs_history = true;
}
// Why don't we set the scrollback length, too?
room.set('messageBufferSize', this.settings.scrollback_length + ((this._roomv && !this._roomv.get('stuckToBottom') && this._roomv.get('controller.model.id') === id) ? 150 : 0));
// For now, we use the legacy function to grab the .css file.
this.load_room(id);
}
FFZ.prototype.remove_room = function(id) {
var room = this.rooms[id];
if ( ! room )
return;
this.log("Removing Room: " + id);
// Remove the CSS
if ( room.css || room.moderator_badge )
utils.update_css(this._room_style, id, null);
// Let the server know we're gone and delete our data for this room.
this.ws_send("unsub", id);
delete this.rooms[id];
// Clean up sets we aren't using any longer.
if ( id.charAt(0) === "_" )
return;
var set = this.emote_sets[room.set];
if ( set ) {
set.users.removeObject(id);
if ( ! this.global_sets.contains(room.set) && ! set.users.length )
this.unload_set(room.set);
}
}
// --------------------
// Chat History
// --------------------
FFZ.prototype._load_history = function(room_id, success, data) {
var room = this.rooms[room_id];
if ( ! room || ! room.room )
return;
if ( success )
this.log("Received " + data.length + " old messages for: " + room_id);
else
return this.log("Error retrieving chat history for: " + room_id);
if ( ! data.length )
return;
return this._insert_history(room_id, data);
}
FFZ.prototype._show_deleted = function(room_id) {
var room = this.rooms[room_id];
if ( ! room || ! room.room )
return;
var old_messages = room.room.get('messages.0.ffz_old_messages');
if ( ! old_messages || ! old_messages.length )
return;
room.room.set('messages.0.ffz_old_messages', undefined);
this._insert_history(room_id, old_messages);
}
FFZ.prototype._insert_history = function(room_id, data) {
var room = this.rooms[room_id];
if ( ! room || ! room.room )
return;
var r = room.room,
messages = r.get('messages'),
tmiSession = r.tmiSession || (TMI._sessions && TMI._sessions[0]),
tmiRoom = r.tmiRoom,
inserted = 0,
last_msg = data[data.length - 1],
now = new Date(),
last_date = typeof last_msg.date === "string" ? utils.parse_date(last_msg.date) : last_msg.date,
age = (now - last_date) / 1000,
is_old = age > 300,
i = data.length,
alternation = r.get('messages.0.ffz_alternate') || false;
if ( is_old )
alternation = ! alternation;
var i = data.length;
while(i--) {
var msg = data[i];
if ( typeof msg.date === "string" )
msg.date = utils.parse_date(msg.date);
msg.ffz_alternate = alternation = ! alternation;
if ( ! msg.room )
msg.room = room_id;
if ( ! msg.color )
msg.color = msg.tags && msg.tags.color ? msg.tags.color : tmiSession && msg.from ? tmiSession.getColor(msg.from.toLowerCase()) : "#755000";
if ( ! msg.labels || ! msg.labels.length ) {
var labels = msg.labels = [];
if ( msg.tags ) {
if ( msg.tags.turbo )
labels.push("turbo");
if ( msg.tags.subscriber )
labels.push("subscriber");
if ( msg.from === room_id )
labels.push("owner")
else {
var ut = msg.tags['user-type'];
if ( ut === 'mod' || ut === 'staff' || ut === 'admin' || ut === 'global_mod' )
labels.push(ut);
}
}
}
if ( ! msg.style ) {
if ( msg.from === "jtv" )
msg.style = "admin";
else if ( msg.from === "twitchnotify" )
msg.style = "notification";
}
if ( ! msg.cachedTokens || ! msg.cachedTokens.length )
this.tokenize_chat_line(msg, true);
if ( r.shouldShowMessage(msg) ) {
if ( messages.length < r.get("messageBufferSize") ) {
// One last thing! Make sure we don't have too many messages.
if ( msg.ffz_old_messages ) {
var max_msgs = r.get("messageBufferSize") - (messages.length + 1);
if ( msg.ffz_old_messages.length > max_msgs )
msg.ffz_old_messages = msg.ffz_old_messages.slice(msg.ffz_old_messages.length - max_msgs);
}
messages.unshiftObject(msg);
inserted += 1;
} else
break;
}
}
if ( is_old ) {
var msg = {
ffz_alternate: ! alternation,
color: "#755000",
date: new Date(),
from: "frankerfacez_admin",
style: "admin",
message: "(Last message is " + utils.human_time(age) + " old.)",
room: room_id
};
this.tokenize_chat_line(msg);
if ( r.shouldShowMessage(msg) ) {
messages.insertAt(inserted, msg);
while( messages.length > r.get('messageBufferSize') )
messages.removeAt(0);
}
}
}
// --------------------
// Receiving Set Info
// --------------------
FFZ.prototype.load_room = function(room_id, callback, tries) {
var f = this;
jQuery.getJSON(constants.API_SERVER + "v1/room/" + room_id)
.done(function(data) {
if ( data.sets ) {
for(var key in data.sets)
data.sets.hasOwnProperty(key) && f._load_set_json(key, undefined, data.sets[key]);
}
f._load_room_json(room_id, callback, data);
}).fail(function(data) {
if ( data.status == 404 )
return typeof callback == "function" && callback(false);
tries = (tries || 0) + 1;
if ( tries < 10 )
return f.load_room(room_id, callback, tries);
return typeof callback == "function" && callback(false);
});
}
FFZ.prototype._load_room_json = function(room_id, callback, data) {
if ( ! data || ! data.room )
return typeof callback == "function" && callback(false);
data = data.room;
// Preserve the pointer to the Room instance.
if ( this.rooms[room_id] )
data.room = this.rooms[room_id].room;
// Preserve everything else.
for(var key in this.rooms[room_id]) {
if ( key !== 'room' && this.rooms[room_id].hasOwnProperty(key) && ! data.hasOwnProperty(key) )
data[key] = this.rooms[room_id][key];
}
data.needs_history = this.rooms[room_id] && this.rooms[room_id].needs_history || false;
this.rooms[room_id] = data;
if ( data.css || data.moderator_badge )
utils.update_css(this._room_style, room_id, moderator_css(data) + (data.css||""));
if ( ! this.emote_sets.hasOwnProperty(data.set) )
this.load_set(data.set, function(success, set) {
if ( set.users.indexOf(room_id) === -1 )
set.users.push(room_id);
});
else if ( this.emote_sets[data.set].users.indexOf(room_id) === -1 )
this.emote_sets[data.set].users.push(room_id);
this.update_ui_link();
if ( callback )
callback(true, data);
}
// --------------------
// Ember Modifications
// --------------------
FFZ.prototype._modify_room = function(room) {
var f = this;
room.reopen({
subsOnlyMode: false,
r9kMode: false,
slowWaiting: false,
slowValue: 0,
mru_list: [],
updateWait: function(value, was_banned) {
var wait = this.get('slowWait') || 0;
this.set('slowWait', value);
if ( wait < 1 && value > 0 ) {
setTimeout(this.ffzUpdateWait.bind(this), 1000);
f._roomv && f._roomv.ffzUpdateStatus();
} else if ( (wait > 0 && value < 1) || was_banned ) {
this.set('ffz_banned', false);
f._roomv && f._roomv.ffzUpdateStatus();
}
},
ffzUpdateWait: function() {
var wait = this.get('slowWait') || 0;
if ( wait < 1 )
return;
this.set('slowWait', --wait);
if ( wait > 0 )
setTimeout(this.ffzUpdateWait.bind(this), 1000);
else {
this.set('ffz_banned', false);
f._roomv && f._roomv.ffzUpdateStatus();
}
},
ffzUpdateStatus: function() {
if ( f._roomv )
f._roomv.ffzUpdateStatus();
}.observes('r9kMode', 'subsOnlyMode', 'slowMode', 'slowValue', 'ffz_banned'),
// Track which rooms the user is currently in.
init: function() {
this._super();
try {
f.add_room(this.id, this);
this.set("ffz_chatters", {});
} catch(err) {
f.error("add_room: " + err);
}
},
willDestroy: function() {
this._super();
try {
f.remove_room(this.id);
} catch(err) {
f.error("remove_room: " + err);
}
},
clearMessages: function(user) {
var t = this;
if ( user ) {
this.get("messages").forEach(function(s, n) {
if ( s.from === user ) {
t.set("messages." + n + ".ffz_deleted", true);
if ( ! f.settings.prevent_clear )
t.set("messages." + n + ".deleted", true);
}
});
if ( f.settings.mod_card_history ) {
var room = f.rooms && f.rooms[t.get('id')],
user_history = room && room.user_history && room.user_history[user]
if ( user_history !== null && user_history !== undefined ) {
var has_delete = false,
last = user_history.length > 0 ? user_history[user_history.length-1] : null;
has_delete = last !== null && last.is_delete;
if ( ! has_delete ) {
user_history.push({from: 'jtv', is_delete: true, style: 'admin', cachedTokens: ['User has been timed out.'], date: new Date()});
while ( user_history.length > 20 )
user_history.shift();
}
}
}
} else {
if ( f.settings.prevent_clear )
this.addTmiMessage("A moderator's attempt to clear chat was ignored.");
else {
var msgs = t.get("messages");
t.set("messages", []);
t.addMessage({
style: 'admin',
message: i18n("Chat was cleared by a moderator"),
ffz_old_messages: msgs
});
}
}
},
pushMessage: function(msg) {
if ( this.shouldShowMessage(msg) ) {
var t, s, n, a = this.get("messageBufferSize");
for (this.get("messages").pushObject(msg), t = this.get("messages.length"), s = t - a, n = 0; s > n; n++)
this.get("messages").removeAt(0);
"admin" === msg.style || ("whisper" === msg.style && ! this.ffz_whisper_room ) || this.incrementProperty("unreadCount", 1);
}
},
addMessage: function(msg) {
try {
if ( msg ) {
var is_whisper = msg.style === 'whisper';
if ( f.settings.group_tabs && f.settings.whisper_room ) {
if ( ( is_whisper && ! this.ffz_whisper_room ) || ( ! is_whisper && this.ffz_whisper_room ) )
return;
}
if ( ! is_whisper )
msg.room = this.get('id');
f.tokenize_chat_line(msg);
// Keep the history.
if ( ! is_whisper && msg.from && msg.from !== 'jtv' && msg.from !== 'twitchnotify' && f.settings.mod_card_history ) {
var room = f.rooms && f.rooms[msg.room];
if ( room ) {
var chat_history = room.user_history = room.user_history || {},
user_history = room.user_history[msg.from] = room.user_history[msg.from] || [];
user_history.push({
from: msg.tags && msg.tags['display-name'] || msg.from,
cachedTokens: msg.cachedTokens,
style: msg.style,
date: msg.date
});
while ( user_history.length > 20 )
user_history.shift();
}
}
// Check for message from us.
if ( ! is_whisper ) {
var user = f.get_user();
if ( user && user.login === msg.from ) {
var was_banned = this.get('ffz_banned');
this.set('ffz_banned', false);
// Update the wait time.
if ( this.get('isModeratorOrHigher') || ! this.get('slowMode') )
this.updateWait(0, was_banned)
else if ( this.get('slowMode') )
this.updateWait(this.get('slowValue'));
}
}
}
} catch(err) {
f.error("Room addMessage: " + err);
}
return this._super(msg);
},
setHostMode: function(e) {
var Chat = App.__container__.lookup('controller:chat');
if ( ! Chat || Chat.get('currentChannelRoom') !== this )
return;
return this._super(e);
},
send: function(text) {
if ( f.settings.group_tabs && f.settings.whisper_room && this.ffz_whisper_room )
return;
try {
if ( text ) {
// Command History
var mru = this.get('mru_list'),
ind = mru.indexOf(text);
if ( ind !== -1 )
mru.splice(ind, 1)
else if ( mru.length > 20 )
mru.pop();
mru.unshift(text);
}
var cmd = text.split(' ', 1)[0].toLowerCase();
if ( cmd === "/ffz" ) {
this.set("messageToSend", "");
f.run_ffz_command(text.substr(5), this.get('id'));
return;
} else if ( cmd.charAt(0) === "/" && f.run_command(text, this.get('id')) ) {
this.set("messageToSend", "");
return;
}
} catch(err) {
f.error("send: " + err);
}
return this._super(text);
},
ffzUpdateUnread: function() {
if ( f.settings.group_tabs ) {
var Chat = App.__container__.lookup('controller:chat');
if ( Chat && Chat.get('currentRoom') === this )
this.resetUnreadCount();
else if ( f._chatv )
f._chatv.ffzTabUnread(this.get('id'));
}
}.observes('unreadCount'),
ffzInitChatterCount: function() {
if ( ! this.tmiRoom )
return;
var room = this;
this.tmiRoom.list().done(function(data) {
var chatters = {};
data = data.data.chatters;
for(var i=0; i < data.admins.length; i++)
chatters[data.admins[i]] = true;
for(var i=0; i < data.global_mods.length; i++)
chatters[data.global_mods[i]] = true;
for(var i=0; i < data.moderators.length; i++)
chatters[data.moderators[i]] = true;
for(var i=0; i < data.staff.length; i++)
chatters[data.staff[i]] = true;
for(var i=0; i < data.viewers.length; i++)
chatters[data.viewers[i]] = true;
room.set("ffz_chatters", chatters);
room.ffzUpdateChatters();
});
},
ffzUpdateChatters: function(add, remove) {
var chatters = this.get("ffz_chatters") || {};
if ( add )
chatters[add] = true;
if ( remove && chatters[remove] )
delete chatters[remove];
if ( ! f.settings.chatter_count )
return;
if ( f._cindex )
f._cindex.ffzUpdateChatters();
try {
if ( window.parent && window.parent.postMessage )
window.parent.postMessage({from_ffz: true, command: 'chatter_count', message: Object.keys(this.get('ffz_chatters') || {}).length}, "http://www.twitch.tv/");
} catch(err) { /* Ignore errors because of security */ }
},
ffzPatchTMI: function() {
if ( this.get('ffz_is_patched') || ! this.get('tmiRoom') )
return;
if ( f.settings.chatter_count )
this.ffzInitChatterCount();
var tmi = this.get('tmiRoom'),
room = this;
// Let's get chatter information!
// TODO: Remove this cause it's terrible.
var connection = tmi._roomConn._connection;
if ( ! connection.ffz_cap_patched ) {
connection.ffz_cap_patched = true;
connection._send("CAP REQ :twitch.tv/membership");
connection.on("opened", function() {
this._send("CAP REQ :twitch.tv/membership");
}, connection);
}
// NOTICE for catching slow-mode updates
tmi.on('notice', function(msg) {
if ( msg.msgId === 'msg_slowmode' ) {
var match = /in (\d+) seconds/.exec(msg.message);
if ( match ) {
room.updateWait(parseInt(match[1]));
}
}
if ( msg.msgId === 'msg_timedout' ) {
var match = /for (\d+) more seconds/.exec(msg.message);
if ( match ) {
room.set('ffz_banned', true);
room.updateWait(parseInt(match[1]));
}
}
if ( msg.msgId === 'msg_banned' ) {
room.set('ffz_banned', true);
f._roomv && f._roomv.ffzUpdateStatus();
}
});
// ROOMSTATE~!
if ( ! connection.ffz_roomstate_patched ) {
connection.ffz_roomstate_patched = true;
connection._socket.off('data', connection._onSocketDataReceived, connection);
connection._socket.on('data', function(data) {
try {
var msg = utils.splitIRCMessage(data.data);
if ( msg.command === 'ROOMSTATE' ) {
// We have ROOMSTATE! Now, let's parse it a bit
// more and send it on.
msg.tags = utils.parseIRCTags(msg.tags);
msg.target = msg.params && msg.params[0];
this._trigger('roomstate', msg);
return;
}
} catch(err) { f.error("Connection onData: " + err); }
return this._onSocketDataReceived(data);
}, connection);
}
// Glorious ROOMSTATE.
if ( ! tmi.ffz_roomstate_patched ) {
tmi.ffz_roomstate_patched = true;
tmi._roomConn.on("roomstate", function(ircMsg) {
if ( ircMsg.target !== this.ircChannel )
return;
this._trigger("roomstate", ircMsg.tags);
}, tmi);
}
// IT IS GLORIOUS!
tmi.on('roomstate', function(state) {
if ( state.hasOwnProperty('slow') ) {
room.set('slowMode', state.slow > 0);
room.set('slowValue', state.slow);
if ( ! room.get('slowMode') )
room.updateWait(0);
}
if ( state.hasOwnProperty('r9k') )
room.set('r9kMode', state.r9k);
if ( state.hasOwnProperty('subs-only') )
room.set('subsOnlyMode', state['subs-only']);
});
// Check this shit.
tmi._roomConn._connection.off("message", tmi._roomConn._onIrcMessage, tmi._roomConn);
tmi._roomConn._onIrcMessage = function(ircMsg) {
if ( ircMsg.target != this.ircChannel )
return;
switch ( ircMsg.command ) {
case "JOIN":
if ( this._session && this._session.nickname === ircMsg.sender ) {
this._onIrcJoin(ircMsg);
} else
f.settings.chatter_count && room.ffzUpdateChatters(ircMsg.sender);
break;
case "PART":
if ( this._session && this._session.nickname === ircMsg.sender ) {
this._resetActiveState();
this._connection._exitedRoomConn();
this._trigger("exited");
} else
f.settings.chatter_count && room.ffzUpdateChatters(null, ircMsg.sender);
break;
default:
break;
}
}
tmi._roomConn._connection.on("message", tmi._roomConn._onIrcMessage, tmi._roomConn);
this.set('ffz_is_patched', true);
}.observes('tmiRoom')
});
}