mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-06-27 12:55:55 +00:00
4.15.4
* Added: Setting to hide the mass gift sub banner at the top of chat. * Changed: Messages for redeeming Channel Points now have custom rendering with less padding and background colors to properly highlight them. * Fixed: Moderation profiles not always applying when navigating between channels. * Fixed: Settings to disable auto-play not working. * Fixed: Remove debug logging when importing a profile.
This commit is contained in:
parent
0fdb988da7
commit
8ac1b2ce91
18 changed files with 481 additions and 44 deletions
153
bin/static_i18n.js
Normal file
153
bin/static_i18n.js
Normal file
|
@ -0,0 +1,153 @@
|
|||
const transformSync = require('@babel/core').transformSync;
|
||||
const type = require('@babel/types');
|
||||
const traverse = require('@babel/traverse').default;
|
||||
|
||||
const fs = require('fs');
|
||||
const glob = require('glob');
|
||||
|
||||
function matchesPattern(member, match, allowPartial = false) {
|
||||
if ( ! type.isMemberExpression(member) )
|
||||
return false;
|
||||
|
||||
const parts = Array.isArray(match) ? match : match.split('.');
|
||||
const nodes = [];
|
||||
|
||||
let node;
|
||||
for(node = member; type.isMemberExpression(node); node = node.object)
|
||||
nodes.push(node.property);
|
||||
|
||||
nodes.push(node);
|
||||
|
||||
if ( nodes.length < parts.length )
|
||||
return false;
|
||||
if ( ! allowPartial && nodes.length > parts.length )
|
||||
return false;
|
||||
|
||||
for(let i = 0, j = nodes.length - 1; i < parts.length; i++, j--) {
|
||||
const node = nodes[j];
|
||||
let value;
|
||||
if ( type.isIdentifier(node) )
|
||||
value = node.name;
|
||||
else if ( type.isStringLiteral(node) )
|
||||
value = node.value;
|
||||
else if ( type.isThisExpression(node) )
|
||||
value = 'this';
|
||||
else
|
||||
return false;
|
||||
|
||||
if ( parts[i] !== value )
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const babelOptions = {
|
||||
ast: true,
|
||||
parserOpts: JSON.parse(fs.readFileSync('.babelrc', 'utf8'))
|
||||
};
|
||||
|
||||
babelOptions.parserOpts.plugins = [
|
||||
'jsx',
|
||||
'dynamicImport',
|
||||
'optionalChaining',
|
||||
'objectRestSpread'
|
||||
]
|
||||
|
||||
function getString(node) {
|
||||
if ( type.isStringLiteral(node) )
|
||||
return node.value;
|
||||
|
||||
if ( type.isTemplateLiteral(node) && (! node.expressions || ! node.expressions.length) && node.quasis && node.quasis.length === 1 )
|
||||
return node.quasis[0].value.cooked;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function extractFromCode(code) {
|
||||
const { ast } = transformSync(code, babelOptions);
|
||||
const matches = [];
|
||||
|
||||
traverse(ast, {
|
||||
CallExpression(path) {
|
||||
const callee = path.get('callee');
|
||||
if ( ! callee )
|
||||
return;
|
||||
|
||||
if ( !( matchesPattern(callee.node, 'this.i18n.t') ||
|
||||
matchesPattern(callee.node, 'i18n.t') ||
|
||||
matchesPattern(callee.node, 't.i18n.t') ||
|
||||
matchesPattern(callee.node, 'this.i18n.tList') ||
|
||||
matchesPattern(callee.node, 'i18n.tList') ||
|
||||
matchesPattern(callee.node, 't.i18n.tList') ||
|
||||
matchesPattern(callee.node, 'this.t') ||
|
||||
matchesPattern(callee.node, 'this.tList') ))
|
||||
return;
|
||||
|
||||
const key = getString(path.get('arguments.0').node);
|
||||
if ( ! key )
|
||||
return;
|
||||
|
||||
matches.push({
|
||||
key,
|
||||
loc: path.node.loc.start,
|
||||
phrase: getString(path.get('arguments.1').node)
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
return matches;
|
||||
}
|
||||
|
||||
function extractFromFiles(files) {
|
||||
const results = [];
|
||||
|
||||
if ( ! Array.isArray(files) )
|
||||
files = [files];
|
||||
|
||||
const scannable = new Set;
|
||||
for(const thing of files) {
|
||||
for(const file of glob.sync(thing, {}))
|
||||
scannable.add(file);
|
||||
}
|
||||
|
||||
for(const file of scannable) {
|
||||
const code = fs.readFileSync(file, 'utf8');
|
||||
const matches = extractFromCode(code);
|
||||
for(const match of matches) {
|
||||
match.source = `${file}:${match.loc.line}:${match.loc.column}`;
|
||||
delete match.loc;
|
||||
results.push(match);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
|
||||
const bits = extractFromFiles([
|
||||
'src/**/*.js',
|
||||
'src/**/*.jsx'
|
||||
]);
|
||||
|
||||
const seen = new Set;
|
||||
const out = [];
|
||||
|
||||
for(const entry of bits) {
|
||||
if ( seen.has(entry.key) )
|
||||
continue;
|
||||
|
||||
seen.add(entry.key);
|
||||
if ( entry.key && entry.phrase )
|
||||
out.push({
|
||||
key: entry.key,
|
||||
phrase: entry.phrase,
|
||||
calls: [
|
||||
`/${entry.source}`
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
fs.writeFileSync('extracted.json', JSON.stringify(out, null, '\t'));
|
||||
|
||||
console.log(`Extracted ${out.length} strings.`);
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "frankerfacez",
|
||||
"author": "Dan Salvato LLC",
|
||||
"version": "4.15.3",
|
||||
"version": "4.15.4",
|
||||
"description": "FrankerFaceZ is a Twitch enhancement suite.",
|
||||
"license": "Apache-2.0",
|
||||
"scripts": {
|
||||
|
|
|
@ -341,8 +341,6 @@ export default {
|
|||
if ( ! allow_update )
|
||||
delete profile_data.url;
|
||||
|
||||
console.log('Importing', profile_data, data, this.import_data);
|
||||
|
||||
const prof = this.context.createProfile(profile_data);
|
||||
|
||||
prof.update({
|
||||
|
|
|
@ -795,7 +795,7 @@ export default class MainMenu extends Module {
|
|||
},
|
||||
|
||||
resize: e => {
|
||||
if ( this.dialog.exclusive || this.site?.router?.current_name === 'squad' )
|
||||
if ( this.dialog.exclusive || this.site?.router?.current_name === 'squad' || this.site?.router?.current_name === 'command-center' )
|
||||
return;
|
||||
|
||||
if ( this.settings.get('context.ui.theatreModeEnabled') )
|
||||
|
|
|
@ -204,7 +204,8 @@ Twilight.CHAT_ROUTES = [
|
|||
'user',
|
||||
'dash',
|
||||
'embed-chat',
|
||||
'squad'
|
||||
'squad',
|
||||
'command-center'
|
||||
];
|
||||
|
||||
|
||||
|
@ -252,6 +253,7 @@ Twilight.ROUTES = {
|
|||
'turbo': '/turbo',
|
||||
'user': '/:userName',
|
||||
'squad': '/:userName/squad',
|
||||
'command-center': '/:userName/commandcenter',
|
||||
'embed-chat': '/embed/:userName/chat'
|
||||
};
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ import Module from 'utilities/module';
|
|||
import { get, has } from 'utilities/object';
|
||||
|
||||
import Twilight from 'site';
|
||||
import { Color } from 'src/utilities/color';
|
||||
|
||||
|
||||
export default class Channel extends Module {
|
||||
|
@ -18,6 +19,7 @@ export default class Channel extends Module {
|
|||
|
||||
this.inject('settings');
|
||||
this.inject('site.fine');
|
||||
this.inject('site.css_tweaks');
|
||||
|
||||
this.joined_raids = new Set;
|
||||
|
||||
|
@ -71,6 +73,20 @@ export default class Channel extends Module {
|
|||
}
|
||||
|
||||
|
||||
updateChannelColor(color) {
|
||||
const parsed = color && Color.RGBA.fromHex(color);
|
||||
if ( parsed ) {
|
||||
this.css_tweaks.setVariable('channel-color', parsed.toCSS());
|
||||
this.css_tweaks.setVariable('channel-color-20', parsed._a(0.2).toCSS());
|
||||
this.css_tweaks.setVariable('channel-color-30', parsed._a(0.3).toCSS());
|
||||
} else {
|
||||
this.css_tweaks.deleteVariable('channel-color');
|
||||
this.css_tweaks.deleteVariable('channel-color-20');
|
||||
this.css_tweaks.deleteVariable('channel-color-30');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
onEnable() {
|
||||
this.ChannelPage.on('mount', this.wrapChannelPage, this);
|
||||
this.RaidController.on('mount', this.wrapRaidController, this);
|
||||
|
@ -84,19 +100,11 @@ export default class Channel extends Module {
|
|||
this.wrapRaidController(inst);
|
||||
});
|
||||
|
||||
this.ChannelPage.on('mount', inst => {
|
||||
const category = get('state.video.game', inst) || get('state.clip.game', inst) || get('state.channel.broadcastSettings.game', inst);
|
||||
|
||||
this.settings.updateContext({
|
||||
channel: get('state.channel.login', inst),
|
||||
channelID: get('state.channel.id', inst),
|
||||
channelColor: get('state.primaryColorHex', inst),
|
||||
category: category?.name,
|
||||
categoryID: category?.id
|
||||
});
|
||||
});
|
||||
this.ChannelPage.on('mount', this.onChannelMounted, this);
|
||||
|
||||
this.ChannelPage.on('unmount', () => {
|
||||
this.updateChannelColor(null);
|
||||
|
||||
this.settings.updateContext({
|
||||
channel: null,
|
||||
channelID: null,
|
||||
|
@ -109,10 +117,13 @@ export default class Channel extends Module {
|
|||
this.ChannelPage.on('update', inst => {
|
||||
const category = get('state.video.game', inst) || get('state.clip.game', inst) || get('state.channel.broadcastSettings.game', inst);
|
||||
|
||||
const color = get('state.primaryColorHex', inst);
|
||||
this.updateChannelColor(color);
|
||||
|
||||
this.settings.updateContext({
|
||||
channel: get('state.channel.login', inst),
|
||||
channelID: get('state.channel.id', inst),
|
||||
channelColor: get('state.primaryColorHex', inst),
|
||||
channelColor: color,
|
||||
category: category?.name,
|
||||
categoryID: category?.id
|
||||
});
|
||||
|
@ -133,7 +144,24 @@ export default class Channel extends Module {
|
|||
|
||||
this.ChannelPage.ready((cls, instances) => {
|
||||
for(const inst of instances)
|
||||
this.wrapChannelPage(inst);
|
||||
this.onChannelMounted(inst);
|
||||
});
|
||||
}
|
||||
|
||||
onChannelMounted(inst) {
|
||||
this.wrapChannelPage(inst);
|
||||
|
||||
const category = get('state.video.game', inst) || get('state.clip.game', inst) || get('state.channel.broadcastSettings.game', inst);
|
||||
|
||||
const color = get('state.primaryColorHex', inst);
|
||||
this.updateChannelColor(color);
|
||||
|
||||
this.settings.updateContext({
|
||||
channel: get('state.channel.login', inst),
|
||||
channelID: get('state.channel.id', inst),
|
||||
channelColor: color,
|
||||
category: category?.name,
|
||||
categoryID: category?.id
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
import {ColorAdjuster} from 'utilities/color';
|
||||
import {setChildren} from 'utilities/dom';
|
||||
import {get, has, make_enum, split_chars, shallow_object_equals, set_equals} from 'utilities/object';
|
||||
import {WEBKIT_CSS as WEBKIT} from 'utilities/constants';
|
||||
import {FFZEvent} from 'utilities/events';
|
||||
|
||||
import Module from 'utilities/module';
|
||||
|
@ -19,6 +20,7 @@ import SettingsMenu from './settings_menu';
|
|||
import EmoteMenu from './emote_menu';
|
||||
import Input from './input';
|
||||
import ViewerCards from './viewer_card';
|
||||
import { isHighlightedReward } from './points';
|
||||
|
||||
|
||||
const REGEX_EMOTES = {
|
||||
|
@ -237,8 +239,29 @@ export default class ChatHook extends Module {
|
|||
Twilight.CHAT_ROUTES
|
||||
);
|
||||
|
||||
this.PointsInfo = this.fine.define(
|
||||
'points-info',
|
||||
n => n.pointIcon !== undefined && n.pointName !== undefined,
|
||||
Twilight.CHAT_ROUTES
|
||||
);
|
||||
|
||||
this.GiftBanner = this.fine.define(
|
||||
'gift-banner',
|
||||
n => n.getBannerText && n.handleCountdownEnd && n.getRemainingTime,
|
||||
Twilight.CHAT_ROUTES
|
||||
);
|
||||
|
||||
// Settings
|
||||
|
||||
this.settings.add('chat.subs.gift-banner', {
|
||||
default: true,
|
||||
ui: {
|
||||
path: 'Chat > Appearance >> Subscriptions',
|
||||
title: 'Display a banner at the top of chat when a mass gift sub happens.',
|
||||
component: 'setting-check-box'
|
||||
}
|
||||
});
|
||||
|
||||
this.settings.add('chat.community-chest.show', {
|
||||
default: true,
|
||||
ui: {
|
||||
|
@ -248,6 +271,16 @@ export default class ChatHook extends Module {
|
|||
}
|
||||
});
|
||||
|
||||
this.settings.add('chat.points.custom-rendering', {
|
||||
default: true,
|
||||
ui: {
|
||||
path: 'Chat > Channel Points >> Appearance',
|
||||
title: 'Use custom rendering for channel points reward messages in chat.',
|
||||
description: 'Custom rendering applies a background color to highlighted messages, which some users may not appreciate.',
|
||||
component: 'setting-check-box'
|
||||
}
|
||||
});
|
||||
|
||||
this.settings.add('chat.points.show-callouts', {
|
||||
default: true,
|
||||
ui: {
|
||||
|
@ -536,6 +569,25 @@ export default class ChatHook extends Module {
|
|||
}
|
||||
|
||||
|
||||
updatePointsInfo(inst) {
|
||||
const icon = inst?.pointIcon,
|
||||
name = inst?.pointName;
|
||||
|
||||
if ( icon ) {
|
||||
this.css_tweaks.set('points-icon', `.ffz--points-icon:before { display: none }
|
||||
.ffz--points-icon:after {
|
||||
display: inline-block;
|
||||
margin: 0 0.5rem -0.6rem;
|
||||
background-image: url("${icon.url}");
|
||||
background-image: ${WEBKIT}image-set(url("${icon.url}") 1x, url("${icon.url2x}") 2x, url("${icon.url4x}") 4x);
|
||||
}`);
|
||||
} else
|
||||
this.css_tweaks.delete('points-icon');
|
||||
|
||||
this.point_name = name || null;
|
||||
}
|
||||
|
||||
|
||||
async grabTypes() {
|
||||
const ct = await this.web_munch.findModule('chat-types'),
|
||||
changes = [];
|
||||
|
@ -579,6 +631,7 @@ export default class ChatHook extends Module {
|
|||
this.PointsClaimButton.forceUpdate();
|
||||
});
|
||||
|
||||
this.chat.context.on('changed:chat.subs.gift-banner', () => this.GiftBanner.forceUpdate(), this);
|
||||
this.chat.context.on('changed:chat.width', this.updateChatCSS, this);
|
||||
this.settings.main_context.on('changed:chat.use-width', this.updateChatCSS, this);
|
||||
this.chat.context.on('changed:chat.font-size', this.updateChatCSS, this);
|
||||
|
@ -652,6 +705,23 @@ export default class ChatHook extends Module {
|
|||
|
||||
const t = this;
|
||||
|
||||
this.PointsInfo.on('mount', this.updatePointsInfo, this);
|
||||
this.PointsInfo.on('update', this.updatePointsInfo, this);
|
||||
this.PointsInfo.on('unmount', () => this.updatePointsInfo(null));
|
||||
this.PointsInfo.ready(() => this.updatePointsInfo(this.PointsInfo.first));
|
||||
|
||||
this.GiftBanner.ready(cls => {
|
||||
const old_render = cls.prototype.render;
|
||||
cls.prototype.render = function() {
|
||||
if ( ! t.chat.context.get('chat.subs.gift-banner') )
|
||||
return null;
|
||||
|
||||
return old_render.call(this);
|
||||
}
|
||||
|
||||
this.GiftBanner.forceUpdate();
|
||||
});
|
||||
|
||||
this.CommunityChestBanner.ready(cls => {
|
||||
const old_render = cls.prototype.render;
|
||||
cls.prototype.render = function() {
|
||||
|
@ -751,13 +821,17 @@ export default class ChatHook extends Module {
|
|||
});
|
||||
|
||||
this.PointsClaimButton.ready(cls => {
|
||||
cls.prototype.ffzHasOffer = function() {
|
||||
return ! this.props.hidden && ! this.state?.error && this.getClaim() != null;
|
||||
};
|
||||
|
||||
const old_render = cls.prototype.render;
|
||||
cls.prototype.render = function() {
|
||||
try {
|
||||
if ( ! this._ffz_timer && t.chat.context.get('chat.points.auto-rewards') )
|
||||
if ( this.ffzHasOffer() && ! this._ffz_timer && t.chat.context.get('chat.points.auto-rewards') )
|
||||
this._ffz_timer = setTimeout(() => {
|
||||
this._ffz_timer = null;
|
||||
if ( this.onClick )
|
||||
if ( this.onClick && this.ffzHasOffer() )
|
||||
this.onClick();
|
||||
}, 1000 + Math.floor(Math.random() * 5000));
|
||||
|
||||
|
@ -777,7 +851,8 @@ export default class ChatHook extends Module {
|
|||
|
||||
this.ChatController.on('mount', this.chatMounted, this);
|
||||
this.ChatController.on('unmount', this.chatUnmounted, this);
|
||||
this.ChatController.on('receive-props', this.chatUpdated, this);
|
||||
//this.ChatController.on('receive-props', this.chatUpdated, this);
|
||||
this.ChatController.on('update', this.chatUpdated, this);
|
||||
|
||||
this.ChatService.ready((cls, instances) => {
|
||||
this.wrapChatService(cls);
|
||||
|
@ -839,7 +914,7 @@ export default class ChatHook extends Module {
|
|||
});
|
||||
|
||||
this.ChatBufferConnector.on('mount', this.connectorMounted, this);
|
||||
this.ChatBufferConnector.on('receive-props', this.connectorUpdated, this);
|
||||
this.ChatBufferConnector.on('update', this.connectorUpdated, this);
|
||||
this.ChatBufferConnector.on('unmount', this.connectorUnmounted, this);
|
||||
|
||||
this.ChatBufferConnector.ready((cls, instances) => {
|
||||
|
@ -1747,6 +1822,29 @@ export default class ChatHook extends Module {
|
|||
}
|
||||
}
|
||||
|
||||
const old_points = this.onChannelPointsRewardEvent;
|
||||
this.onChannelPointsRewardEvent = function(e) {
|
||||
try {
|
||||
if ( t.chat.context.get('chat.points.custom-rendering') ) {
|
||||
const reward = e.rewardID && get(e.rewardID, i.props.rewardMap);
|
||||
if ( reward ) {
|
||||
const out = i.convertMessage(e);
|
||||
|
||||
out.ffz_type = 'points';
|
||||
out.ffz_reward = reward;
|
||||
|
||||
return i.postMessageToCurrentChannel(e, out);
|
||||
}
|
||||
}
|
||||
|
||||
} catch(err) {
|
||||
t.log.error(err);
|
||||
t.log.capture(err, {extra: e});
|
||||
}
|
||||
|
||||
return old_points.call(i, e);
|
||||
}
|
||||
|
||||
const old_host = this.onHostingEvent;
|
||||
this.onHostingEvent = function (e, _t) {
|
||||
t.emit('tmi:host', e, _t);
|
||||
|
@ -1943,6 +2041,11 @@ export default class ChatHook extends Module {
|
|||
channelID: null
|
||||
});
|
||||
|
||||
this.settings.updateContext({
|
||||
moderator: false,
|
||||
chatHidden: false
|
||||
});
|
||||
|
||||
this.chat.context.updateContext({
|
||||
moderator: false,
|
||||
channel: null,
|
||||
|
@ -1960,30 +2063,34 @@ export default class ChatHook extends Module {
|
|||
if ( ! chat._ffz_room || props.channelID != chat._ffz_room.id ) {
|
||||
this.removeRoom(chat);
|
||||
if ( chat._ffz_mounted )
|
||||
this.chatMounted(chat, props);
|
||||
this.chatMounted(chat);
|
||||
return;
|
||||
}
|
||||
|
||||
if ( props.bitsConfig !== chat.props.bitsConfig )
|
||||
this.updateRoomBitsConfig(chat, props.bitsConfig);
|
||||
this.updateRoomBitsConfig(chat, chat.props.bitsConfig);
|
||||
|
||||
// TODO: Check if this is the room for the current channel.
|
||||
|
||||
if ( props.isEmbedded || props.isPopout )
|
||||
let login = chat.props.channelLogin;
|
||||
if ( login )
|
||||
login = login.toLowerCase();
|
||||
|
||||
if ( chat.props.isEmbedded || chat.props.isPopout )
|
||||
this.settings.updateContext({
|
||||
channel: props.channelLogin && props.channelLogin.toLowerCase(),
|
||||
channelID: props.channelID
|
||||
channel: login,
|
||||
channelID: chat.props.channelID
|
||||
});
|
||||
|
||||
this.settings.updateContext({
|
||||
moderator: props.isCurrentUserModerator,
|
||||
chatHidden: props.isHidden
|
||||
moderator: chat.props.isCurrentUserModerator,
|
||||
chatHidden: chat.props.isHidden
|
||||
});
|
||||
|
||||
this.chat.context.updateContext({
|
||||
moderator: props.isCurrentUserModerator,
|
||||
channel: props.channelLogin && props.channelLogin.toLowerCase(),
|
||||
channelID: props.channelID,
|
||||
moderator: chat.props.isCurrentUserModerator,
|
||||
channel: login,
|
||||
channelID: chat.props.channelID,
|
||||
/*ui: {
|
||||
theme: props.theme
|
||||
}*/
|
||||
|
@ -2027,8 +2134,8 @@ export default class ChatHook extends Module {
|
|||
}
|
||||
|
||||
connectorUpdated(inst, props) { // eslint-disable-line class-methods-use-this
|
||||
const buffer = inst.props.messageBufferAPI,
|
||||
new_buffer = props.messageBufferAPI;
|
||||
const buffer = props.messageBufferAPI,
|
||||
new_buffer = inst.props.messageBufferAPI;
|
||||
|
||||
if ( buffer === new_buffer )
|
||||
return;
|
||||
|
|
|
@ -11,6 +11,7 @@ import RichContent from './rich_content';
|
|||
import { has } from 'utilities/object';
|
||||
import { KEYS } from 'utilities/constants';
|
||||
import { print_duration } from 'src/utilities/time';
|
||||
import { getRewardTitle, getRewardCost, isHighlightedReward } from './points';
|
||||
|
||||
const SUB_TIERS = {
|
||||
1000: 1,
|
||||
|
@ -357,19 +358,26 @@ other {# messages were deleted by a moderator.}
|
|||
}
|
||||
}
|
||||
|
||||
let room = msg.roomLogin ? msg.roomLogin : msg.channel ? msg.channel.slice(1) : undefined;
|
||||
let room = msg.roomLogin ? msg.roomLogin : msg.channel ? msg.channel.slice(1) : undefined,
|
||||
room_id = msg.roomId ? msg.roomId : this.props.channelID;
|
||||
|
||||
if ( ! room && this.props.channelID ) {
|
||||
const r = t.chat.getRoom(this.props.channelID, null, true);
|
||||
if ( ! room && room_id ) {
|
||||
const r = t.chat.getRoom(room_id, null, true);
|
||||
if ( r && r.login )
|
||||
room = msg.roomLogin = r.login;
|
||||
}
|
||||
|
||||
if ( ! room_id && room ) {
|
||||
const r = t.chat.getRoom(null, room_id, true);
|
||||
if ( r && r.id )
|
||||
room_id = msg.roomId = r.id;
|
||||
}
|
||||
|
||||
//if ( ! msg.message && msg.messageParts )
|
||||
// t.chat.detokenizeMessage(msg);
|
||||
|
||||
const u = t.site.getUser(),
|
||||
r = {id: this.props.channelID, login: room};
|
||||
r = {id: room_id, login: room};
|
||||
|
||||
if ( u ) {
|
||||
u.moderator = this.props.isCurrentUserModerator;
|
||||
|
@ -542,7 +550,7 @@ other {# messages were deleted by a moderator.}
|
|||
sub_list,
|
||||
out && e('div', {
|
||||
className: 'chat-line--inline chat-line__message',
|
||||
'data-room-id': this.props.channelID,
|
||||
'data-room-id': room_id,
|
||||
'data-room': room,
|
||||
'data-user-id': user.userID,
|
||||
'data-user': user.userLogin && user.userLogin.toLowerCase(),
|
||||
|
@ -596,7 +604,7 @@ other {# messages were deleted by a moderator.}
|
|||
]),
|
||||
out && e('div', {
|
||||
className: 'chat-line--inline chat-line__message',
|
||||
'data-room-id': this.props.channelID,
|
||||
'data-room-id': room_id,
|
||||
'data-room': room,
|
||||
'data-user-id': user.userID,
|
||||
'data-user': user.userLogin && user.userLogin.toLowerCase(),
|
||||
|
@ -658,7 +666,7 @@ other {# messages were deleted by a moderator.}
|
|||
]),
|
||||
out && e('div', {
|
||||
className: 'chat-line--inline chat-line__message',
|
||||
'data-room-id': this.props.channelID,
|
||||
'data-room-id': room_id,
|
||||
'data-room': room,
|
||||
'data-user-id': user.userID,
|
||||
'data-user': user.userLogin && user.userLogin.toLowerCase(),
|
||||
|
@ -687,13 +695,46 @@ other {# messages were deleted by a moderator.}
|
|||
system_msg,
|
||||
out && e('div', {
|
||||
className: 'chat-line--inline chat-line__message',
|
||||
'data-room-id': this.props.channelID,
|
||||
'data-room-id': room_id,
|
||||
'data-room': room,
|
||||
'data-user-id': user.userID,
|
||||
'data-user': user.userLogin && user.userLogin.toLowerCase(),
|
||||
}, out)
|
||||
];
|
||||
}
|
||||
|
||||
} else if ( msg.ffz_type === 'points' && msg.ffz_reward ) {
|
||||
const reward = e('span', {className: 'ffz--points-reward'}, getRewardTitle(msg.ffz_reward, t.i18n)),
|
||||
cost = e('span', {className: 'ffz--points-cost'}, [
|
||||
e('span', {className: 'ffz--points-icon'}),
|
||||
t.i18n.formatNumber(getRewardCost(msg.ffz_reward))
|
||||
]);
|
||||
|
||||
cls = `ffz--points-line tw-pd-l-1 tw-pd-y-05 tw-pd-r-2${isHighlightedReward(msg.ffz_reward) ? ' ffz--points-highlight' : ''}`;
|
||||
out = [
|
||||
e('div', {className: 'tw-c-text-alt-2'}, [
|
||||
out ? null : t.actions.renderInline(msg, this.props.showModerationIcons, u, r, e),
|
||||
out ?
|
||||
t.i18n.tList('chat.points.redeemed', 'Redeemed {reward} {cost}', {reward, cost}) :
|
||||
t.i18n.tList('chat.points.user-redeemed', '{user} redeemed {reward} {cost}', {
|
||||
reward, cost,
|
||||
user: e('span', {
|
||||
role: 'button',
|
||||
className: 'chatter-name',
|
||||
onClick: this.ffz_user_click_handler
|
||||
}, e('span', {
|
||||
className: 'tw-c-text-base tw-strong'
|
||||
}, user.userDisplayName))
|
||||
})
|
||||
]),
|
||||
out && e('div', {
|
||||
className: 'chat-line--inline chat-line__message',
|
||||
'data-room-id': room_id,
|
||||
'data-room': room,
|
||||
'data-user-id': user.userID,
|
||||
'data-user': user.userLogin && user.userLogin.toLowerCase()
|
||||
}, out)
|
||||
]
|
||||
}
|
||||
|
||||
if ( ! out )
|
||||
|
@ -702,7 +743,7 @@ other {# messages were deleted by a moderator.}
|
|||
return e('div', {
|
||||
className: `${cls}${msg.mentioned ? ' ffz-mentioned' : ''}${bg_css ? ' ffz-custom-color' : ''}`,
|
||||
style: {backgroundColor: bg_css},
|
||||
'data-room-id': this.props.channelID,
|
||||
'data-room-id': room_id,
|
||||
'data-room': room,
|
||||
'data-user-id': user.userID,
|
||||
'data-user': user.userLogin && user.userLogin.toLowerCase(),
|
||||
|
|
45
src/sites/twitch-twilight/modules/chat/points.js
Normal file
45
src/sites/twitch-twilight/modules/chat/points.js
Normal file
|
@ -0,0 +1,45 @@
|
|||
export function isAutomaticReward(reward) {
|
||||
return reward?.__typename === 'CommunityPointsAutomaticReward';
|
||||
}
|
||||
|
||||
export function isCustomReward(reward) {
|
||||
return reward?.__typename === 'CommunityPointsCustomReward';
|
||||
}
|
||||
|
||||
export function isHighlightedReward(reward) {
|
||||
return isAutomaticReward(reward) && reward.type === 'SEND_HIGHLIGHTED_MESSAGE';
|
||||
}
|
||||
|
||||
export function getRewardCost(reward) {
|
||||
if ( isAutomaticReward(reward) )
|
||||
return reward.cost || reward.defaultCost;
|
||||
|
||||
return reward.cost;
|
||||
}
|
||||
|
||||
export function getRewardColor(reward) {
|
||||
if ( isAutomaticReward(reward) )
|
||||
return reward.backgroundColor || reward.defaultBackgroundColor;
|
||||
|
||||
return reward.backgroundColor;
|
||||
}
|
||||
|
||||
export function getRewardTitle(reward, i18n) {
|
||||
if ( isCustomReward(reward) )
|
||||
return reward.title;
|
||||
|
||||
switch(reward.type) {
|
||||
case 'SEND_HIGHLIGHTED_MESSAGE':
|
||||
return i18n.t('chat.points.highlighted', 'Highlight My Message');
|
||||
case 'SINGLE_MESSAGE_BYPASS_SUB_MODE':
|
||||
return i18n.t('chat.points.bypass-sub', 'Send a Message in Sub-Only Mode');
|
||||
case 'CHOSEN_SUB_EMOTE_UNLOCK':
|
||||
return i18n.t('chat.points.choose-emote', 'Choose an Emote to Unlock');
|
||||
case 'RANDOM_SUB_EMOTE_UNLOCK':
|
||||
return i18n.t('chat.points.random-emote', 'Unlock a Random Sub Emote');
|
||||
case 'CHOSEN_MODIFIED_SUB_EMOTE_UNLOCK':
|
||||
return i18n.t('chat.points.modify-emote', 'Modify a Single Emote');
|
||||
default:
|
||||
return i18n.t('chat.points.reward', 'Reward');
|
||||
}
|
||||
}
|
|
@ -4,6 +4,8 @@
|
|||
|
||||
.vod-message,
|
||||
|
||||
.ffz--points-line,
|
||||
|
||||
.chat-line__message:not(.chat-line--inline),
|
||||
.chat-line__moderation,
|
||||
.chat-line__status,
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
.thread-message__timestamp,
|
||||
.thread-message__warning,
|
||||
|
||||
.ffz--points-line,
|
||||
.chat-line__message:not(.chat-line--inline),
|
||||
.chat-line__moderation,
|
||||
.chat-line__status,
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
.thread-message__timestamp,
|
||||
.thread-message__warning,
|
||||
|
||||
.ffz--points-line,
|
||||
.chat-line__message:not(.chat-line--inline),
|
||||
.chat-line__moderation,
|
||||
.chat-line__status,
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
.thread-message__timestamp,
|
||||
.thread-message__warning,
|
||||
|
||||
.ffz--points-line,
|
||||
.chat-line__message:not(.chat-line--inline),
|
||||
.chat-line__moderation,
|
||||
.chat-line__status,
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
padding: .5rem 1rem !important;
|
||||
}
|
||||
|
||||
.ffz--points-line,
|
||||
.user-notice-line {
|
||||
padding: .5rem 1rem !important;
|
||||
padding-left: .6rem !important;
|
||||
|
|
|
@ -31,4 +31,8 @@
|
|||
.tw-root--theme-dark & {
|
||||
background-color: rgba(255,255,255,0.05) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.ffz--points-highlight:nth-child(2n+0) {
|
||||
background-color: var(--ffz-channel-color-30);
|
||||
}
|
|
@ -45,6 +45,12 @@ export default class MenuButton extends SiteModule {
|
|||
n => n.exitSquadMode && n.props && n.props.squadID,
|
||||
['squad']
|
||||
);
|
||||
|
||||
this.MultiController = this.fine.define(
|
||||
'multi-controller',
|
||||
n => n.handleAddStream && n.handleRemoveStream && n.getInitialStreamLayout,
|
||||
['command-center']
|
||||
);
|
||||
}
|
||||
|
||||
get loading() {
|
||||
|
@ -162,6 +168,9 @@ export default class MenuButton extends SiteModule {
|
|||
|
||||
for(const inst of this.SquadBar.instances)
|
||||
this.updateButton(inst);
|
||||
|
||||
for(const inst of this.MultiController.instances)
|
||||
this.updateButton(inst);
|
||||
}
|
||||
|
||||
|
||||
|
@ -175,6 +184,10 @@ export default class MenuButton extends SiteModule {
|
|||
this.SquadBar.on('mount', this.updateButton, this);
|
||||
this.SquadBar.on('update', this.updateButton, this);
|
||||
|
||||
this.MultiController.ready(() => this.update());
|
||||
this.MultiController.on('mount', this.updateButton, this);
|
||||
this.MultiController.on('update', this.updateButton, this);
|
||||
|
||||
this.on(':clicked', () => this.important_update = false);
|
||||
|
||||
this.once(':clicked', this.loadMenu);
|
||||
|
@ -200,6 +213,12 @@ export default class MenuButton extends SiteModule {
|
|||
is_squad = true;
|
||||
}
|
||||
|
||||
if ( ! container && inst.handleAddStream ) {
|
||||
container = this.fine.searchTree(inst, n => n.classList && n.classList.contains('multiview-stream-page__header'));
|
||||
if ( container )
|
||||
is_squad = true;
|
||||
}
|
||||
|
||||
if ( ! container )
|
||||
return;
|
||||
|
||||
|
|
|
@ -499,7 +499,7 @@ export default class Player extends Module {
|
|||
const player = this.props.mediaPlayerInstance,
|
||||
events = this.props.playerEvents;
|
||||
|
||||
if ( player && player.pause )
|
||||
if ( player && player.pause && player.getPlayerState?.() === 'Playing' )
|
||||
player.pause();
|
||||
else if ( events ) {
|
||||
const immediatePause = () => {
|
||||
|
|
|
@ -155,6 +155,40 @@
|
|||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.ffz--points-highlight {
|
||||
background-color: var(--ffz-channel-color-20);
|
||||
}
|
||||
|
||||
.ffz--points-line {
|
||||
border-left: 4px solid var(--ffz-channel-color);
|
||||
|
||||
.ffz--points-reward,
|
||||
.ffz--points-cost {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.ffz--points-reward {
|
||||
word-wrap: break-word;
|
||||
}
|
||||
}
|
||||
|
||||
.ffz--points-icon {
|
||||
&:before {
|
||||
content: '\e83c';
|
||||
font-family: 'ffz-fontello';
|
||||
font-size: 1.6rem;
|
||||
font-weight: normal;
|
||||
font-variant: normal;
|
||||
margin: 0 0.5rem;
|
||||
}
|
||||
|
||||
&:after {
|
||||
content: '';
|
||||
height: 2rem;
|
||||
width: 2rem;
|
||||
background-size: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.ffz--emote-picker {
|
||||
section:not(.filtered) heading {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue