1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-06-27 21:05:53 +00:00
**Note**: This update does not add proper support for the 'Message Effects' power-up. I still need to investigate how to best implement that, since Twitch's implementation uses a canvas and custom rendering logic.

* Added: Support for the "Gigantify an Emote" power-up.
* Added: Support for "Gigantifing" an emote using a community points reward. Just set up a custom reward that lets a user enter a message, and have the exact string `FFZ:GE` somewhere in the reward's title or description. Only appears as giant for users with FrankerFaceZ. This was added because the native power-up does not support emotes from add-ons.
* Added: Option to disable "Gigantifying" emotes.
* Fixed: Use the correct currency when displaying a community redemption that cost bits rather than points.
* Fixed: The emote menu not appearing correctly when choosing an emote for the "Gigantify an Emote" and "On-Screen Celebration" power-ups.
This commit is contained in:
SirStendec 2024-06-14 14:27:26 -04:00
parent 340551dc83
commit f79f1ba21d
22 changed files with 176 additions and 31 deletions

View file

@ -923,6 +923,20 @@
"search": [
"bluesky"
]
},
{
"uid": "32afa7efa546b2a97abc8247aa37cd95",
"css": "bits",
"code": 59473,
"src": "custom_icons",
"selected": true,
"svg": {
"path": "M500 100L150 600 500 900 850 600 500 100ZM500 274.4L691.1 583.1 500 652.5 308.9 583.1 500 274.4Z",
"width": 1000
},
"search": [
"bits"
]
}
]
}

View file

