1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-06-27 21:05:53 +00:00

3.5.330. Refactor timeout and ban notices. Add the ability to click the name of banned users in timeout notices. Fix logviewer dynamic updates. Reformat logviewer ban notices to match the rest of FFZ. Fix a memory leak in room. Fix an infinite loop in notifications.

This commit is contained in:
SirStendec 2016-10-14 20:43:34 -04:00
parent 13efe21492
commit 36f0b33d04
13 changed files with 389 additions and 260 deletions

View file

@ -1,3 +1,11 @@
<div class="list-header">3.5.330 <time datetime="2016-10-14">(2016-10-14)</time></div>
<ul class="chat-menu-content menu-side-padding">
<li>Added: Click the user's name in timeout and ban notices to open their moderation card.</li>
<li>Changed: Refactor how timeout and ban notices are rendered.</li>
<li>Fixed: Gradual memory leak in chat keeping old message objects around.</li>
<li>Fixed: Attempting to display a notification after the user has specifically denied the permission would create an infinite loop.</li>
</ul>
<div class="list-header">3.5.329 <time datetime="2016-10-14">(2016-10-14)</time></div>
<ul class="chat-menu-content menu-side-padding">
<li>Fixed: Issue causing Ember to stop responding to observers regarding chat messages.</li>

View file

