mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-06-28 15:27:43 +00:00
4.20.84
* Fixed: Case-sensitive blocked terms not functioning correctly. * Fixed: Settings in the FFZ Control Center not reverting to a default appearance when reset. * Fixed: Current Channel and Channel Color not being properly detected in the mod view, channel pages, and dashboard. * Fixed: The channel points reward queue not functioning correctly. * Changed: Allow highlighting and blocking by add-on badge, not just Twitch badge. * Changed: Don't allocate `user.badges` and `user.emote_sets` until they're actually used to save on memory. * Changed: Don't default the `Chat > Bits and Cheering >> Display animated cheers.` setting to the `Animated Emotes` setting. * API Added: `badges.setBulk`, `badges.deleteBulk`, and `badges.extendBulk` for setting badges on users in bulk using an optimized data structure. * API Added: Tokenizers can set `msg.ffz_halt_tokens = true` to prevent further tokenizers running. Useful when just discarding a message.
This commit is contained in:
parent
a8b28b2d27
commit
1cdff0ec67
31 changed files with 533 additions and 1158 deletions
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "frankerfacez",
|
"name": "frankerfacez",
|
||||||
"author": "Dan Salvato LLC",
|
"author": "Dan Salvato LLC",
|
||||||
"version": "4.20.83",
|
"version": "4.20.84",
|
||||||
"description": "FrankerFaceZ is a Twitch enhancement suite.",
|
"description": "FrankerFaceZ is a Twitch enhancement suite.",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
import {NEW_API, SERVER, API_SERVER, IS_WEBKIT, IS_FIREFOX, WEBKIT_CSS as WEBKIT} from 'utilities/constants';
|
import {NEW_API, SERVER, API_SERVER, IS_WEBKIT, IS_FIREFOX, WEBKIT_CSS as WEBKIT} from 'utilities/constants';
|
||||||
|
|
||||||
import {createElement, ManagedStyle} from 'utilities/dom';
|
import {createElement, ManagedStyle} from 'utilities/dom';
|
||||||
import {has, maybe_call} from 'utilities/object';
|
import {has, maybe_call, SourcedSet} from 'utilities/object';
|
||||||
import Module from 'utilities/module';
|
import Module from 'utilities/module';
|
||||||
import { ColorAdjuster } from 'src/utilities/color';
|
import { ColorAdjuster } from 'src/utilities/color';
|
||||||
|
|
||||||
|
@ -181,11 +181,16 @@ export default class Badges extends Module {
|
||||||
|
|
||||||
this.style = new ManagedStyle('badges');
|
this.style = new ManagedStyle('badges');
|
||||||
|
|
||||||
|
// Bulk data structure for badges applied to a lot of users.
|
||||||
|
// This lets us avoid allocating lots of individual user
|
||||||
|
// objects when we don't need to do so.
|
||||||
|
this.bulk = new Map;
|
||||||
|
|
||||||
// Special data structure for supporters to greatly reduce
|
// Special data structure for supporters to greatly reduce
|
||||||
// memory usage and speed things up for people who only have
|
// memory usage and speed things up for people who only have
|
||||||
// a supporter badge.
|
// a supporter badge.
|
||||||
this.supporter_id = null;
|
//this.supporter_id = null;
|
||||||
this.supporters = new Set;
|
//this.supporters = new Set;
|
||||||
|
|
||||||
this.badges = {};
|
this.badges = {};
|
||||||
this.twitch_badges = {};
|
this.twitch_badges = {};
|
||||||
|
@ -340,17 +345,23 @@ export default class Badges extends Module {
|
||||||
if ( include_addons )
|
if ( include_addons )
|
||||||
for(const key in this.badges)
|
for(const key in this.badges)
|
||||||
if ( has(this.badges, key) ) {
|
if ( has(this.badges, key) ) {
|
||||||
const badge = this.badges[key],
|
const badge = this.badges[key];
|
||||||
image = badge.urls ? (badge.urls[2] || badge.urls[1]) : badge.image;
|
|
||||||
|
|
||||||
if ( badge.no_visibility )
|
if ( badge.no_visibility )
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
let image = badge.urls ? (badge.urls[2] || badge.urls[1]) : badge.image,
|
||||||
|
color = badge.color || 'transparent';
|
||||||
|
|
||||||
|
if ( ! badge.addon ) {
|
||||||
|
image = `//cdn.frankerfacez.com/badge/${badge.id}/2/rounded`;
|
||||||
|
color = 'transparent';
|
||||||
|
}
|
||||||
|
|
||||||
(badge.addon ? addon : ffz).push({
|
(badge.addon ? addon : ffz).push({
|
||||||
id: key,
|
id: key,
|
||||||
provider: 'ffz',
|
provider: 'ffz',
|
||||||
name: badge.title,
|
name: badge.title,
|
||||||
color: badge.color || 'transparent',
|
color,
|
||||||
image,
|
image,
|
||||||
styleImage: `url("${image}")`
|
styleImage: `url("${image}")`
|
||||||
});
|
});
|
||||||
|
@ -539,6 +550,11 @@ export default class Badges extends Module {
|
||||||
|
|
||||||
|
|
||||||
render(msg, createElement, skip_hide = false) { // eslint-disable-line class-methods-use-this
|
render(msg, createElement, skip_hide = false) { // eslint-disable-line class-methods-use-this
|
||||||
|
if ( ! msg.badges && ! msg.ffz_badges )
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// TODO: A lot of this can be cached
|
||||||
|
|
||||||
const hidden_badges = skip_hide ? {} : (this.parent.context.get('chat.badges.hidden') || {}),
|
const hidden_badges = skip_hide ? {} : (this.parent.context.get('chat.badges.hidden') || {}),
|
||||||
badge_style = this.parent.context.get('chat.badges.style'),
|
badge_style = this.parent.context.get('chat.badges.style'),
|
||||||
custom_mod = this.parent.context.get('chat.badges.custom-mod'),
|
custom_mod = this.parent.context.get('chat.badges.custom-mod'),
|
||||||
|
@ -556,14 +572,14 @@ export default class Badges extends Module {
|
||||||
twitch_badges = msg.badges || {},
|
twitch_badges = msg.badges || {},
|
||||||
dynamic_data = msg.badgeDynamicData || {},
|
dynamic_data = msg.badgeDynamicData || {},
|
||||||
|
|
||||||
user = msg.user || {},
|
//user = msg.user || {},
|
||||||
user_id = user.id,
|
//user_id = user.id,
|
||||||
user_login = user.login,
|
//user_login = user.login,
|
||||||
room_id = msg.roomID,
|
room_id = msg.roomID,
|
||||||
room_login = msg.roomLogin,
|
room_login = msg.roomLogin,
|
||||||
|
|
||||||
room = this.parent.getRoom(room_id, room_login, true),
|
room = this.parent.getRoom(room_id, room_login, true),
|
||||||
badges = this.getBadges(user_id, user_login, room_id, room_login);
|
badges = msg.ffz_badges; // this.getBadges(user_id, user_login, room_id, room_login);
|
||||||
|
|
||||||
let last_slot = 50, slot;
|
let last_slot = 50, slot;
|
||||||
|
|
||||||
|
@ -616,105 +632,107 @@ export default class Badges extends Module {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const handled_ids = new Set;
|
if ( Array.isArray(badges) ) {
|
||||||
|
const handled_ids = new Set;
|
||||||
|
|
||||||
for(const badge of badges)
|
for(const badge of badges)
|
||||||
if ( badge && badge.id != null ) {
|
if ( badge && badge.id != null ) {
|
||||||
if ( handled_ids.has(badge.id) )
|
if ( handled_ids.has(badge.id) )
|
||||||
continue;
|
|
||||||
|
|
||||||
handled_ids.add(badge.id);
|
|
||||||
|
|
||||||
const full_badge = this.badges[badge.id] || {},
|
|
||||||
is_hidden = hidden_badges[badge.id];
|
|
||||||
|
|
||||||
if ( is_hidden || (is_hidden == null && (full_badge.addon ? addon_hidden : ffz_hidden)) )
|
|
||||||
continue;
|
|
||||||
|
|
||||||
const slot = has(badge, 'slot') ? badge.slot : full_badge.slot,
|
|
||||||
old_badge = slotted[slot],
|
|
||||||
urls = badge.urls || (badge.image ? {1: badge.image} : null),
|
|
||||||
color = badge.color || full_badge.color || 'transparent',
|
|
||||||
no_invert = badge.no_invert,
|
|
||||||
masked = color !== 'transparent' && is_mask,
|
|
||||||
|
|
||||||
bu = (urls || full_badge.urls || {1: full_badge.image}),
|
|
||||||
bd = {
|
|
||||||
provider: 'ffz',
|
|
||||||
image: bu[4] || bu[2] || bu[1],
|
|
||||||
color: badge.color || full_badge.color,
|
|
||||||
title: badge.title || full_badge.title,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Hacky nonsense.
|
|
||||||
if ( ! full_badge.addon ) {
|
|
||||||
bd.image = `//cdn.frankerfacez.com/badge/${badge.id}/4/rounded`;
|
|
||||||
bd.color = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
let style;
|
|
||||||
|
|
||||||
if ( old_badge ) {
|
|
||||||
old_badge.badges.push(bd);
|
|
||||||
|
|
||||||
const replaces = has(badge, 'replaces') ? badge.replaces : full_badge.replaces,
|
|
||||||
replaces_type = badge.replaces_type || full_badge.replaces_type;
|
|
||||||
if ( replaces && (!replaces_type || replaces_type === old_badge.id) ) {
|
|
||||||
old_badge.replaced = badge.id;
|
|
||||||
old_badge.content = badge.content || full_badge.content || old_badge.content;
|
|
||||||
} else
|
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
style = old_badge.props.style;
|
handled_ids.add(badge.id);
|
||||||
|
|
||||||
} else if ( slot == null )
|
const full_badge = this.badges[badge.id] || {},
|
||||||
continue;
|
is_hidden = hidden_badges[badge.id];
|
||||||
|
|
||||||
else {
|
if ( is_hidden || (is_hidden == null && (full_badge.addon ? addon_hidden : ffz_hidden)) )
|
||||||
style = {};
|
continue;
|
||||||
const props = {
|
|
||||||
className: 'ffz-tooltip ffz-badge',
|
|
||||||
'data-tooltip-type': 'badge',
|
|
||||||
'data-provider': 'ffz',
|
|
||||||
'data-badge': badge.id,
|
|
||||||
style
|
|
||||||
};
|
|
||||||
|
|
||||||
slotted[slot] = {
|
const slot = has(badge, 'slot') ? badge.slot : full_badge.slot,
|
||||||
id: badge.id,
|
old_badge = slotted[slot],
|
||||||
props,
|
urls = badge.urls || (badge.image ? {1: badge.image} : null),
|
||||||
badges: [bd],
|
color = badge.color || full_badge.color || 'transparent',
|
||||||
content: badge.content || full_badge.content
|
no_invert = badge.no_invert,
|
||||||
|
masked = color !== 'transparent' && is_mask,
|
||||||
|
|
||||||
|
bu = (urls || full_badge.urls || {1: full_badge.image}),
|
||||||
|
bd = {
|
||||||
|
provider: 'ffz',
|
||||||
|
image: bu[4] || bu[2] || bu[1],
|
||||||
|
color: badge.color || full_badge.color,
|
||||||
|
title: badge.title || full_badge.title,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Hacky nonsense.
|
||||||
|
if ( ! full_badge.addon ) {
|
||||||
|
bd.image = `//cdn.frankerfacez.com/badge/${badge.id}/4/rounded`;
|
||||||
|
bd.color = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let style;
|
||||||
|
|
||||||
|
if ( old_badge ) {
|
||||||
|
old_badge.badges.push(bd);
|
||||||
|
|
||||||
|
const replaces = has(badge, 'replaces') ? badge.replaces : full_badge.replaces,
|
||||||
|
replaces_type = badge.replaces_type || full_badge.replaces_type;
|
||||||
|
if ( replaces && (!replaces_type || replaces_type === old_badge.id) ) {
|
||||||
|
old_badge.replaced = badge.id;
|
||||||
|
old_badge.content = badge.content || full_badge.content || old_badge.content;
|
||||||
|
} else
|
||||||
|
continue;
|
||||||
|
|
||||||
|
style = old_badge.props.style;
|
||||||
|
|
||||||
|
} else if ( slot == null )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
else {
|
||||||
|
style = {};
|
||||||
|
const props = {
|
||||||
|
className: 'ffz-tooltip ffz-badge',
|
||||||
|
'data-tooltip-type': 'badge',
|
||||||
|
'data-provider': 'ffz',
|
||||||
|
'data-badge': badge.id,
|
||||||
|
style
|
||||||
|
};
|
||||||
|
|
||||||
|
slotted[slot] = {
|
||||||
|
id: badge.id,
|
||||||
|
props,
|
||||||
|
badges: [bd],
|
||||||
|
content: badge.content || full_badge.content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (no_invert) {
|
||||||
|
slotted[slot].full_size = true;
|
||||||
|
slotted[slot].no_invert = true;
|
||||||
|
|
||||||
|
style.background = 'unset';
|
||||||
|
style.backgroundSize = 'unset';
|
||||||
|
style[CSS_MASK_IMAGE] = 'unset';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( (has_image || color === 'transparent') && urls ) {
|
||||||
|
const image = `url("${urls[1]}")`;
|
||||||
|
let image_set;
|
||||||
|
if ( urls[2] || urls[4] )
|
||||||
|
image_set = `${WEBKIT}image-set(${image} 1x${urls[2] ? `, url("${urls[2]}") 2x` : ''}${urls[4] ? `, url("${urls[4]}") 4x` : ''})`;
|
||||||
|
|
||||||
|
style[masked && !no_invert ? CSS_MASK_IMAGE : 'backgroundImage'] = image;
|
||||||
|
if ( image_set )
|
||||||
|
style[masked && !no_invert ? CSS_MASK_IMAGE : 'backgroundImage'] = image_set;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( is_colored && badge.color ) {
|
||||||
|
if ( masked && !no_invert )
|
||||||
|
style.backgroundImage = `linear-gradient(${badge.color},${badge.color})`;
|
||||||
|
else
|
||||||
|
style.backgroundColor = badge.color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (no_invert) {
|
|
||||||
slotted[slot].full_size = true;
|
|
||||||
slotted[slot].no_invert = true;
|
|
||||||
|
|
||||||
style.background = 'unset';
|
|
||||||
style.backgroundSize = 'unset';
|
|
||||||
style[CSS_MASK_IMAGE] = 'unset';
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( (has_image || color === 'transparent') && urls ) {
|
|
||||||
const image = `url("${urls[1]}")`;
|
|
||||||
let image_set;
|
|
||||||
if ( urls[2] || urls[4] )
|
|
||||||
image_set = `${WEBKIT}image-set(${image} 1x${urls[2] ? `, url("${urls[2]}") 2x` : ''}${urls[4] ? `, url("${urls[4]}") 4x` : ''})`;
|
|
||||||
|
|
||||||
style[masked && !no_invert ? CSS_MASK_IMAGE : 'backgroundImage'] = image;
|
|
||||||
if ( image_set )
|
|
||||||
style[masked && !no_invert ? CSS_MASK_IMAGE : 'backgroundImage'] = image_set;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( is_colored && badge.color ) {
|
|
||||||
if ( masked && !no_invert )
|
|
||||||
style.backgroundImage = `linear-gradient(${badge.color},${badge.color})`;
|
|
||||||
else
|
|
||||||
style.backgroundColor = badge.color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for(const slot in slotted)
|
for(const slot in slotted)
|
||||||
if ( has(slotted, slot) ) {
|
if ( has(slotted, slot) ) {
|
||||||
|
@ -785,19 +803,56 @@ export default class Badges extends Module {
|
||||||
|
|
||||||
getBadges(user_id, user_login, room_id, room_login) {
|
getBadges(user_id, user_login, room_id, room_login) {
|
||||||
const room = this.parent.getRoom(room_id, room_login, true),
|
const room = this.parent.getRoom(room_id, room_login, true),
|
||||||
global_user = this.parent.getUser(user_id, user_login, true),
|
global_user = this.parent.getUser(user_id, user_login, true);
|
||||||
room_user = room && room.getUser(user_id, user_login, true);
|
|
||||||
|
|
||||||
const out = (global_user ? global_user.badges._cache : []).concat(
|
if ( global_user ) {
|
||||||
room_user ? room_user.badges._cache : []);
|
user_id = user_id ?? global_user.id;
|
||||||
|
user_login = user_login ?? global_user.login;
|
||||||
|
}
|
||||||
|
|
||||||
if ( this.supporter_id && this.supporters.has(`${user_id}`) )
|
const room_user = room && room.getUser(user_id, user_login, true);
|
||||||
out.push({id: this.supporter_id});
|
|
||||||
|
const out = (global_user?.badges ? global_user.badges._cache : []).concat(
|
||||||
|
room_user?.badges ? room_user.badges._cache : []);
|
||||||
|
|
||||||
|
if ( this.bulk.size ) {
|
||||||
|
const str_user = String(user_id);
|
||||||
|
for(const [badge_id, users] of this.bulk) {
|
||||||
|
if ( users?._cache.has(str_user) )
|
||||||
|
out.push({id: badge_id});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
setBulk(source, badge_id, entries) {
|
||||||
|
let set = this.bulk.get(badge_id);
|
||||||
|
if ( ! set )
|
||||||
|
this.bulk.set(badge_id, set = new SourcedSet(true));
|
||||||
|
|
||||||
|
set.set(source, entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteBulk(source, badge_id) {
|
||||||
|
const set = this.bulk.get(badge_id);
|
||||||
|
if ( set )
|
||||||
|
set.delete(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
extendBulk(source, badge_id, entries) {
|
||||||
|
let set = this.bulk.get(badge_id);
|
||||||
|
if ( ! set )
|
||||||
|
this.bulk.set(badge_id, set = new SourcedSet(true));
|
||||||
|
|
||||||
|
if ( ! Array.isArray(entries) )
|
||||||
|
entries = [entries];
|
||||||
|
|
||||||
|
set.extend(source, ...entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
async loadGlobalBadges(tries = 0) {
|
async loadGlobalBadges(tries = 0) {
|
||||||
let response, data;
|
let response, data;
|
||||||
|
|
||||||
|
@ -839,15 +894,17 @@ export default class Badges extends Module {
|
||||||
if ( data.users )
|
if ( data.users )
|
||||||
for(const badge_id in data.users)
|
for(const badge_id in data.users)
|
||||||
if ( has(data.users, badge_id) ) {
|
if ( has(data.users, badge_id) ) {
|
||||||
const badge = this.badges[badge_id];
|
const badge = this.badges[badge_id],
|
||||||
|
name = badge?.name;
|
||||||
let c = 0;
|
let c = 0;
|
||||||
|
|
||||||
if ( badge?.name === 'supporter' ) {
|
if ( name === 'supporter' || name === 'bot' ) {
|
||||||
this.supporter_id = badge_id;
|
this.setBulk('ffz-global', badge_id, data.users[badge_id].map(x => String(x)));
|
||||||
|
/*this.supporter_id = badge_id;
|
||||||
for(const user_id of data.users[badge_id])
|
for(const user_id of data.users[badge_id])
|
||||||
this.supporters.add(`${user_id}`);
|
this.supporters.add(`${user_id}`);*/
|
||||||
|
|
||||||
c = this.supporters.size;
|
c = data.users[badge_id].length; // this.supporters.size;
|
||||||
} else
|
} else
|
||||||
for(const user_id of data.users[badge_id]) {
|
for(const user_id of data.users[badge_id]) {
|
||||||
const user = this.parent.getUser(user_id, undefined);
|
const user = this.parent.getUser(user_id, undefined);
|
||||||
|
|
|
@ -506,9 +506,9 @@ export default class Emotes extends Module {
|
||||||
room_user = room && room.getUser(user_id, user_login, true),
|
room_user = room && room.getUser(user_id, user_login, true),
|
||||||
user = this.parent.getUser(user_id, user_login, true);
|
user = this.parent.getUser(user_id, user_login, true);
|
||||||
|
|
||||||
return (user ? user.emote_sets._cache : []).concat(
|
return (user?.emote_sets ? user.emote_sets._cache : []).concat(
|
||||||
room_user ? room_user.emote_sets._cache : [],
|
room_user?.emote_sets ? room_user.emote_sets._cache : [],
|
||||||
room ? room.emote_sets._cache : [],
|
room?.emote_sets ? room.emote_sets._cache : [],
|
||||||
this.default_sets._cache
|
this.default_sets._cache
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -519,7 +519,7 @@ export default class Emotes extends Module {
|
||||||
}
|
}
|
||||||
|
|
||||||
_withSources(out, seen, emote_sets) { // eslint-disable-line class-methods-use-this
|
_withSources(out, seen, emote_sets) { // eslint-disable-line class-methods-use-this
|
||||||
if ( ! emote_sets._sources )
|
if ( ! emote_sets?._sources )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
for(const [provider, data] of emote_sets._sources)
|
for(const [provider, data] of emote_sets._sources)
|
||||||
|
@ -560,8 +560,11 @@ export default class Emotes extends Module {
|
||||||
if ( ! room )
|
if ( ! room )
|
||||||
return [];
|
return [];
|
||||||
|
|
||||||
if ( ! room_user )
|
if ( ! room_user?.emote_sets )
|
||||||
return room.emote_sets._cache;
|
return room.emote_sets ? room.emote_sets._cache : [];
|
||||||
|
|
||||||
|
else if ( ! room.emote_sets )
|
||||||
|
return room_user.emote_sets._cache;
|
||||||
|
|
||||||
return room_user.emote_sets._cache.concat(room.emote_sets._cache);
|
return room_user.emote_sets._cache.concat(room.emote_sets._cache);
|
||||||
}
|
}
|
||||||
|
@ -589,7 +592,7 @@ export default class Emotes extends Module {
|
||||||
|
|
||||||
getGlobalSetIDs(user_id, user_login) {
|
getGlobalSetIDs(user_id, user_login) {
|
||||||
const user = this.parent.getUser(user_id, user_login, true);
|
const user = this.parent.getUser(user_id, user_login, true);
|
||||||
if ( ! user )
|
if ( ! user?.emote_sets )
|
||||||
return this.default_sets._cache;
|
return this.default_sets._cache;
|
||||||
|
|
||||||
return user.emote_sets._cache.concat(this.default_sets._cache);
|
return user.emote_sets._cache.concat(this.default_sets._cache);
|
||||||
|
|
|
@ -504,7 +504,7 @@ export default class Chat extends Module {
|
||||||
path: 'Chat > Filtering > Highlight >> Badges',
|
path: 'Chat > Filtering > Highlight >> Badges',
|
||||||
component: 'badge-highlighting',
|
component: 'badge-highlighting',
|
||||||
colored: true,
|
colored: true,
|
||||||
data: () => this.badges.getSettingsBadges()
|
data: () => this.badges.getSettingsBadges(true)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -540,7 +540,7 @@ export default class Chat extends Module {
|
||||||
path: 'Chat > Filtering > Block >> Badges @{"description": "**Note:** This section is for filtering messages out of chat from users with specific badges. If you wish to hide a badge, go to [Chat > Badges >> Visibility](~chat.badges.tabs.visibility)."}',
|
path: 'Chat > Filtering > Block >> Badges @{"description": "**Note:** This section is for filtering messages out of chat from users with specific badges. If you wish to hide a badge, go to [Chat > Badges >> Visibility](~chat.badges.tabs.visibility)."}',
|
||||||
component: 'badge-highlighting',
|
component: 'badge-highlighting',
|
||||||
removable: true,
|
removable: true,
|
||||||
data: () => this.badges.getSettingsBadges()
|
data: () => this.badges.getSettingsBadges(true)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -687,13 +687,31 @@ export default class Chat extends Module {
|
||||||
if ( ! val || ! val.length )
|
if ( ! val || ! val.length )
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
const out = [
|
const data = [
|
||||||
[[], []],
|
[ // no-remove
|
||||||
[[], []]
|
[ // sensitive
|
||||||
|
[], [] // word
|
||||||
|
],
|
||||||
|
[ // intensitive
|
||||||
|
[], []
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[ // remove
|
||||||
|
[ // sensitive
|
||||||
|
[], [] // word
|
||||||
|
],
|
||||||
|
[ // intensiitve
|
||||||
|
[], []
|
||||||
|
]
|
||||||
|
]
|
||||||
];
|
];
|
||||||
|
|
||||||
|
let had_remove = false,
|
||||||
|
had_non = false;
|
||||||
|
|
||||||
for(const item of val) {
|
for(const item of val) {
|
||||||
const t = item.t,
|
const t = item.t,
|
||||||
|
sensitive = item.s,
|
||||||
word = has(item, 'w') ? item.w : t !== 'raw';
|
word = has(item, 'w') ? item.w : t !== 'raw';
|
||||||
let v = item.v;
|
let v = item.v;
|
||||||
|
|
||||||
|
@ -706,15 +724,21 @@ export default class Chat extends Module {
|
||||||
if ( ! v || ! v.length )
|
if ( ! v || ! v.length )
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
out[item.remove ? 1 : 0][word ? 0 : 1].push(v);
|
if ( item.remove )
|
||||||
|
had_remove = true;
|
||||||
|
else
|
||||||
|
had_non = true;
|
||||||
|
|
||||||
|
data[item.remove ? 1 : 0][sensitive ? 0 : 1][word ? 0 : 1].push(v);
|
||||||
}
|
}
|
||||||
|
|
||||||
return out.map(data => {
|
if ( ! had_remove && ! had_non )
|
||||||
if ( data[0].length )
|
return null;
|
||||||
data[1].push(`(^|.*?${SEPARATORS})(?:${data[0].join('|')})(?=$|${SEPARATORS})`);
|
|
||||||
|
|
||||||
return data[1].length ? new RegExp(data[1].join('|'), 'gi') : null;
|
return {
|
||||||
});
|
remove: had_remove ? formatTerms(data[1]) : null,
|
||||||
|
non: had_non ? formatTerms(data[0]) : null
|
||||||
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -936,6 +960,13 @@ export default class Chat extends Module {
|
||||||
path: 'Chat > Appearance >> Emotes',
|
path: 'Chat > Appearance >> Emotes',
|
||||||
title: 'Animated Emotes',
|
title: 'Animated Emotes',
|
||||||
|
|
||||||
|
default(ctx) {
|
||||||
|
const temp = ctx.get('ffzap.betterttv.gif_emoticons_mode');
|
||||||
|
if ( temp == null )
|
||||||
|
return ctx.get('context.bttv.gifs') ? 1 : 0;
|
||||||
|
return temp === 2 ? 1 : 0;
|
||||||
|
},
|
||||||
|
|
||||||
getExtraTerms: () => GIF_TERMS,
|
getExtraTerms: () => GIF_TERMS,
|
||||||
|
|
||||||
description: 'This controls whether or not animated emotes are allowed to play in chat. When this is `Disabled`, emotes will appear as static images. Setting this to `Enable on Hover` may cause performance issues.',
|
description: 'This controls whether or not animated emotes are allowed to play in chat. When this is `Disabled`, emotes will appear as static images. Setting this to `Enable on Hover` may cause performance issues.',
|
||||||
|
@ -966,13 +997,7 @@ export default class Chat extends Module {
|
||||||
});
|
});
|
||||||
|
|
||||||
this.settings.add('chat.bits.animated', {
|
this.settings.add('chat.bits.animated', {
|
||||||
requires: ['chat.emotes.animated'],
|
default: true,
|
||||||
default: null,
|
|
||||||
process(ctx, val) {
|
|
||||||
if ( val == null )
|
|
||||||
val = ctx.get('chat.emotes.animated') ? true : false
|
|
||||||
},
|
|
||||||
|
|
||||||
ui: {
|
ui: {
|
||||||
path: 'Chat > Bits and Cheering >> Appearance',
|
path: 'Chat > Bits and Cheering >> Appearance',
|
||||||
title: 'Display animated cheers.',
|
title: 'Display animated cheers.',
|
||||||
|
@ -1395,6 +1420,9 @@ export default class Chat extends Module {
|
||||||
if ( msg.deletedAt !== undefined )
|
if ( msg.deletedAt !== undefined )
|
||||||
msg.deleted = !!msg.deletedAt;
|
msg.deleted = !!msg.deletedAt;
|
||||||
|
|
||||||
|
// Addon Badges
|
||||||
|
msg.ffz_badges = this.badges.getBadges(user.id, user.login, msg.roomID, msg.roomLogin);
|
||||||
|
|
||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1634,7 +1662,7 @@ export default class Chat extends Module {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
tokenizeMessage(msg, user) {
|
tokenizeMessage(msg, user, haltable = false) {
|
||||||
if ( msg.content && ! msg.message )
|
if ( msg.content && ! msg.message )
|
||||||
msg.message = msg.content.text;
|
msg.message = msg.content.text;
|
||||||
|
|
||||||
|
@ -1646,8 +1674,13 @@ export default class Chat extends Module {
|
||||||
|
|
||||||
let tokens = [{type: 'text', text: msg.message}];
|
let tokens = [{type: 'text', text: msg.message}];
|
||||||
|
|
||||||
for(const tokenizer of this.__tokenizers)
|
for(const tokenizer of this.__tokenizers) {
|
||||||
tokens = tokenizer.process.call(this, tokens, msg, user);
|
tokens = tokenizer.process.call(this, tokens, msg, user, haltable);
|
||||||
|
if ( haltable && msg.ffz_halt_tokens ) {
|
||||||
|
msg.ffz_halt_tokens = undefined;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return tokens;
|
return tokens;
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ export default class Room {
|
||||||
this.refs = new Set;
|
this.refs = new Set;
|
||||||
this.style = new ManagedStyle(`room--${login}`);
|
this.style = new ManagedStyle(`room--${login}`);
|
||||||
|
|
||||||
this.emote_sets = new SourcedSet;
|
this.emote_sets = null; // new SourcedSet;
|
||||||
this.badges = null;
|
this.badges = null;
|
||||||
this.users = {};
|
this.users = {};
|
||||||
this.user_ids = {};
|
this.user_ids = {};
|
||||||
|
@ -305,9 +305,11 @@ export default class Room {
|
||||||
|
|
||||||
this.data = d;
|
this.data = d;
|
||||||
|
|
||||||
if ( d.set )
|
if ( d.set ) {
|
||||||
|
if ( ! this.emote_sets )
|
||||||
|
this.emote_sets = new SourcedSet;
|
||||||
this.emote_sets.set('main', d.set);
|
this.emote_sets.set('main', d.set);
|
||||||
else
|
} else if ( this.emote_sets )
|
||||||
this.emote_sets.delete('main');
|
this.emote_sets.delete('main');
|
||||||
|
|
||||||
|
|
||||||
|
@ -342,6 +344,9 @@ export default class Room {
|
||||||
if ( this.destroyed )
|
if ( this.destroyed )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if ( ! this.emote_sets )
|
||||||
|
this.emote_sets = new SourcedSet;
|
||||||
|
|
||||||
let changed = false;
|
let changed = false;
|
||||||
if ( ! this.emote_sets.sourceIncludes(provider, set_id) ) {
|
if ( ! this.emote_sets.sourceIncludes(provider, set_id) ) {
|
||||||
this.emote_sets.push(provider, set_id);
|
this.emote_sets.push(provider, set_id);
|
||||||
|
@ -357,7 +362,7 @@ export default class Room {
|
||||||
}
|
}
|
||||||
|
|
||||||
removeSet(provider, set_id) {
|
removeSet(provider, set_id) {
|
||||||
if ( this.destroyed )
|
if ( this.destroyed || ! this.emote_sets )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if ( this.emote_sets.sourceIncludes(provider, set_id) ) {
|
if ( this.emote_sets.sourceIncludes(provider, set_id) ) {
|
||||||
|
|
|
@ -464,7 +464,7 @@ export const BlockedUsers = {
|
||||||
type: 'user_block',
|
type: 'user_block',
|
||||||
priority: 100,
|
priority: 100,
|
||||||
|
|
||||||
process(tokens, msg, user) {
|
process(tokens, msg, user, haltable) {
|
||||||
if ( user && user.login && user.login == msg.user.login && ! this.context.get('chat.filtering.process-own') )
|
if ( user && user.login && user.login == msg.user.login && ! this.context.get('chat.filtering.process-own') )
|
||||||
return tokens;
|
return tokens;
|
||||||
|
|
||||||
|
@ -476,39 +476,69 @@ export const BlockedUsers = {
|
||||||
if ( regexes[1] && (regexes[1].test(u.login) || regexes[1].test(u.displayName)) ) {
|
if ( regexes[1] && (regexes[1].test(u.login) || regexes[1].test(u.displayName)) ) {
|
||||||
msg.deleted = true;
|
msg.deleted = true;
|
||||||
msg.ffz_removed = true;
|
msg.ffz_removed = true;
|
||||||
}
|
if ( haltable )
|
||||||
|
msg.ffz_halt_tokens = true;
|
||||||
|
|
||||||
if ( ! msg.deleted && regexes[0] && (regexes[0].test(u.login) || regexes[0].test(u.displayName)) )
|
} else if ( ! msg.deleted && regexes[0] && (regexes[0].test(u.login) || regexes[0].test(u.displayName)) )
|
||||||
msg.deleted = true;
|
msg.deleted = true;
|
||||||
|
|
||||||
return tokens;
|
return tokens;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const BadgeHighlights = {
|
function getBadgeIDs(msg) {
|
||||||
type: 'badge_highlight',
|
let keys = msg.badges ? Object.keys(msg.badges) : null;
|
||||||
|
if ( ! msg.ffz_badges )
|
||||||
|
return keys;
|
||||||
|
|
||||||
|
if ( ! keys )
|
||||||
|
keys = [];
|
||||||
|
|
||||||
|
for(const badge of msg.ffz_badges)
|
||||||
|
if ( badge?.id )
|
||||||
|
keys.push(badge.id);
|
||||||
|
|
||||||
|
return keys;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const BadgeStuff = {
|
||||||
|
type: 'badge_stuff',
|
||||||
priority: 80,
|
priority: 80,
|
||||||
|
|
||||||
process(tokens, msg, user) {
|
process(tokens, msg, user, haltable) {
|
||||||
if ( user && user.login && user.login == msg.user.login && ! this.context.get('chat.filtering.process-own') )
|
if ( user && user.login && user.login == msg.user.login && ! this.context.get('chat.filtering.process-own') )
|
||||||
return tokens;
|
return tokens;
|
||||||
|
|
||||||
const badges = msg.badges;
|
const colors = this.context.get('chat.filtering.highlight-basic-badges--colors'),
|
||||||
if ( ! badges )
|
list = this.context.get('chat.filtering.highlight-basic-badges-blocked--list');
|
||||||
|
|
||||||
|
if ( ! colors && ! list )
|
||||||
return tokens;
|
return tokens;
|
||||||
|
|
||||||
const colors = this.context.get('chat.filtering.highlight-basic-badges--colors');
|
const keys = getBadgeIDs(msg);
|
||||||
if ( ! colors || ! colors.size )
|
if ( ! keys || ! keys.length )
|
||||||
return tokens;
|
return tokens;
|
||||||
|
|
||||||
for(const badge of Object.keys(badges)) {
|
for(const badge of keys) {
|
||||||
if ( colors.has(badge) ) {
|
if ( list && list[1].includes(badge) ) {
|
||||||
|
msg.deleted = true;
|
||||||
|
msg.ffz_removed = true;
|
||||||
|
if ( haltable )
|
||||||
|
msg.ffz_halt_tokens = true;
|
||||||
|
return tokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( list && ! msg.deleted && list[0].includes(badge) )
|
||||||
|
msg.deleted = true;
|
||||||
|
|
||||||
|
if ( colors && colors.has(badge) ) {
|
||||||
const color = colors.get(badge);
|
const color = colors.get(badge);
|
||||||
(msg.highlights = (msg.highlights || new Set())).add('badge');
|
(msg.highlights = (msg.highlights || new Set())).add('badge');
|
||||||
msg.mentioned = true;
|
msg.mentioned = true;
|
||||||
if ( color ) {
|
if ( color ) {
|
||||||
msg.mention_color = color;
|
msg.mention_color = color;
|
||||||
return tokens;
|
if ( ! list )
|
||||||
|
return tokens;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -517,25 +547,27 @@ export const BadgeHighlights = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const BlockedBadges = {
|
/*export const BlockedBadges = {
|
||||||
type: 'badge_block',
|
type: 'badge_block',
|
||||||
priority: 100,
|
priority: 100,
|
||||||
process(tokens, msg, user) {
|
process(tokens, msg, user, haltable) {
|
||||||
if ( user && user.login && user.login == msg.user.login && ! this.context.get('chat.filtering.process-own') )
|
if ( user && user.login && user.login == msg.user.login && ! this.context.get('chat.filtering.process-own') )
|
||||||
return tokens;
|
return tokens;
|
||||||
|
|
||||||
const badges = msg.badges;
|
|
||||||
if ( ! badges )
|
|
||||||
return tokens;
|
|
||||||
|
|
||||||
const list = this.context.get('chat.filtering.highlight-basic-badges-blocked--list');
|
const list = this.context.get('chat.filtering.highlight-basic-badges-blocked--list');
|
||||||
if ( ! list || (! list[0].length && ! list[1].length) )
|
if ( ! list || (! list[0].length && ! list[1].length) )
|
||||||
return tokens;
|
return tokens;
|
||||||
|
|
||||||
for(const badge of Object.keys(badges)) {
|
const keys = getBadgeIDs(msg);
|
||||||
|
if ( ! keys || ! keys.length )
|
||||||
|
return tokens;
|
||||||
|
|
||||||
|
for(const badge of keys) {
|
||||||
if ( list[1].includes(badge) ) {
|
if ( list[1].includes(badge) ) {
|
||||||
msg.deleted = true;
|
msg.deleted = true;
|
||||||
msg.ffz_removed = true;
|
msg.ffz_removed = true;
|
||||||
|
if ( haltable )
|
||||||
|
msg.ffz_halt_tokens = true;
|
||||||
return tokens;
|
return tokens;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -545,7 +577,7 @@ export const BlockedBadges = {
|
||||||
|
|
||||||
return tokens;
|
return tokens;
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
|
|
||||||
export const CustomHighlights = {
|
export const CustomHighlights = {
|
||||||
type: 'highlight',
|
type: 'highlight',
|
||||||
|
@ -649,7 +681,7 @@ export const CustomHighlights = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function blocked_process(tokens, msg, regex, do_remove) {
|
function blocked_process(tokens, msg, regexes, do_remove, haltable) {
|
||||||
const out = [];
|
const out = [];
|
||||||
for(const token of tokens) {
|
for(const token of tokens) {
|
||||||
if ( token.type !== 'text' ) {
|
if ( token.type !== 'text' ) {
|
||||||
|
@ -657,11 +689,23 @@ function blocked_process(tokens, msg, regex, do_remove) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
regex.lastIndex = 0;
|
|
||||||
const text = token.text;
|
const text = token.text;
|
||||||
let idx = 0, match;
|
let idx = 0, match;
|
||||||
|
|
||||||
while((match = regex.exec(text))) {
|
while(idx < text.length) {
|
||||||
|
if ( regexes[0] )
|
||||||
|
regexes[0].lastIndex = idx;
|
||||||
|
if ( regexes[1] )
|
||||||
|
regexes[1].lastIndex = idx;
|
||||||
|
|
||||||
|
match = regexes[0] ? regexes[0].exec(text) : null;
|
||||||
|
const second = regexes[1] ? regexes[1].exec(text) : null;
|
||||||
|
if ( second && (! match || match.index > second.index) )
|
||||||
|
match = second;
|
||||||
|
|
||||||
|
if ( ! match )
|
||||||
|
break;
|
||||||
|
|
||||||
const raw_nix = match.index,
|
const raw_nix = match.index,
|
||||||
offset = match[1] ? match[1].length : 0,
|
offset = match[1] ? match[1].length : 0,
|
||||||
nix = raw_nix + offset;
|
nix = raw_nix + offset;
|
||||||
|
@ -669,15 +713,18 @@ function blocked_process(tokens, msg, regex, do_remove) {
|
||||||
if ( idx !== nix )
|
if ( idx !== nix )
|
||||||
out.push({type: 'text', text: text.slice(idx, nix)});
|
out.push({type: 'text', text: text.slice(idx, nix)});
|
||||||
|
|
||||||
|
if ( do_remove ) {
|
||||||
|
msg.ffz_removed = true;
|
||||||
|
if ( haltable )
|
||||||
|
return tokens;
|
||||||
|
}
|
||||||
|
|
||||||
out.push({
|
out.push({
|
||||||
type: 'blocked',
|
type: 'blocked',
|
||||||
text: match[0].slice(offset)
|
text: match[0].slice(offset)
|
||||||
});
|
});
|
||||||
|
|
||||||
if ( do_remove )
|
idx = raw_nix + match[0].length
|
||||||
msg.ffz_removed = true;
|
|
||||||
|
|
||||||
idx = raw_nix + match[0].length;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( idx < text.length )
|
if ( idx < text.length )
|
||||||
|
@ -715,7 +762,7 @@ export const BlockedTerms = {
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
process(tokens, msg, user) {
|
process(tokens, msg, user, haltable) {
|
||||||
if ( ! tokens || ! tokens.length )
|
if ( ! tokens || ! tokens.length )
|
||||||
return tokens;
|
return tokens;
|
||||||
|
|
||||||
|
@ -726,11 +773,16 @@ export const BlockedTerms = {
|
||||||
if ( ! regexes )
|
if ( ! regexes )
|
||||||
return tokens;
|
return tokens;
|
||||||
|
|
||||||
if ( regexes[0] )
|
if ( regexes.remove ) {
|
||||||
tokens = blocked_process(tokens, msg, regexes[0], false);
|
tokens = blocked_process(tokens, msg, regexes.remove, true, haltable);
|
||||||
|
if ( haltable && msg.ffz_removed ) {
|
||||||
|
msg.ffz_halt_tokens = true;
|
||||||
|
return tokens;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ( regexes[1] )
|
if ( regexes.non )
|
||||||
tokens = blocked_process(tokens, msg, regexes[1], true);
|
tokens = blocked_process(tokens, msg, regexes.non, false, haltable);
|
||||||
|
|
||||||
return tokens;
|
return tokens;
|
||||||
}
|
}
|
||||||
|
@ -792,7 +844,7 @@ export const AutomoddedTerms = {
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
|
|
||||||
process(tokens, msg) {
|
process(tokens, msg, user, haltable) {
|
||||||
if ( ! tokens || ! tokens.length || ! msg.flags || ! Array.isArray(msg.flags.list) )
|
if ( ! tokens || ! tokens.length || ! msg.flags || ! Array.isArray(msg.flags.list) )
|
||||||
return tokens;
|
return tokens;
|
||||||
|
|
||||||
|
@ -827,6 +879,8 @@ export const AutomoddedTerms = {
|
||||||
|
|
||||||
if ( remove ) {
|
if ( remove ) {
|
||||||
msg.ffz_removed = true;
|
msg.ffz_removed = true;
|
||||||
|
if ( haltable )
|
||||||
|
msg.ffz_halt_tokens = true;
|
||||||
return tokens;
|
return tokens;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,8 +11,8 @@ export default class User {
|
||||||
this.manager = manager;
|
this.manager = manager;
|
||||||
this.room = room;
|
this.room = room;
|
||||||
|
|
||||||
this.emote_sets = new SourcedSet;
|
this.emote_sets = null; //new SourcedSet;
|
||||||
this.badges = new SourcedSet;
|
this.badges = null; // new SourcedSet;
|
||||||
|
|
||||||
this._id = id;
|
this._id = id;
|
||||||
this.login = login;
|
this.login = login;
|
||||||
|
@ -31,6 +31,9 @@ export default class User {
|
||||||
this.emote_sets = null;
|
this.emote_sets = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ( this.badges )
|
||||||
|
this.badges = null;
|
||||||
|
|
||||||
const parent = this.room || this.manager;
|
const parent = this.room || this.manager;
|
||||||
|
|
||||||
if ( parent ) {
|
if ( parent ) {
|
||||||
|
@ -100,11 +103,17 @@ export default class User {
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
|
|
||||||
addBadge(provider, badge_id, data) {
|
addBadge(provider, badge_id, data) {
|
||||||
|
if ( this.destroyed )
|
||||||
|
return;
|
||||||
|
|
||||||
if ( data )
|
if ( data )
|
||||||
data.id = badge_id;
|
data.id = badge_id;
|
||||||
else
|
else
|
||||||
data = {id: badge_id};
|
data = {id: badge_id};
|
||||||
|
|
||||||
|
if ( ! this.badges )
|
||||||
|
this.badges = new SourcedSet;
|
||||||
|
|
||||||
if ( this.badges.has(provider) )
|
if ( this.badges.has(provider) )
|
||||||
for(const old_b of this.badges.get(provider))
|
for(const old_b of this.badges.get(provider))
|
||||||
if ( old_b.id == badge_id ) {
|
if ( old_b.id == badge_id ) {
|
||||||
|
@ -119,6 +128,9 @@ export default class User {
|
||||||
|
|
||||||
|
|
||||||
getBadge(badge_id) {
|
getBadge(badge_id) {
|
||||||
|
if ( ! this.badges )
|
||||||
|
return null;
|
||||||
|
|
||||||
for(const badge of this.badges._cache)
|
for(const badge of this.badges._cache)
|
||||||
if ( badge.id == badge_id )
|
if ( badge.id == badge_id )
|
||||||
return badge;
|
return badge;
|
||||||
|
@ -126,7 +138,7 @@ export default class User {
|
||||||
|
|
||||||
|
|
||||||
removeBadge(provider, badge_id) {
|
removeBadge(provider, badge_id) {
|
||||||
if ( ! this.badges.has(provider) )
|
if ( ! this.badges || ! this.badges.has(provider) )
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
for(const old_b of this.badges.get(provider))
|
for(const old_b of this.badges.get(provider))
|
||||||
|
@ -147,6 +159,9 @@ export default class User {
|
||||||
if ( this.destroyed )
|
if ( this.destroyed )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if ( ! this.emote_sets )
|
||||||
|
this.emote_sets = new SourcedSet;
|
||||||
|
|
||||||
if ( ! this.emote_sets.sourceIncludes(provider, set_id) ) {
|
if ( ! this.emote_sets.sourceIncludes(provider, set_id) ) {
|
||||||
this.emote_sets.push(provider, set_id);
|
this.emote_sets.push(provider, set_id);
|
||||||
this.manager.emotes.refSet(set_id);
|
this.manager.emotes.refSet(set_id);
|
||||||
|
@ -156,7 +171,7 @@ export default class User {
|
||||||
}
|
}
|
||||||
|
|
||||||
removeSet(provider, set_id) {
|
removeSet(provider, set_id) {
|
||||||
if ( this.destroyed )
|
if ( this.destroyed || ! this.emote_sets )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if ( this.emote_sets.sourceIncludes(provider, set_id) ) {
|
if ( this.emote_sets.sourceIncludes(provider, set_id) ) {
|
||||||
|
|
|
@ -3,8 +3,9 @@
|
||||||
<div class="tw-align-items-center tw-flex tw-flex-nowrap tw-flex-row tw-full-width">
|
<div class="tw-align-items-center tw-flex tw-flex-nowrap tw-flex-row tw-full-width">
|
||||||
<div class="tw-mg-r-1">
|
<div class="tw-mg-r-1">
|
||||||
<img
|
<img
|
||||||
v-if="current"
|
v-if="current && current.image"
|
||||||
:src="current.image"
|
:src="current.image"
|
||||||
|
:style="{backgroundColor: current.color || null}"
|
||||||
class="ffz--badge-term-image"
|
class="ffz--badge-term-image"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
@ -50,7 +51,34 @@
|
||||||
</figure>
|
</figure>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="removable" class="tw-flex-shrink-0 tw-mg-r-05 tw-relative tw-tooltip__container">
|
<div
|
||||||
|
v-if="removable && (editing || display.remove)"
|
||||||
|
class="tw-flex-shrink-0 tw-mg-r-05 tw-mg-y-05 tw-flex tw-align-items-center ffz-checkbox tw-relative tw-tooltip__container"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
v-if="editing"
|
||||||
|
:id="'remove$' + id"
|
||||||
|
v-model="edit_data.remove"
|
||||||
|
type="checkbox"
|
||||||
|
class="ffz-min-width-unset ffz-checkbox__input"
|
||||||
|
>
|
||||||
|
|
||||||
|
<label
|
||||||
|
v-if="editing"
|
||||||
|
:for="'remove$' + id"
|
||||||
|
class="ffz-min-width-unset ffz-checkbox__label"
|
||||||
|
>
|
||||||
|
<span class="tw-mg-l-05 ffz-i-trash" />
|
||||||
|
</label>
|
||||||
|
<span
|
||||||
|
v-else-if="term.remove"
|
||||||
|
class="ffz-i-trash tw-pd-x-1"
|
||||||
|
/>
|
||||||
|
<div class="tw-tooltip tw-tooltip--down tw-tooltip--align-right">
|
||||||
|
{{ t('setting.terms.remove.on', 'Remove matching messages from chat.') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!--div v-if="removable" class="tw-flex-shrink-0 tw-mg-r-05 tw-relative tw-tooltip__container">
|
||||||
<button
|
<button
|
||||||
v-if="editing"
|
v-if="editing"
|
||||||
:class="{active: edit_data.remove}"
|
:class="{active: edit_data.remove}"
|
||||||
|
@ -74,7 +102,7 @@
|
||||||
{{ t('setting.terms.remove.off', 'Do not remove matching messages from chat.') }}
|
{{ t('setting.terms.remove.off', 'Do not remove matching messages from chat.') }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div-->
|
||||||
<div v-if="adding" class="tw-flex-shrink-0">
|
<div v-if="adding" class="tw-flex-shrink-0">
|
||||||
<button
|
<button
|
||||||
class="tw-button"
|
class="tw-button"
|
||||||
|
@ -165,14 +193,14 @@ export default {
|
||||||
data() {
|
data() {
|
||||||
if ( this.adding )
|
if ( this.adding )
|
||||||
return {
|
return {
|
||||||
editor_id: id++,
|
id: id++,
|
||||||
deleting: false,
|
deleting: false,
|
||||||
editing: true,
|
editing: true,
|
||||||
edit_data: deep_copy(this.term)
|
edit_data: deep_copy(this.term)
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
editor_id: id++,
|
id: id++,
|
||||||
deleting: false,
|
deleting: false,
|
||||||
editing: false,
|
editing: false,
|
||||||
edit_data: null
|
edit_data: null
|
||||||
|
|
|
@ -166,24 +166,33 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
_uses_changed(uses) {
|
_uses_changed(uses) {
|
||||||
if ( this.source )
|
if ( this._uses_cb )
|
||||||
this.source.off('changed', this._source_setting_changed, this);
|
clearTimeout(this._uses_cb);
|
||||||
|
|
||||||
// We primarily only care about the main source.
|
// We don't do this immediately because this code will be
|
||||||
uses = uses ? uses[0] : null;
|
// running inside a "changed" event handler and unregistering
|
||||||
|
// our listener will throw a concurrent modification exception.
|
||||||
|
this._uses_cb = setTimeout(() => {
|
||||||
|
if ( this.source )
|
||||||
|
this.source.off('changed', this._source_setting_changed, this);
|
||||||
|
|
||||||
const source = this.source = this.context.profile_keys[uses],
|
// We primarily only care about the main source.
|
||||||
setting = this.item.setting;
|
uses = uses ? uses[0] : null;
|
||||||
|
|
||||||
if ( source ) {
|
const source = this.source = uses == null ? null : this.context.profile_keys[uses],
|
||||||
source.on('changed', this._source_setting_changed, this);
|
setting = this.item.setting;
|
||||||
this.source_value = deep_copy(source.get(setting));
|
|
||||||
|
|
||||||
} else
|
if ( source ) {
|
||||||
this.source_value = undefined;
|
source.on('changed', this._source_setting_changed, this);
|
||||||
|
this.source_value = deep_copy(source.get(setting));
|
||||||
|
|
||||||
if ( ! this.has_value )
|
} else
|
||||||
this.value = this.isInherited ? this.source_value : this.default_value;
|
this.source_value = undefined;
|
||||||
|
|
||||||
|
this.has_value = this.profile.has(this.item.setting);
|
||||||
|
if ( ! this.has_value )
|
||||||
|
this.value = this.isInherited ? this.source_value : this.default_value;
|
||||||
|
}, 0);
|
||||||
},
|
},
|
||||||
|
|
||||||
set(value) {
|
set(value) {
|
||||||
|
|
|
@ -175,6 +175,7 @@ export default class Line extends Module {
|
||||||
roomLogin: room && room.login,
|
roomLogin: room && room.login,
|
||||||
roomID: room && room.id,
|
roomID: room && room.id,
|
||||||
badges,
|
badges,
|
||||||
|
ffz_badges: this.chat.badges.getBadges(author.id, author.login, room?.id, room?.login),
|
||||||
messageParts: msg.message.fragments
|
messageParts: msg.message.fragments
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,100 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Site Support: Twitch Clips
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
import BaseSite from '../base';
|
|
||||||
|
|
||||||
import WebMunch from 'utilities/compat/webmunch';
|
|
||||||
import Fine from 'utilities/compat/fine';
|
|
||||||
import Apollo from 'utilities/compat/apollo';
|
|
||||||
|
|
||||||
import {createElement} from 'utilities/dom';
|
|
||||||
|
|
||||||
import MAIN_URL from 'site/styles/main.scss';
|
|
||||||
|
|
||||||
//import Switchboard from './switchboard';
|
|
||||||
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// The Site
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
export default class Clippy extends BaseSite {
|
|
||||||
constructor(...args) {
|
|
||||||
super(...args);
|
|
||||||
|
|
||||||
this.inject(WebMunch);
|
|
||||||
this.inject(Fine);
|
|
||||||
this.inject(Apollo, false);
|
|
||||||
|
|
||||||
//this.inject(Switchboard);
|
|
||||||
}
|
|
||||||
|
|
||||||
async populateModules() {
|
|
||||||
const ctx = await require.context('site/modules', true, /(?:^(?:\.\/)?[^/]+|index)\.jsx?$/);
|
|
||||||
const modules = await this.populate(ctx, this.log);
|
|
||||||
this.log.info(`Loaded descriptions of ${Object.keys(modules).length} modules.`);
|
|
||||||
}
|
|
||||||
|
|
||||||
async onLoad() {
|
|
||||||
await this.populateModules();
|
|
||||||
}
|
|
||||||
|
|
||||||
onEnable() {
|
|
||||||
const thing = this.fine.searchTree(null, n => n.props && n.props.store),
|
|
||||||
store = this.store = thing && thing.props && thing.props.store;
|
|
||||||
|
|
||||||
if ( ! store )
|
|
||||||
return new Promise(r => setTimeout(r, 50)).then(() => this.onEnable());
|
|
||||||
|
|
||||||
// Share Context
|
|
||||||
store.subscribe(() => this.updateContext());
|
|
||||||
this.updateContext();
|
|
||||||
|
|
||||||
this.settings.updateContext({
|
|
||||||
clips: true
|
|
||||||
});
|
|
||||||
|
|
||||||
document.head.appendChild(createElement('link', {
|
|
||||||
href: MAIN_URL,
|
|
||||||
rel: 'stylesheet',
|
|
||||||
type: 'text/css',
|
|
||||||
crossOrigin: 'anonymouse'
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
updateContext() {
|
|
||||||
try {
|
|
||||||
const state = this.store.getState(),
|
|
||||||
history = this.router && this.router.history;
|
|
||||||
|
|
||||||
this.settings.updateContext({
|
|
||||||
location: history && history.location,
|
|
||||||
ui: state && state.ui,
|
|
||||||
session: state && state.session
|
|
||||||
});
|
|
||||||
} catch(err) {
|
|
||||||
this.log.error('Error updating context.', err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getSession() {
|
|
||||||
const state = this.store && this.store.getState();
|
|
||||||
return state && state.session;
|
|
||||||
}
|
|
||||||
|
|
||||||
getUser() {
|
|
||||||
if ( this._user )
|
|
||||||
return this._user;
|
|
||||||
|
|
||||||
const session = this.getSession();
|
|
||||||
return this._user = session && session.user;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Clippy.DIALOG_EXCLUSIVE = '.clips-root';
|
|
||||||
Clippy.DIALOG_MAXIMIZED = '.clips-root>.tw-full-height .scrollable-area';
|
|
||||||
Clippy.DIALOG_SELECTOR = '.clips-root>.tw-full-height .scrollable-area';
|
|
|
@ -1,13 +0,0 @@
|
||||||
query FFZ_GetBadges {
|
|
||||||
badges {
|
|
||||||
id
|
|
||||||
setID
|
|
||||||
version
|
|
||||||
title
|
|
||||||
clickAction
|
|
||||||
clickURL
|
|
||||||
image1x: imageURL(size: NORMAL)
|
|
||||||
image2x: imageURL(size: DOUBLE)
|
|
||||||
image4x: imageURL(size: QUADRUPLE)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,191 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Chat Hooks
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
import {get} from 'utilities/object';
|
|
||||||
import {ColorAdjuster} from 'utilities/color';
|
|
||||||
|
|
||||||
import Module from 'utilities/module';
|
|
||||||
|
|
||||||
import Line from './line';
|
|
||||||
import BADGE_QUERY from './get_badges.gql';
|
|
||||||
|
|
||||||
|
|
||||||
export default class Chat extends Module {
|
|
||||||
constructor(...args) {
|
|
||||||
super(...args);
|
|
||||||
|
|
||||||
this.should_enable = true;
|
|
||||||
|
|
||||||
this.colors = new ColorAdjuster;
|
|
||||||
this.inverse_colors = new ColorAdjuster;
|
|
||||||
|
|
||||||
this.inject('settings');
|
|
||||||
this.inject('i18n');
|
|
||||||
|
|
||||||
this.inject('chat');
|
|
||||||
|
|
||||||
this.inject('site');
|
|
||||||
this.inject('site.fine');
|
|
||||||
this.inject('site.css_tweaks');
|
|
||||||
|
|
||||||
this.inject(Line);
|
|
||||||
|
|
||||||
this.ChatController = this.fine.define(
|
|
||||||
'clip-chat-controller',
|
|
||||||
n => n.filterChatLines
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
onEnable() {
|
|
||||||
this.chat.context.on('changed:chat.font-size', this.updateChatCSS, this);
|
|
||||||
this.chat.context.on('changed:chat.font-family', this.updateChatCSS, this);
|
|
||||||
this.chat.context.on('changed:chat.lines.emote-alignment', this.updateChatCSS, this);
|
|
||||||
this.chat.context.on('changed:chat.adjustment-mode', this.updateColors, this);
|
|
||||||
this.chat.context.on('changed:chat.adjustment-contrast', this.updateColors, this);
|
|
||||||
this.chat.context.on('changed:theme.is-dark', this.updateColors, this);
|
|
||||||
|
|
||||||
this.ChatController.on('mount', this.chatMounted, this);
|
|
||||||
this.ChatController.on('unmount', this.chatMounted, this);
|
|
||||||
this.ChatController.on('update', this.chatUpdated, this);
|
|
||||||
this.ChatController.on('receive-props', this.chatUpdated, this);
|
|
||||||
|
|
||||||
this.ChatController.ready((cls, instances) => {
|
|
||||||
for(const inst of instances)
|
|
||||||
this.chatMounted(inst);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.loadBadges();
|
|
||||||
this.updateChatCSS();
|
|
||||||
this.updateColors();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
updateChatCSS() {
|
|
||||||
const size = this.chat.context.get('chat.font-size'),
|
|
||||||
emote_alignment = this.chat.context.get('chat.lines.emote-alignment'),
|
|
||||||
lh = Math.round((20/12) * size);
|
|
||||||
|
|
||||||
let font = this.chat.context.get('chat.font-family') || 'inherit';
|
|
||||||
if ( font.indexOf(' ') !== -1 && font.indexOf(',') === -1 && font.indexOf('"') === -1 && font.indexOf("'") === -1 )
|
|
||||||
font = `"${font}"`;
|
|
||||||
|
|
||||||
this.css_tweaks.setVariable('chat-font-size', `${size/10}rem`);
|
|
||||||
this.css_tweaks.setVariable('chat-line-height', `${lh/10}rem`);
|
|
||||||
this.css_tweaks.setVariable('chat-font-family', font);
|
|
||||||
|
|
||||||
this.css_tweaks.toggle('chat-font', size !== 12 || font);
|
|
||||||
|
|
||||||
this.css_tweaks.toggle('emote-alignment-padded', emote_alignment === 1);
|
|
||||||
this.css_tweaks.toggle('emote-alignment-baseline', emote_alignment === 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
updateColors() {
|
|
||||||
const is_dark = this.chat.context.get('theme.is-dark'),
|
|
||||||
mode = this.chat.context.get('chat.adjustment-mode'),
|
|
||||||
contrast = this.chat.context.get('chat.adjustment-contrast'),
|
|
||||||
c = this.colors,
|
|
||||||
ic = this.inverse_colors;
|
|
||||||
|
|
||||||
// TODO: Get the background color from the theme system.
|
|
||||||
// Updated: Use the lightest/darkest colors from alternating rows for better readibility.
|
|
||||||
c._base = is_dark ? '#191919' : '#e0e0e0'; //#0e0c13' : '#faf9fa';
|
|
||||||
c.mode = mode;
|
|
||||||
c.contrast = contrast;
|
|
||||||
|
|
||||||
ic._base = is_dark ? '#dad8de' : '#19171c';
|
|
||||||
ic.mode = mode;
|
|
||||||
ic.contrast = contrast;
|
|
||||||
|
|
||||||
this.line.updateLines();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async loadBadges() {
|
|
||||||
let data;
|
|
||||||
try {
|
|
||||||
data = await this.resolve('site.apollo').client.query({
|
|
||||||
query: BADGE_QUERY
|
|
||||||
});
|
|
||||||
} catch(err) {
|
|
||||||
this.log.warn('Error loading badge data.', err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( data && data.data && data.data.badges )
|
|
||||||
this.chat.badges.updateTwitchBadges(data.data.badges);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// ========================================================================
|
|
||||||
// Room Handling
|
|
||||||
// ========================================================================
|
|
||||||
|
|
||||||
addRoom(thing, props) {
|
|
||||||
if ( ! props )
|
|
||||||
props = thing.props;
|
|
||||||
|
|
||||||
const channel_id = get('data.clip.broadcaster.id', props);
|
|
||||||
if ( ! channel_id )
|
|
||||||
return null;
|
|
||||||
|
|
||||||
const room = thing._ffz_room = this.chat.getRoom(channel_id, null, false, true);
|
|
||||||
room.ref(thing);
|
|
||||||
return room;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
removeRoom(thing) { // eslint-disable-line class-methods-use-this
|
|
||||||
if ( ! thing._ffz_room )
|
|
||||||
return;
|
|
||||||
|
|
||||||
thing._ffz_room.unref(thing);
|
|
||||||
thing._ffz_room = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// ========================================================================
|
|
||||||
// Chat Controller
|
|
||||||
// ========================================================================
|
|
||||||
|
|
||||||
chatMounted(chat, props) {
|
|
||||||
if ( ! props )
|
|
||||||
props = chat.props;
|
|
||||||
|
|
||||||
if ( ! this.addRoom(chat, props) )
|
|
||||||
return;
|
|
||||||
|
|
||||||
this.updateRoomBadges(chat, get('data.clip.video.owner.broadcastBadges', props));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
chatUmounted(chat) {
|
|
||||||
this.removeRoom(chat);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
chatUpdated(chat, props) {
|
|
||||||
if ( ! chat._ffz_room || props?.data?.clip?.broadcaster?.id !== chat._ffz_room.id ) {
|
|
||||||
this.chatUmounted(chat);
|
|
||||||
this.chatMounted(chat, props);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const new_room_badges = get('data.clip.video.owner.broadcastBadges', props),
|
|
||||||
old_room_badges = get('data.clip.video.owner.broadcastBadges', chat.props);
|
|
||||||
|
|
||||||
if ( new_room_badges !== old_room_badges )
|
|
||||||
this.updateRoomBadges(chat, new_room_badges);
|
|
||||||
}
|
|
||||||
|
|
||||||
updateRoomBadges(chat, badges) { // eslint-disable-line class-methods-use-this
|
|
||||||
const room = chat._ffz_room;
|
|
||||||
if ( ! room )
|
|
||||||
return;
|
|
||||||
|
|
||||||
room.updateBadges(badges);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,180 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Chat Line
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
import Module from 'utilities/module';
|
|
||||||
|
|
||||||
import {createElement} from 'react';
|
|
||||||
import { split_chars } from 'utilities/object';
|
|
||||||
|
|
||||||
|
|
||||||
export default class Line extends Module {
|
|
||||||
constructor(...args) {
|
|
||||||
super(...args);
|
|
||||||
|
|
||||||
this.inject('settings');
|
|
||||||
this.inject('i18n');
|
|
||||||
|
|
||||||
this.inject('chat');
|
|
||||||
|
|
||||||
this.inject('site');
|
|
||||||
this.inject('site.fine');
|
|
||||||
|
|
||||||
this.ChatLine = this.fine.define(
|
|
||||||
'clip-chat-line',
|
|
||||||
n => n.renderFragments && n.renderUserBadges
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
onEnable() {
|
|
||||||
this.chat.context.on('changed:chat.emoji.style', this.updateLines, this);
|
|
||||||
this.chat.context.on('changed:chat.bits.stack', this.updateLines, this);
|
|
||||||
this.chat.context.on('changed:chat.badges.style', this.updateLines, this);
|
|
||||||
this.chat.context.on('changed:chat.badges.hidden', this.updateLines, this);
|
|
||||||
this.chat.context.on('changed:chat.badges.custom-mod', this.updateLines, this);
|
|
||||||
this.chat.context.on('changed:chat.rich.enabled', this.updateLines, this);
|
|
||||||
this.chat.context.on('changed:chat.rich.hide-tokens', this.updateLines, this);
|
|
||||||
this.chat.context.on('changed:chat.rich.all-links', this.updateLines, this);
|
|
||||||
this.chat.context.on('changed:chat.rich.minimum-level', this.updateLines, this);
|
|
||||||
this.chat.context.on('changed:tooltip.link-images', this.maybeUpdateLines, this);
|
|
||||||
this.chat.context.on('changed:tooltip.link-nsfw-images', this.maybeUpdateLines, this);
|
|
||||||
|
|
||||||
this.ChatLine.ready(cls => {
|
|
||||||
const t = this,
|
|
||||||
old_render = cls.prototype.render;
|
|
||||||
|
|
||||||
cls.prototype.render = function() {
|
|
||||||
try {
|
|
||||||
this._ffz_no_scan = true;
|
|
||||||
|
|
||||||
const msg = t.standardizeMessage(this.props.node, this.props.video),
|
|
||||||
is_action = msg.is_action,
|
|
||||||
user = msg.user,
|
|
||||||
color = t.parent.colors.process(user.color),
|
|
||||||
|
|
||||||
u = t.site.getUser();
|
|
||||||
|
|
||||||
const tokens = msg.ffz_tokens = msg.ffz_tokens || t.chat.tokenizeMessage(msg, u);
|
|
||||||
|
|
||||||
return (<div class="tw-mg-b-1 tw-font-size-5 tw-c-text-alt clip-chat__message">
|
|
||||||
<div class="tw-animation tw-animation--animate tw-animation--duration-short tw-animation--fill-mode-both tw-animation--slide-in-bottom tw-animation--timing-ease" data-room-id={msg.roomID} data-room={msg.roomLogin} data-user-id={user.id} data-user={user.login}>
|
|
||||||
<span class="chat-line__message--badges">{
|
|
||||||
t.chat.badges.render(msg, createElement)
|
|
||||||
}</span>
|
|
||||||
<a
|
|
||||||
class="tw-font-size-5 tw-strong clip-chat__message-author notranslate"
|
|
||||||
href={`https://www.twitch.tv/${user.login}/clips`}
|
|
||||||
style={{color}}
|
|
||||||
>
|
|
||||||
<span class="chat-author__display-name">{ user.displayName }</span>
|
|
||||||
{user.isIntl && <span class="chat-author__intl-login"> ({user.login})</span>}
|
|
||||||
</a>
|
|
||||||
<span>{is_action ? ' ' : ': '}</span>
|
|
||||||
<span class="message" style={{color: is_action ? color : null}}>{
|
|
||||||
t.chat.renderTokens(tokens, createElement)
|
|
||||||
}</span>
|
|
||||||
</div>
|
|
||||||
</div>)
|
|
||||||
|
|
||||||
} catch(err) {
|
|
||||||
t.log.error(err);
|
|
||||||
t.log.capture(err, {extra:{props: this.props}});
|
|
||||||
}
|
|
||||||
|
|
||||||
return old_render.call(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.ChatLine.forceUpdate();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
maybeUpdateLines() {
|
|
||||||
if ( this.chat.context.get('chat.rich.all-links') )
|
|
||||||
this.updateLines();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
updateLines() {
|
|
||||||
for(const inst of this.ChatLine.instances) {
|
|
||||||
const msg = inst.props.node;
|
|
||||||
if ( msg )
|
|
||||||
msg._ffz_message = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.ChatLine.forceUpdate();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
standardizeMessage(msg, video) {
|
|
||||||
if ( ! msg || ! msg.message )
|
|
||||||
return msg;
|
|
||||||
|
|
||||||
if ( msg._ffz_message )
|
|
||||||
return msg._ffz_message;
|
|
||||||
|
|
||||||
const room = this.chat.getRoom(video.owner.id, null, true, true),
|
|
||||||
author = msg.commenter || {},
|
|
||||||
badges = {};
|
|
||||||
|
|
||||||
if ( msg.message.userBadges )
|
|
||||||
for(const badge of msg.message.userBadges)
|
|
||||||
if ( badge )
|
|
||||||
badges[badge.setID] = badge.version;
|
|
||||||
|
|
||||||
const out = msg._ffz_message = {
|
|
||||||
user: {
|
|
||||||
color: author.chatColor,
|
|
||||||
id: author.id,
|
|
||||||
login: author.login,
|
|
||||||
displayName: author.displayName,
|
|
||||||
isIntl: author.login && author.displayName && author.displayName.trim().toLowerCase() !== author.login,
|
|
||||||
type: 'user'
|
|
||||||
},
|
|
||||||
roomLogin: room && room.login,
|
|
||||||
roomID: room && room.id,
|
|
||||||
badges,
|
|
||||||
messageParts: msg.message.fragments
|
|
||||||
};
|
|
||||||
|
|
||||||
this.detokenizeMessage(out, msg);
|
|
||||||
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
detokenizeMessage(msg) { // eslint-disable-line class-methods-use-this
|
|
||||||
const out = [],
|
|
||||||
parts = msg.messageParts,
|
|
||||||
l = parts.length,
|
|
||||||
emotes = {};
|
|
||||||
|
|
||||||
let idx = 0;
|
|
||||||
|
|
||||||
for(let i=0; i < l; i++) {
|
|
||||||
const part = parts[i],
|
|
||||||
text = part && part.text;
|
|
||||||
|
|
||||||
if ( ! text || ! text.length )
|
|
||||||
continue;
|
|
||||||
|
|
||||||
const len = split_chars(text).length;
|
|
||||||
|
|
||||||
if ( part.emote ) {
|
|
||||||
const id = part.emote.emoteID,
|
|
||||||
em = emotes[id] = emotes[id] || [];
|
|
||||||
|
|
||||||
em.push({startIndex: idx, endIndex: idx + len - 1});
|
|
||||||
}
|
|
||||||
|
|
||||||
out.push(text);
|
|
||||||
idx += len;
|
|
||||||
}
|
|
||||||
|
|
||||||
msg.message = out.join('');
|
|
||||||
msg.ffz_emotes = emotes;
|
|
||||||
|
|
||||||
return msg;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,87 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// CSS Tweaks for Twitch Clips
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
import Module from 'utilities/module';
|
|
||||||
import {ManagedStyle} from 'utilities/dom';
|
|
||||||
import {has} from 'utilities/object';
|
|
||||||
|
|
||||||
|
|
||||||
const CLASSES = {
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
export default class CSSTweaks extends Module {
|
|
||||||
constructor(...args) {
|
|
||||||
super(...args);
|
|
||||||
|
|
||||||
this.should_enable = true;
|
|
||||||
|
|
||||||
this.inject('settings');
|
|
||||||
this.inject('site.theme');
|
|
||||||
|
|
||||||
this.style = new ManagedStyle;
|
|
||||||
this.chunks = {};
|
|
||||||
this.chunks_loaded = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
toggleHide(key, val) {
|
|
||||||
const k = `hide--${key}`;
|
|
||||||
if ( ! val ) {
|
|
||||||
this.style.delete(k);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( ! has(CLASSES, key) )
|
|
||||||
throw new Error(`cannot find class for "${key}"`);
|
|
||||||
|
|
||||||
this.style.set(k, `${CLASSES[key]} { display: none !important }`);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async toggle(key, val) {
|
|
||||||
if ( ! val ) {
|
|
||||||
this.style.delete(key);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( ! this.chunks_loaded )
|
|
||||||
await this.populate();
|
|
||||||
|
|
||||||
if ( ! has(this.chunks, key) )
|
|
||||||
throw new Error(`cannot find chunk "${key}"`);
|
|
||||||
|
|
||||||
this.style.set(key, this.chunks[key]);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
set(key, val) { return this.style.set(key, val) }
|
|
||||||
delete(key) { return this.style.delete(key) }
|
|
||||||
|
|
||||||
setVariable(key, val, scope = 'body') {
|
|
||||||
this.style.set(`var--${key}`, `${scope} { --ffz-${key}: ${val}; }`);
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteVariable(key) { this.style.delete(`var--${key}`) }
|
|
||||||
|
|
||||||
|
|
||||||
populate() {
|
|
||||||
if ( this.chunks_loaded )
|
|
||||||
return;
|
|
||||||
|
|
||||||
return new Promise(async r => {
|
|
||||||
const raw = (await import(/* webpackChunkName: "site-css-tweaks" */ './styles.js')).default;
|
|
||||||
for(const key of raw.keys()) {
|
|
||||||
const k = key.slice(2, key.length - (key.endsWith('.scss') ? 5 : 4));
|
|
||||||
this.chunks[k] = raw(key).default;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.chunks_loaded = true;
|
|
||||||
r();
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
export default require.context('!raw-loader!sass-loader!./styles', false, /\.s?css$/);
|
|
|
@ -1,9 +0,0 @@
|
||||||
.clips-chat-replay {
|
|
||||||
a.clip-chat__message-author,
|
|
||||||
.message {
|
|
||||||
font-size: var(--ffz-chat-font-size) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
line-height: var(--ffz-chat-line-height);
|
|
||||||
font-family: var(--ffz-chat-font-family);
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
.message > .chat-line__message--emote {
|
|
||||||
vertical-align: baseline;
|
|
||||||
padding-top: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message > .chat-line__message--emote.ffz-emoji {
|
|
||||||
padding-top: 0px;
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
.message > .chat-line__message--emote {
|
|
||||||
margin: -1px 0 0;
|
|
||||||
}
|
|
|
@ -1,104 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Settings Sync
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
import Module from 'utilities/module';
|
|
||||||
import {createElement} from 'utilities/dom';
|
|
||||||
|
|
||||||
const VALID_KEYS = [
|
|
||||||
'client-id',
|
|
||||||
'profiles'
|
|
||||||
];
|
|
||||||
|
|
||||||
export default class SettingsSync extends Module {
|
|
||||||
constructor(...args) {
|
|
||||||
super(...args);
|
|
||||||
|
|
||||||
this.should_enable = true;
|
|
||||||
|
|
||||||
this.inject('settings');
|
|
||||||
}
|
|
||||||
|
|
||||||
onEnable() {
|
|
||||||
const frame = this.frame = createElement('iframe');
|
|
||||||
frame.src = '//www.twitch.tv/p/ffz_bridge/';
|
|
||||||
frame.id = 'ffz-settings-bridge';
|
|
||||||
frame.style.width = 0;
|
|
||||||
frame.style.height = 0;
|
|
||||||
|
|
||||||
window.addEventListener('message', this.onMessage.bind(this));
|
|
||||||
document.body.appendChild(frame);
|
|
||||||
}
|
|
||||||
|
|
||||||
send(msg) {
|
|
||||||
try {
|
|
||||||
this.frame.contentWindow.postMessage(msg, '*');
|
|
||||||
} catch(err) { this.log.error('send error', err); /* no-op */ }
|
|
||||||
}
|
|
||||||
|
|
||||||
onMessage(event) {
|
|
||||||
const msg = event.data;
|
|
||||||
if ( ! msg || ! msg.ffz_type )
|
|
||||||
return;
|
|
||||||
|
|
||||||
if ( msg.ffz_type === 'ready' )
|
|
||||||
this.send({ffz_type: 'load'});
|
|
||||||
else if ( msg.ffz_type === 'loaded' )
|
|
||||||
this.onLoad(msg.data);
|
|
||||||
else if ( msg.ffz_type === 'change' )
|
|
||||||
this.onChange(msg);
|
|
||||||
else
|
|
||||||
this.log.info('Unknown Message', msg.ffz_type, msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
onLoad(data) {
|
|
||||||
if ( ! data )
|
|
||||||
return;
|
|
||||||
|
|
||||||
const provider = this.settings.provider,
|
|
||||||
old_keys = new Set(provider.keys());
|
|
||||||
|
|
||||||
for(const [key, value] of Object.entries(data)) {
|
|
||||||
old_keys.delete(key);
|
|
||||||
if ( ! this.isValidSetting(key) || provider.get(key) === value )
|
|
||||||
continue;
|
|
||||||
|
|
||||||
provider.set(key, value);
|
|
||||||
provider.emit('changed', key, value, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
for(const key of old_keys) {
|
|
||||||
provider.delete(key);
|
|
||||||
provider.emit('changed', key, undefined, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onChange(msg) {
|
|
||||||
const key = msg.key,
|
|
||||||
value = msg.value,
|
|
||||||
deleted = msg.deleted;
|
|
||||||
|
|
||||||
if ( ! this.isValidSetting(key) )
|
|
||||||
return;
|
|
||||||
|
|
||||||
if ( deleted )
|
|
||||||
this.settings.provider.delete(key);
|
|
||||||
else
|
|
||||||
this.settings.provider.set(key, value);
|
|
||||||
|
|
||||||
this.settings.provider.emit('changed', key, value, deleted);
|
|
||||||
}
|
|
||||||
|
|
||||||
isValidSetting(key) {
|
|
||||||
if ( ! key.startsWith('p:') )
|
|
||||||
return VALID_KEYS.includes(key);
|
|
||||||
|
|
||||||
const idx = key.indexOf(':', 2);
|
|
||||||
if ( idx === -1 )
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return this.settings.definitions.has(key.slice(idx + 1));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,102 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Menu Module
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
import Module from 'utilities/module';
|
|
||||||
import {createElement} from 'utilities/dom';
|
|
||||||
|
|
||||||
//import THEME_CSS_URL from 'site/styles/theme.scss';
|
|
||||||
|
|
||||||
|
|
||||||
export default class ThemeEngine extends Module {
|
|
||||||
constructor(...args) {
|
|
||||||
super(...args);
|
|
||||||
this.inject('settings');
|
|
||||||
|
|
||||||
this.inject('site');
|
|
||||||
|
|
||||||
this.should_enable = true;
|
|
||||||
|
|
||||||
this.settings.add('theme.dark', {
|
|
||||||
requires: ['theme.is-dark'],
|
|
||||||
default: false,
|
|
||||||
process(ctx, val) {
|
|
||||||
return ctx.get('theme.is-dark') ? val : false
|
|
||||||
},
|
|
||||||
|
|
||||||
ui: {
|
|
||||||
path: 'Appearance @{"description": "Personalize the appearance of Twitch. Change the color scheme and fonts and tune the layout to optimize your experience."} > Theme >> General',
|
|
||||||
title: 'Gray (no Purple)',
|
|
||||||
description: '<em>Requires Dark Theme to be Enabled.</em><br>I see my website and I want it painted black...<br>This is a very early feature and will change as there is time.',
|
|
||||||
component: 'setting-check-box'
|
|
||||||
},
|
|
||||||
|
|
||||||
changed: val => this.updateSetting(val)
|
|
||||||
});
|
|
||||||
|
|
||||||
this.settings.add('theme.can-dark', {
|
|
||||||
requires: ['context.route.name'],
|
|
||||||
process() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.settings.add('theme.is-dark', {
|
|
||||||
requires: ['theme.can-dark', 'context.ui.theme'],
|
|
||||||
process(ctx) {
|
|
||||||
return ctx.get('theme.can-dark') && ctx.get('context.ui.theme') === 1;
|
|
||||||
},
|
|
||||||
changed: () => this.updateCSS()
|
|
||||||
});
|
|
||||||
|
|
||||||
this.settings.add('theme.tooltips-dark', {
|
|
||||||
requires: ['theme.is-dark'],
|
|
||||||
process(ctx) {
|
|
||||||
return ! ctx.get('theme.is-dark')
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this._style = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
updateCSS() {
|
|
||||||
const dark = this.settings.get('theme.is-dark'),
|
|
||||||
gray = this.settings.get('theme.dark');
|
|
||||||
|
|
||||||
document.body.classList.toggle('tw-root--theme-dark', dark);
|
|
||||||
document.body.classList.toggle('tw-root--theme-ffz', gray);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
toggleStyle(enable) {
|
|
||||||
if ( ! this._style ) {
|
|
||||||
if ( ! enable )
|
|
||||||
return;
|
|
||||||
|
|
||||||
this._style = createElement('link', {
|
|
||||||
rel: 'stylesheet',
|
|
||||||
type: 'text/css',
|
|
||||||
//href: THEME_CSS_URL
|
|
||||||
});
|
|
||||||
|
|
||||||
} else if ( ! enable ) {
|
|
||||||
this._style.remove();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
document.head.appendChild(this._style);
|
|
||||||
}
|
|
||||||
|
|
||||||
updateSetting(enable) {
|
|
||||||
this.toggleStyle(enable);
|
|
||||||
this.updateCSS();
|
|
||||||
}
|
|
||||||
|
|
||||||
onEnable() {
|
|
||||||
this.updateSetting(this.settings.get('theme.dark'));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
.chat-line__message--emote {
|
|
||||||
vertical-align: middle;
|
|
||||||
margin: -.5rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat-author__display-name,
|
|
||||||
.chat-author__intl-login {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ffz-emoji {
|
|
||||||
width: calc(var(--ffz-chat-font-size) * 1.5);
|
|
||||||
height: calc(var(--ffz-chat-font-size) * 1.5);
|
|
||||||
|
|
||||||
&.preview-image {
|
|
||||||
width: 7.2rem;
|
|
||||||
height: 7.2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.emote-autocomplete-provider__image {
|
|
||||||
width: 1.8rem;
|
|
||||||
height: 1.8rem;
|
|
||||||
margin: .5rem;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
@import 'styles/main.scss';
|
|
||||||
|
|
||||||
@import 'chat.scss';
|
|
|
@ -1,80 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Switchboard
|
|
||||||
// A hack for React Router to make it load a module.
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
import Module from 'utilities/module';
|
|
||||||
import pathToRegexp from 'path-to-regexp';
|
|
||||||
|
|
||||||
|
|
||||||
export default class Switchboard extends Module {
|
|
||||||
constructor(...args) {
|
|
||||||
super(...args);
|
|
||||||
|
|
||||||
this.inject('site.web_munch');
|
|
||||||
this.inject('site.fine');
|
|
||||||
}
|
|
||||||
|
|
||||||
async onEnable() {
|
|
||||||
await this.parent.awaitElement('.clips-root');
|
|
||||||
if ( this.web_munch._require || this.web_munch.v4 === false )
|
|
||||||
return;
|
|
||||||
|
|
||||||
const da_switch = this.fine.searchTree(null, n =>
|
|
||||||
n.context && n.context.router &&
|
|
||||||
n.props && n.props.children &&
|
|
||||||
n.componentWillMount && n.componentWillMount.toString().includes('Switch')
|
|
||||||
);
|
|
||||||
|
|
||||||
if ( ! da_switch )
|
|
||||||
return new Promise(r => setTimeout(r, 50)).then(() => this.onEnable());
|
|
||||||
|
|
||||||
|
|
||||||
// Identify Router
|
|
||||||
this.log.info(`Found Switch with ${da_switch.props.children.length} routes.`);
|
|
||||||
|
|
||||||
const location = da_switch.context.router.route.location.pathname;
|
|
||||||
|
|
||||||
for(const route of da_switch.props.children) {
|
|
||||||
if ( ! route.props || ! route.props.component )
|
|
||||||
continue;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const reg = pathToRegexp(route.props.path);
|
|
||||||
if ( ! reg.exec || reg.exec(location) )
|
|
||||||
continue;
|
|
||||||
|
|
||||||
} catch(err) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.log.info('Found Non-Matching Route', route.props.path);
|
|
||||||
|
|
||||||
let component;
|
|
||||||
|
|
||||||
try {
|
|
||||||
component = new route.props.component;
|
|
||||||
} catch(err) {
|
|
||||||
this.log.error('Error instantiating component for forced chunk loading.', err);
|
|
||||||
component = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( ! component || ! component.props || ! component.props.children || ! component.props.children.props || ! component.props.children.props.loader )
|
|
||||||
continue;
|
|
||||||
|
|
||||||
try {
|
|
||||||
component.props.children.props.loader().then(() => {
|
|
||||||
this.log.info('Successfully forced a chunk to load using route', route.props.path)
|
|
||||||
});
|
|
||||||
} catch(err) {
|
|
||||||
this.log.warn('Unexpected result trying to use component loader to force loading of another chunk.');
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.log.warn('Unable to use any of the available routes.');
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -123,6 +123,10 @@ export default class Channel extends Module {
|
||||||
const user = resp?.data?.user;
|
const user = resp?.data?.user;
|
||||||
if ( user )
|
if ( user )
|
||||||
user.hosting = null;
|
user.hosting = null;
|
||||||
|
|
||||||
|
const userOrError = resp?.data?.userOrError;
|
||||||
|
if ( userOrError )
|
||||||
|
userOrError.hosting = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
this.apollo.registerModifier('UseHosting', strip_host, false);
|
this.apollo.registerModifier('UseHosting', strip_host, false);
|
||||||
|
@ -505,11 +509,17 @@ export default class Channel extends Module {
|
||||||
updateRoot(el) {
|
updateRoot(el) {
|
||||||
const root = this.fine.getReactInstance(el);
|
const root = this.fine.getReactInstance(el);
|
||||||
|
|
||||||
let channel = null, state = root?.return?.return?.return?.memoizedState, i = 0;
|
let channel = null, node = root, j=0, i=0;
|
||||||
while(state != null && channel == null && i < 50 ) {
|
while(node != null && channel == null && j < 10) {
|
||||||
state = state?.next;
|
let state = node?.memoizedState;
|
||||||
channel = state?.memoizedState?.current?.previousData?.result?.data?.user;
|
i=0;
|
||||||
i++;
|
while(state != null && channel == null && i < 25) {
|
||||||
|
state = state?.next;
|
||||||
|
channel = state?.memoizedState?.current?.previousData?.result?.data?.userOrError;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
node = node?.return;
|
||||||
|
j++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( channel && channel.id ) {
|
if ( channel && channel.id ) {
|
||||||
|
|
|
@ -1238,7 +1238,7 @@ export default class ChatHook extends Module {
|
||||||
service.postMessageToCurrentChannel({}, msg);
|
service.postMessageToCurrentChannel({}, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
event.preventDefault();
|
//event.preventDefault();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1445,6 +1445,9 @@ export default class ChatHook extends Module {
|
||||||
if ( blocked_types.has(types[msg.type]) )
|
if ( blocked_types.has(types[msg.type]) )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if ( msg.type === types.ChannelPointsReward )
|
||||||
|
return;
|
||||||
|
|
||||||
if ( msg.type === types.RewardGift && ! t.chat.context.get('chat.bits.show-rewards') )
|
if ( msg.type === types.RewardGift && ! t.chat.context.get('chat.bits.show-rewards') )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -1470,6 +1473,8 @@ export default class ChatHook extends Module {
|
||||||
}
|
}
|
||||||
|
|
||||||
m.ffz_tokens = m.ffz_tokens || t.chat.tokenizeMessage(m, u, r);
|
m.ffz_tokens = m.ffz_tokens || t.chat.tokenizeMessage(m, u, r);
|
||||||
|
if ( m.ffz_removed )
|
||||||
|
return;
|
||||||
|
|
||||||
const event = new FFZEvent({
|
const event = new FFZEvent({
|
||||||
message: m,
|
message: m,
|
||||||
|
@ -2487,6 +2492,11 @@ export default class ChatHook extends Module {
|
||||||
// Chat Containers
|
// Chat Containers
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
|
|
||||||
|
get shouldUpdateChannel() {
|
||||||
|
const route = this.router.current_name;
|
||||||
|
return Twilight.POPOUT_ROUTES.includes(route) || Twilight.SUNLIGHT_ROUTES.includes(route);
|
||||||
|
}
|
||||||
|
|
||||||
containerMounted(cont, props) {
|
containerMounted(cont, props) {
|
||||||
if ( ! props )
|
if ( ! props )
|
||||||
props = cont.props;
|
props = cont.props;
|
||||||
|
@ -2497,7 +2507,7 @@ export default class ChatHook extends Module {
|
||||||
this.updateRoomBitsConfig(cont, props.bitsConfig);
|
this.updateRoomBitsConfig(cont, props.bitsConfig);
|
||||||
|
|
||||||
if ( props.data ) {
|
if ( props.data ) {
|
||||||
if ( Twilight.POPOUT_ROUTES.includes(this.router.current_name) ) {
|
if ( this.shouldUpdateChannel ){
|
||||||
const color = props.data.user?.primaryColorHex;
|
const color = props.data.user?.primaryColorHex;
|
||||||
this.resolve('site.channel').updateChannelColor(color);
|
this.resolve('site.channel').updateChannelColor(color);
|
||||||
|
|
||||||
|
@ -2516,7 +2526,7 @@ export default class ChatHook extends Module {
|
||||||
|
|
||||||
|
|
||||||
containerUnmounted(cont) {
|
containerUnmounted(cont) {
|
||||||
if ( Twilight.POPOUT_ROUTES.includes(this.router.current_name) ) {
|
if ( this.shouldUpdateChannel ) {
|
||||||
this.resolve('site.channel').updateChannelColor();
|
this.resolve('site.channel').updateChannelColor();
|
||||||
|
|
||||||
this.settings.updateContext({
|
this.settings.updateContext({
|
||||||
|
|
|
@ -1005,6 +1005,7 @@ other {# messages were deleted by a moderator.}
|
||||||
user = msg?.user;
|
user = msg?.user;
|
||||||
if ( user && ((id && id == user.id) || (login && login == user.login)) ) {
|
if ( user && ((id && id == user.id) || (login && login == user.login)) ) {
|
||||||
msg.ffz_tokens = null;
|
msg.ffz_tokens = null;
|
||||||
|
msg.ffz_badges = null;
|
||||||
msg.highlights = msg.mentioned = msg.mention_color = null;
|
msg.highlights = msg.mentioned = msg.mention_color = null;
|
||||||
inst.forceUpdate();
|
inst.forceUpdate();
|
||||||
}
|
}
|
||||||
|
@ -1031,6 +1032,7 @@ other {# messages were deleted by a moderator.}
|
||||||
const msg = inst.props.message;
|
const msg = inst.props.message;
|
||||||
if ( msg ) {
|
if ( msg ) {
|
||||||
msg.ffz_tokens = null;
|
msg.ffz_tokens = null;
|
||||||
|
msg.ffz_badges = null;
|
||||||
msg.highlights = msg.mentioned = msg.mention_color = null;
|
msg.highlights = msg.mentioned = msg.mention_color = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1039,6 +1041,7 @@ other {# messages were deleted by a moderator.}
|
||||||
const msg = inst.props.message;
|
const msg = inst.props.message;
|
||||||
if ( msg ) {
|
if ( msg ) {
|
||||||
msg.ffz_tokens = null;
|
msg.ffz_tokens = null;
|
||||||
|
msg.ffz_badges = null;
|
||||||
msg.highlights = msg.mentioned = msg.mention_color = null;
|
msg.highlights = msg.mentioned = msg.mention_color = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ export default class Dashboard extends Module {
|
||||||
|
|
||||||
this.SunlightManager = this.fine.define(
|
this.SunlightManager = this.fine.define(
|
||||||
'sunlight-manager',
|
'sunlight-manager',
|
||||||
n => n.props?.channelID && n.handleChange && has(n, 'hasVisitedStreamManager'),
|
n => n.props?.channelID && n.props.channelLogin && has(n.props, 'hostedChannel'),
|
||||||
Twilight.SUNLIGHT_ROUTES
|
Twilight.SUNLIGHT_ROUTES
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ export default class ModView extends Module {
|
||||||
|
|
||||||
this.should_enable = true;
|
this.should_enable = true;
|
||||||
|
|
||||||
|
this._cached_color = null;
|
||||||
this._cached_channel = null;
|
this._cached_channel = null;
|
||||||
this._cached_id = null;
|
this._cached_id = null;
|
||||||
|
|
||||||
|
@ -76,7 +77,7 @@ export default class ModView extends Module {
|
||||||
|
|
||||||
checkNavigation() {
|
checkNavigation() {
|
||||||
if ( this.router.current_name === 'mod-view' ) {
|
if ( this.router.current_name === 'mod-view' ) {
|
||||||
this.channel.updateChannelColor();
|
this.channel.updateChannelColor(this._cached_color);
|
||||||
this.checkRoot();
|
this.checkRoot();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -92,35 +93,52 @@ export default class ModView extends Module {
|
||||||
updateRoot(el) {
|
updateRoot(el) {
|
||||||
const root = this.fine.getReactInstance(el);
|
const root = this.fine.getReactInstance(el);
|
||||||
|
|
||||||
let channel = null, state = root?.child?.memoizedState, i = 0;
|
let channel = null, node = root, j = 0, i;
|
||||||
while(state != null && channel == null && i < 50 ) {
|
while(node != null && channel == null && j < 10) {
|
||||||
state = state?.next;
|
let state = node.memoizedState;
|
||||||
channel = state?.memoizedState?.current?.previousData?.result?.data?.user;
|
i = 0;
|
||||||
i++;
|
while(state != null && channel == null && i < 50) {
|
||||||
|
state = state?.next;
|
||||||
|
channel = state?.memoizedState?.current?.previousData?.result?.data?.user;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
node = node?.child;
|
||||||
|
j++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( channel?.id && this._cached_id != channel.id ) {
|
if ( channel?.id ) {
|
||||||
this._cached_id = channel.id;
|
if ( this._cached_id != channel.id ) {
|
||||||
this._cached_channel = channel;
|
this._cached_id = channel.id;
|
||||||
this.updateSubscription(channel.login);
|
this._cached_channel = channel;
|
||||||
|
this._cached_color = null;
|
||||||
|
this.updateSubscription(channel.login);
|
||||||
|
|
||||||
this.getChannelColor(el, channel.id).then(color => {
|
this.getChannelColor(el, channel.id).then(color => {
|
||||||
this.channel.updateChannelColor(color);
|
if ( this._cached_id != channel.id )
|
||||||
this.settings.updateContext({
|
return;
|
||||||
channelColor: color
|
|
||||||
|
this._cached_color = color;
|
||||||
|
this.channel.updateChannelColor(color);
|
||||||
|
this.settings.updateContext({
|
||||||
|
channelColor: color
|
||||||
|
});
|
||||||
|
|
||||||
|
}).catch(() => {
|
||||||
|
if ( this._cached_id != channel.id )
|
||||||
|
return;
|
||||||
|
|
||||||
|
this._cached_color = null;
|
||||||
|
this.channel.updateChannelColor();
|
||||||
|
this.settings.updateContext({
|
||||||
|
channelColor: null
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}).catch(() => {
|
|
||||||
this.channel.updateChannelColor();
|
|
||||||
this.settings.updateContext({
|
this.settings.updateContext({
|
||||||
channelColor: null
|
channel: channel.login,
|
||||||
|
channelID: channel.id
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
|
|
||||||
this.settings.updateContext({
|
|
||||||
channel: channel.login,
|
|
||||||
channelID: channel.id
|
|
||||||
});
|
|
||||||
|
|
||||||
} else
|
} else
|
||||||
this.removeRoot();
|
this.removeRoot();
|
||||||
|
|
||||||
|
@ -178,6 +196,7 @@ export default class ModView extends Module {
|
||||||
removeRoot() {
|
removeRoot() {
|
||||||
this._cached_id = null;
|
this._cached_id = null;
|
||||||
this._cached_channel = null;
|
this._cached_channel = null;
|
||||||
|
this._cached_color = null;
|
||||||
this.updateSubscription();
|
this.updateSubscription();
|
||||||
this.channel.updateChannelColor();
|
this.channel.updateChannelColor();
|
||||||
this.settings.updateContext({
|
this.settings.updateContext({
|
||||||
|
@ -202,8 +221,9 @@ export default class ModView extends Module {
|
||||||
title = bcast?.title,
|
title = bcast?.title,
|
||||||
game = bcast?.game;
|
game = bcast?.game;
|
||||||
|
|
||||||
if ( channel?.id && channel.id != this._cached_id )
|
// This doesn't work because hosting in mod view.
|
||||||
this.checkRoot();
|
//if ( channel?.id && channel.id != this._cached_id )
|
||||||
|
// this.checkRoot();
|
||||||
|
|
||||||
if ( title != el._cached_title || game?.id != el._cached_game ) {
|
if ( title != el._cached_title || game?.id != el._cached_game ) {
|
||||||
el._cached_title = title;
|
el._cached_title = title;
|
||||||
|
|
|
@ -74,7 +74,10 @@ export default class VideoChatHook extends Module {
|
||||||
async onEnable() {
|
async onEnable() {
|
||||||
this.chat.context.on('changed:chat.video-chat.enabled', this.updateLines, this);
|
this.chat.context.on('changed:chat.video-chat.enabled', this.updateLines, this);
|
||||||
this.chat.context.on('changed:chat.video-chat.timestamps', this.updateLines, this);
|
this.chat.context.on('changed:chat.video-chat.timestamps', this.updateLines, this);
|
||||||
this.on('chat:updated-lines', this.updateLines, this);
|
this.on('chat.overrides:changed', id => this.updateLinesByUser(id), this);
|
||||||
|
this.on('chat:update-lines', this.updateLines, this);
|
||||||
|
this.on('chat:update-lines-by-user', this.updateLinesByUser, this);
|
||||||
|
this.on('i18n:update', this.updateLines, this);
|
||||||
|
|
||||||
this.VideoChatController.on('mount', this.chatMounted, this);
|
this.VideoChatController.on('mount', this.chatMounted, this);
|
||||||
this.VideoChatController.on('unmount', this.chatUnmounted, this);
|
this.VideoChatController.on('unmount', this.chatUnmounted, this);
|
||||||
|
@ -365,6 +368,21 @@ export default class VideoChatHook extends Module {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
updateLinesByUser(id, login) {
|
||||||
|
for(const inst of this.VideoChatLine.instances) {
|
||||||
|
const context = inst.props.messageContext;
|
||||||
|
if ( ! context.comment )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const author = context.author;
|
||||||
|
if ( author && ((id && id == author.id) || (login && login == author.name))) {
|
||||||
|
context.comment._ffz_message = null;
|
||||||
|
inst.forceUpdate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
// Message Standardization
|
// Message Standardization
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
|
@ -388,6 +406,7 @@ export default class VideoChatHook extends Module {
|
||||||
},
|
},
|
||||||
roomLogin: room && room.login,
|
roomLogin: room && room.login,
|
||||||
roomID: room && room.id,
|
roomID: room && room.id,
|
||||||
|
ffz_badges: this.chat.badges.getBadges(author.id, author.login, room?.id, room?.login),
|
||||||
badges: comment.userBadges,
|
badges: comment.userBadges,
|
||||||
messageParts: comment.message.tokens,
|
messageParts: comment.message.tokens,
|
||||||
is_action: comment.message.isAction,
|
is_action: comment.message.isAction,
|
||||||
|
|
|
@ -620,18 +620,22 @@ export function generateHex(length = 40) {
|
||||||
|
|
||||||
|
|
||||||
export class SourcedSet {
|
export class SourcedSet {
|
||||||
constructor() {
|
constructor(use_set = false) {
|
||||||
this._cache = [];
|
this._use_set = use_set;
|
||||||
|
this._cache = use_set ? new Set : [];
|
||||||
}
|
}
|
||||||
|
|
||||||
_rebuild() {
|
_rebuild() {
|
||||||
if ( ! this._sources )
|
if ( ! this._sources )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
this._cache = [];
|
const use_set = this._use_set,
|
||||||
|
cache = this._cache = use_set ? new Set : [];
|
||||||
for(const items of this._sources.values())
|
for(const items of this._sources.values())
|
||||||
for(const i of items)
|
for(const i of items)
|
||||||
if ( ! this._cache.includes(i) )
|
if ( use_set )
|
||||||
|
cache.add(i);
|
||||||
|
else if ( ! cache.includes(i) )
|
||||||
this._cache.push(i);
|
this._cache.push(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -644,7 +648,7 @@ export class SourcedSet {
|
||||||
}
|
}
|
||||||
|
|
||||||
includes(val) {
|
includes(val) {
|
||||||
return this._cache.includes(val);
|
return this._use_set ? this._cache.has(val) : this._cache.includes(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(key) {
|
delete(key) {
|
||||||
|
@ -659,12 +663,17 @@ export class SourcedSet {
|
||||||
this._sources = new Map;
|
this._sources = new Map;
|
||||||
|
|
||||||
const had = this.has(key);
|
const had = this.has(key);
|
||||||
this._sources.set(key, [false, items]);
|
if ( had )
|
||||||
|
items = [...this._sources.get(key), ...items];
|
||||||
|
|
||||||
|
this._sources.set(key, items);
|
||||||
if ( had )
|
if ( had )
|
||||||
this._rebuild();
|
this._rebuild();
|
||||||
else
|
else
|
||||||
for(const i of items)
|
for(const i of items)
|
||||||
if ( ! this._cache.includes(i) )
|
if ( this._use_set )
|
||||||
|
this._cache.add(i);
|
||||||
|
else if ( ! this._cache.includes(i) )
|
||||||
this._cache.push(i);
|
this._cache.push(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -673,13 +682,18 @@ export class SourcedSet {
|
||||||
this._sources = new Map;
|
this._sources = new Map;
|
||||||
|
|
||||||
const had = this.has(key);
|
const had = this.has(key);
|
||||||
this._sources.set(key, [val]);
|
if ( ! Array.isArray(val) )
|
||||||
|
val = [val];
|
||||||
|
|
||||||
|
this._sources.set(key, val);
|
||||||
if ( had )
|
if ( had )
|
||||||
this._rebuild();
|
this._rebuild();
|
||||||
|
else
|
||||||
else if ( ! this._cache.includes(val) )
|
for(const i of val)
|
||||||
this._cache.push(val);
|
if ( this._use_set )
|
||||||
|
this._cache.add(i);
|
||||||
|
else if ( ! this._cache.includes(i) )
|
||||||
|
this._cache.push(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
push(key, val) {
|
push(key, val) {
|
||||||
|
@ -694,7 +708,9 @@ export class SourcedSet {
|
||||||
return;
|
return;
|
||||||
|
|
||||||
old_val.push(val);
|
old_val.push(val);
|
||||||
if ( ! this._cache.includes(val) )
|
if ( this._use_set )
|
||||||
|
this._cache.add(val);
|
||||||
|
else if ( ! this._cache.includes(val) )
|
||||||
this._cache.push(val);
|
this._cache.push(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue