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

3.5.521. Emote data stuff. Bugfixes. Rich content in chat. Server socket changes.

This commit is contained in:
SirStendec 2017-09-15 14:41:00 -04:00
parent aa084d71ec
commit d97a44326a
14 changed files with 996 additions and 194 deletions

View file

@ -1,3 +1,44 @@
<div class="list-header">3.5.521 <time datetime="2017-09-15">(2017-09-15)</time></div>
<ul class="chat-menu-content menu-side-padding">
<li>Changed: Hopefully a more resilient way of grabbing emote data.</li>
</ul>
<div class="list-header">3.5.520 <time datetime="2017-09-06">(2017-09-06)</time></div>
<ul class="chat-menu-content menu-side-padding">
<li>Fixed: Issue with sub emoticons not being tab-completable without prefixes and showing as having an unknown source.</li>
</ul>
<div class="list-header">3.5.519 <time datetime="2017-09-05">(2017-09-05)</time></div>
<ul class="chat-menu-content menu-side-padding">
<li>The Emote Data Update</li>
<li>&nbsp;</li>
<li>Changed: Start querying the socket server for emote set associations rather than pre-loading data into the client.</li>
<li>Changed: Use an update badges API for the My Emoticons menu.</li>
<li>Changed: Support multiple sub tiers with the Channel emoticons menu.</li>
</ul>
<div class="list-header">3.5.518 <time datetime="2017-09-02">(2017-09-02)</time></div>
<ul class="chat-menu-content menu-side-padding">
<li>Fixed: Bug with rich content attached to chat messages not getting removed when a message is timed out.</li>
</ul>
<div class="list-header">3.5.517 <time datetime="2017-09-02">(2017-09-02)</time></div>
<ul class="chat-menu-content menu-side-padding">
<li>Changed: Dark theme CSS tweak for the directory.</li>
</ul>
<div class="list-header">3.5.516 <time datetime="2017-09-01">(2017-09-01)</time></div>
<ul class="chat-menu-content menu-side-padding">
<li>Fixed: Typo in video url regular expression.</li>
</ul>
<div class="list-header">3.5.515 <time datetime="2017-09-01">(2017-09-01)</time></div>
<ul class="chat-menu-content menu-side-padding">
<li>Added: Option to disable rich content in chat.</li>
<li>Changed: Add support for rich content in chat. This includes shared purchases, clip information, and video information.</li>
<li>API Added: <code>replaces</code> field to emoticons to allow extension emotes to replace Twitch emotes.</li>
</ul>
<div class="list-header">3.5.514 <time datetime="2017-09-01">(2017-09-01)</time></div>
<ul class="chat-menu-content menu-side-padding">
<li>Fixed: Schedule parsing issue with null values.</li>
@ -25,46 +66,5 @@
<li>Fixed: Re-sub messages not showing in channels without custom sub badges and with <code>Chat Appearance > Old-Style Subscriber Notices</code> enabled.</li>
</ul>
<div class="list-header">3.5.509 <time datetime="2017-08-07">(2017-08-07)</time></div>
<ul class="chat-menu-content menu-side-padding">
<li>API Changed: Throw the <code>room-recent-highlights</code> event even if the Recent Highlights feature is disabled.</li>
</ul>
<div class="list-header">3.5.508 <time datetime="2017-08-07">(2017-08-07)</time></div>
<ul class="chat-menu-content menu-side-padding">
<li>API Added: <code>room-recent-highlights</code> event for triggering behaviors when new highlighted messages are displayed.</li>
<li>API Changed: Allow extensions to repress chat messages by marking them as removed in the <code>room-message</code> API event.</li>
</ul>
<div class="list-header">3.5.507 <time datetime="2017-08-02">(2017-08-02)</time></div>
<ul class="chat-menu-content menu-side-padding">
<li>Fixed: Dark theme for the dashboard.</li>
<li>Fixed: Moderation cards not rendering correctly when you start by opening your own mod card.</li>
</ul>
<div class="list-header">3.5.506 <time datetime="2017-07-26">(2017-07-26)</time></div>
<ul class="chat-menu-content menu-side-padding">
<li>Added: Setting to hide Trending Emotes in chat.</li>
<li>Fixed: Make Minimal Chat input hide the trending emotes stuff too.</li>
</ul>
<div class="list-header">3.5.505 <time datetime="2017-07-24">(2017-07-24)</time></div>
<ul class="chat-menu-content menu-side-padding">
<li>Changed: Minor dark theme tweaks.</li>
<li>Fixed: A scrollbar would appear at the side of the page at times due to tooltips.</li>
<li>Fixed: Don't escape HTML for legacy sidebar tooltips.</li>
</ul>
<div class="list-header">3.5.504 <time datetime="2017-07-13">(2017-07-13)</time></div>
<ul class="chat-menu-content menu-side-padding">
<li>Added: Support for the <code>AUTOMOD_SMALLER</code> experiment. In-Line AutoMod is not yet working when you are part of that experiment.</li>
</ul>
<div class="list-header">3.5.503 <time datetime="2017-07-13">(2017-07-13)</time></div>
<ul class="chat-menu-content menu-side-padding">
<li>Fixed: Emotes parsing in local messages.</li>
<li>Fixed: Use the <code>user-emotes</code> service in all places since <code>tmiSession</code>'s emote parser has been removed.</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

