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

3.5.432. Add more stats to mod cards, including username history. De-duplicate mod card stats code with about menu debugging code. Closes #105.

This commit is contained in:
SirStendec 2017-02-26 19:11:28 -05:00
parent 81945e601d
commit df7f5d6ba0
10 changed files with 267 additions and 135 deletions

View file

@ -1,3 +1,12 @@
<div class="list-header">3.5.432 <time datetime="2017-02-26">(2017-02-26)</time></div>
<ul class="chat-menu-content menu-side-padding">
<li>Added: The Stats tab of moderation cards now tells you a user's ID, if and when they followed a channel, and how many times they've been banned in the channel.</li>
<li>Added: Name History, so you can see what a user's previous usernames have been. This data is sourced from CommanderRoot's tools and Twitch's own API.</li>
<li>Changed: Refactor stats rendering for moderation cards and the debugging menu to reduce duplicate code.</li>
<li>Fixed: Theater Hover Stats could cover up timestamps when viewing a video.</li>
<li>Fixed: Follower-only moderation action messages weren't being de-duplicated correctly.</li>
</ul>
<div class="list-header">3.5.431 <time datetime="2017-02-17">(2017-02-17)</time></div>
<ul class="chat-menu-content menu-side-padding">
<li>API Changed: Add different modes for merging badges.</li>
@ -63,19 +72,5 @@
<li>Fixed: Tooltips for links to Clips.</li>
</ul>
<div class="list-header">3.5.421 <time datetime="2017-01-24">(2017-01-24)</time></div>
<ul class="chat-menu-content menu-side-padding">
<li>Fixed: Error initializing bits rendering breaking other parts of the client.</li>
</ul>
<div class="list-header">3.5.<span id="u420">420</span> <time datetime="2017-01-20">(2017-01-20)</time></div>
<ul class="chat-menu-content menu-side-padding">
<li>Added: Tab-completion for the <code>/reset</code> command which clears the current Top Cheer.</li>
<li>Fixed: Show "Dismiss for Everyone" button on Top Cheers when user is a moderator.</li>
<li>Fixed: Color and positioning of chat header.</li>
<li>&nbsp;</li>
<li>This is also the 420th commit to FrankerFaceZ's GitHub repository.</li>
</ul>
<div class="list-header" id="ffz-old-news-button"><a href="#">View Older</a></div>
<div id="ffz-old-news"></div>

View file

@ -1,3 +1,17 @@
<div class="list-header">3.5.421 <time datetime="2017-01-24">(2017-01-24)</time></div>
<ul class="chat-menu-content menu-side-padding">
<li>Fixed: Error initializing bits rendering breaking other parts of the client.</li>
</ul>
<div class="list-header">3.5.420 <time datetime="2017-01-20">(2017-01-20)</time></div>
<ul class="chat-menu-content menu-side-padding">
<li>Added: Tab-completion for the <code>/reset</code> command which clears the current Top Cheer.</li>
<li>Fixed: Show "Dismiss for Everyone" button on Top Cheers when user is a moderator.</li>
<li>Fixed: Color and positioning of chat header.</li>
<li>&nbsp;</li>
<li>This is also the 420th commit to FrankerFaceZ's GitHub repository.</li>
</ul>
<div class="list-header">3.5.419 <time datetime="2017-01-20">(2017-01-20)</time></div>
<ul class="chat-menu-content menu-side-padding">
<li>Added: Dismiss button for Top Cheers.</li>

View file

