diff --git a/package.json b/package.json index d68e5812..1f7af396 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "frankerfacez", "author": "Dan Salvato LLC", - "version": "4.20.37", + "version": "4.20.38", "description": "FrankerFaceZ is a Twitch enhancement suite.", "license": "Apache-2.0", "scripts": { diff --git a/src/modules/chat/index.js b/src/modules/chat/index.js index b88b9a28..b60849a6 100644 --- a/src/modules/chat/index.js +++ b/src/modules/chat/index.js @@ -1388,9 +1388,15 @@ export default class Chat extends Module { ret = `${content.alt}${content.cheerAmount}`; } else if ( content.images ) { - const url = (content.images.themed ? content.images.dark : content.images.sources), - match = url && /\/emoticons\/v1\/(\d+)\/[\d.]+$/.exec(url['1x']), + const url = (content.images.themed ? content.images.dark : content.images.sources); + let id = content.emoteID; + if ( ! id ) { + const match = url && ( + /\/emoticons\/v1\/(\d+)\/[\d.]+$/.exec(url['1x']) || + /\/emoticons\/v2\/(\d+)\//.exec(url['1x']) + ); id = match && match[1]; + } ret = content.alt; diff --git a/src/sites/twitch-twilight/modules/channel.jsx b/src/sites/twitch-twilight/modules/channel.jsx index 2899c687..98de2b22 100644 --- a/src/sites/twitch-twilight/modules/channel.jsx +++ b/src/sites/twitch-twilight/modules/channel.jsx @@ -369,6 +369,24 @@ export default class Channel extends Module { return; } + // TODO: See if we can read this data directly from Apollo's cache. + // Also, test how it works with videos and clips. + /*const raw_game = el.querySelector('a[data-a-target="stream-game-link"]')?.textContent; + if ( ! el._ffz_game_cache_updating && el._ffz_game_cache !== raw_game ) { + el._ffz_game_cache_updating = true; + el._ffz_game_cache = raw_game; + + this.twitch_data.getUserGame(props.channelID, props.channelLogin).then(game => { + el._ffz_game_cache_updating = false; + this.settings.updateContext({ + category: game?.displayName, + categoryID: game?.id + }) + }).catch(() => { + el._ffz_game_cache_updating = false; + }); + }*/ + const other_props = react.child.child?.child?.child?.child?.child?.child?.child?.child?.child?.memoizedProps, title = other_props?.title; @@ -438,8 +456,19 @@ export default class Channel extends Module { getViewerCount: () => { const thing = cont.querySelector('p[data-a-target="animated-channel-viewers-count"]'), r = thing && this.fine.getReactInstance(thing), - p = r?.memoizedProps?.children?.props; + c = r?.memoizedProps?.children; + // Sometimes, on early loads, the animated element + // is actually a static string. + if ( typeof c === 'string' && /^[0-9,.]+$/.test(c) ) { + try { + const val = parseInt(c.replace(/,/, ''), 10); + if ( ! isNaN(val) && isFinite(val) && val > 0 ) + return val; + } catch(err) { /* no-op */ } + } + + const p = c?.props; if ( p && p.value != null ) return p.value; diff --git a/src/sites/twitch-twilight/modules/chat/settings_menu.jsx b/src/sites/twitch-twilight/modules/chat/settings_menu.jsx index ddafb703..3ff318b1 100644 --- a/src/sites/twitch-twilight/modules/chat/settings_menu.jsx +++ b/src/sites/twitch-twilight/modules/chat/settings_menu.jsx @@ -25,11 +25,11 @@ export default class SettingsMenu extends Module { Twilight.CHAT_ROUTES ); - this.ModSettingsMenu = this.fine.define( + /*this.ModSettingsMenu = this.fine.define( 'chat-mod-settings', n => n.renderModerationSettingsLink && n.onChatClear, Twilight.CHAT_ROUTES - ); + );*/ } async onEnable() { @@ -71,7 +71,7 @@ export default class SettingsMenu extends Module { } ); - const f = t.chat.context.get('chat.scroller.freeze'), + /*const f = t.chat.context.get('chat.scroller.freeze'), reason = f === 2 ? t.i18n.t('key.ctrl', 'Ctrl Key') : f === 3 ? t.i18n.t('key.meta', 'Meta Key') : f === 4 ? t.i18n.t('key.alt', 'Alt Key') : @@ -96,7 +96,7 @@ export default class SettingsMenu extends Module {
- ); + );*/ return val; } @@ -171,7 +171,7 @@ export default class SettingsMenu extends Module { this.SettingsMenu.forceUpdate(); }); - this.ModSettingsMenu.ready(cls => { + /*this.ModSettingsMenu.ready(cls => { const old_render = cls.prototype.render; cls.prototype.render = function() { @@ -193,7 +193,7 @@ export default class SettingsMenu extends Module { } this.ModSettingsMenu.forceUpdate(); - }) + })*/ this.SettingsMenu.on('unmount', inst => { inst.ffzSettingsClick = null; diff --git a/src/sites/twitch-twilight/modules/css_tweaks/index.js b/src/sites/twitch-twilight/modules/css_tweaks/index.js index c8d41a85..808648eb 100644 --- a/src/sites/twitch-twilight/modules/css_tweaks/index.js +++ b/src/sites/twitch-twilight/modules/css_tweaks/index.js @@ -33,7 +33,7 @@ const CLASSES = { 'player-rerun-bar': '.channel-root__player-container div.tw-c-text-overlay:not([data-a-target="hosting-ui-header"])', 'pinned-cheer': '.pinned-cheer,.pinned-cheer-v2,.channel-leaderboard', - 'whispers': 'body .whispers-open-threads,.tw-core-button[data-a-target="whisper-box-button"]', + 'whispers': 'body .whispers-open-threads,.tw-core-button[data-a-target="whisper-box-button"],.whispers__pill', 'dir-live-ind': '.live-channel-card[data-ffz-type="live"] .tw-channel-status-text-indicator, article[data-ffz-type="live"] .tw-channel-status-text-indicator', 'profile-hover': '.preview-card .tw-relative:hover .ffz-channel-avatar', diff --git a/src/utilities/data/user-game.gql b/src/utilities/data/user-game.gql new file mode 100644 index 00000000..6d75ac07 --- /dev/null +++ b/src/utilities/data/user-game.gql @@ -0,0 +1,12 @@ +query FFZ_UserGame($id: ID, $login: String) { + user(id: $id, login: $login) { + id + broadcastSettings { + id + game { + id + displayName + } + } + } +} \ No newline at end of file diff --git a/src/utilities/translation-core.js b/src/utilities/translation-core.js index 003eb5db..5aa92b20 100644 --- a/src/utilities/translation-core.js +++ b/src/utilities/translation-core.js @@ -51,9 +51,9 @@ export const DEFAULT_TYPES = { number(val, node) { if ( typeof val !== 'number' ) { - let new_val = parseInt(val, 10); + let new_val = parseFloat(val); if ( isNaN(new_val) || ! isFinite(new_val) ) - new_val = parseFloat(val); + new_val = parseInt(val, 10); if ( isNaN(new_val) || ! isFinite(new_val) ) return val; @@ -254,7 +254,16 @@ export default class TranslationCore { formatNumber(value, format) { let formatter = this.numberFormats.get(format); if ( ! formatter ) { - formatter = new Intl.NumberFormat(this.locale, this.formats.number[format]); + if ( this.formats.number[format] ) + formatter = new Intl.NumberFormat(this.locale, this.formats.number[format]); + else if ( typeof format === 'number' ) + formatter = new Intl.NumberFormat(this.locale, { + minimumFractionDigits: format, + maximumFractionDigits: format + }); + else + formatter = new Intl.NumberFormat(this.locale); + this.numberFormats.set(format, formatter); } diff --git a/src/utilities/twitch-data.js b/src/utilities/twitch-data.js index fd910c8b..5263c97a 100644 --- a/src/utilities/twitch-data.js +++ b/src/utilities/twitch-data.js @@ -265,6 +265,29 @@ export default class TwitchData extends Module { return get('data.user', data); } + /** + * Queries Apollo for the user's current game, details given the user id or name. One of (id, login) MUST be specified + * @function getUserGame + * @memberof TwitchData + * @async + * + * @param {int|string|null|undefined} id - the user id number (can be an integer string) + * @param {string|null|undefined} login - the username + * @returns {Object} information about the requested user + * + * @example + * + * console.log(this.twitch_data.getUserGame(19571641, null)); + */ + async getUserGame(id, login) { + const data = await this.queryApollo( + await import(/* webpackChunkName: 'queries' */ './data/user-game.gql'), + { id, login } + ); + + return get('data.user.broadcastSettings.game', data); + } + /** * Queries Apollo for the logged in user's relationship to the channel with given the id or name. One of (id, login) MUST be specified * @function getUserSelf