@ -102,7 +102,7 @@ FFZ.prototype.setup_channel = function() {
id = target && target.get('id'),
display_name = target && target.get('display_name');
if ( display_name )
if ( display_name && display_name !== 'jtv' )
FFZ.capitalization[name] = [display_name, Date.now()];
if ( f._chatv )

View file

@ -52,7 +52,7 @@ FFZ.prototype.setup_profile_following = function() {
if ( ! user || ! user.name )
continue;
if ( user.display_name )
if ( user.display_name && user.display_name !== 'jtv' )
FFZ.capitalization[user.name] = [user.display_name, now];
user_cache[user.name] = [

View file

@ -1163,7 +1163,13 @@ FFZ.prototype._modify_chat_subline = function(component) {
} else if ( f._click_emote(e.target, e) )
return;
else if ( e.target.classList.contains('from') || e.target.parentElement.classList.contains('from') ) {
else if ( cl.contains('ban-target') || cl.contains('from') || cl.contains('to') || e.target.parentElement.classList.contains('from') || e.target.parentElement.classList.contains('to') ) {
var target = cl.contains('ban-target') ?
e.target.getAttribute('data-user') :
(cl.contains('from') || e.target.parentElement.classList.contains('from')) ?
from :
this.get('msgObject.to');
var n = this.get('element'),
bounds = n && n.getBoundingClientRect() || document.body.getBoundingClientRect(),
x = 0, right;
@ -1176,26 +1182,10 @@ FFZ.prototype._modify_chat_subline = function(component) {
right: right,
top: bounds.top + bounds.height,
real_top: bounds.top,
sender: from
sender: target
});
} else if ( e.target.classList.contains('to') || e.target.parentElement.classList.contains('to') ) {
var n = this.get('element'),
bounds = n && n.getBoundingClientRect() || document.body.getBoundingClientRect(),
x = 0, right;
if ( bounds.left > 400 )
right = bounds.left - 40;
this.sendAction("showModOverlay", {
left: bounds.left,
right: right,
top: bounds.top + bounds.height,
real_top: bounds.top,
sender: this.get('msgObject.to')
});
} else if ( e.target.classList.contains('undelete') ) {
} else if ( cl.contains('undelete') ) {
e.preventDefault();
this.set("msgObject.deleted", false);
}

View file

@ -655,7 +655,7 @@ FFZ.prototype.modify_moderation_card = function(component) {
user_id = this.get('cardInfo.user.id');
if (( this._lv_sock_room && this._lv_sock_room !== room_id ) || (this._lv_sock_user && this._lv_sock_user !== user_id) ) {
f.lv_ws_unsub(this._lv_sock_room + '-' + this._lv_sock_user);
f.lv_ws_unsub('logs-' + this._lv_sock_room + '-' + this._lv_sock_user);
this._lv_sock_room = null;
this._lv_sock_user = null;
}
@ -738,7 +738,7 @@ FFZ.prototype.modify_moderation_card = function(component) {
t._lv_sock_room = room_id;
t._lv_sock_user = user_id;
f.lv_ws_sub(room_id + '-' + user_id);
f.lv_ws_sub('logs-' + room_id + '-' + user_id);
var requests = t._lv_log_requests;
t._lv_log_requests = null;
@ -785,13 +785,48 @@ FFZ.prototype.modify_moderation_card = function(component) {
// Remove the line itself.
line.parentElement.removeChild(line);
} else if ( cmd === "log-update" ) {
if ( ! this._lv_logs || ! this._lv_logs.data || data.nick !== this._lv_logs.data.user.nick )
return;
// Parse the message. Store the data.
var message = f.lv_parse_message(data),
msgs = this._lv_logs.data.before,
ind = -1,
i = msgs.length;
// Find the existing entry.
while(--i) {
var msg = msgs[i];
if ( msg.lv_id === message.lv_id ) {
ind = i;
break;
}
}
// Nothing to update, so don't.
if ( ind === -1 )
return;
msgs[ind] = message;
var el = this.get('element'),
container = el && el.querySelector('.ffz-tab-container'),
line = container && container.querySelector('.lv-history .chat-line[data-lv-id="' + message.lv_id + '"]');
if ( ! line )
return;
var new_line = f._build_mod_card_history(message, this, false,
FFZ.mod_card_pages.history.render_adjacent.bind(f, this, container, message));
line.parentElement.insertBefore(new_line, line);
line.parentElement.removeChild(line);
} else if ( cmd === "log-add" ) {
if ( ! this._lv_logs || ! this._lv_logs.data || data.nick !== this._lv_logs.data.user.nick )
return;
// Parse the message. Store the data.
var t,
message = f.lv_parse_message(data);
var message = f.lv_parse_message(data);
this._lv_logs.data.before.push(message);
this._lv_logs.data.user[message.is_ban ? 'timeouts' : 'messages'] += 1;
@ -819,7 +854,7 @@ FFZ.prototype.modify_moderation_card = function(component) {
}
history.appendChild(f._build_mod_card_history(message, this, false,
FFZ.mod_card_pages.history.render_adjacent.bind(f, t, container, message)));
FFZ.mod_card_pages.history.render_adjacent.bind(f, this, container, message)));
if ( was_at_bottom )
setTimeout(function() { history.scrollTop = history.scrollHeight; })
@ -852,7 +887,7 @@ FFZ.prototype.modify_moderation_card = function(component) {
f._mod_card = undefined;
if ( this._lv_sock_room && this._lv_sock_user ) {
f.lv_ws_unsub(this._lv_sock_room + '-' + this._lv_sock_user);
f.lv_ws_unsub('logs-' + this._lv_sock_room + '-' + this._lv_sock_user);
this._lv_sock_room = null;
this._lv_sock_user = null;
}

View file

@ -14,7 +14,8 @@ var FFZ = window.FrankerFaceZ,
'emoteonly': 'emote_only_on',
'emoteonlyoff': 'emote_only_off',
'host': 'host_on',
'unhost': 'host_off'
'unhost': 'host_off',
'clear': 'clear_chat'
},
STATUS_BADGES = [
@ -161,6 +162,31 @@ FFZ.prototype.setup_room = function() {
}
// --------------------
// Ban Message Formatting
// --------------------
FFZ.prototype.format_ban_notice = function(username, is_me, duration, count, reasons, moderators, notices) {
var name = this.format_display_name(FFZ.get_capitalization(username), username, true),
duration_tip = [];
for(var mod_id in notices) {
var notice = notices[mod_id];
if ( ! Array.isArray(notice) )
notice = [notice];
var nd = notice[0] === -Infinity ? 'unban' : isFinite(notice[0]) ? utils.duration_string(notice[0], true) : 'ban';
duration_tip.push(utils.sanitize(mod_id) + ' - ' + nd + (notice[1] ? ': ' + utils.sanitize(notice[1]) : ''));
}
return (is_me ? 'You have' : '<span data-user="' + utils.quote_san(username) + '" class="ban-target html-tooltip" title="' + utils.quote_attr(name[1] || '') + '">' + name[0] + '</span> has') +
' been ' + (duration_tip.length ? '<span class="ban-tip html-tooltip" title="' + utils.quote_attr(duration_tip.join('<br>')) + '">' : '') + (duration === -Infinity ? 'unbanned' :
(duration === 1 ? 'purged' : isFinite(duration) ? 'timed out for ' + utils.duration_string(duration) : 'banned')) +
(count > 1 ? ' (' + utils.number_commas(count) + ' times)' : '') +
(moderators && moderators.length ? ' by ' + utils.sanitize(moderators.join(', ')) : '') + (duration_tip.length ? '</span>' : '') +
(reasons && reasons.length ? ' with reason' + utils.pluralize(reasons.length) + ': ' + utils.sanitize(reasons.join(', ')) : '.');
}
// --------------------
// PubSub is fucking awful
// --------------------
@ -263,17 +289,11 @@ FFZ.prototype._modify_chat_pubsub = function(pubsub) {
return;
var ps = pubsub._pubsub(),
token = user.chat_oauth_token;
token = user.chat_oauth_token,
internal_topics = ps._client & ps._client._listens && ps._client._listens._events || {};
for(var i=0; i < pubsub.chatTopics.length; i++) {
var topic = pubsub.chatTopics[i];
// Try stupid stuff to remove duplicate events.
if ( topic.substr(0, 23) === 'chat_moderator_actions.' )
ps.Unlisten({
topic: topic.split('.', 2).join('.'),
success: function() {},
failure: function() {}
});
ps.Unlisten({
topic: topic,
@ -281,6 +301,14 @@ FFZ.prototype._modify_chat_pubsub = function(pubsub) {
failure: function(topic, t) { f.log("[PubSub] Failed to unlisten to topic: " + topic, t); }.bind(this, topic)
});
// Find the event and manually remove our listeners. We still want the topic so
// we don't clean it up too much, but we need to get rid of the existing bound
// functions to avoid anything screwing up.
var it = internal_topics[topic];
if ( it && it.length )
it.splice(0, it.length);
// Now, register our own event handler.
ps.Listen({
topic: topic,
auth: token,
@ -1306,29 +1334,173 @@ FFZ.prototype._modify_room = function(room) {
},
addLoginModerationMessage: function(event) {
// Throw out messages that are for other rooms.
// Throw out messages that are for other rooms or that don't have topics.
var room_id = '.' + this.get("roomProperties._id");
if ( event.topic && event.topic.substr(-room_id.length) !== room_id || event.created_by === this.get("session.userData.login") )
if ( ! event.topic || event.topic.substr(-room_id.length) !== room_id || event.created_by === this.get("session.userData.login") )
return;
// f.log("Login Moderation", event);
//f.log("Login Moderation for " + this.get('id') + ' [' + room_id + ']', event);
// In case we get unexpected input, do the other thing.
if ( ["ban", "unban", "timeout"].indexOf(event.moderation_action) === -1 )
return this._super(event);
var tags = {
'ban-duration': event.moderation_action === 'unban' ? -Infinity : event.args[1],
'ban-reason': event.args[2],
'ban-moderator': event.created_by
};
var msg_id,
reason = event.args[2],
duration = event.moderator_action === 'unban' ? -Infinity : event.args[1];
this.clearMessages(event.args[0].toLowerCase(), tags, false, event.moderation_action !== 'unban');
if ( typeof duration === "string" )
duration = parseInt(duration);
if ( isNaN(duration) )
duration = Infinity;
if ( reason ) {
var match = constants.UUID_TEST.exec(reason);
if ( match ) {
msg_id = match[1];
reason = reason.substr(0, reason.length - match[0].length);
if ( ! reason.length )
reason = null;
}
}
this.addBanNotice(event.args[0].toLowerCase(), duration, reason, event.created_by, msg_id, true);
},
clearMessages: function(user, tags, disable_log, report_only) {
var t = this;
addBanNotice: function(username, duration, reason, moderator, msg_id, report_only) {
var current_user = f.get_user(),
is_me = current_user && current_user.login === username,
show_notice = is_me || this.ffzShouldDisplayNotice(),
show_reason = is_me || this.get('isModeratorOrHigher'),
show_moderator = f.settings.get_twitch('showModerationActions'),
now = new Date,
room_id = this.get('id'),
ffz_room = f.rooms[room_id],
ban_history, last_ban;
// Find an existing ban to modify.
if ( ffz_room ) {
var ban_history = ffz_room.ban_history = ffz_room.ban_history || {};
last_ban = ban_history[username];
// Only overwrite bans in the last 15 seconds.
if ( ! last_ban || Math.abs(now - last_ban.date) > 15000 )
last_ban = null;
}
// If we have an existing ban, modify that.
if ( last_ban ) {
if ( reason && last_ban.reasons.indexOf(reason) === -1 )
last_ban.reasons.push(reason);
if ( moderator && last_ban.moderators.indexOf(moderator) === -1 )
last_ban.moderators.push(moderator);
if ( moderator )
last_ban.notices[moderator] = [duration, reason];
if ( ! report_only )
last_ban.count++;
// Don't update the displayed duration if the new end time is within five
// seconds to avoid changing messages when bots do multiple timeouts.
var end_time = now.getTime() + (duration * 1000);
if ( Math.abs(end_time - last_ban.end_time) > 5000 ) {
last_ban.duration = duration;
last_ban.end_time = end_time;
} else
duration = last_ban.duration;
last_ban.message = f.format_ban_notice(username, is_me, duration, last_ban.count, show_reason && last_ban.reasons, show_moderator && last_ban.moderators, show_moderator && last_ban.notices);
last_ban.cachedTokens = [{type: "raw", html: last_ban.message}];
if ( last_ban._line )
Ember.propertyDidChange(last_ban._line, 'ffzTokenizedMessage');
} else {
var notices = {};
if ( moderator )
notices[moderator] = [duration, reason];
var count = report_only ? 0 : 1,
msg = f.format_ban_notice(username, is_me, duration, count, show_reason && reason && [reason], show_moderator && moderator && [moderator], show_moderator && notices),
message = {
style: 'admin',
date: now,
room: room_id,
ffz_ban_target: username,
reasons: reason ? [reason] : [],
moderators: moderator ? [moderator] : [],
notices: notices,
duration: duration,
end_time: now.getTime() + (duration * 1000),
count: count,
message: msg,
cachedTokens: [{type: "raw", html: msg}]
};
if ( ban_history )
ban_history[username] = message;
if ( show_notice )
this.addMessage(message);
this.addUserHistory(message);
}
},
addUserHistory: function(message) {
var room_id = this.get('id'),
ffz_room = f.rooms[room_id];
if ( ! ffz_room || ! f.settings.mod_card_history )
return;
var username = message.ffz_ban_target || message.from,
historical = message.tags && message.tags.historical,
chat_history = ffz_room.user_history = ffz_room.user_history || {},
user_history = chat_history[username] = chat_history[username] || [];
if ( historical ) {
if ( user_history.length >= 20 )
return;
user_history.unshift(message);
} else {
user_history.push(message);
while ( user_history.length > 20 )
user_history.shift();
}
if ( f._mod_card && f._mod_card.ffz_room_id === room_id && f._mod_card.get('cardInfo.user.id') === username) {
var el = f._mod_card.get('element'),
history = el && el.querySelector('.chat-history.live-history');
if ( history ) {
var was_at_top = history.scrollTop >= (history.scrollHeight - history.clientHeight),
line = f._build_mod_card_history(message, f._mod_card);
if ( historical )
history.insertBefore(line, history.firstElementChild);
else
history.appendChild(line);
if ( was_at_top )
setTimeout(function() { history.scrollTop = history.scrollHeight });
if ( history.childElementCount > 20 )
history.removeChild(history.firstElementChild);
}
}
},
clearMessages: function(user, tags, disable_log) {
var t = this;
if ( user ) {
var duration = Infinity,
reason = undefined,
@ -1353,10 +1525,6 @@ FFZ.prototype._modify_room = function(room) {
if ( tags && tags['ban-moderator'] && (is_me || t.get('isModeratorOrHigher')) )
moderator = tags['ban-moderator'];
// Does anything really matter?
if ( ! report_only && duration !== -Infinity ) {
// Is there a UUID on the end of the ban reason?
if ( reason ) {
var match = constants.UUID_TEST.exec(reason);
@ -1368,19 +1536,18 @@ FFZ.prototype._modify_room = function(room) {
}
}
// If we were banned, set the state and update the UI.
if ( is_me ) {
t.set('ffz_banned', true);
if ( typeof duration === "number" && duration && isFinite(duration) && !isNaN(duration) )
t.updateWait(duration)
else if ( duration ) {
t.set('slowWait', 0);
f._roomv && f._roomv.ffzUpdateStatus();
}
if ( duration )
if ( isFinite(duration) )
t.updateWait(duration);
else {
t.set('slowWait', 0);
f._roomv && f._roomv.ffzUpdateStatus();
}
}
// Mark the user as recently banned.
if ( ! t.ffzRecentlyBanned )
t.ffzRecentlyBanned = [];
@ -1389,7 +1556,6 @@ FFZ.prototype._modify_room = function(room) {
while ( t.ffzRecentlyBanned.length > 100 )
t.ffzRecentlyBanned.shift();
// Are we deleting a specific message?
if ( msg_id && this.ffz_ids ) {
var msg = this.ffz_ids[msg_id];
@ -1479,143 +1645,27 @@ FFZ.prototype._modify_room = function(room) {
}
}
// End of report_only check.
}
// Now we need to see about displaying a ban notice.
if ( ! disable_log ) {
// Look up the user's last ban.
var show_notice = is_me || this.ffzShouldDisplayNotice(),
show_reason = is_me || this.get('isModeratorOrHigher'),
show_moderator = f.settings.get_twitch('showModerationActions'),
room = f.rooms && f.rooms[t.get('id')],
now = new Date,
end_time = now + (duration * 1000),
ban_history, last_ban;
if ( room ) {
ban_history = room.ban_history = room.ban_history || {};
last_ban = ban_history[user];
// Only overwrite a ban in the last 15 seconds.
if ( ! last_ban || Math.abs(now - last_ban.date) > 15000 )
last_ban = null;
}
// Display a notice in chat.
var message = (is_me ?
"You have" : ffz.format_display_name(FFZ.get_capitalization(user), user, true, false, true)[0] + " has") +
" been " + (duration === -Infinity ? 'unbanned' :
(duration === 1 ? 'purged' :
(isFinite(duration) ? "timed out for " + utils.duration_string(duration, true) : "banned")));
if ( show_notice ) {
if ( ! last_ban ) {
var msg = {
style: "admin",
date: now,
ffz_ban_target: user,
reasons: reason ? [reason] : [],
moderators: moderator ? [moderator] : [],
msg_ids: msg_id ? [msg_id] : [],
durations: [duration],
end_time: end_time,
timeouts: report_only ? 0 : 1,
message: message + (show_reason && show_moderator && moderator ? ' by ' + moderator : '') + (show_reason && reason ? ' with reason: ' + reason : '.')
};
if ( ban_history )
ban_history[user] = msg;
this.addMessage(msg);
} else {
if ( msg_id && last_ban.msg_ids.indexOf(msg_id) === -1 )
last_ban.msg_ids.push(msg_id);
if ( reason && last_ban.reasons.indexOf(reason) === -1 )
last_ban.reasons.push(reason);
if ( moderator && last_ban.moderators.indexOf(moderator) === -1 )
last_ban.moderators.push(moderator);
if ( last_ban.durations.indexOf(duration) === -1 )
last_ban.durations.push(duration);
last_ban.end_time = end_time;
if ( ! report_only )
last_ban.timeouts++;
last_ban.message = message +
(last_ban.timeouts > 1 ? ' (' + utils.number_commas(last_ban.timeouts) + ' times)' : '') +
(!show_reason || !show_moderator || last_ban.moderators.length === 0 ? '' : ' by ' + last_ban.moderators.join(', ') ) +
(!show_reason || last_ban.reasons.length === 0 ? '.' : ' with reason' + utils.pluralize(last_ban.reasons.length) + ': ' + last_ban.reasons.join(', '));
last_ban.cachedTokens = [{type: "text", text: last_ban.message}];
// Now that we've reset the tokens, if there's a line for this,
if ( last_ban._line )
Ember.propertyDidChange(last_ban._line, 'ffzTokenizedMessage');
}
}
// Mod Card History
if ( room && f.settings.mod_card_history ) {
var chat_history = room.user_history = room.user_history || {},
user_history = room.user_history[user] = room.user_history[user] || [],
last_ban = user_history.length > 0 ? user_history[user_history.length-1] : null;
if ( ! last_ban || ! last_ban.is_delete || Math.abs(now - last_ban.date) > 15000 )
last_ban = null;
if ( last_ban ) {
if ( msg_id && last_ban.msg_ids.indexOf(msg_id) === -1 )
last_ban.msg_ids.push(msg_id);
if ( reason && last_ban.reasons.indexOf(reason) === -1 )
last_ban.reasons.push(reason);
if ( last_ban.durations.indexOf(duration) === -1 )
last_ban.durations.push(duration);
last_ban.end_time = end_time;
last_ban.timeouts++;
last_ban.cachedTokens = [message + ' (' + utils.number_commas(last_ban.timeouts) + ' times)' + (last_ban.reasons.length === 0 ? '.' : ' with reason' + utils.pluralize(last_ban.reasons.length) + ': ' + last_ban.reasons.join(', '))];
} else {
user_history.push({
from: 'jtv',
is_delete: true,
style: 'admin',
date: now,
ffz_ban_target: user,
reasons: reason ? [reason] : [],
msg_ids: msg_id ? [msg_id] : [],
durations: [duration],
end_time: end_time,
timeouts: 1,
cachedTokens: message + (reason ? ' with reason: ' + reason : '.')
})
while ( user_history.length > 20 )
user_history.shift();
}
}
}
if ( ! disable_log )
this.addBanNotice(user, duration, reason, null, msg_id);
} else {
if ( f.settings.prevent_clear )
this.addTmiMessage("A moderator's attempt to clear chat was ignored.");
t.addMessage({
style: 'admin',
message: "A moderator's attempt to clear chat was ignored.",
tags: {
'msg-id': 'clear_chat'
}
});
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
message: i18n("Chat was cleared by a moderator"),
tags: {
'msg-id': 'clear_chat'
}
});
}
}
@ -1679,8 +1729,22 @@ FFZ.prototype._modify_room = function(room) {
Math.max(0, room_messages.length - this.messageBufferSize) + new_messages.length : 0,
to_remove = raw_remove - raw_remove % 2,
removed = room_messages.slice(0, to_remove),
trimmed = room_messages.slice(to_remove, room_messages.length);
// Garbage collect removed messages.
for(var i=0; i < removed.length; i++) {
var msg = removed[i],
msg_id = msg.tags && msg.tags.id,
notice_type = msg.tags && msg.tags['msg-id'];
if ( msg_id && this.ffz_ids && this.ffz_ids[msg_id] )
delete this.ffz_ids[msg_id];
if ( notice_type && this.ffz_last_notices && this.ffz_last_notices[notice_type] === msg )
delete this.ffz_last_notices[notice_type];
}
var earliest_message;
for(var i=0; i < trimmed.length; i++)
if ( trimmed[i].date ) {
@ -1892,6 +1956,10 @@ FFZ.prototype._modify_room = function(room) {
msg.labels = this.tmiRoom.getLabels(msg.from);
}
// Tag the broadcaster.
if ( room_id === msg.from )
msg.tags.mod = true;
// Tokenization
f.tokenize_chat_line(msg, false, this.get('roomProperties.hide_chat_links'));
@ -1935,63 +2003,19 @@ FFZ.prototype._modify_room = function(room) {
// 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] || [],
last_history = user_history.length && user_history[user_history.length - 1],
new_msg = {
from: msg.from,
tags: {
'display-name': msg.tags && msg.tags['display-name'],
bits: msg.tags && msg.tags.bits
},
message: msg.message,
cachedTokens: msg.cachedTokens,
style: msg.style,
date: msg.date
};
if ( msg.tags && msg.tags.historical ) {
// If it's historical, insert it at the beginning. And stuff.
if ( user_history.length < 20 )
user_history.unshift(new_msg);
} else {
// Preserve message order if we *just* received a ban.
if ( last_history && last_history.is_delete && (msg.date - last_history.date) <= 200 ) {
user_history.splice(user_history.length - 1, 0, new_msg);
} else
user_history.push(new_msg);
if ( user_history.length > 20 )
user_history.shift();
}
if ( f._mod_card && f._mod_card.ffz_room_id === msg.room && f._mod_card.get('cardInfo.user.id') === msg.from ) {
var el = f._mod_card.get('element'),
history = el && el.querySelector('.chat-history.live-history');
if ( history ) {
var was_at_top = history.scrollTop >= (history.scrollHeight - history.clientHeight),
el = f._build_mod_card_history(msg, f._mod_card);
if ( msg.tags && msg.tags.historical )
history.insertBefore(el, history.firstElementChild);
else
history.appendChild(el);
if ( was_at_top )
setTimeout(function() { history.scrollTop = history.scrollHeight; })
// Don't do infinite scrollback.
if ( history.childElementCount > 100 )
history.removeChild(history.firstElementChild);
}
}
}
}
if ( ! is_whisper && msg.from && msg.from !== 'jtv' && msg.from !== 'twitchnotify' )
this.addUserHistory({
from: msg.from,
tags: {
id: msg.tags && msg.tags.id,
'display-name': msg.tags && msg.tags['display-name'],
bits: msg.tags && msg.tags.bits
},
message: msg.message,
cachedTokens: msg.cachedTokens,
style: msg.style,
date: msg.date
});
// Clear the last ban for that user.
var f_room = f.rooms && f.rooms[msg.room],

View file

@ -54,7 +54,7 @@ FFZ.prototype.modify_viewer_list = function(component) {
// We can get capitalization for the broadcaster from the channel.
if ( Channel && Channel.get('channelModel.id') === room_id ) {
var display_name = Channel.get('channelModel.displayName');
if ( display_name )
if ( display_name && display_name !== 'jtv' )
FFZ.capitalization[broadcaster] = [display_name, Date.now()];
}

View file

@ -35,7 +35,7 @@ FFZ.channel_metadata = {};
// Version
var VER = FFZ.version_info = {
major: 3, minor: 5, revision: 329,
major: 3, minor: 5, revision: 330,
toString: function() {
return [VER.major, VER.minor, VER.revision].join(".") + (VER.extra || "");
}

View file

@ -575,7 +575,7 @@ FFZ.prototype.tokenize_conversation_line = function(message, prevent_notificatio
// Capitalization
var display_name = message.get('from.displayName');
if ( display_name && display_name.length )
if ( display_name && display_name.length && display_name !== 'jtv' )
FFZ.capitalization[from_user] = [display_name.trim(), Date.now()];
// Mentions!
@ -626,7 +626,7 @@ FFZ.prototype.tokenize_vod_line = function(msgObject, delete_links) {
tokens = this.tokenize_emoji(tokens);
var display = msgObject.get('tags.display-name');
if ( display && display.length )
if ( display && display.length && display !== 'jtv' )
FFZ.capitalization[from_user] = [display.trim(), Date.now()];
if ( ! from_me ) {
@ -709,7 +709,7 @@ FFZ.prototype.tokenize_chat_line = function(msgObject, prevent_notification, del
// Capitalization
var display = tags['display-name'];
if ( display && display.length )
if ( display && display.length && display !== 'jtv' )
FFZ.capitalization[from_user] = [display.trim(), Date.now()];

View file

@ -1,6 +1,8 @@
var FFZ = window.FrankerFaceZ,
utils = require('../utils'),
constants = require('../constants');
constants = require('../constants'),
BAN_REGEX = /^<([^ ]+) has been (banned|timed out|unbanned)(?: for ([^.]+))?\.?(?: Reasons?: ([^(>]+))?(?: ?\((\d+) times\))?>$/;
// ----------------
@ -90,12 +92,35 @@ FFZ.prototype.lv_parse_message = function(message) {
room = ffz_room && ffz_room.room;
parsed.lv_id = message.id;
parsed.date = new Date(message.time * 1000);
parsed.mod_logs = message.modlog;
parsed.date = typeof message.time === "number" ? new Date(message.time * 1000) : utils.parse_date(message.time);
// Check for ban notices. Those are identifiable via display-name.
parsed.is_ban = parsed.tags['display-name'] === 'jtv';
if ( parsed.is_ban )
// Is this administrative?
parsed.is_admin = parsed.tags['display-name'] === 'jtv';
if ( parsed.is_admin ) {
parsed.style = 'admin';
parsed.tags['display-name'] = undefined;
}
// Is this a ban?
var match = parsed.is_admin && BAN_REGEX.exec(parsed.message);
if ( match ) {
var unban = match[2] === 'unbanned',
duration = unban ? -Infinity : match[2] === 'banned' ? Infinity : utils.parse_lv_duration(match[3]),
reasons = match[4] ? match[4].trim().split(/\s*,\s*/) : [],
ban_count = match[5] && parseInt(match[5]) || 1;
parsed.message = this.format_ban_notice(
parsed.from, false, duration, ban_count, reasons,
parsed.mod_logs && Object.keys(parsed.mod_logs),
parsed.mod_logs);
parsed.cachedTokens = [{type: "raw", html: parsed.message}];
} else if ( parsed.from === "jtv" && parsed.mod_logs ) {
parsed.message = Object.keys(parsed.mod_logs).join(", ") + " used: " + parsed.message;
parsed.cachedTokens = [{type: "text", text: parsed.message}];
}
if ( parsed.tags.color )
parsed.color = parsed.tags.color;
@ -109,7 +134,9 @@ FFZ.prototype.lv_parse_message = function(message) {
parsed.tags.mod = parsed.from === parsed.room || badges.hasOwnProperty('staff') || badges.hasOwnProperty('admin') || badges.hasOwnProperty('global_mod') || badges.hasOwnProperty('moderator');
}
this.tokenize_chat_line(parsed, true, room && room.get('roomProperties.hide_chat_links'));
if ( ! parsed.cachedTokens )
this.tokenize_chat_line(parsed, true, room && room.get('roomProperties.hide_chat_links'));
return parsed;
}
@ -670,7 +697,7 @@ FFZ.mod_card_pages.notes = {
// We want to listen to get new notes for this user.
mod_card._lv_sock_room = room_id;
mod_card._lv_sock_user = user_id;
f.lv_ws_sub(room_id + '-' + user_id);
f.lv_ws_sub('logs-' + room_id + '-' + user_id);
if ( data.length ) {
var last_line = null;

View file

@ -130,7 +130,7 @@ FFZ.prototype.clear_notifications = function() {
FFZ.prototype.show_notification = function(message, title, tag, timeout, on_click, on_close) {
var perm = Notification.permission;
if ( perm === "denied " )
if ( perm === "denied" )
return false;
if ( perm === "granted" ) {

View file

@ -70,6 +70,8 @@ var createElement = function(tag, className, content) {
return num + "th";
},
lv_duration_regex = / ?(\d+) ?(\w+)/g,
date_regex = /^(\d{4}|\+\d{6})(?:-?(\d{2})(?:-?(\d{2})(?:T(\d{2})(?::?(\d{2})(?::?(\d{2})(?:(?:\.|,)(\d{1,}))?)?)?(Z|([\-+])(\d{2})(?::?(\d{2}))?)?)?)?)?$/,
parse_date = function(str) {
@ -873,10 +875,33 @@ module.exports = FFZ.utils = {
minutes = Math.floor(seconds / 60);
seconds %= 60;
var out = DURATIONS[val] = (weeks ? weeks + 'w' : '') + ((days || (weeks && (hours || minutes || seconds))) ? days + 'd' : '') + ((hours || ((weeks || days) && (minutes || seconds))) ? hours + 'h' : '') + ((minutes || ((weeks || days || hours) && seconds)) ? minutes + 'm' : '') + (seconds ? seconds + 's' : '');
var out = DURATIONS[val] = (weeks ? weeks + 'w' : '') +
(days ? days + 'd' : '') +
(hours ? hours + 'h' : '') +
(minutes ? minutes + 'm' : '') +
(seconds ? seconds + 's' : '');
return out;
},
parse_lv_duration: function(input) {
var match, value = 0;
while(match = lv_duration_regex.exec(input)) {
var mod = match[2],
val = parseInt(match[1]);
if ( mod === 'd' )
value += val * 86400;
else if ( mod === 'hrs' )
value += val * 3600;
else if ( mod === 'min' )
value += val * 60;
else if ( mod === 'sec' )
value += val;
}
return value;
},
format_unread: function(count) {
if ( count < 1 )
return "";

View file

@ -3611,6 +3611,26 @@ body:not(.ffz-channel-bar-bottom).ffz-small-player.ffz-minimal-channel-bar #play
background-repeat: no-repeat;
}
/* Ban Notices */
.theatre .chat-messages .chat-line.admin .message,
.dark .chat-messages .chat-line.admin .message,
.force-dark .chat-messages .chat-line.admin .message,
.theatre .chat-messages .chat-line.notification .message,
.dark .chat-messages .chat-line.notification .message,
.force-dark .chat-messages .chat-line.notification .message {
color: #777
}
.ban-tip { border-bottom: 1px dotted rgba(102,102,102,0.5); }
.chat-display .chat-line .ban-target { font-weight: bold }
.chat-display .chat-line .ban-target:hover {
text-decoration: underline;
cursor: pointer;
}
/* Following Menu */
#ffz-metadata-popup .scroller {