@ -661,7 +661,7 @@ body.ffz-dark:not([data-page="teams#show"]),
.ffz-dark .card .card__info a,
.ffz-dark .items-grid .meta .title,
.ffz-dark .items-grid .meta p a {
color: #9c9c9c !important;
color: #9c9c9c;
}
.ffz-dark .event-calendar {
@ -1862,6 +1862,7 @@ body.ffz-dark:not([data-page="teams#show"]),
.ffz-dark .search-result-view__block.isActive { background-color: #222 }
.ffz-dark .search-result-view__titlesep:hover,
.ffz-dark a:hover .card__title,
.ffz-dark .card__title a:hover,
.ffz-dark .card__info a:hover {
color: #ccd;

View file

@ -1,3 +1,44 @@
<div class="list-header">3.5.509 <time datetime="2017-08-07">(2017-08-07)</time></div>
<ul class="chat-menu-content menu-side-padding">
<li>API Changed: Throw the <code>room-recent-highlights</code> event even if the Recent Highlights feature is disabled.</li>
</ul>
<div class="list-header">3.5.508 <time datetime="2017-08-07">(2017-08-07)</time></div>
<ul class="chat-menu-content menu-side-padding">
<li>API Added: <code>room-recent-highlights</code> event for triggering behaviors when new highlighted messages are displayed.</li>
<li>API Changed: Allow extensions to repress chat messages by marking them as removed in the <code>room-message</code> API event.</li>
</ul>
<div class="list-header">3.5.507 <time datetime="2017-08-02">(2017-08-02)</time></div>
<ul class="chat-menu-content menu-side-padding">
<li>Fixed: Dark theme for the dashboard.</li>
<li>Fixed: Moderation cards not rendering correctly when you start by opening your own mod card.</li>
</ul>
<div class="list-header">3.5.506 <time datetime="2017-07-26">(2017-07-26)</time></div>
<ul class="chat-menu-content menu-side-padding">
<li>Added: Setting to hide Trending Emotes in chat.</li>
<li>Fixed: Make Minimal Chat input hide the trending emotes stuff too.</li>
</ul>
<div class="list-header">3.5.505 <time datetime="2017-07-24">(2017-07-24)</time></div>
<ul class="chat-menu-content menu-side-padding">
<li>Changed: Minor dark theme tweaks.</li>
<li>Fixed: A scrollbar would appear at the side of the page at times due to tooltips.</li>
<li>Fixed: Don't escape HTML for legacy sidebar tooltips.</li>
</ul>
<div class="list-header">3.5.504 <time datetime="2017-07-13">(2017-07-13)</time></div>
<ul class="chat-menu-content menu-side-padding">
<li>Added: Support for the <code>AUTOMOD_SMALLER</code> experiment. In-Line AutoMod is not yet working when you are part of that experiment.</li>
</ul>
<div class="list-header">3.5.503 <time datetime="2017-07-13">(2017-07-13)</time></div>
<ul class="chat-menu-content menu-side-padding">
<li>Fixed: Emotes parsing in local messages.</li>
<li>Fixed: Use the <code>user-emotes</code> service in all places since <code>tmiSession</code>'s emote parser has been removed.</li>
</ul>
<div class="list-header">3.5.502 <time datetime="2017-07-13">(2017-07-13)</time></div>
<ul class="chat-menu-content menu-side-padding">
<li>Fixed: Dark theme for chat.</li>

View file

@ -37,9 +37,10 @@ var commandHandlers = map[Command]CommandHandler{
"twitch_emote": C2SHandleBunchedCommand,
"get_link": C2SHandleBunchedCommand,
"get_display_name": C2SHandleBunchedCommand,
"get_emote": C2SHandleBunchedCommand,
"get_emote_set": C2SHandleBunchedCommand,
"has_logs": C2SHandleBunchedCommand,
"update_follow_buttons": C2SHandleRemoteCommand,
"chat_history": C2SHandleRemoteCommand,
"user_history": C2SHandleRemoteCommand,
}
func setupInterning() {
@ -56,12 +57,12 @@ func setupInterning() {
CommandPool._Intern_Setup("track_follow")
CommandPool._Intern_Setup("emoticon_uses")
CommandPool._Intern_Setup("twitch_emote")
CommandPool._Intern_Setup("get_emote")
CommandPool._Intern_Setup("get_emote_set")
CommandPool._Intern_Setup("has_logs")
CommandPool._Intern_Setup("get_link")
CommandPool._Intern_Setup("get_display_name")
CommandPool._Intern_Setup("update_follow_buttons")
CommandPool._Intern_Setup("chat_history")
CommandPool._Intern_Setup("user_history")
CommandPool._Intern_Setup("adjacent_history")
}
// DispatchC2SCommand handles a C2S Command in the provided ClientMessage.

View file

@ -126,6 +126,7 @@ module.exports = FrankerFaceZ.constants = {
//modifier: '262f'
},
COMMERCE_CARET: '<svg version="1.1" x="0" y="0" viewbox="0 0 792 612" enable-background="new 0 0 792 612"><path fill="#D8D8D8" d="M777.1,444.6c19.8,19.8,19.8,49.5,0,69.3c-19.8,19.8-49.5,19.8-69.3,0L361.4,167.4c-19.8-19.8-19.8-49.5,0-69.3c19.8-19.8,49.5-19.8,69.3,0L777.1,444.6z"></path><path fill="#D8D8D8" d="M430.6,98.1c19.8,19.8,19.8,49.5,0,69.3L84.1,513.9c-19.8,19.8-49.5,19.8-69.3,0c-19.8-19.8-19.8-49.5,0-69.3L361.3,98.1C381.1,78.3,410.9,78.3,430.6,98.1z"></path></svg',
ZREKNARF: svg('glyph_views svg-zreknarf', 16, 12.5, SVGPATH, '0 0 249 195'),
CHAT_BUTTON: svg('emoticons', 24, 18, SVGPATH, '0 0 249 195'),
ROOMS: svg('glyph_views svg-roomlist', 16, 16, 'M1,13v-2h14v2H1z M1,5h13v2H1V5z M1,2h10v2H1V2z M12,10H1V8h11V10z'),

View file

@ -783,7 +783,7 @@ FFZ.prototype.modify_chat_input = function(component) {
room_id = room && room.get('id'),
user_emotes = utils.ember_lookup('service:user-emotes'),
set_name, replacement, url, is_inventory, is_sub_set, fav_list,
set_data, set_name, replacement, url, is_inventory, is_sub_set, fav_list,
emote_set, emote, emote_id, code, sort_factor, is_fav,
prefix_length, per_pref,
@ -804,7 +804,8 @@ FFZ.prototype.modify_chat_input = function(component) {
is_inventory = f._twitch_inventory_sets.indexOf(set_id) !== -1;
fav_list = f.settings.favorite_emotes['twitch-' + (is_inventory ? 'inventory' : set_id)] || [];
is_sub_set = false;
set_name = f._twitch_set_to_channel[set_id];
set_data = !is_inventory && f.get_twitch_set(set_id);
set_name = set_data && set_data.c_name;
if ( ! emote_set )
continue;

View file

@ -1,16 +1,50 @@
var FFZ = window.FrankerFaceZ,
utils = require("../utils"),
constants = require("../constants"),
bits_service,
CLIP_ERROR = constants.TWITCH_BASE + '86/1.0',
CLIP_FALLBACK = 'https://clips-media-assets.twitch.tv/404-preview-86x45.jpg',
TB_TOOLTIP = 'This message was flagged by AutoMod. Should it be allowed?',
BAN_SPLIT = /[\/\.](?:ban ([^ ]+)|timeout ([^ ]+)(?: (\d+))?|timeout_message ([^ ]+) ([^ ]+)(?: (\d+))?)(?: (.*))?$/;
FFZ._fallback_image = function(img) {
var src = img.dataset.fallbackUrl;
if ( ! src )
return;
img.dataset.fallbackUrl = '';
img.src = src;
}
// ---------------------
// Settings
// ---------------------
FFZ.settings_info.chat_rich_content = {
type: "boolean",
value: true,
category: "Chat Appearance",
name: "Rich Content in Chat",
help: "Display rich content in chat, such as clip embeds and blocks about people's purchases.",
on_update: function(val) {
var CL = utils.ember_resolve('component:chat/chat-line'),
views = CL ? utils.ember_views() : [];
for(var vid in views) {
var view = views[vid];
if ( view instanceof CL && view.ffzRender )
view.ffzRender();
}
}
}
FFZ.settings_info.automod_inline = {
type: "boolean",
value: false,
@ -809,6 +843,10 @@ FFZ.settings_info.chat_ts_size = {
// ---------------------
FFZ.prototype.setup_line = function() {
bits_service = utils.ember_lookup('service:bits-emotes');
if ( ! bits_service )
bits_service = utils.ember_lookup('service:bits-rendering-config');
// Tipsy Handler
jQuery(document.body).on("mouseleave", ".tipsy", function() {
this.parentElement.removeChild(this);
@ -934,8 +972,11 @@ FFZ.prototype._modify_chat_line = function(component, is_vod) {
this.$(".mod-icons").replaceWith(this.buildModIconsHTML());
if ( this.get("msgObject.deleted") ) {
this.$(".message").replaceWith(this.buildDeletedMessageHTML());
} else
this.$(".ffz-rich-content").html("");
} else {
this.$(".deleted,.message").replaceWith(this.buildMessageHTML());
this.$(".ffz-rich-content").html(this.buildRichContentHTML());
}
}),
clickedChanged: Ember.observer("hasClickedFlaggedMessage", function() {
@ -1092,33 +1133,230 @@ FFZ.prototype._modify_chat_line = function(component, is_vod) {
var tags = this.get('msgObject.tags') || {},
msg_type = tags['msg-id'],
out = '';
out;
if ( msg_type === 'bits-hashtag' )
/*if ( msg_type === 'bits-hashtag' )
out = f.render_token(true,false,true, {type: "bits", prefix: "Cheer", amount: parseInt(tags['msg-param-total'])}) +
utils.sanitize(tags['system-msg'] || '')
.replace('{hashtag}', '<strong>#' + utils.sanitize(tags['msg-param-hashtag']) + '</strong>')
.replace('{link}', '<a target="_blank" href="' + utils.quote_san(tags['msg-param-link']) + '">' + utils.sanitize(tags['msg-param-linkname']) + '</a>')
.replace('{link}', '<a target="_blank" href="' + utils.quote_san(tags['msg-param-link']) + '">' + utils.sanitize(tags['msg-param-linkname']) + '</a>')*/
else if ( msg_type === 'purchase' ) {
var Intl = utils.ember_lookup('service:intl');
out = Intl && ('<p class="purchase-message-title pd-t-0 float-left">' +
Intl.t('gameCommerce.purchaseNotifications.message', {
userName: tags['login'],
purchaseTitle: tags['msg-param-title']
}) +
'</p><div><img class="purchase-notif__box-art float-right mg-t-0 mg-1-1" src="' +
utils.quote_san(tags['msg-param-imageURL']) +
'"></div>');
if ( msg_type === 'purchase' ) {
var Intl = utils.ember_lookup('service:intl'),
commerce = this.get('msgObject.tags.content.commerce.firstObject');
if ( commerce && Intl )
out = '<p class="purchase-message-title">' +
Intl.t(
commerce.numCrates ?
'gameCommerce.purchaseNotifications.plusCrates.systemMessage' :
'gameCommerce.purchaseNotifications.systemMessage',
{
userName: tags['login'],
purchaseTitle: tags['msg-param-title'],
numCrates: commerce.numCrates,
htmlSafe: true
}) + '</p>';
}
else if ( msg_type === 'raid' || msg_type === 'unraid' )
// TODO: This.
return '';
else
out = utils.sanitize(this.get('systemMsg'));
return out ? '<div class="system-msg">' + out + '</div>' : '';
},
buildRichContentHTML: function() {
if ( ! f.settings.chat_rich_content || this.get('msgObject.deleted') )
return '';
var content = this.get('msgObject.tags.content') || {},
out = '';
for(var pk in content) {
if ( pk === 'commerce' )
out += this.buildPurchaseContentHTML();
else if ( pk in FFZ.rich_content_providers )
out += this.buildRichEmbedHTML(this.ffzGetContent(pk));
}
return out;
},
ffzUpdateRichContent: function() {
if ( this.get('msgObject.tags.content') )
this.$(".ffz-rich-content").html(this.buildRichContentHTML());
},
ffzGetContent: function(provider_key, info) {
info = info || this.get('msgObject.tags.content.' + provider_key + '.firstObject')
if ( ! info || ! info.data )
return {
loaded: true,
errored: true
};
var t = this,
provider = FFZ.rich_content_providers[provider_key],
content_info = this._ffz_content_info = this._ffz_content_info || {},
content = content_info[info.index] = content_info[info.index] || {
embed_type: provider.display_name || provider_key,
input: info.data
};
if ( ! content._started ) {
content._started = true;
provider.get_info.call(f, content.input).then(function(data) {
content.data = data;
content.loaded = true;
t.isDestroyed || t.ffzUpdateRichContent();
}).catch(function() {
content.errored = true;
content.loaded = true;
t.isDestroyed || t.ffzUpdateRichContent();
})
}
return content;
},
buildRichEmbedHTML: function(content) {
var data = content.data || {},
out = '<div class="chat-chip pd-y-05 mg-t-05">' +
'<div class="card card--row card--sm">';
if ( ! content.loaded || content.errored ) {
out += '<div class="card__layout">' +
'<figure class="card__img chat-chip-img' + (content.errored ? ' chat-chip-img--error' : '') + '">';
if ( content.loaded )
out += '<img src="' + utils.quote_attr(content.errored ? CLIP_ERROR : data.image) + '" data-fallback-url="' + utils.quote_attr(CLIP_FALLBACK) + '" onerror="FrankerFaceZ._fallback_image(this)">';
else
out += '<div class="loading-spinner"></div>';
out += '</figure>' +
'<div class="card__body">' +
'<h3 class="card__title ellipsis">' +
(content.errored ?
'Something went wrong' :
'Loading ' + content.embed_type + '...') +
'</h3>' +
'<p class="card__info ellipsis">' +
(content.errored ?
"We couldn't find that " + content.embed_type + '.' :
'...') +
'</p>' +
'</div>' +
'</div>';
} else {
out += '<a class="card__layout" href="' + utils.quote_attr(data.url) + '" target="_blank" rel="noopener noreferrer" class="card__layout">' +
'<figure class="card__img chat-chip-img">' +
'<img src="' + utils.quote_attr(data.image) + '" data-fallback-url="' + utils.quote_attr(CLIP_FALLBACK) + '" onerror="FrankerFaceZ._fallback_image(this)">' +
'</figure>' +
'<div class="card__body">' +
'<h3 class="card__title ellipsis">' + data.title + '</h3>';
for(var i=0; i < data.by_lines.length; i++)
out += '<p class="card__info ellipsis">' + data.by_lines[i] + '</p>';
out += '</div>' +
'</a>';
}
return out + '</div></div>';
},
buildPurchaseContentHTML: function() {
var commerce = this.get('msgObject.tags.content.commerce.firstObject'),
out;
if ( ! commerce || ! commerce.purchased || ! commerce.purchased.length )
return '';
var purchased = commerce.purchased[0],
crated = commerce.crated || [],
show_drawer = crated.length > 2,
drawer_open = this._ffz_commerce_drawer_open,
image_url = purchased.boxart,
title = purchased.title,
Intl = utils.ember_lookup('service:intl');
out = '<div class="chat-commerce-rich-content chat-chip flex flex--column full-width mg-t-05 pd-0">' +
'<div class="flex flex--nowrap">' +
'<div class="flex__item--noShrink flex__item--noGrow mg-05">' +
'<img class="chat-commerce-rich-content__image chat-commerce-rich-content__image--xlarge" src="' + utils.quote_attr(image_url) + '">' +
'</div>' +
'<div class="flex__item--grow mg-05 mg-r-05">' +
'<div class="font-size-4">' + utils.sanitize(title) + '</div>';
if ( Intl && commerce.numCrates > 0 ) {
out += '<div class="chat-commerce-rich-content__subtext mg-t-05">' +
Intl.t('gameCommerce.purchaseNotifications.plusCrates.richContentMessage', {
numCrates: commerce.numCrates,
numRewards: crated.length
}) +
'</div>';
}
out += '</div>';
if ( crated.length ) {
out += '<div class="border-1 pd-05 flex flex--nowrap justify-content-center align-items-center font-size-4 flex__item--noShrink' + (show_drawer ? ' chat-commerce-right-content__pocket--button' : '') + '">';
if ( show_drawer ) {
if ( drawer_open )
out += '<div><figure class="icon chat-commerce-rich-content__caret">' + constants.COMMERCE_CARET + '</figure></div>';
else
out += '<div class="flex flex--nowrap">' +
this.buildPurchaseContentItemHTML(crated[0], 'align-self-center') +
'<div class="align-self-center pill pill--red flex__item--noShrink">+' +
((crated.length||0) - 1) + '</div>' +
'</div>';
} else
for(var i=0; i < crated.length; i++)
out += this.buildPurchaseContentItemHTML(crated[i]);
out += '</div>';
}
out += '</div></div>';
if ( show_drawer && drawer_open ) {
out += '<div class="chat-commerce-rich-content__drawer flex flex--horizontalEnd align-content-center align-items-center pd-05 pd-t-0">';
for(var i=0; i < crated.length; i++)
out += this.buildPurchaseContentItemHTML(crated[i], 'mg-r-05 mg-t-05');
out += '</div>';
}
return out;
},
buildPurchaseContentItemHTML: function(loot, extra_classes) {
var loot_type = loot.type,
out;
if ( loot_type === 'emoticon' )
out = '<img src="//static-cdn.jtvnw.net/emoticons/v1/' + utils.quote_san(loot.id) + '/2.0">';
else if ( loot_type === 'bits' ) {
var amount = loot.quantity,
tier = bits_service.ffz_get_tier('Cheer', amount) || [null, null];
if ( tier[1] )
out = '<span class="emoticon js-bits-emote-image ffz-bit bit-prefix-Cheer bit-tier-' + tier[0] + ' inventory-bits__image" data-prefix="Cheer" data-amount="' + utils.number_commas(amount) + '"></span>';
} else if ( loot.img )
out = '<img src="' + utils.quote_san(loot.img) + '">';
return out ? '<div class="chat-commerce-rich-content__' + utils.quote_san(loot_type) + ' chat-commerce-rich-content__image' + (extra_classes ? ' ' + extra_classes : '') + '">' + out + '</div>' : '';
},
buildBadgesHTML: function() {
if ( ! this.get('msgObject.room') && this.get('msgObject.payday_timestamp') )
this.set('msgObject.room', Chat.get('currentRoom.id'));
@ -1225,6 +1463,9 @@ FFZ.prototype._modify_chat_line = function(component, is_vod) {
if ( (this.get('isAutoModPromptSmaller') || ! f.settings.automod_inline) && this.get('msgObject.autoModRejected') )
output += this.buildAutoModHTML();
if ( this.get('msgObject.tags.content') )
output += '<div class="ffz-rich-content">' + this.buildRichContentHTML() + '</div>';
el.innerHTML = output;
},
@ -1533,6 +1774,8 @@ FFZ.prototype._modify_chat_subline = function(component, is_whisper) {
if ( ! target )
return;
e.preventDefault();
var n = this.get('element'),
bounds = n && n.getBoundingClientRect() || document.body.getBoundingClientRect(),
x = 0, right;
@ -1551,6 +1794,11 @@ FFZ.prototype._modify_chat_subline = function(component, is_whisper) {
} else if ( cl.contains('undelete') ) {
e.preventDefault();
this.set("msgObject.deleted", false);
} else if ( cl.contains('chat-commerce-right-content__pocket--button') ) {
e.preventDefault();
this._ffz_commerce_drawer_open = !this._ffz_commerce_drawer_open;
this.ffzUpdateRichContent();
}
}
});
@ -1697,7 +1945,7 @@ FFZ.get_capitalization = function(name, callback) {
try {
typeof waiting[i] === "function" && waiting[i](cap_name);
} catch(err) { }
});
}, true);
}
return old_data ? old_data[0] : name;

View file

@ -3,6 +3,11 @@ var FFZ = window.FrankerFaceZ,
constants = require('../constants'),
utils = require('../utils'),
tmimotes,
commerce,
CLIP_URL = /\b(?:https?:\/\/)?clips\.twitch\.tv\/(\w+)(?:\/)?(\w+)?(?:\/edit)?\b/,
VIDEO_URL = /\b(?:https?:\/\/)?(?:www\.)?twitch\.tv\/(?:\w+\/v|videos)\/(\w+)(?:\?[\w\.-=]*)?\b/,
FFZ_URL = /\b(?:https?:\/\/)?(?:www\.)?frankerfacez\.com\/emoticon\/(\d+)(?:-\w*)?\b/,
NOTICE_MAPPING = {
'slow': 'slow_on',
@ -58,6 +63,10 @@ FFZ.prototype.setup_room = function() {
tmimotes = window.require && window.require("web-client/utilities/tmi-emotes").default;
} catch(err) { }
try {
commerce = window.require && window.require("web-client/utils/commerce/chat");
} catch(err) { }
this.log("Creating room style element.");
var f = this,
s = this._room_style = document.createElement("style");
@ -2321,6 +2330,55 @@ FFZ.prototype._modify_room = function(room) {
}
}
// Handle Rich Content
if ( commerce && commerce.addCommerceParamsAsRichContent )
commerce.addCommerceParamsAsRichContent(msg);
/*var first_clip = CLIP_URL.exec(msg.message);
if ( first_clip ) {
msg.tags.content = msg.tags.content || {};
msg.tags.content.clips = msg.tags.content.clips || [];
msg.tags.content.clips.push({
index: first_clip.index,
removeOriginal: true,
data: {
url: first_clip[0],
slug: first_clip[1]
}
})
} else {
var first_vid = VIDEO_URL.exec(msg.message);
if ( first_vid ) {
msg.tags.content = msg.tags.content || {};
msg.tags.content.clips = msg.tags.content.clips || [];
msg.tags.content.clips.push({
index: first_vid.index,
removeOriginal: true,
data: {
is_video: true,
url: first_vid[0],
video: first_vid[1]
}
})
} /*else {
var first_ffz = FFZ_URL.exec(msg.message);
if ( first_ffz ) {
msg.tags.content = msg.tags.content || {};
msg.tags.content.ffz_emotes = msg.tags.content.ffz_emotes || [];
msg.tags.content.ffz_emotes.push({
index: first_ffz.index,
removeOriginal: true,
data: {
url: first_ffz[0],
id: first_ffz[1]
}
})
}
}
}*/
// Tokenization
f.tokenize_chat_line(msg, false, this.get('roomProperties.hide_chat_links'));

View file

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

View file

@ -4,6 +4,9 @@
background-color: rgba(0,0,0, 0.1);
}
.theme--dark .special-message .system-msg,
.special-message .system-msg { background-color: transparent }
/* Dark: Alternating Background */
.ffz-dark .conversation-chat-lines > div:nth-child(2n+0),

View file

@ -21,7 +21,8 @@ var FFZ = window.FrankerFaceZ,
LINK = /(?:https?:\/\/)?(?:[-a-zA-Z0-9@:%_\+~#=]+\.)+[a-z]{2,6}\b(?:[-a-zA-Z0-9@:%_\+.~#?&\/\/=()]*)/g,
CLIP_URL = /^(?:https?:\/\/)?clips\.twitch\.tv\/(\w+?\/?\w*?)(?:\/edit)?(?:[\?#]|$)/,
VIDEO_URL = /^(?:https?:\/\/)?(?:www\.)twitch\.tv\/(?:\w+\/v|videos)\/(\w+)$/,
VIDEO_URL = /^(?:https?:\/\/)?(?:www\.)?twitch\.tv\/(?:\w+\/v|videos)\/(\w+)$/,
FFZ_EMOTE_URL = /^(?:https?:\/\/)?(?:www\.)?frankerfacez\.com\/emoticon\/(\d+)(?:-\w*)?$/,
LINK_SPLIT = /^(?:(https?):\/\/)?(?:(.*?)@)?([^\/:]+)(?::(\d+))?(.*?)(?:\?(.*?))?(?:\#(.*?))?$/,
YOUTUBE_CHECK = /^(?:https?:\/\/)?(?:m\.|www\.)?youtu(?:be\.com|\.be)\/(?:v\/|watch\/|.*?(?:embed|watch).*?v=)?([a-zA-Z0-9\-_]+)$/,
@ -200,7 +201,7 @@ FFZ.prototype.setup_tokenization = function() {
this._twitch_set_to_channel = {};
this._link_data = {};
this.load_twitch_emote_data();
//this.load_twitch_emote_data();
utils.toggle_cls('ffz-clickable-mentions')(this.settings.clickable_mentions);
try {
@ -347,7 +348,7 @@ FFZ.prototype.format_display_name = function(display_name, user_id, disable_alia
// Twitch Emote Data
// ---------------------
FFZ.prototype.load_twitch_emote_data = function(tries) {
/*FFZ.prototype.load_twitch_emote_data = function(tries) {
var f = this;
f._twitch_set_to_channel[0] = "--global--";
f._twitch_set_to_channel[33] = "--turbo-faces--";
@ -355,7 +356,7 @@ FFZ.prototype.load_twitch_emote_data = function(tries) {
f._twitch_set_to_channel[19194] = "--prime--";
f._twitch_set_to_channel[19151] = "--curse--";
this.log("Loading Twitch Emote Data (Try " + (tries || 0) + ")");
/*this.log("Loading Twitch Emote Data (Try " + (tries || 0) + ")");
jQuery.ajax(constants.SERVER + "twitch_emotes.json")
.done(function(data) {
@ -381,7 +382,91 @@ FFZ.prototype.load_twitch_emote_data = function(tries) {
tries = (tries || 0) + 1;
if ( tries < 10 )
setTimeout(f.load_twitch_emote_data.bind(f, tries), 1000);
});
});*
}*/
var UNSET = {};
FFZ.prototype.get_twitch_set_for = function(emote_id, callback) {
if ( typeof emote_id !== "number" )
emote_id = parseInt(emote_id);
if ( isNaN(emote_id) || ! isFinite(emote_id) )
return null;
if ( this._twitch_emote_to_set.hasOwnProperty(emote_id) && this._twitch_emote_to_set[emote_id] !== UNSET )
return this._twitch_emote_to_set[emote_id];
this._twitch_emote_to_set[emote_id] = null;
var f = this,
use_ss = this._ws_open && Math.random() > .5,
cb = function(success, data) {
if ( ! success ) {
f._twitch_emote_to_set[emote_id] = UNSET;
return;
}
var set_id = null;
if ( data ) {
set_id = data['s_id'];
f._twitch_set_to_channel[set_id] = data;
}
f._twitch_emote_to_set[emote_id] = set_id;
if ( callback )
callback(set_id);
};
if ( use_ss )
this.ws_send("get_emote", emote_id, cb);
else
fetch(constants.API_SERVER = "ed/emote/" + emote_id)
.then(function(resp) {
if ( ! resp.ok )
return cb(false, null);
resp.json().then(function(data) {
cb(true, data);
})
});
}
FFZ.prototype.get_twitch_set = function(set_id, callback) {
if ( typeof set_id !== "number" )
set_id = parseInt(set_id);
if ( isNaN(set_id) || ! isFinite(set_id) )
return null;
if ( this._twitch_set_to_channel.hasOwnProperty(set_id) && this._twitch_set_to_channel[set_id] !== UNSET )
return this._twitch_set_to_channel[set_id];
this._twitch_set_to_channel[set_id] = null;
var f = this,
use_ss = this._ws_open && Math.random() > .5,
cb = function(success, data) {
if ( ! success ) {
f._twitch_set_to_channel[set_id] = UNSET;
return;
}
f._twitch_set_to_channel[set_id] = data || null;
if ( callback )
callback(data || null);
};
if ( use_ss )
this.ws_send("get_emote_set", set_id, cb);
else
fetch(constants.API_SERVER + "ed/set/" + set_id)
.then(function(resp) {
if ( ! resp.ok )
return cb(false, null);
resp.json().then(function(data) {
cb(true, data);
})
});
}
@ -423,6 +508,7 @@ FFZ.prototype.render_tooltip = function(el) {
preview_url, width=0, height=0, image, set_id, emote, emote_set,
emote_id = this.getAttribute('data-ffz-emote'),
modifiers = this.getAttribute('data-modifier-info'),
sellout_text = this.getAttribute('data-sellout'),
mod_text = '';
if ( modifiers ) {
@ -433,6 +519,9 @@ FFZ.prototype.render_tooltip = function(el) {
}).join('<br>');
}
if ( sellout_text )
mod_text = '<hr>' + sellout_text + mod_text;
if ( emote_id ) {
if ( emote_id == "93269" )
return '';
@ -480,7 +569,8 @@ FFZ.prototype.render_tooltip = function(el) {
emote_id = this.getAttribute('data-emote');
if ( emote_id ) {
set_id = f._twitch_emote_to_set[emote_id];
emote_set = set_id && f._twitch_set_to_channel[set_id];
var set_data = set_id && f.get_twitch_set(set_id);
emote_set = set_data && set_data.c_name;
var set_type = "Channel",
favorite_key = 'twitch-' + set_id;
@ -615,6 +705,11 @@ FFZ.prototype.tokenize_conversation_line = function(message, prevent_notificatio
if ( helpers && helpers.emoticonizeMessage && emotes && this.settings.parse_emoticons )
tokens = helpers.emoticonizeMessage(tokens, emotes);
// Pre-load emote information.
if ( emotes )
for(var emote_id in emotes)
this.get_twitch_set_for(emote_id);
// FrankerFaceZ Extras
tokens = this._remove_banned(tokens);
@ -667,6 +762,11 @@ FFZ.prototype.tokenize_vod_line = function(msgObject, delete_links) {
if ( helpers && helpers.emoticonizeMessage && emotes && this.settings.parse_emoticons )
tokens = helpers.emoticonizeMessage(tokens, emotes);
// Pre-load emote information.
if ( emotes )
for(var emote_id in emotes)
this.get_twitch_set_for(emote_id);
// FrankerFaceZ Extras
tokens = this._remove_banned(tokens);
@ -713,6 +813,185 @@ FFZ.prototype._tokenize_bits = function(tokens) {
}
FFZ.prototype.tokenize_rich_content = function(tokens, content) {
'use strict';
// First, we want to get the indices of all the existing rich content elements.
// This should only really be grabbing commerce content.
var indices = [];
for(var content_type in content) {
var cont = content[content_type];
for(var i=0; i < cont.length; i++) {
var c = cont[i];
if ( c.removeOriginal && c.index >= 0 )
indices.push(c.index);
}
}
// Sanitize tokens.
if ( typeof tokens === 'string' )
tokens = [{type: 'text', text: tokens}];
var providers = FFZ.rich_content_providers;
// Iterate tokens.
var idx = 0;
for(var i=0; i < tokens.length; i++) {
var token = tokens[i];
if ( typeof token === 'string' )
token = tokens[i] = {type: 'text', text: token};
// If a token's index matches rich content, then the token is being
// expressed as rich content and it should be suppressed when the
// message is rendered with rich content.
if ( indices.indexOf(idx) !== -1 ) {
token.rich_removed = true;
} else {
// However, if it doesn't match existing rich content, then we
// should proceed to check it using our rich content providers.
for(var pk in providers) {
var provider = providers[pk];
if ( ! provider.type || token.type === provider.type ) {
var cont = provider.extract(token);
if ( cont ) {
token.rich_removed = provider.remove_token;
content[pk] = content[pk] || [];
content[pk].push({
index: idx,
removeOriginal: provider.remove_token,
data: cont
});
break;
}
}
}
}
idx += token.length || (token.text && token.text.length) || 1;
}
return tokens;
}
FFZ.rich_content_providers = {
/*ffz_emote: {
token_type: 'link',
remove_token: true,
display_name: 'FFZ emote',
extract: function(token) {
var href = token.link || token.text,
match = FFZ_EMOTE_URL.exec(href);
if ( match )
return {
url: href,
id: match[1]
}
},
get_info: function(info) { return new Promise(function(s,f) {
fetch("https://api.frankerfacez.com/v1/emote/" + info.id)
.then(utils.json).then(function(data) {
if ( ! data || ! data.emote )
return f();
var em = data.emote;
s({
image: em.urls[2] || em.urls[1],
url: info.url,
title: utils.sanitize(em.name) + ' by <span class="user-token" data-user="' + utils.quote_attr(em.owner.name) + '">' + utils.sanitize(em.owner.display_name) + '</span>',
by_lines: [
(em.public ? 'Public' : 'Private') + ' FFZ Emote'
]
})
})
})}
},*/
video: {
token_type: 'link',
remove_token: true,
extract: function(token) {
var href = token.link || token.text,
match = VIDEO_URL.exec(href);
if ( match )
return {
url: href,
id: match[1]
}
},
get_info: function(info) { return new Promise(function(s,f) {
utils.api.get("videos/" + info.id, undefined, {version: 5}).then(function(data) {
var published = utils.parse_date(data.recorded_at),
now = new Date,
raw_age = (now - published) / 1000,
age = raw_age >= 86400 ? published.toLocaleDateString() : utils.full_human_time(raw_age);
s({
image: data.preview.small,
title: utils.sanitize(data.title || 'Untitled Video'),
url: info.url,
by_lines: [
'<span class="user-token" data-user="' + utils.quote_attr(data.channel.name) + '">' + utils.sanitize(data.channel.display_name) + '</span>' +
(data.game === 'Creative' ? ' being Creative' : data.game ? ' playing ' + utils.sanitize(data.game) : ''),
utils.time_to_string(data.length || 0) +
' &mdash; ' + utils.number_commas(data.views) + ' Views' +
(published ?
' &mdash; <span class="html-tooltip" title="Published: <nobr>' +
utils.quote_san(published.toLocaleString()) + '</nobr>">' +
utils.sanitize(age) +
'</span>' : '')
]
})
}).fail(f)
})}
},
clip: {
token_type: 'link',
remove_token: true,
get_info: function(info) { return new Promise(function(s,f) {
var Clips = utils.ember_lookup('service:clips');
if ( ! Clips )
return f();
Clips.fetchClipBySlug(info.slug).then(function(data) {
s({
image: data.thumbnails.tiny,
title: utils.sanitize(data.title || 'Untitled Clip'),
url: info.url,
by_lines: [
'<span class="user-token" data-user="' + utils.quote_attr(data.broadcaster_login) + '">' + utils.sanitize(data.broadcaster_display_name) + '</span>' +
(data.game === 'Creative' ? ' being Creative' : data.game ? ' playing ' + utils.sanitize(data.game) : ''),
'Clipped by <span class="user-token" data-user="' + utils.quote_attr(data.curator_login) + '">' + utils.sanitize(data.curator_display_name) + '</span> &mdash; ' +
utils.number_commas(data.views) + ' View' + utils.pluralize(data.views)
]
});
}).catch(f)
})},
extract: function(token) {
var href = token.link || token.text,
match = CLIP_URL.exec(href);
if ( match )
return {
url: href,
slug: match[1]
}
}
}
}
FFZ.prototype.tokenize_chat_line = function(msgObject, prevent_notification, delete_links, disable_cache) {
if ( msgObject.cachedTokens && ! disable_cache )
return msgObject.cachedTokens;
@ -749,6 +1028,11 @@ FFZ.prototype.tokenize_chat_line = function(msgObject, prevent_notification, del
if ( helpers && helpers.emoticonizeMessage && this.settings.parse_emoticons )
tokens = helpers.emoticonizeMessage(tokens, emotes);
// Pre-load emote information.
if ( emotes )
for(var emote_id in emotes)
this.get_twitch_set_for(emote_id);
// FrankerFaceZ Extras
tokens = this._remove_banned(tokens);
@ -898,6 +1182,12 @@ FFZ.prototype.tokenize_chat_line = function(msgObject, prevent_notification, del
// Tokenize users last.
tokens = this.tokenize_users(tokens);
// Take care of rich content.
if ( ! tags.content )
tags.content = {};
this.tokenize_rich_content(tokens, tags.content);
if ( ! disable_cache )
msgObject.cachedTokens = tokens;
@ -951,6 +1241,11 @@ FFZ.prototype.tokenize_feed_body = function(message, emotes, user_id, room_id) {
if ( helpers && helpers.emoticonizeMessage && this.settings.parse_emoticons )
message = helpers.emoticonizeMessage(message, emotes);
// Pre-load emote information.
if ( emotes )
for(var emote_id in emotes)
this.get_twitch_set_for(emote_id);
// Tokenize Lines
var tokens = [], token;
@ -986,7 +1281,7 @@ FFZ.prototype.render_token = function(render_links, warn_links, render_bits, tok
if ( ! token )
return "";
if ( token.hidden )
if ( token.hidden || (this.settings.chat_rich_content && token.rich_removed) )
return "";
else if ( token.type === "raw" )
@ -1076,16 +1371,15 @@ FFZ.prototype.render_token = function(render_links, warn_links, render_bits, tok
video_info = VIDEO_URL.exec(href);
if ( clip_info ) {
var clips = utils.ember_lookup('service:store');
clips && clips.findRecord && clips.findRecord('clip', clip_info[1]).then(function(data) {
//clips && clips.getClipInfo(clip_info[1]).then(function(data) {
var Clips = utils.ember_lookup('service:clips');
Clips && Clips.fetchClipBySlug(clip_info[1]).then(function(data) {
data &&
success(true, {
image: data.get('scaledPreviewUrl'),
image: data.thumbnails.medium,
image_iframe: false,
html: '<span class="ffz-clip-title">' + utils.sanitize(data.get('title')) + '</span>' +
'Channel: ' + utils.sanitize(data.get('broadcasterDisplayName')) +
'<br>Game: ' + utils.sanitize(data.get('game'))
html: '<span class="ffz-clip-title">' + utils.sanitize(data.title) + '</span>' +
'Channel: ' + utils.sanitize(data.broadcaster_display_name) +
'<br>Game: ' + utils.sanitize(data.game)
});
});
@ -1286,8 +1580,24 @@ FFZ.prototype.tokenize_emotes = function(user, room, tokens, do_report) {
if ( token.type === "text" )
token = token.text;
else {
if ( ! token.modifiers && token.type === 'emoticon' )
token.modifiers = [];
if ( token.type === 'emoticon' ) {
emote = emotes[token.altText];
if ( emote && emote.replaces ) {
token = _.extend({}, emote.token);
token.modifiers = [];
new_tokens.push(token);
last_token = token;
if ( do_report && room )
this.add_usage(room, emote);
continue;
}
if ( ! token.modifiers )
token.modifiers = [];
}
new_tokens.push(token);
last_token = token;

View file

@ -453,17 +453,18 @@ FFZ.menu_pages.channel = {
has_product = true;
var Ticket = utils.ember_resolve('model:ticket'),
tickets = Ticket && Ticket.find('user', {channel: room_id}),
is_subscribed = tickets ? tickets.get('content') : false,
subbed_products = _.pluck(tickets && tickets.get('content') || [], 'product'),
subbed_plans = _.pluck(subbed_products, 'short_name'),
subbed_emote_sets = _.uniq(_.flatten(_.map(subbed_products, function(x) { return x.features.emoticon_set_ids }))),
is_subscribed = subbed_plans.length > 0,
is_loaded = tickets ? tickets.get('isLoaded') : false,
icon = room.room.get("badgeSet.subscriber.image"),
icon,
grid = document.createElement("div"),
header = document.createElement("div"),
c = 0;
// Weird is_subscribed check. Might be more accurate?
is_subscribed = is_subscribed && is_subscribed.length > 0;
// See if we've loaded. If we haven't loaded the ticket yet
// then try loading it, and then re-render the menu.
if ( tickets && ! is_subscribed && ! is_loaded ) {
@ -481,6 +482,10 @@ FFZ.menu_pages.channel = {
tickets.load();
}
// Null-conditional try/catch
try {
icon = room.badges.subscriber.versions[0].image_url_1x;
} catch(err) { }
grid.className = "emoticon-grid top-set";
header.className = "heading";
@ -493,108 +498,168 @@ FFZ.menu_pages.channel = {
header.innerHTML = '<span class="right">Twitch</span>Subscriber Emoticons';
grid.appendChild(header);
var known_sets = [];
var all_emotes = {},
plans = product.get("plans") || [],
pwe = 0;
for(var emotes=product.get("emoticons") || [], i=0; i < emotes.length; i++) {
var emote = emotes[i];
if ( emote.state !== "active" )
continue;
if ( ! plans.length ) {
// If we have a product with no defined plans, fake a plan with the required
// information to keep our script happy.
plans.push({
name: product.name,
product_url: product.product_url,
price: product.price,
var s = document.createElement('span'),
can_use = is_subscribed || !emote.subscriber_only,
img_set = 'image-set(url("' + constants.TWITCH_BASE + emote.id + '/1.0") 1x, url("' + constants.TWITCH_BASE + emote.id + '/2.0") 2x), url("' + constants.TWITCH_BASE + emote.id + '/3.0") 4x)';
emoticon_set_ids: _.uniq(_.pluck(product.emoticons, 'emoticon_set')),
emoticons: product.emoticons
})
}
s.className = 'emoticon ffz-tooltip ffz-tooltip-no-credit' + (!can_use ? " locked" : "");
for(var i=0; i < plans.length; i++) {
var plan = plans[i],
emotes = plan.emoticons || [],
jm = emotes.length;
if ( known_sets.indexOf(emote.emoticon_set) === -1 )
known_sets.push(emote.emoticon_set);
if ( jm > 0 )
pwe++;
if ( emote.emoticon_set ) {
var favs = this.settings.favorite_emotes["twitch-" + emote.emoticon_set];
s.classList.add('ffz-can-favorite');
s.classList.toggle('ffz-favorite', favs && favs.indexOf(emote.id) !== -1 || false);
for(var j = 0; j < jm; j++) {
var emote = emotes[j];
if ( emote.state !== "active" )
continue;
var ae = all_emotes[emote.regex] = all_emotes[emote.regex] || [];
ae.push([emote, i]);
}
}
for(var ek in all_emotes) {
var ems = all_emotes[ek];
for(var i=0, l = ems.length; i < l; i++) {
var emote = ems[i][0],
plan_idx = ems[i][1],
plan = plans[plan_idx],
s = utils.createElement('span', 'emoticon ffz-tooltip ffz-tooltip-no-credit'),
set_id = emote.emoticon_set,
can_use = ! emote.subscriber_only || subbed_emote_sets.indexOf(set_id) !== -1,
img_set = utils.build_srcset(emote.id);
if ( ! can_use ) {
s.classList.add('locked');
s.dataset.sellout = 'Subscribe for ' + utils.sanitize(plan.price) + ' to unlock <nobr>this emote.</nobr>';
}
if ( set_id ) {
var faves = this.settings.favorite_emotes["twitch-" + set_id];
s.classList.add('ffz-can-favorite');
if ( faves && faves.indexOf(emote.id) !== -1 )
s.classList.add('ffz-favorite');
}
s.dataset.plan = plan_idx;
s.dataset.emote = emote.id;
s.dataset.set = set_id;
s.alt = emote.regex;
s.style.backgroundImage = 'url("' + constants.TWITCH_BASE + emote.id + '/1.0")';
s.style.backgroundImage = img_set;
s.style.width = (10 + emote.width) + "px";
s.style.height = (10 + emote.height) + "px";
s.addEventListener('click', function(can_use, emote_id, code, set_id, plan_idx, e) {
e.preventDefault();
if ( ( e.shiftKey || e.shiftLeft) && this.settings.clickable_emoticons )
window.open("https://twitchemotes.com/emote/" + emote_id);
else if ( can_use )
this._add_emote(view, code, "twitch-" + set_id, emote_id, e);
else {
var plan = plans[plan_idx],
url = plan && plan.product_url;
url && window.open(url);
}
}.bind(this, can_use, emote.id, emote.regex, set_id, plan_idx));
grid.appendChild(s);
c++;
}
}
if ( c > 0 ) {
inner.appendChild(grid);
var msg;
if ( ! is_loaded ) {
msg = 'Loading sub information...';
} else if ( ! is_subscribed && plans.length ) {
var sub_message = utils.createElement('div', null, 'Subscribe to unlock ' + (pwe === 1 ? utils.number_commas(c) : 'some') + ' Sub Emotes'),
sub_container = utils.createElement('div', 'mg-l-1 mg-r-1 align-center', sub_message),
had_prime = false,
filter_grid = function(price, sets) {
var kids = grid.querySelectorAll('.emoticon'),
count = 0;
for(var i=0, l = kids.length; i < l; i++) {
var emote = kids[i],
set_id = parseInt(emote.dataset.set),
would_unlock = sets ? sets.indexOf(set_id) !== -1 : false;
if ( would_unlock )
count++;
emote.classList.toggle('unlocked', would_unlock);
}
sub_message.textContent = price ?
'Subscribe for ' + price +
' to unlock ' + utils.number_commas(count) + ' Sub Emotes'
: sub_message.dataset.original;
};
sub_message.dataset.original = sub_message.textContent;
for(var i=0; i < plans.length; i++) {
var plan = plans[i],
btn = utils.createElement('a', 'ffz-sub-button button mg-t-1', utils.sanitize(plan.price));
sub_container.appendChild(btn);
btn.href = plan.product_url;
btn.target = '_blank';
btn.rel = 'noopener noreferrer';
btn.addEventListener('mouseover', filter_grid.bind(this, plan.price, plan.emoticon_set_ids));
btn.addEventListener('mouseout', filter_grid.bind(this, null, null));
}
inner.appendChild(sub_container);
} else if ( plans.length ) {
// We are subscribed. Check to see if the subscription will expire.
var content = tickets.get('content.lastObject'),
pp = content && content.purchase_profile,
ends_at = content && utils.parse_date(content.access_end);
if ( pp && ends_at ) {
var now = Date.now() - (this._ws_server_offset || 0),
end_time = ends_at ? Math.floor((ends_at.getTime() - now) / 1000) : null,
provider = pp.payment_provider,
renews = pp.will_renew;
msg = 'Subscription ' + (renews ? 'renews' : 'expires') +
' in ' + utils.time_to_string(end_time, true, true);
}
}
s.setAttribute('data-emote', emote.id);
s.alt = emote.regex;
if ( msg ) {
var sub_message = utils.createElement('div', null, msg),
sub_container = utils.createElement('div', 'mg-l-1 mg-r-1 align-center', sub_message);
s.style.backgroundImage = 'url("' + constants.TWITCH_BASE + emote.id + '/1.0")';
s.style.backgroundImage = '-webkit-' + img_set;
s.style.backgroundImage = '-moz-' + img_set;
s.style.backgroundImage = '-ms-' + img_set;
s.style.backgroundImage = img_set;
s.style.width = (10+emote.width) + "px";
s.style.height = (10+emote.height) + "px";
s.addEventListener('click', function(can_use, id, code, emote_set, e) {
if ( (e.shiftKey || e.shiftLeft) && f.settings.clickable_emoticons )
window.open("https://twitchemotes.com/emote/" + id);
else if ( can_use )
this._add_emote(view, code, "twitch-" + emote_set, id, e);
else
return;
e.preventDefault();
}.bind(this, can_use, emote.id, emote.regex, emote.emoticon_set));
grid.appendChild(s);
c++;
}
if ( reported_sets.indexOf(product.get('id')) === -1 && known_sets.length ) {
reported_sets.push(product.get('id'));
this.log("Sets for " + product.get('id') + " [" + product.get('ticketProductId') + "]: " + JSON.stringify(known_sets));
this.ws_send("report_twitch_set", [product.get('id'), product.get('ticketProductId'), known_sets]);
}
if ( c > 0 )
inner.appendChild(grid);
if ( c > 0 && ! is_subscribed ) {
var sub_message = document.createElement("div"),
nonsub_message = document.createElement("div"),
unlock_text = document.createElement("span"),
sub_link = document.createElement("a");
sub_message.className = "subscribe-message";
nonsub_message.className = "non-subscriber-message";
sub_message.appendChild(nonsub_message);
unlock_text.className = "unlock-text";
unlock_text.innerHTML = "Subscribe to unlock Emoticons";
nonsub_message.appendChild(unlock_text);
sub_link.className = "action js-sub-button subscribe-button button button--purchase";
sub_link.href = product.get("product_url");
sub_link.innerHTML = '<span class="subscribe-text">Subscribe</span><span class="subscribe-price button__num-block">' + product.get("price") + '</span>';
nonsub_message.appendChild(sub_link);
inner.appendChild(sub_message);
} else if ( c > 0 ) {
var last_content = tickets.get("content");
last_content = last_content.length > 0 ? last_content[last_content.length-1] : undefined;
if ( last_content && last_content.purchase_profile && !last_content.purchase_profile.will_renew ) {
var ends_at = utils.parse_date(last_content.access_end || ""),
provider = last_content.purchase_profile && last_content.purchase_profile.payment_provider,
sub_message = document.createElement("div"),
nonsub_message = document.createElement("div"),
unlock_text = document.createElement("span"),
now = Date.now() - (this._ws_server_offset || 0),
end_time = ends_at ? Math.floor((ends_at.getTime() - now) / 1000) : null;
sub_message.className = "subscribe-message";
nonsub_message.className = "non-subscriber-message";
sub_message.appendChild(nonsub_message);
unlock_text.className = "unlock-text";
unlock_text.innerHTML = "Subscription expires in " + utils.time_to_string(end_time, true, true);
if ( provider === "samus" )
unlock_text.innerHTML += '<br>(Twitch Prime Free Sub)';
nonsub_message.appendChild(unlock_text);
inner.appendChild(sub_message);
inner.appendChild(sub_container);
}
}
}

View file

@ -80,6 +80,12 @@ FFZ.settings_info.favorite_emotes = {
FFZ.prototype.setup_my_emotes = function() {
var UserEmotes = utils.ember_lookup('service:user-emotes');
if ( UserEmotes ) {
this.modify_user_emotes(UserEmotes);
UserEmotes.ffzUpdateData();
}
this._twitch_badges = {};
this._twitch_badges["--inventory--"] = "//cdn.frankerfacez.com/script/inventory_icon.svg";
this._twitch_badges["--global--"] = "//cdn.frankerfacez.com/script/twitch_logo.png";
@ -89,6 +95,27 @@ FFZ.prototype.setup_my_emotes = function() {
}
FFZ.prototype.modify_user_emotes = function(service) {
var f = this;
service.reopen({
ffzUpdateData: function() {
var emotes = (this.get('allEmotes') || {})['emoticon_sets'] || {};
for(var set_id in emotes) {
f.get_twitch_set(set_id);
var es = emotes[set_id] || [],
esl = es.length;
for(var i=0; i < esl; i++)
f._twitch_emote_to_set[es[i].id] = set_id;
}
if ( f._inputv )
Ember.propertyDidChange(f._inputv, 'ffz_emoticons');
}.observes('allEmotes')
});
}
// -------------------
// Menu Page
// -------------------
@ -101,7 +128,7 @@ FFZ.menu_pages.myemotes = {
var user = this.get_user(),
controller = utils.ember_lookup('controller:chat'),
user_emotes = utils.ember_lookup('service:user-emotes'),
twitch_sets = (user_emotes && user_emotes.allEmotes || {'emoticon_sets': {}})['emoticon_sets'] || {},
twitch_sets = (user_emotes && user_emotes.allEmotes || {})['emoticon_sets'] || {},
ffz_sets = user && this.users[user.login] && this.users[user.login].sets || [],
sk = twitch_sets && Object.keys(twitch_sets);
@ -229,7 +256,7 @@ FFZ.menu_pages.myemotes = {
render_lists: function(view, container, favorites_only) {
var controller = utils.ember_lookup('controller:chat'),
user_emotes = utils.ember_lookup('service:user-emotes'),
twitch_sets = (user_emotes && user_emotes.allEmotes || {'emoticon_sets': {}})['emoticon_sets'] || {},
twitch_sets = (user_emotes && user_emotes.allEmotes || {})['emoticon_sets'] || {},
user = this.get_user(),
ffz_sets = this.getEmotes(user && user.login, null),
@ -261,7 +288,8 @@ FFZ.menu_pages.myemotes = {
continue;
}
var raw_id = this._twitch_set_to_channel[set_id],
var raw_data = this.get_twitch_set(set_id),
raw_id = raw_data && raw_data.c_name,
menu_id = raw_id ? raw_id.toLowerCase() : 'unknown',
favorites_list = this.settings.favorite_emotes["twitch-" + set_id];
@ -528,7 +556,8 @@ FFZ.menu_pages.myemotes = {
collapsed = ! favorites_only && this.settings.emote_menu_collapsed.indexOf('twitch-' + set_id) === -1,
f = this,
channel_id = set_id === 'inventory' ? '--inventory--' : (this._twitch_set_to_channel[set_id] || 'twitch_unknown'), title,
set_data = this.get_twitch_set(set_id),
channel_id = set_id === 'inventory' ? '--inventory--' : (set_data && set_data.c_name || 'twitch_unknown'), title,
favorites = this.settings.favorite_emotes["twitch-" + set_id] || [],
c = 0;
@ -561,16 +590,18 @@ FFZ.menu_pages.myemotes = {
heading.style.backgroundImage = 'url("' + icon + '")';
if ( icon.indexOf('.svg') !== -1 )
heading.style.backgroundSize = "18px";
} else {
} else if ( set_data ) {
var f = this;
utils.api.get("chat/" + channel_id + "/badges", null, {version: 3})
.done(function(data) {
if ( data.subscriber && data.subscriber.image ) {
f._twitch_badges[channel_id] = data.subscriber.image;
localStorage.ffzTwitchBadges = JSON.stringify(f._twitch_badges);
heading.style.backgroundImage = 'url("' + data.subscriber.image + '")';
}
});
fetch("https://badges.twitch.tv/v1/badges/channels/" + set_data.c_id + "/display?language=" + (Twitch.receivedLanguage || "en"), {
headers: {
'Client-ID': constants.CLIENT_ID
}
}).then(utils.json).then(function(data) {
try {
var badge = f._twitch_badges[channel_id] = data.badge_sets.subscriber.versions[0].image_url_1x;
heading.style.backgroundImage = 'url("' + badge + '")';
} catch(err) { /* Lament JS's lack of null coalescing operators */ }
});
}
menu.classList.add('collapsable');

View file

@ -564,6 +564,32 @@ body.ffz-bttv-dark .ffz-ui-toggle.blue.live:hover svg.svg-emoticons path { fill:
/* Dark Menu */
.ffz-ui-popup.emoticon-selector .emoticon-selector-box .emoticon.locked:before {
background-color: rgba(255,255,255,0.5);
display: block;
width: 100%;
height: 100%;
padding: 0;
background-position: calc(100% - 2.5px) calc(100% - 2.5px);
}
.ffz-dark .ffz-ui-popup.emoticon-selector .emoticon-selector-box .emoticon.locked:before,
.theatre .ffz-ui-popup.emoticon-selector .emoticon-selector-box .emoticon.locked:before,
.theme--dark .ffz-ui-popup.emoticon-selector .emoticon-selector-box .emoticon.locked:before,
.dark .ffz-ui-popup.emoticon-selector .emoticon-selector-box .emoticon.locked:before {
background-color: rgba(16,16,16,0.5);
}
.ffz-sub-button + .ffz-sub-button { border-left: 1px solid #ffffff }
.ffz-dark .ffz-sub-button + .ffz-sub-button,
.theatre .ffz-sub-button + .ffz-sub-button,
.theme--dark .ffz-sub-button + .ffz-sub-button,
.dark .ffz-sub-button + .ffz-sub-button { border-left-color: #101010 }
.emoticon.locked.unlocked:before { display: none !important }
#ffz-chat-menu { background-color: transparent !important; }
.ffz-dark .ember-chat .chat-menu .list-header,
@ -2660,6 +2686,8 @@ body:not([data-current-path^="user."]) .ffz-sidebar-swap .ember-chat .chat-inter
background-color: #191919;
}
.ffz-no-blue .theme--dark .chat-chip,
.ffz-no-blue .app-main.theatre .bits-footer,
.ffz-no-blue .theme--dark .bits-footer,
.ffz-no-blue .dark .bits-footer,
@ -2745,10 +2773,23 @@ body:not([data-current-path^="user."]) .ffz-sidebar-swap .ember-chat .chat-inter
background-color: #080808;
}
.ffz-no-blue .theme--dark .chat-commerce-rich-content__drawer,
.ffz-no-blue .top-nav-drawer {
background-color: #101010;
}
.ffz-no-blue .theme--dark .chat-commerce-right-content__pocket--button:hover {
background-color: #171717;
}
.theme--dark .chat-chip {
box-shadow: 0 1px 2px 0 rgba(0,0,0,.85);
}
.ffz-rich-content p.card__info {
line-height: 1.4rem;
}
/* Following Count */
li[data-name="following"] a {
@ -2993,6 +3034,7 @@ li[data-name="following"] a {
/* Directory Logos */
.chat-commerce-right-content__pocket--button > *,
.item .meta .title a a,
.item .meta .title a img { pointer-events: none }
@ -3713,7 +3755,7 @@ body.ffz-bttv #ffz-feed-tabs .tabs { margin-bottom: 0 }
margin: 0 5px;
}
.ffz-bit:after {
.ffz-bit:not(.inventory-bits__image):after {
content: attr(data-amount);
}