@ -1,7 +1,7 @@
{
"name": "frankerfacez",
"author": "Dan Salvato LLC",
"version": "4.72.1",
"version": "4.72.2",
"description": "FrankerFaceZ is a Twitch enhancement suite.",
"private": true,
"license": "Apache-2.0",

Binary file not shown.

View file

@ -1,7 +1,7 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg">
<metadata>Copyright (C) 2023 by original authors @ fontello.com</metadata>
<metadata>Copyright (C) 2024 by original authors @ fontello.com</metadata>
<defs>
<font id="ffz-fontello" horiz-adv-x="1000" >
<font-face font-family="ffz-fontello" font-weight="400" font-stretch="normal" units-per-em="1000" ascent="850" descent="-150" />
@ -168,6 +168,8 @@
<glyph glyph-name="bluesky" unicode="&#xe850;" d="M563 406c-51 99-190 284-319 375-124 87-171 72-202 58-35-16-42-71-42-103 0-32 18-265 29-304 38-128 174-171 299-157 7 1 13 1 20 2-6-1-13-2-20-2-183-28-346-94-132-332 235-244 322 52 367 202 44-150 96-435 362-202 200 202 55 304-128 332-7 0-13 1-20 2 7-1 13-1 20-2 125-14 261 29 299 157 11 39 29 272 29 304 0 32-6 87-42 103-31 14-78 29-202-58-129-91-268-276-318-375z" horiz-adv-x="1125" />
<glyph glyph-name="bits" unicode="&#xe851;" d="M500 750l-350-500 350-300 350 300-350 500z m0-174l191-309-191-69-191 69 191 309z" horiz-adv-x="1000" />
<glyph glyph-name="move" unicode="&#xf047;" d="M1000 350q0-14-11-25l-142-143q-11-11-26-11t-25 11-10 25v72h-215v-215h72q14 0 25-10t11-25-11-25l-143-143q-10-11-25-11t-25 11l-143 143q-11 10-11 25t11 25 25 10h72v215h-215v-72q0-14-10-25t-25-11-25 11l-143 143q-11 11-11 25t11 25l143 143q10 11 25 11t25-11 10-25v-72h215v215h-72q-14 0-25 10t-11 25 11 26l143 142q11 11 25 11t25-11l143-142q11-11 11-26t-11-25-25-10h-72v-215h215v72q0 14 10 25t25 11 26-11l142-143q11-10 11-25z" horiz-adv-x="1000" />
<glyph glyph-name="link-ext" unicode="&#xf08e;" d="M786 332v-178q0-67-47-114t-114-47h-464q-67 0-114 47t-47 114v464q0 66 47 113t114 48h393q7 0 12-5t5-13v-36q0-8-5-13t-12-5h-393q-37 0-63-26t-27-63v-464q0-37 27-63t63-27h464q37 0 63 27t26 63v178q0 8 5 13t13 5h36q8 0 13-5t5-13z m214 482v-285q0-15-11-25t-25-11-25 11l-98 98-364-364q-5-6-13-6t-12 6l-64 64q-6 5-6 12t6 13l364 364-98 98q-11 11-11 25t11 25 25 11h285q15 0 25-11t11-25z" horiz-adv-x="1000" />

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Before After
Before After

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -520,6 +520,16 @@ export default class Emotes extends Module {
}
});
this.settings.add('chat.emotes.allow-gigantify', {
default: true,
ui: {
path: 'Chat > Appearance >> Emotes',
title: 'Allow "Gigantify an Emote" Power-Up',
description: 'How big is too big? Giant? Disable this and the emotes will be displayed normally.',
component: 'setting-check-box'
}
});
this.settings.add('chat.fix-bad-emotes', {
default: true,
ui: {

View file

@ -11,7 +11,7 @@ import Module, { buildAddonProxy } from 'utilities/module';
import {Color} from 'utilities/color';
import {createElement, ManagedStyle} from 'utilities/dom';
import {getFontsList} from 'utilities/fonts';
import {timeout, has, addWordSeparators, glob_to_regex, escape_regex, split_chars, makeAddonIdChecker, deep_copy, SourcedSet} from 'utilities/object';
import {timeout, has, addWordSeparators, glob_to_regex, escape_regex, split_chars, makeAddonIdChecker, deep_copy, SourcedSet, getTwitchEmoteURL} from 'utilities/object';
import Badges from './badges';
import Emotes from './emotes';
@ -2492,6 +2492,29 @@ export default class Chat extends Module {
}
pluckLastEmote(tokens) {
if ( ! Array.isArray(tokens) )
return;
let i = tokens.length;
while(i--) {
const token = tokens[i];
if (token.type === 'emote' && token.provider !== 'emoji') {
if ( this.context.get('chat.emotes.allow-gigantify') ) {
token.gigantify = true;
token.hidden = true;
return token;
} else {
if (token.gigantify)
token.hidden = false;
return null;
}
} else if (token.hidden)
continue;
}
}
pluckRichContent(tokens, msg) { // eslint-disable-line class-methods-use-this
if ( ! this.context.get('chat.rich.enabled') || this.context.get('chat.rich.minimum-level') > this.getUserLevel(msg) )
return;
@ -2544,6 +2567,58 @@ export default class Chat extends Module {
}
renderGiantEmote(token, e) {
if ( ! e )
e = createElement;
const animated = token.anim === 1;
let src, hoverSrc, height;
if (token.provider === 'twitch') {
src = getTwitchEmoteURL(token.id, 4, animated, true);
height = 112;
} else if (token.provider === 'ffz') {
const emote_set = this.emotes.emote_sets[token.set],
emote = emote_set?.emotes?.[token.id];
if ( emote ) {
const urls = emote.urls;
if ( urls?.[4] ) {
src = urls[4];
height = emote.height * 4;
} else if ( urls?.[2] ) {
src = urls[2];
height = emote.height * 2;
} else if ( urls?.[1] ) {
src = urls[1];
height = emote.height;
}
}
} else
src = null;
if ( ! src )
return null;
return e('img', {
className: `chat-image chat-line__message--emote ffz--pointer-events ffz-tooltip${hoverSrc ? ' ffz-hover-emote' : ''}${token.provider === 'twitch' ? ' twitch-emote' : token.provider === 'ffz' ? ' ffz-emote' : token.provider === 'emoji' ? ' ffz-emoji' : ''}`,
src,
height: `${height}px`,
alt: token.text,
'data-tooltip-type': 'emote',
'data-provider': token.provider,
'data-id': token.id,
'data-set': token.set,
'data-code': token.code,
'data-variant': token.variant,
onClick: this.emotes.handleClick
});
}
renderTokens(tokens, e, reply) {
if ( ! e )
e = createElement;

View file

@ -461,7 +461,11 @@ export default class EmoteMenu extends Module {
cls.prototype.render = function() {
this._ffz_no_scan = false;
if ( ! this.props || ! has(this.props, 'channelID') || ! t.chat.context.get('chat.emote-menu.enabled') ) {
if ( ! this.props ||
this.props.emotePickerSource === 'bits-rewards' ||
! has(this.props, 'channelID') ||
! t.chat.context.get('chat.emote-menu.enabled')
) {
return old_render.call(this);
}

View file

@ -140,11 +140,10 @@ const CHAT_TYPES = make_enum(
'CommunityIntroduction',
'Shoutout',
'AnnouncementMessage',
'MidnightSquid',
'CharityDonation',
'MessageIdUpdate',
'PinnedChat',
'ViewerMilestone'
'ViewerMilestone',
'GigantifiedEmote'
);

View file

@ -12,7 +12,7 @@ import { has } from 'utilities/object';
import { KEYS, RERENDER_SETTINGS, UPDATE_BADGE_SETTINGS, UPDATE_TOKEN_SETTINGS } from 'utilities/constants';
import { print_duration } from 'utilities/time';
import { getRewardTitle, getRewardCost } from './points';
import { getRewardTitle, getRewardCost, isGiantEmoteReward, doesRewardCostBits } from './points';
import awaitMD, {getMD} from 'utilities/markdown';
const SUB_TIERS = {
@ -203,13 +203,14 @@ export default class ChatLine extends Module {
// We need to get the message's tokens to see if it has a message or not.
const user = msg.user,
is_bits = doesRewardCostBits(msg.ffz_reward),
tokens = msg.ffz_tokens = msg.ffz_tokens || this.chat.tokenizeMessage(msg, current_user),
has_message = tokens.length > 0;
// Elements for the reward and cost with nice formatting.
const reward = e('span', {className: 'ffz--points-reward'}, getRewardTitle(msg.ffz_reward, this.i18n)),
cost = e('span', {className: 'ffz--points-cost'}, [
e('span', {className: 'ffz--points-icon'}),
e('span', {className: is_bits ? 'ffz-i-bits' : 'ffz--points-icon'}),
this.i18n.formatNumber(getRewardCost(msg.ffz_reward))
]);
@ -1065,6 +1066,8 @@ other {# messages were deleted by a moderator.}
: null;
const is_action = t.parent.message_types && t.parent.message_types.Action === msg.messageType,
is_giant_emote = this.props.isLastEmoteGigantified || isGiantEmoteReward(msg.ffz_reward),
action_style = is_action ? t.chat.context.get('chat.me-style') : 0,
action_italic = action_style >= 2,
action_color = action_style === 1 || action_style === 3;
@ -1072,7 +1075,8 @@ other {# messages were deleted by a moderator.}
const raw_color = t.overrides.getColor(user.id) || user.color,
color = t.parent.colors.process(raw_color);
const rich_content = show && FFZRichContent && t.chat.pluckRichContent(tokens, msg);
const rich_content = show && FFZRichContent && t.chat.pluckRichContent(tokens, msg),
giant_emote = is_giant_emote && t.chat.pluckLastEmote(tokens, msg);
// First, render the user block.
const username = t.chat.formatUser(user, e),
@ -1184,6 +1188,11 @@ other {# messages were deleted by a moderator.}
// Rich Content
rich_content
? e(FFZRichContent, rich_content)
: null,
// Giant Emote
giant_emote
? e('div', {className: 'chat-line__message--ffz-giant-emote'}, t.chat.renderGiantEmote(giant_emote, e))
: null
];
}

View file

@ -1,3 +1,7 @@
export function doesRewardCostBits(reward) {
return reward.pricingType === 'BITS';
}
export function isAutomaticReward(reward) {
return reward?.__typename === 'CommunityPointsAutomaticReward';
}
@ -10,11 +14,21 @@ export function isHighlightedReward(reward) {
return isAutomaticReward(reward) && reward.type === 'SEND_HIGHLIGHTED_MESSAGE';
}
export function getRewardCost(reward) {
if ( isAutomaticReward(reward) )
return reward.cost || reward.defaultCost;
export function isGiantEmoteReward(reward) {
return reward && (reward.title?.includes?.('FFZ:GE') ||
reward.prompt?.includes?.('FFZ:GE'));
}
return reward.cost;
export function getRewardCost(reward) {
const is_bits = doesRewardCostBits(reward);
if ( isAutomaticReward(reward) )
return is_bits
? (reward.bitsCost || reward.defaultBitsCost)
: (reward.cost || reward.defaultCost);
return is_bits
? reward.bitsCost
: reward.cost;
}
export function getRewardColor(reward) {
@ -29,6 +43,8 @@ export function getRewardTitle(reward, i18n) {
return reward.title;
switch(reward.type) {
case 'SEND_ANIMATED_MESSAGE':
return i18n.t('chat.points.animated', 'Message Effects');
case 'SEND_HIGHLIGHTED_MESSAGE':
return i18n.t('chat.points.highlighted', 'Highlight My Message');
case 'SINGLE_MESSAGE_BYPASS_SUB_MODE':
@ -42,4 +58,4 @@ export function getRewardTitle(reward, i18n) {
default:
return i18n.t('chat.points.reward', 'Reward');
}
}
}

View file

@ -1,4 +1,9 @@
.twitch-emote {
max-height: 32px;
max-width: 64px;
}
}
.chat-line__message--ffz-giant-emote .twitch-emote {
max-height: 128px;
max-width: 256px;
}

View file

@ -3,6 +3,10 @@
overflow: unset !important;
}
.chat-line__message--ffz-giant-emote {
padding: 0.5rem;
}
.chat-line__message--emote {
vertical-align: middle;
margin: -.5rem 0;

View file

@ -135,7 +135,8 @@ export const RERENDER_SETTINGS = [
'chat.bits.cheer-notice',
'chat.filtering.hidden-tokens',
'chat.hype.message-style',
'chat.filtering.show-reasons'
'chat.filtering.show-reasons',
'chat.emotes.allow-gigantify'
] as const;
/**

View file

@ -123,5 +123,6 @@ export default [
"artist",
"discord",
"tiktok",
"bluesky"
"bluesky",
"bits"
] as const;

View file

@ -80,6 +80,7 @@
.ffz-i-artist:before { content: '\e84e'; } /* '' */
.ffz-i-discord:before { content: '\e84f'; } /* '' */
.ffz-i-bluesky:before { content: '\e850'; } /* '' */
.ffz-i-bits:before { content: '\e851'; } /* '' */
.ffz-i-move:before { content: '\f047'; } /* '' */
.ffz-i-link-ext:before { content: '\f08e'; } /* '' */
.ffz-i-twitter:before { content: '\f099'; } /* '' */

File diff suppressed because one or more lines are too long

View file

@ -80,6 +80,7 @@
.ffz-i-artist { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe84e;&nbsp;'); }
.ffz-i-discord { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe84f;&nbsp;'); }
.ffz-i-bluesky { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe850;&nbsp;'); }
.ffz-i-bits { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe851;&nbsp;'); }
.ffz-i-move { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf047;&nbsp;'); }
.ffz-i-link-ext { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf08e;&nbsp;'); }
.ffz-i-twitter { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf099;&nbsp;'); }

View file

@ -91,6 +91,7 @@
.ffz-i-artist { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe84e;&nbsp;'); }
.ffz-i-discord { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe84f;&nbsp;'); }
.ffz-i-bluesky { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe850;&nbsp;'); }
.ffz-i-bits { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe851;&nbsp;'); }
.ffz-i-move { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf047;&nbsp;'); }
.ffz-i-link-ext { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf08e;&nbsp;'); }
.ffz-i-twitter { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf099;&nbsp;'); }

View file

@ -1,11 +1,11 @@
@font-face {
font-family: 'ffz-fontello';
src: url('../font/ffz-fontello.eot?3490719');
src: url('../font/ffz-fontello.eot?3490719#iefix') format('embedded-opentype'),
url('../font/ffz-fontello.woff2?3490719') format('woff2'),
url('../font/ffz-fontello.woff?3490719') format('woff'),
url('../font/ffz-fontello.ttf?3490719') format('truetype'),
url('../font/ffz-fontello.svg?3490719#ffz-fontello') format('svg');
src: url('../font/ffz-fontello.eot?28778935');
src: url('../font/ffz-fontello.eot?28778935#iefix') format('embedded-opentype'),
url('../font/ffz-fontello.woff2?28778935') format('woff2'),
url('../font/ffz-fontello.woff?28778935') format('woff'),
url('../font/ffz-fontello.ttf?28778935') format('truetype'),
url('../font/ffz-fontello.svg?28778935#ffz-fontello') format('svg');
font-weight: normal;
font-style: normal;
}
@ -15,7 +15,7 @@
@media screen and (-webkit-min-device-pixel-ratio:0) {
@font-face {
font-family: 'ffz-fontello';
src: url('../font/ffz-fontello.svg?3490719#ffz-fontello') format('svg');
src: url('../font/ffz-fontello.svg?28778935#ffz-fontello') format('svg');
}
}
*/
@ -135,6 +135,7 @@
.ffz-i-artist:before { content: '\e84e'; } /* '' */
.ffz-i-discord:before { content: '\e84f'; } /* '' */
.ffz-i-bluesky:before { content: '\e850'; } /* '' */
.ffz-i-bits:before { content: '\e851'; } /* '' */
.ffz-i-move:before { content: '\f047'; } /* '' */
.ffz-i-link-ext:before { content: '\f08e'; } /* '' */
.ffz-i-twitter:before { content: '\f099'; } /* '' */