@ -374,9 +374,9 @@ FFZ.prototype.setup_layout = function() {
'.ffz-theater-stats .app-main.theatre .cn-hosting--bottom,' +
'.ffz-theater-stats .app-main.theatre .cn-metabar__more {' +
'max-width: calc(100% - 350px);' +
'bottom: ' + (theatre_video_bottom + 55) + 'px !important}' +
'bottom: ' + (theatre_video_bottom + 85) + 'px !important}' +
'.ffz-theater-stats:not(.ffz-theatre-conversations):not(.ffz-top-conversations) .app-main.theatre .cn-metabar__more {' +
'bottom: ' + (theatre_video_bottom + 90) + 'px !important}';
'bottom: ' + (theatre_video_bottom + 130) + 'px !important}';
} else {
out += '.ffz-sidebar-swap .player-mini{left:' + (width + 10) + 'px !important}' +

View file

@ -17,6 +17,7 @@ var FFZ = window.FrankerFaceZ,
C: 67,
H: 72,
S: 83,
Y: 89,
N: 78
},
@ -1140,9 +1141,12 @@ FFZ.prototype.modify_moderation_card = function(component) {
else if ( t.lv_view && key === keycodes.H )
return t.ffzChangePage('history');
else if ( t.lv_view && key === keycodes.S )
else if ( key === keycodes.S )
return t.ffzChangePage('stats');
else if ( key === keycodes.Y )
return t.ffzChangePage('name_history');
else if ( t.lv_view_notes && key === keycodes.N )
return t.ffzChangePage('notes');
@ -1367,6 +1371,9 @@ FFZ.prototype.modify_moderation_card = function(component) {
if ( page_id === 'default' )
tab.classList.add('active');
if ( page.needs_lv )
tab.classList.add('needs-lv');
tab.setAttribute('data-page', page_id);
tabs.appendChild(tab);

View file

@ -15,7 +15,9 @@ var FFZ = window.FrankerFaceZ,
'emoteonlyoff': 'emote_only_off',
'host': 'host_on',
'unhost': 'host_off',
'clear': 'clear_chat'
'clear': 'clear_chat',
'followersoff': 'followers_off',
'followers': 'followers_on'
},
STATUS_BADGES = [
@ -1346,7 +1348,19 @@ FFZ.prototype._modify_room = function(room) {
var target_notice = NOTICE_MAPPING[event.moderation_action];
if ( target_notice ) {
var last_notice = this.ffz_last_notices && this.ffz_last_notices[target_notice];
var last_notice;
if ( ! this.ffz_last_notices )
last_notice = null;
else if ( Array.isArray(target_notice) )
for(var i=0; i < target_notice.length; i++) {
var tn = this.ffz_last_notices[target_notice[i]];
if ( tn ) {
last_notice = tn;
break;
}
}
else
last_notice = this.ffz_last_notices[target_notice];
if ( last_notice && ! last_notice.has_owner ) {
last_notice.message += ' (By: ' + event.created_by + ')';
@ -1369,7 +1383,7 @@ FFZ.prototype._modify_room = function(room) {
if ( ! event.topic || event.topic.substr(-room_id.length) !== room_id || event.created_by === this.get("session.userData.login") )
return;
//f.log("Login Moderation for " + this.get('id') + ' [' + room_id + ']', event);
f.log("Login Moderation for " + this.get('id') + ' [' + room_id + ']', event);
// In case we get unexpected input, do the other thing.
if ( f.has_bttv || ["ban", "unban", "timeout", "untimeout"].indexOf(event.moderation_action) === -1 )

View file

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

View file

@ -355,10 +355,10 @@ FFZ.debugging_blocks = {
}
}
FFZ.prototype._sorted_debug_blocks = function() {
FFZ.prototype._sorted_blocks = function(blocks) {
var segments = [];
for(var key in FFZ.debugging_blocks) {
var info = FFZ.debugging_blocks[key];
for(var key in blocks) {
var info = blocks[key];
if ( ! info )
continue;
@ -383,7 +383,7 @@ FFZ.prototype.get_debugging_info = function() {
'FrankerFaceZ - Debugging Information',
(new Date).toISOString(), ''];
var segments = f._sorted_debug_blocks(),
var segments = f._sorted_blocks(FFZ.debugging_blocks),
promises = [];
for(var i=0; i < segments.length; i++) {
@ -615,101 +615,14 @@ FFZ.menu_pages.about = {
name: "Debug",
wide: true,
render: function(view, container) {
var f = this;
// Heading!
container.appendChild(createElement('div', 'chat-menu-content center',
'<h1>FrankerFaceZ</h1><div class="ffz-about-subheading">woofs for nerds</div>'));
var segments = this._sorted_debug_blocks();
for(var i=0; i < segments.length; i++) {
var info = segments[i][1],
output;
if ( info.type === 'list' )
output = createElement('ul', 'chat-menu-content menu-side-padding version-list');
else if ( info.type === 'text' )
output = createElement('pre', 'chat-menu-content menu-side-padding');
else
continue;
container.appendChild(createElement('div', 'list-header', info.title));
container.appendChild(output);
var update_content = function(info, output, func) {
// If we've removed this from the DOM, stop updating it!
if ( ! document.body.contains(output) )
return;
var result = info.render.call(f);
if ( ! (result instanceof Promise) )
result = Promise.resolve(result);
result.then(function(data) {
if ( info.type === 'list' ) {
var handled_keys = [],
had_keys = output.childElementCount > 0;
for(var i=0; i < data.length; i++) {
var pair = data[i];
if ( pair === null ) {
if ( ! had_keys ) {
var line = createElement('li', '', '<br>');
line.setAttribute('data-key', 'null');
handled_keys.push('null');
output.appendChild(line);
}
continue;
}
var key = pair[0], value = pair[1],
line = output.querySelector('li[data-key="' + key + '"]');
if ( value === null )
continue;
handled_keys.push(key);
if ( ! line ) {
line = createElement('li');
line.setAttribute('data-key', key);
line.innerHTML = key + '<span></span>';
output.appendChild(line);
}
line.querySelector('span').innerHTML = value;
}
var lines = output.querySelectorAll('li');
for(var i=0; i < lines.length; i++) {
var line = lines[i];
if ( handled_keys.indexOf(line.getAttribute('data-key')) === -1 )
output.removeChild(line);
}
} else if ( info.type === 'text' ) {
output.textContent = data;
}
if ( info.refresh )
setTimeout(func.bind(f, info, output, func), typeof info.refresh === "number" ? info.refresh : 1000);
}).catch(function(err) {
f.error("Debugging Menu Error", err);
if ( info.type === 'list' )
output.innerHTML = '<li><i>An error occured while updating this information.</i></li>';
else
output.innerHTML = 'An error occured while updating this information.';
if ( info.refresh )
setTimeout(func.bind(f, info, output, func), typeof info.refresh === "number" ? info.refresh : 1000);
});
};
update_content.call(f, info, output, update_content);
}
// Stuff
utils.render_update_list(this,
this._sorted_blocks(FFZ.debugging_blocks),
container, 'chat-menu-content menu-side-padding');
}
}
}

View file

@ -313,6 +313,7 @@ FFZ.mod_card_pages.default = {
FFZ.mod_card_pages.history = {
title: "Chat <span>H</span>istory",
needs_lv: true,
render_more: function(mod_card, el, history, ref_id, is_user, is_after) {
var f = this,
@ -542,34 +543,87 @@ FFZ.mod_card_pages.history = {
}
FFZ.mod_card_pages.stats = {
title: "<span>S</span>tatistics",
title: "<span>S</span>tats",
render: function(mod_card, el) {
var f = this;
utils.render_update_list(this, this._sorted_blocks(FFZ.mod_stats_blocks), el, 'moderation-card__actions', true);
}
};
FFZ.mod_stats_blocks = {
basics: {
refresh: false,
type: 'list',
render: function() {
var f = this,
mod_card = this._mod_card,
user_id = mod_card.get('cardInfo.user._id'),
controller = utils.ember_lookup('controller:chat'),
room = controller && controller.get('currentRoom');
return new Promise(function(succeed, fail) {
var room_id = room.get('roomProperties._id'),
room_name = room.get('channel.display_name') || room.get('name');
if ( room.get('isGroupRoom') || ! room_id )
succeed([
['User ID', user_id]
]);
utils.api.get("users/" + user_id + "/follows/channels/" + room_id, null, {version: 5}).done(function(data) {
var now = Date.now() - (f._ws_server_offset || 0),
since = utils.parse_date(data.created_at),
age = Math.floor((now - since.getTime()) / 1000);
succeed([
['User ID', user_id],
['Follows ' + utils.sanitize(room_name), '<span class="html-tooltip" title="Since: ' + utils.quote_san(since.toLocaleString()) + '">' + utils.human_time(age, 10) + '</span>']
]);
}).fail(function() {
succeed([
['User ID', user_id],
['Does not follow ' + utils.sanitize(room_name), '']
]);
});
});
}
},
logviewer: {
refresh: 1000,
type: 'list',
render: function() {
var mod_card = this._mod_card,
controller = utils.ember_lookup('controller:chat'),
room_id = controller && controller.get('currentRoom.id'),
user_id = mod_card.get('cardInfo.user.id'),
ffz_room = f.rooms && f.rooms[room_id];
user_name = mod_card.get('cardInfo.user.id'),
ffz_room = this.rooms && this.rooms[room_id];
var container = utils.createElement('ul', 'moderation-card__actions version-list');
el.appendChild(container);
if ( ffz_room.has_logs && mod_card.lv_view ) {
container.classList.add('loading');
if ( ! ffz_room.has_logs || ! mod_card.lv_view )
return [];
return new Promise(function(succeed) {
mod_card.lvGetLogs().then(function(data) {
container.classList.remove('loading');
container.innerHTML = '<li>Messages <span>' + utils.number_commas(data.user.messages) + '</span></li><li>Timeouts <span> ' + utils.number_commas(data.user.timeouts) + '</span></li>';
succeed([
['Messages', utils.number_commas(data.user.messages || 0)],
['Timeouts', utils.number_commas(data.user.timeouts || 0)],
['Bans', utils.number_commas(data.user.bans || 0)],
['Chat Log Source', '<a target="_blank" href="https://cbenni.com/' + utils.quote_attr(room_id) + '?user=' + utils.quote_attr(user_name) + '">CBenni\'s Logviewer</a>']
]);
})
});
}
}
var notice = utils.createElement('div', 'moderation-card__actions');
notice.innerHTML = 'Chat Log Source: <a target="_blank" href="https://cbenni.com/' + room_id + '?user=' + user_id + '">CBenni\'s Logviewer</a>';
el.appendChild(notice);
}
}
}
FFZ.mod_card_pages.notes = {
title: "<span>N</span>otes",
needs_lv: true,
add_note: function(mod_card, el, note, history, last_line, do_scroll) {
if ( ! history )
@ -785,3 +839,36 @@ FFZ.mod_card_pages.notes = {
}
}
}
FFZ.mod_card_pages.name_history = {
title: "Name Histor<span>y</span>",
render: function(mod_card, el) {
var f = this,
user_id = mod_card.get('cardInfo.user._id'),
history = utils.createElement('ul', 'moderation-card__actions chat-history lv-history');
el.appendChild(history);
// Start loading!
history.classList.add('loading');
f.ws_send("get_name_history", user_id, function(success, data) {
history.classList.remove('loading');
if ( success ) {
for(var i=0; i < data.length; i++) {
var changed_at = data[i][0],
changed = changed_at ? utils.parse_date(changed_at).toLocaleString() : 'Unknown';
history.appendChild(utils.createElement('li', 'chat-line message-line admin no-messages',
'<span class="timestamp">' + utils.sanitize(changed) + ':</span>' +
'<span class="message">' + utils.sanitize(data[i][1]) + '</span>'));
}
} else
history.appendChild(utils.createElement('li', 'chat-line message-line admin no-messages',
'<span class="message">Unable to load username history for this user.</span>'));
});
}
}

View file

@ -1185,5 +1185,107 @@ module.exports = FFZ.utils = {
WEBKIT + 'mask-image:' + image + ';' +
(is_svg ? '}' : WEBKIT + 'mask-image:' + image_set + '}')
);
},
// Render Updating List
render_update_list: function(ffz, segments, container, extra_classes, set_loading) {
extra_classes = extra_classes ? extra_classes + ' ' : '';
for(var i=0; i < segments.length; i++) {
var info = segments[i][1],
output;
if ( info.type === 'list' )
output = createElement('ul', extra_classes + 'version-list');
else if ( info.type === 'text' )
output = createElement('pre', extra_classes);
else
continue;
if ( info.title )
container.appendChild(createElement('div', 'list-header', info.title));
if ( set_loading )
output.classList.add('loading');
container.appendChild(output);
var update_content = function(info, output, func) {
if ( ! document.body.contains(output) )
return;
var result = info.render.call(ffz);
if ( ! (result instanceof Promise) )
result = Promise.resolve(result);
result.then(function(data) {
if ( set_loading )
output.classList.remove('loading');
if ( info.type === 'list' ) {
var handled_keys = [],
had_keys = output.childElementCount > 0;
for(var i=0; i < data.length; i++) {
var pair = data[i];
if ( pair === null ) {
if ( ! had_keys ) {
var line = createElement('li', '', '<br>');
line.setAttribute('data-key', 'null');
handled_keys.push('null');
output.appendChild(line);
}
continue;
}
var key = pair[0], value = pair[1],
line = output.querySelector('li[data-key="' + key + '"]');
if ( value === null )
continue;
handled_keys.push(key);
if ( ! line ) {
line = createElement('li');
line.setAttribute('data-key', key);
line.innerHTML = key + '<span></span>';
output.appendChild(line);
}
line.querySelector('span').innerHTML = value;
}
var lines = output.querySelectorAll('li');
for(var i=0; i < lines.length; i++) {
var line = lines[i];
if ( handled_keys.indexOf(line.getAttribute('data-key')) === -1 )
output.removeChild(line);
}
} else if ( info.type === 'text' ) {
output.textContent = data;
}
if ( info.refresh )
setTimeout(func.bind(ffz, info, output, func), typeof info.refresh === "number" ? info.refresh : 1000);
}).catch(function(err) {
ffz.error("Debugging Menu Error", err);
if ( info.type === 'list' )
output.innerHTML = '<li><i>An error occured while updating this information.</i></li>';
else
output.innerHTML = 'An error occured while updating this information.';
if ( info.refresh )
setTimeout(func.bind(ffz, info, output, func), typeof info.refresh === "number" ? info.refresh : 1000);
});
};
update_content.call(ffz, info, output, update_content);
}
}
}

View file

@ -31,6 +31,7 @@ body > div.tipsy .tipsy-arrow { opacity: 0.8; }
.ffz-hide-thumb-info-on-hover .video.item:hover .progress-bar-wrapper,
.ffz-hide-thumb-info-on-hover .card-carousel__item:hover .card__boxpin,
.moderation-card__actions:not(.loading):empty,
.cn-bar-spacer.show,
.ffz-minimal-dashboard[data-current-path="user.dashboards.index"] #left_col,
.ffz-minimal-dashboard[data-current-path="user.dashboards.index"] #left_close,
@ -47,8 +48,7 @@ body > div.tipsy .tipsy-arrow { opacity: 0.8; }
.ffz-following-row[data-following="false"] .switch,
.ffz-moderation-card:not(.lv-notes) ul.menu li[data-page="notes"],
.ffz-moderation-card:not(.lv-logs) ul.menu li[data-page="history"],
.ffz-moderation-card:not(.lv-logs) ul.menu li[data-page="stats"],
.ffz-moderation-card:not(.lv-tabs) ul.menu,
.ffz-moderation-card:not(.lv-tabs) ul.menu li.needs-lv,
.ffz-hide-prime .drawer .warp__list.js-offers,
.ffz-hide-prime-collapsed .drawer.closed .js-offers,
.ffz-hide-friends-collapsed .drawer.closed .friend-list,
@ -332,7 +332,7 @@ body.ffz-bttv-dark .ffz-ui-toggle.blue.live:hover svg.svg-emoticons path { fill:
.ffz-theater-stats .theatre .cn-hosting--bottom,
.ffz-theater-stats .theatre .cn-metabar__more {
bottom: 55px;
bottom: 85px;
}
.ffz-theater-stats:not(.ffz-theatre-conversations):not(.ffz-top-conversations) .theatre .cn-hosting--bottom,