1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-06-28 15:27:43 +00:00
* Added: Highlight rules now have priorities. Priorities allow for finer control over which color is used to highlight a message, as well as giving priority to specific rules when highlighting individual words.
* Added: Setting to set a priority for the built-in highlighting when someone mentions you by name.
* Changed: The `chat:receive-message` event no longer fires if there are no registered event listeners, to improve performance.
* Changed: Attempt to more accurately display re-sub notices in Chat on Videos by parsing out the re-sub message from the start of the message.
* Changed: Apply the user's selected `Action Style` to Chat on Videos.
* Fixed: Messages being re-tokenized incorrectly sometimes due to an incorrect flag being passed by the chat line component.
* Fixed: Update the logic for inserting a "Live Message Separator" to duplicate the logic used by Twitch.
* API Added: `chat:buffer-message` event that is fired when a chat message is copied into the buffer of visible messages. If you wish to run an event when a chat message becomes visible to the user, this is what you want to use.
* API Added: `FFZEvent` instances now have a `_reset()` method for resetting their defaultPrevented / propagationStopped state. For use by the constructing method only. This is intended for allowing the re-use of FFZEvent instances when firing many events in succession.
* API Changed: The various settings used for applying filters during message tokenization have been renamed to start with `__filter:`
This commit is contained in:
SirStendec 2021-04-30 17:38:49 -04:00
parent e7803c7db1
commit ae85bf76b9
11 changed files with 452 additions and 106 deletions

View file

@ -1,7 +1,7 @@
{ {
"name": "frankerfacez", "name": "frankerfacez",
"author": "Dan Salvato LLC", "author": "Dan Salvato LLC",
"version": "4.21.4", "version": "4.22.0",
"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",

View file

@ -24,6 +24,17 @@ import Actions from './actions';
export const SEPARATORS = '[\\s`~<>!-#%-\\x2A,-/:;\\x3F@\\x5B-\\x5D_\\x7B}\\u00A1\\u00A7\\u00AB\\u00B6\\u00B7\\u00BB\\u00BF\\u037E\\u0387\\u055A-\\u055F\\u0589\\u058A\\u05BE\\u05C0\\u05C3\\u05C6\\u05F3\\u05F4\\u0609\\u060A\\u060C\\u060D\\u061B\\u061E\\u061F\\u066A-\\u066D\\u06D4\\u0700-\\u070D\\u07F7-\\u07F9\\u0830-\\u083E\\u085E\\u0964\\u0965\\u0970\\u0AF0\\u0DF4\\u0E4F\\u0E5A\\u0E5B\\u0F04-\\u0F12\\u0F14\\u0F3A-\\u0F3D\\u0F85\\u0FD0-\\u0FD4\\u0FD9\\u0FDA\\u104A-\\u104F\\u10FB\\u1360-\\u1368\\u1400\\u166D\\u166E\\u169B\\u169C\\u16EB-\\u16ED\\u1735\\u1736\\u17D4-\\u17D6\\u17D8-\\u17DA\\u1800-\\u180A\\u1944\\u1945\\u1A1E\\u1A1F\\u1AA0-\\u1AA6\\u1AA8-\\u1AAD\\u1B5A-\\u1B60\\u1BFC-\\u1BFF\\u1C3B-\\u1C3F\\u1C7E\\u1C7F\\u1CC0-\\u1CC7\\u1CD3\\u2010-\\u2027\\u2030-\\u2043\\u2045-\\u2051\\u2053-\\u205E\\u207D\\u207E\\u208D\\u208E\\u2329\\u232A\\u2768-\\u2775\\u27C5\\u27C6\\u27E6-\\u27EF\\u2983-\\u2998\\u29D8-\\u29DB\\u29FC\\u29FD\\u2CF9-\\u2CFC\\u2CFE\\u2CFF\\u2D70\\u2E00-\\u2E2E\\u2E30-\\u2E3B\\u3001-\\u3003\\u3008-\\u3011\\u3014-\\u301F\\u3030\\u303D\\u30A0\\u30FB\\uA4FE\\uA4FF\\uA60D-\\uA60F\\uA673\\uA67E\\uA6F2-\\uA6F7\\uA874-\\uA877\\uA8CE\\uA8CF\\uA8F8-\\uA8FA\\uA92E\\uA92F\\uA95F\\uA9C1-\\uA9CD\\uA9DE\\uA9DF\\uAA5C-\\uAA5F\\uAADE\\uAADF\\uAAF0\\uAAF1\\uABEB\\uFD3E\\uFD3F\\uFE10-\\uFE19\\uFE30-\\uFE52\\uFE54-\\uFE61\\uFE63\\uFE68\\uFE6A\\uFE6B\\uFF01-\\uFF03\\uFF05-\\uFF0A\\uFF0C-\\uFF0F\\uFF1A\\uFF1B\\uFF1F\\uFF20\\uFF3B-\\uFF3D\\uFF3F\\uFF5B\\uFF5D\\uFF5F-\\uFF65]'; export const SEPARATORS = '[\\s`~<>!-#%-\\x2A,-/:;\\x3F@\\x5B-\\x5D_\\x7B}\\u00A1\\u00A7\\u00AB\\u00B6\\u00B7\\u00BB\\u00BF\\u037E\\u0387\\u055A-\\u055F\\u0589\\u058A\\u05BE\\u05C0\\u05C3\\u05C6\\u05F3\\u05F4\\u0609\\u060A\\u060C\\u060D\\u061B\\u061E\\u061F\\u066A-\\u066D\\u06D4\\u0700-\\u070D\\u07F7-\\u07F9\\u0830-\\u083E\\u085E\\u0964\\u0965\\u0970\\u0AF0\\u0DF4\\u0E4F\\u0E5A\\u0E5B\\u0F04-\\u0F12\\u0F14\\u0F3A-\\u0F3D\\u0F85\\u0FD0-\\u0FD4\\u0FD9\\u0FDA\\u104A-\\u104F\\u10FB\\u1360-\\u1368\\u1400\\u166D\\u166E\\u169B\\u169C\\u16EB-\\u16ED\\u1735\\u1736\\u17D4-\\u17D6\\u17D8-\\u17DA\\u1800-\\u180A\\u1944\\u1945\\u1A1E\\u1A1F\\u1AA0-\\u1AA6\\u1AA8-\\u1AAD\\u1B5A-\\u1B60\\u1BFC-\\u1BFF\\u1C3B-\\u1C3F\\u1C7E\\u1C7F\\u1CC0-\\u1CC7\\u1CD3\\u2010-\\u2027\\u2030-\\u2043\\u2045-\\u2051\\u2053-\\u205E\\u207D\\u207E\\u208D\\u208E\\u2329\\u232A\\u2768-\\u2775\\u27C5\\u27C6\\u27E6-\\u27EF\\u2983-\\u2998\\u29D8-\\u29DB\\u29FC\\u29FD\\u2CF9-\\u2CFC\\u2CFE\\u2CFF\\u2D70\\u2E00-\\u2E2E\\u2E30-\\u2E3B\\u3001-\\u3003\\u3008-\\u3011\\u3014-\\u301F\\u3030\\u303D\\u30A0\\u30FB\\uA4FE\\uA4FF\\uA60D-\\uA60F\\uA673\\uA67E\\uA6F2-\\uA6F7\\uA874-\\uA877\\uA8CE\\uA8CF\\uA8F8-\\uA8FA\\uA92E\\uA92F\\uA95F\\uA9C1-\\uA9CD\\uA9DE\\uA9DF\\uAA5C-\\uAA5F\\uAADE\\uAADF\\uAAF0\\uAAF1\\uABEB\\uFD3E\\uFD3F\\uFE10-\\uFE19\\uFE30-\\uFE52\\uFE54-\\uFE61\\uFE63\\uFE68\\uFE6A\\uFE6B\\uFF01-\\uFF03\\uFF05-\\uFF0A\\uFF0C-\\uFF0F\\uFF1A\\uFF1B\\uFF1F\\uFF20\\uFF3B-\\uFF3D\\uFF3F\\uFF5B\\uFF5D\\uFF5F-\\uFF65]';
function sortPriorityColorTerms(list) {
list.sort((a,b) => {
if ( a[0] < b[0] ) return 1;
if ( a[0] > b[0] ) return -1;
if ( ! a[1] && b[1] ) return 1;
if ( a[1] && ! b[1] ) return -1;
return 0;
});
return list;
}
function addSeparators(str) { function addSeparators(str) {
return `(^|.*?${SEPARATORS})(?:${str})(?=$|${SEPARATORS})` return `(^|.*?${SEPARATORS})(?:${str})(?=$|${SEPARATORS})`
} }
@ -251,7 +262,8 @@ export default class Chat extends Module {
path: 'Chat > Filtering > Highlight', path: 'Chat > Filtering > Highlight',
sort: 1000, sort: 1000,
component: 'setting-spacer', component: 'setting-spacer',
top: '30rem' top: '30rem',
force_seen: true
}); });
this.settings.add('chat.filtering.click-to-reveal', { this.settings.add('chat.filtering.click-to-reveal', {
@ -402,14 +414,15 @@ export default class Chat extends Module {
type: 'array_merge', type: 'array_merge',
always_inherit: true, always_inherit: true,
ui: { ui: {
path: 'Chat > Filtering > Highlight >> Users', path: 'Chat > Filtering > Highlight @{"description": "These settings allow you to highlight messages in chat based on their contents. Setting priorities on rules allows you to determine which highlight color should be applied if a message matches multiple rules. Rules with a higher priority take priority over rules with lower priorities."} >> Users',
component: 'basic-terms', component: 'basic-terms',
colored: true, colored: true,
words: false words: false,
priority: true
} }
}); });
this.settings.add('chat.filtering.highlight-basic-users--color-regex', { this.settings.add('__filter:highlight-users', {
requires: ['chat.filtering.highlight-basic-users'], requires: ['chat.filtering.highlight-basic-users'],
equals: 'requirements', equals: 'requirements',
process(ctx) { process(ctx) {
@ -417,10 +430,11 @@ export default class Chat extends Module {
if ( ! val || ! val.length ) if ( ! val || ! val.length )
return null; return null;
const colors = new Map; const temp = new Map;
for(const item of val) { for(const item of val) {
const c = item.c || null, const c = item.c || null,
p = item.p || 0,
t = item.t; t = item.t;
let v = item.v; let v = item.v;
@ -440,6 +454,12 @@ export default class Chat extends Module {
continue; continue;
} }
let colors = temp.get(p);
if ( ! colors ) {
colors = new Map;
temp.set(p, colors);
}
if ( colors.has(c) ) if ( colors.has(c) )
colors.get(c).push(v); colors.get(c).push(v);
else { else {
@ -447,11 +467,19 @@ export default class Chat extends Module {
} }
} }
for(const [key, list] of colors) { const out = [];
colors.set(key, new RegExp(`^(?:${list.join('|')})$`, 'gi')); for(const [priority, list] of temp) {
for(const [color, entries] of list) {
out.push([
priority,
color,
new RegExp(`^(?:${entries.join('|')})$`, 'gi')
]);
//list.set(k, new RegExp(`^(?:${entries.join('|')})$`, 'gi'));
}
} }
return colors; return sortPriorityColorTerms(out);
} }
}); });
@ -469,7 +497,7 @@ export default class Chat extends Module {
}); });
this.settings.add('chat.filtering.highlight-basic-users-blocked--regex', { this.settings.add('__filter:block-users', {
requires: ['chat.filtering.highlight-basic-users-blocked'], requires: ['chat.filtering.highlight-basic-users-blocked'],
equals: 'requirements', equals: 'requirements',
process(ctx) { process(ctx) {
@ -513,12 +541,13 @@ 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,
priority: true,
data: () => this.badges.getSettingsBadges(true) data: () => this.badges.getSettingsBadges(true)
} }
}); });
this.settings.add('chat.filtering.highlight-basic-badges--colors', { this.settings.add('__filter:highlight-badges', {
requires: ['chat.filtering.highlight-basic-badges'], requires: ['chat.filtering.highlight-basic-badges'],
equals: 'requirements', equals: 'requirements',
process(ctx) { process(ctx) {
@ -526,17 +555,19 @@ export default class Chat extends Module {
if ( ! val || ! val.length ) if ( ! val || ! val.length )
return null; return null;
const colors = new Map; const badges = new Map;
for(const item of val) { for(const item of val) {
const c = item.c || null, const c = item.c || null,
p = item.p || 0,
v = item.v; v = item.v;
if ( ! colors.has(v) ) const existing = badges.get(v);
colors.set(v, c); if ( ! existing || existing[0] < p || (c && ! existing[1] && existing[0] <= p) )
badges.set(v, [p, c]);
} }
return colors; return badges;
} }
}); });
@ -553,7 +584,7 @@ export default class Chat extends Module {
} }
}); });
this.settings.add('chat.filtering.highlight-basic-badges-blocked--list', { this.settings.add('__filter:block-badges', {
requires: ['chat.filtering.highlight-basic-badges-blocked'], requires: ['chat.filtering.highlight-basic-badges-blocked'],
equals: 'requirements', equals: 'requirements',
process(ctx) { process(ctx) {
@ -582,11 +613,12 @@ export default class Chat extends Module {
path: 'Chat > Filtering > Highlight >> Terms @{"description": "Please see [Chat > Filtering > Syntax Help](~) for details on how to use terms."}', path: 'Chat > Filtering > Highlight >> Terms @{"description": "Please see [Chat > Filtering > Syntax Help](~) for details on how to use terms."}',
component: 'basic-terms', component: 'basic-terms',
colored: true, colored: true,
priority: true,
highlight: true highlight: true
} }
}); });
this.settings.add('chat.filtering.highlight-basic-terms--color-regex', { this.settings.add('__filter:highlight-terms', {
requires: ['chat.filtering.highlight-tokens', 'chat.filtering.highlight-basic-terms'], requires: ['chat.filtering.highlight-tokens', 'chat.filtering.highlight-basic-terms'],
equals: 'requirements', equals: 'requirements',
process(ctx) { process(ctx) {
@ -595,12 +627,14 @@ export default class Chat extends Module {
if ( ! val || ! val.length ) if ( ! val || ! val.length )
return null; return null;
const colors = new Map; const temp = new Map;
//const colors = new Map;
let has_highlight = false, let has_highlight = false,
has_non = false; has_non = false;
for(const item of val) { for(const item of val) {
const c = item.c || null, const c = item.c || null,
p = item.p || 0,
highlight = can_highlight && (has(item, 'h') ? item.h : true), highlight = can_highlight && (has(item, 'h') ? item.h : true),
sensitive = item.s, sensitive = item.s,
t = item.t, t = item.t,
@ -628,6 +662,12 @@ export default class Chat extends Module {
else else
has_non = true; has_non = true;
let colors = temp.get(p);
if ( ! colors ) {
colors = new Map;
temp.set(p, colors);
}
let data = colors.get(c); let data = colors.get(c);
if ( ! data ) if ( ! data )
colors.set(c, data = [ colors.set(c, data = [
@ -656,20 +696,36 @@ export default class Chat extends Module {
return null; return null;
const out = { const out = {
hl: has_highlight ? new Map : null, hl: has_highlight ? [] : null,
non: has_non ? new Map : null non: has_non ? [] : null
}; };
for(const [key, list] of colors) { for(const [priority, colors] of temp) {
for(const [color, list] of colors) {
const highlights = formatTerms(list[0]), const highlights = formatTerms(list[0]),
non_highlights = formatTerms(list[1]); non_highlights = formatTerms(list[1]);
if ( highlights[0] || highlights[1] ) if ( highlights[0] || highlights[1] )
out.hl.set(key, highlights); out.hl.push([
priority,
color,
highlights
]);
if ( non_highlights[0] || non_highlights[1] ) if ( non_highlights[0] || non_highlights[1] )
out.non.set(key, non_highlights); out.non.push([
priority,
color,
non_highlights
]);
} }
}
if ( has_highlight )
sortPriorityColorTerms(out.hl);
if ( has_non )
sortPriorityColorTerms(out.non);
return out; return out;
} }
@ -688,7 +744,7 @@ export default class Chat extends Module {
}); });
this.settings.add('chat.filtering.highlight-basic-blocked--regex', { this.settings.add('__filter:block-terms', {
requires: ['chat.filtering.highlight-basic-blocked'], requires: ['chat.filtering.highlight-basic-blocked'],
equals: 'requirements', equals: 'requirements',
process(ctx) { process(ctx) {
@ -780,6 +836,18 @@ export default class Chat extends Module {
} }
}); });
this.settings.add('chat.filtering.mention-priority', {
default: 0,
ui: {
path: 'Chat > Filtering > General >> Appearance',
title: 'Mention Priority',
component: 'setting-text-box',
type: 'number',
process: 'to_int',
description: 'Mentions of your name have this priority for the purpose of highlighting. See [Chat > Filtering > Highlight](~) for more details.'
}
});
this.settings.add('chat.filtering.mention-color', { this.settings.add('chat.filtering.mention-color', {
default: '', default: '',
ui: { ui: {
@ -1546,7 +1614,7 @@ export default class Chat extends Module {
for(let i=0; i < l; i++) { for(let i=0; i < l; i++) {
const part = parts[i], const part = parts[i],
content = part.content; content = part.ffz_content ?? part.content;
if ( ! content ) if ( ! content )
continue; continue;
@ -1696,6 +1764,9 @@ export default class Chat extends Module {
if ( ! this.context.get('chat.rich.enabled') || this.context.get('chat.rich.minimum-level') > this.getUserLevel(msg) ) if ( ! this.context.get('chat.rich.enabled') || this.context.get('chat.rich.minimum-level') > this.getUserLevel(msg) )
return; return;
if ( ! Array.isArray(tokens) )
return;
const providers = this.__rich_providers; const providers = this.__rich_providers;
for(const token of tokens) { for(const token of tokens) {
@ -1728,7 +1799,7 @@ export default class Chat extends Module {
} }
} }
return tokens; return tokens || [];
} }

View file

@ -346,7 +346,8 @@ export const Mentions = {
if ( ! tokens || ! tokens.length ) if ( ! tokens || ! tokens.length )
return tokens; return tokens;
const can_highlight_user = user && user.login && user.login == msg.user.login && ! this.context.get('chat.filtering.process-own'); const can_highlight_user = user && user.login && user.login == msg.user.login && ! this.context.get('chat.filtering.process-own'),
priority = this.context.get('chat.filtering.mention-priority');
let regex, login, display, mentionable = false; let regex, login, display, mentionable = false;
if ( user && user.login && ! can_highlight_user ) { if ( user && user.login && ! can_highlight_user ) {
@ -408,6 +409,10 @@ export const Mentions = {
if ( mentioned ) { if ( mentioned ) {
(msg.highlights = (msg.highlights || new Set())).add('mention'); (msg.highlights = (msg.highlights || new Set())).add('mention');
msg.mentioned = true; msg.mentioned = true;
if ( msg.color_priority == null || priority > msg.color_priority ) {
msg.mention_color = null;
msg.color_priority = priority;
}
} }
// Push the remaining text from the token. // Push the remaining text from the token.
@ -438,17 +443,20 @@ export const UserHighlights = {
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 colors = this.context.get('chat.filtering.highlight-basic-users--color-regex'); const list = this.context.get('__filter:highlight-users');
if ( ! colors || ! colors.size ) if ( ! list || ! list.length )
return tokens; return tokens;
const u = msg.user; const u = msg.user;
for(const [color, regex] of colors) { for(const [priority, color, regex] of list) {
if ( regex.test(u.login) || regex.test(u.displayName) ) { if ( regex.test(u.login) || regex.test(u.displayName) ) {
(msg.highlights = (msg.highlights || new Set())).add('user'); (msg.highlights = (msg.highlights || new Set())).add('user');
msg.mentioned = true; msg.mentioned = true;
if ( color ) { if ( color ) {
if ( msg.color_priority == null || priority > msg.color_priority ) {
msg.mention_color = color; msg.mention_color = color;
msg.color_priority = priority;
}
return tokens; return tokens;
} }
} }
@ -467,7 +475,7 @@ export const BlockedUsers = {
return tokens; return tokens;
const u = msg.user, const u = msg.user,
regexes = this.context.get('chat.filtering.highlight-basic-users-blocked--regex'); regexes = this.context.get('__filter:block-users');
if ( ! regexes ) if ( ! regexes )
return tokens; return tokens;
@ -501,16 +509,16 @@ function getBadgeIDs(msg) {
export const BadgeStuff = { export const BadgeStuff = {
type: 'badge_stuff', type: 'badge_stuff',
priority: 80, priority: 97,
process(tokens, msg, user, haltable) { 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 colors = this.context.get('chat.filtering.highlight-basic-badges--colors'), const highlights = this.context.get('__filter:highlight-badges'),
list = this.context.get('chat.filtering.highlight-basic-badges-blocked--list'); list = this.context.get('__filter:block-badges');
if ( ! colors && ! list ) if ( ! highlights && ! list )
return tokens; return tokens;
const keys = getBadgeIDs(msg); const keys = getBadgeIDs(msg);
@ -529,12 +537,15 @@ export const BadgeStuff = {
if ( list && ! msg.deleted && list[0].includes(badge) ) if ( list && ! msg.deleted && list[0].includes(badge) )
msg.deleted = true; msg.deleted = true;
if ( colors && colors.has(badge) ) { if ( highlights && highlights.has(badge) ) {
const color = colors.get(badge); const details = highlights.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 ( details[1] ) {
msg.mention_color = color; if ( msg.color_priority == null || details[0] > msg.color_priority ) {
msg.mention_color = details[1];
msg.color_priority = details[0];
}
if ( ! list ) if ( ! list )
return tokens; return tokens;
} }
@ -552,7 +563,7 @@ export const BadgeStuff = {
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 list = this.context.get('chat.filtering.highlight-basic-badges-blocked--list'); const list = this.context.get('__filter:block-badges');
if ( ! list || (! list[0].length && ! list[1].length) ) if ( ! list || (! list[0].length && ! list[1].length) )
return tokens; return tokens;
@ -594,12 +605,16 @@ export const CustomHighlights = {
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 data = this.context.get('chat.filtering.highlight-basic-terms--color-regex'); const data = this.context.get('__filter:highlight-terms');
if ( ! data ) if ( ! data )
return tokens; return tokens;
let had_match = false;
if ( data.non ) { if ( data.non ) {
for(const [color, regexes] of data.non) { for(const [priority, color, regexes] of data.non) {
if ( had_match && msg.color_priority != null && msg.color_priority > priority )
break;
let matched = false; let matched = false;
if ( regexes[0] ) { if ( regexes[0] ) {
regexes[0].lastIndex = 0; regexes[0].lastIndex = 0;
@ -613,16 +628,23 @@ export const CustomHighlights = {
if ( matched ) { if ( matched ) {
(msg.highlights = (msg.highlights || new Set())).add('term'); (msg.highlights = (msg.highlights || new Set())).add('term');
msg.mentioned = true; msg.mentioned = true;
msg.mention_color = color || msg.mention_color; had_match = true;
if ( color ) {
if ( msg.color_priority == null || priority > msg.color_priority ) {
msg.mention_color = color;
msg.color_priority = priority;
}
break; break;
} }
} }
} }
}
if ( ! data.hl ) if ( ! data.hl )
return tokens; return tokens;
for(const [color, regexes] of data.hl) { for(const [priority, color, regexes] of data.hl) {
const out = []; const out = [];
for(const token of tokens) { for(const token of tokens) {
if ( token.type !== 'text' ) { if ( token.type !== 'text' ) {
@ -656,8 +678,10 @@ export const CustomHighlights = {
(msg.highlights = (msg.highlights || new Set())).add('term'); (msg.highlights = (msg.highlights || new Set())).add('term');
msg.mentioned = true; msg.mentioned = true;
if ( ! msg.mention_color ) if ( color && (msg.color_priority == null || priority > msg.color_priority) ) {
msg.mention_color = color; msg.mention_color = color;
msg.color_priority = priority;
}
out.push({ out.push({
type: 'highlight', type: 'highlight',
@ -767,7 +791,7 @@ export const BlockedTerms = {
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 regexes = this.context.get('chat.filtering.highlight-basic-blocked--regex'); const regexes = this.context.get('__filter:block-terms');
if ( ! regexes ) if ( ! regexes )
return tokens; return tokens;
@ -800,7 +824,7 @@ const AM_DESCRIPTIONS = {
export const AutomoddedTerms = { export const AutomoddedTerms = {
type: 'amterm', type: 'amterm',
priority: 99, priority: 95,
component: () => import(/* webpackChunkName: 'vue-chat' */ './components/chat-automod-blocked.vue'), component: () => import(/* webpackChunkName: 'vue-chat' */ './components/chat-automod-blocked.vue'),

View file

@ -4,6 +4,7 @@
:term="default_term" :term="default_term"
:badges="data" :badges="data"
:colored="item.colored" :colored="item.colored"
:priority="item.priority"
:removable="item.removable" :removable="item.removable"
:adding="true" :adding="true"
@save="new_term" @save="new_term"
@ -18,6 +19,7 @@
:term="term.v" :term="term.v"
:badges="data" :badges="data"
:colored="item.colored" :colored="item.colored"
:priority="item.priority"
:removable="item.removable" :removable="item.removable"
@remove="remove(term)" @remove="remove(term)"
@save="save(term, $event)" @save="save(term, $event)"
@ -29,7 +31,7 @@
<script> <script>
import SettingMixin from '../setting-mixin'; import SettingMixin from '../setting-mixin';
import {deep_copy} from 'utilities/object'; import {deep_copy, has} from 'utilities/object';
let last_id = 0; let last_id = 0;
@ -42,6 +44,7 @@ export default {
default_term: { default_term: {
v: '', v: '',
c: '', c: '',
p: 0,
remove: false remove: false
} }
} }
@ -60,9 +63,31 @@ export default {
const out = []; const out = [];
if ( Array.isArray(this.val) ) if ( Array.isArray(this.val) )
for(const term of this.val) for(const term of this.val) {
if ( term && term.v ) {
if ( ! has(term.v, 'p') )
term.v.p = 0;
}
if ( term && term.t !== 'inherit' ) if ( term && term.t !== 'inherit' )
out.push(term); out.push(term);
}
if ( this.item.priority || this.item.colored ) {
out.sort((a,b) => {
if ( a.v && b.v ) {
if ( this.item.priority ) {
if ( a.v.p < b.v.p ) return 1;
if ( a.v.p > b.v.p ) return -1;
}
if ( this.item.colored ) {
if ( ! a.v.c && b.v.c ) return 1;
if ( a.v.c && ! b.v.c ) return -1;
}
}
return 0;
});
}
return out; return out;
}, },

View file

@ -51,6 +51,24 @@
</figure> </figure>
</div> </div>
</div> </div>
<div
v-if="priority"
:class="editing ? 'tw-mg-r-05' : 'tw-mg-x-05'"
class="tw-flex-shrink-0 tw-relative tw-tooltip__container"
>
<span v-if="! editing">{{ term.p }}</span>
<input
v-else
v-model.number="edit_data.p"
type="number"
step="1"
class="tw-block tw-border-radius-medium tw-font-size-6 ffz-min-width-unset ffz-input tw-pd-x-1 tw-pd-y-05"
style="width: 5rem"
>
<div class="tw-tooltip tw-tooltip--down tw-tooltip--align-right">
{{ t('settings.terms.priority.tip', 'Priority') }}
</div>
</div>
<div <div
v-if="removable && (editing || display.remove)" 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" 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"
@ -180,6 +198,10 @@ export default {
type: Boolean, type: Boolean,
default: false default: false
}, },
priority: {
type: Boolean,
default: false
},
removable: { removable: {
type: Boolean, type: Boolean,
default: false default: false
@ -258,6 +280,13 @@ export default {
}, },
save() { save() {
if ( this.priority && this.edit_data.p ) {
if ( typeof this.edit_data.p === 'number' )
this.edit_data.p = Math.floor(this.edit_data.p);
else
this.edit_data.p = 0;
}
if ( this.valid ) if ( this.valid )
this.$emit('save', this.edit_data); this.$emit('save', this.edit_data);
this.cancel(); this.cancel();

View file

@ -6,6 +6,7 @@
:highlight="item.highlight" :highlight="item.highlight"
:words="item.words" :words="item.words"
:removable="item.removable" :removable="item.removable"
:priority="item.priority"
:adding="true" :adding="true"
@save="new_term" @save="new_term"
/> />
@ -21,6 +22,7 @@
:highlight="item.highlight" :highlight="item.highlight"
:words="item.words" :words="item.words"
:removable="item.removable" :removable="item.removable"
:priority="item.priority"
@remove="remove(term)" @remove="remove(term)"
@save="save(term, $event)" @save="save(term, $event)"
/> />
@ -48,6 +50,7 @@ export default {
s: false, s: false,
h: false, h: false,
w: true, w: true,
p: 0,
remove: false remove: false
} }
} }
@ -74,6 +77,9 @@ export default {
if ( ! has(term.v, 'h') ) if ( ! has(term.v, 'h') )
term.v.h = true; term.v.h = true;
if ( ! has(term.v, 'p') )
term.v.p = 0;
if ( term.v.t === 'raw' ) if ( term.v.t === 'raw' )
term.v.t = 'regex'; term.v.t = 'regex';
} }
@ -82,6 +88,32 @@ export default {
out.push(term); out.push(term);
} }
out.sort((a,b) => {
if ( a.v && b.v ) {
if ( this.item.removable ) {
if ( ! a.v.remove && b.v.remove ) return 1;
if ( a.v.remove && ! b.v.remove ) return -1;
}
if ( this.item.priority ) {
if ( a.v.p < b.v.p ) return 1;
if ( a.v.p > b.v.p ) return -1;
}
if ( this.item.colored ) {
if ( ! a.v.c && b.v.c ) return 1;
if ( a.v.c && ! b.v.c ) return -1;
}
if ( this.item.highlight ) {
if ( ! a.v.h && b.v.h ) return -1;
if ( a.v.h && ! b.v.h ) return 1;
}
if ( this.item.words ?? true ) {
if ( ! a.v.w && b.v.w ) return -1;
if ( a.v.w && ! b.v.w ) return 1;
}
}
return 0;
});
return out; return out;
}, },

View file

@ -56,6 +56,24 @@
</option> </option>
</select> </select>
</div> </div>
<div
v-if="priority"
:class="editing ? 'tw-mg-r-05' : 'tw-mg-x-05'"
class="tw-flex-shrink-0 tw-relative tw-tooltip__container"
>
<span v-if="! editing">{{ term.p }}</span>
<input
v-else
v-model.number="edit_data.p"
type="number"
step="1"
class="tw-block tw-border-radius-medium tw-font-size-6 ffz-min-width-unset ffz-input tw-pd-x-1 tw-pd-y-05"
style="width: 5rem"
>
<div class="tw-tooltip tw-tooltip--down tw-tooltip--align-right">
{{ t('settings.terms.priority.tip', 'Priority') }}
</div>
</div>
<div <div
v-if="editing || display.s" v-if="editing || display.s"
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" 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"
@ -243,6 +261,10 @@ export default {
type: Boolean, type: Boolean,
default: false default: false
}, },
priority: {
type: Boolean,
default: false
},
words: { words: {
type: Boolean, type: Boolean,
default: true default: true
@ -360,6 +382,13 @@ export default {
}, },
save() { save() {
if ( this.priority && this.edit_data.p ) {
if ( typeof this.edit_data.p === 'number' )
this.edit_data.p = Math.floor(this.edit_data.p);
else
this.edit_data.p = 0;
}
this.$emit('save', this.edit_data); this.$emit('save', this.edit_data);
this.cancel(); this.cancel();
} }

View file

@ -1492,18 +1492,17 @@ export default class ChatHook extends Module {
room = m.roomLogin = r.login; room = m.roomLogin = r.login;
} }
const u = t.site.getUser(), const u = t.site.getUser();
r = {id: room_id, login: room};
if ( u && cont ) { if ( u && cont ) {
u.moderator = cont.props.isCurrentUserModerator; u.moderator = cont.props.isCurrentUserModerator;
u.staff = cont.props.isStaff; u.staff = cont.props.isStaff;
} }
m.ffz_tokens = m.ffz_tokens || t.chat.tokenizeMessage(m, u, r); m.ffz_tokens = m.ffz_tokens || t.chat.tokenizeMessage(m, u, true);
if ( m.ffz_removed ) if ( m.ffz_removed )
return; return;
if ( t.hasListeners('chat:receive-message') ) {
const event = new FFZEvent({ const event = new FFZEvent({
message: m, message: m,
channel: room, channel: room,
@ -1513,6 +1512,7 @@ export default class ChatHook extends Module {
t.emit('chat:receive-message', event); t.emit('chat:receive-message', event);
if ( event.defaultPrevented || m.ffz_removed ) if ( event.defaultPrevented || m.ffz_removed )
return; return;
}
} else if ( msg.type === types.ModerationAction && false && inst.markUserEventDeleted && inst.unsetModeratedUser ) { } else if ( msg.type === types.ModerationAction && false && inst.markUserEventDeleted && inst.unsetModeratedUser ) {
if ( !((! msg.level || ! msg.level.length) && msg.targetUserLogin && msg.targetUserLogin === inst.props.currentUserLogin) ) { if ( !((! msg.level || ! msg.level.length) && msg.targetUserLogin && msg.targetUserLogin === inst.props.currentUserLogin) ) {
@ -1793,7 +1793,9 @@ export default class ChatHook extends Module {
has_newer = this.hasNewerLeft(), has_newer = this.hasNewerLeft(),
paused = this.isPaused(), paused = this.isPaused(),
max_size = t.chat.context.get('chat.scrollback-length'), max_size = t.chat.context.get('chat.scrollback-length'),
do_remove = t.chat.context.get('chat.filtering.remove-deleted'); do_remove = t.chat.context.get('chat.filtering.remove-deleted'),
event = t.hasListeners('chat:buffer-message') ? new FFZEvent() : null;
let added = 0, let added = 0,
buffered = this.slidingWindowEnd, buffered = this.slidingWindowEnd,
@ -1804,17 +1806,38 @@ export default class ChatHook extends Module {
if ( do_remove !== 0 && (do_remove > 1 || ! see_deleted) && this.isDeletable(msg.event) && msg.event.deleted ) if ( do_remove !== 0 && (do_remove > 1 || ! see_deleted) && this.isDeletable(msg.event) && msg.event.deleted )
continue; continue;
if ( event ) {
event._reset();
event.message = msg.event;
t.emit('chat:buffer-message', event);
if ( event.defaultPrevented || msg.event.ffz_removed )
continue;
}
const last = this.buffer[this.buffer.length - 1], const last = this.buffer[this.buffer.length - 1],
type = last?.type; type = last?.type;
if ( type === ct.Connected ) { if ( !(
! this.props.isLoadingHistoricalMessages &&
! this.props.historicalMessages ||
type !== ct.Connected ||
msg.event.type === ct.Connected ||
this.buffer.find(e => e.type === ct.LiveMessageSeparator)
) )
this.buffer.push({
type: ct.LiveMessageSeparator,
id: 'live-message-separator'
});
/*if ( type === ct.Connected ) {
const non_null = this.buffer.filter(x => x && ct[x.type] && ! NULL_TYPES.includes(ct[x.type])); const non_null = this.buffer.filter(x => x && ct[x.type] && ! NULL_TYPES.includes(ct[x.type]));
if ( non_null.length > 1 ) if ( non_null.length > 1 )
this.buffer.push({ this.buffer.push({
type: ct.LiveMessageSeparator, type: ct.LiveMessageSeparator,
id: 'live-message-separator' id: 'live-message-separator'
}); });
} }*/
this.buffer.push(msg.event); this.buffer.push(msg.event);
changed = true; changed = true;

View file

@ -82,12 +82,13 @@ export default class ChatLine extends Module {
this.chat.context.on('changed:chat.filtering.show-deleted', this.updateLines, this); this.chat.context.on('changed:chat.filtering.show-deleted', this.updateLines, this);
this.chat.context.on('changed:chat.filtering.process-own', this.updateLines, this); this.chat.context.on('changed:chat.filtering.process-own', this.updateLines, this);
this.chat.context.on('changed:chat.timestamp-format', this.updateLines, this); this.chat.context.on('changed:chat.timestamp-format', this.updateLines, this);
this.chat.context.on('changed:chat.filtering.highlight-basic-terms--color-regex', this.updateLines, this); this.chat.context.on('changed:chat.filtering.mention-priority', this.updateLines, this);
this.chat.context.on('changed:chat.filtering.highlight-basic-users--color-regex', this.updateLines, this); this.chat.context.on('changed:__filter:highlight-terms', this.updateLines, this);
this.chat.context.on('changed:chat.filtering.highlight-basic-badges--colors', this.updateLines, this); this.chat.context.on('changed:__filter:highlight-users', this.updateLines, this);
this.chat.context.on('changed:chat.filtering.highlight-basic-blocked--regex', this.updateLines, this); this.chat.context.on('changed:__filter:highlight-badges', this.updateLines, this);
this.chat.context.on('changed:chat.filtering.highlight-basic-users-blocked--regex', this.updateLines, this); this.chat.context.on('changed:__filter:block-terms', this.updateLines, this);
this.chat.context.on('changed:chat.filtering.highlight-basic-badges-blocked--list', this.updateLines, this); this.chat.context.on('changed:__filter:block-users', this.updateLines, this);
this.chat.context.on('changed:__filter:block-badges', this.updateLines, this);
this.on('chat:get-tab-commands', e => { this.on('chat:get-tab-commands', e => {
if ( this.experiments.getTwitchAssignmentByName('chat_replies') === 'control' ) if ( this.experiments.getTwitchAssignmentByName('chat_replies') === 'control' )
@ -197,7 +198,7 @@ export default class ChatLine extends Module {
raw_color = t.overrides.getColor(user.id) || user.color, raw_color = t.overrides.getColor(user.id) || user.color,
color = t.parent.colors.process(raw_color), color = t.parent.colors.process(raw_color),
tokens = msg.ffz_tokens = msg.ffz_tokens || t.chat.tokenizeMessage(msg, null, null), tokens = msg.ffz_tokens = msg.ffz_tokens || t.chat.tokenizeMessage(msg, null),
contents = t.chat.renderTokens(tokens, e), contents = t.chat.renderTokens(tokens, e),
override_name = t.overrides.getName(user.id); override_name = t.overrides.getName(user.id);
@ -438,7 +439,7 @@ other {# messages were deleted by a moderator.}
} }
if ( ! room_id && room ) { if ( ! room_id && room ) {
const r = t.chat.getRoom(null, room_id, true); const r = t.chat.getRoom(null, room, true);
if ( r && r.id ) if ( r && r.id )
room_id = msg.roomId = r.id; room_id = msg.roomId = r.id;
} }
@ -460,7 +461,7 @@ other {# messages were deleted by a moderator.}
u.can_reply = reply_mode === 2 && can_reply; u.can_reply = reply_mode === 2 && can_reply;
} }
const tokens = msg.ffz_tokens = msg.ffz_tokens || t.chat.tokenizeMessage(msg, u, r), const tokens = msg.ffz_tokens = msg.ffz_tokens || t.chat.tokenizeMessage(msg, u),
rich_content = FFZRichContent && t.chat.pluckRichContent(tokens, msg), rich_content = FFZRichContent && t.chat.pluckRichContent(tokens, msg),
bg_css = msg.mentioned && msg.mention_color ? t.parent.inverse_colors.process(msg.mention_color) : null; bg_css = msg.mentioned && msg.mention_color ? t.parent.inverse_colors.process(msg.mention_color) : null;
@ -973,7 +974,7 @@ other {# messages were deleted by a moderator.}
const u = t.site.getUser(), const u = t.site.getUser(),
r = {id: this.props.channelID, login: room}, r = {id: this.props.channelID, login: room},
tokens = msg.ffz_tokens = msg.ffz_tokens || t.chat.tokenizeMessage(msg, u, r), tokens = msg.ffz_tokens = msg.ffz_tokens || t.chat.tokenizeMessage(msg, u),
rich_content = FFZRichContent && t.chat.pluckRichContent(tokens, msg), rich_content = FFZRichContent && t.chat.pluckRichContent(tokens, msg),
bg_css = msg.mentioned && msg.mention_color ? t.parent.inverse_colors.process(msg.mention_color) : null; bg_css = msg.mentioned && msg.mention_color ? t.parent.inverse_colors.process(msg.mention_color) : null;
@ -1033,7 +1034,7 @@ other {# messages were deleted by a moderator.}
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.ffz_badges = null;
msg.highlights = msg.mentioned = msg.mention_color = null; msg.highlights = msg.mentioned = msg.mention_color = msg.color_priority = null;
inst.forceUpdate(); inst.forceUpdate();
} }
} }
@ -1060,7 +1061,7 @@ other {# messages were deleted by a moderator.}
if ( msg ) { if ( msg ) {
msg.ffz_tokens = null; msg.ffz_tokens = null;
msg.ffz_badges = null; msg.ffz_badges = null;
msg.highlights = msg.mentioned = msg.mention_color = null; msg.highlights = msg.mentioned = msg.mention_color = msg.color_priority = null;
} }
} }
@ -1069,7 +1070,7 @@ other {# messages were deleted by a moderator.}
if ( msg ) { if ( msg ) {
msg.ffz_tokens = null; msg.ffz_tokens = null;
msg.ffz_badges = null; msg.ffz_badges = null;
msg.highlights = msg.mentioned = msg.mention_color = null; msg.highlights = msg.mentioned = msg.mention_color = msg.color_priority = null;
} }
} }

View file

@ -11,6 +11,25 @@ import {formatBitsConfig} from '../chat';
import Module from 'utilities/module'; import Module from 'utilities/module';
const SUB_REGEX = /^([^\s]+) subscribed ([^.]+)\. They've subscribed for (\d+) months(?:[^!]+streak)?!/;
const SUB_TIERS = {
1000: 1,
2000: 2,
3000: 3
};
function parseParamInt(param) {
try {
if ( /^[\d-]+$/.test(param) )
param = parseInt(param, 10);
} catch(err) { /* no-op */ }
if ( typeof param !== 'number' || isNaN(param) || ! isFinite(param) )
param = 0;
return param;
}
export default class VideoChatHook extends Module { export default class VideoChatHook extends Module {
constructor(...args) { constructor(...args) {
@ -212,6 +231,9 @@ export default class VideoChatHook extends Module {
cls.prototype.ffzRenderMessage = function(msg, reply) { cls.prototype.ffzRenderMessage = function(msg, reply) {
const is_action = msg.is_action, const is_action = msg.is_action,
action_style = is_action ? t.chat.context.get('chat.me-style') : 0,
action_italic = action_style >= 2,
action_color = action_style === 1 || action_style === 3,
user = msg.user, user = msg.user,
color = t.site_chat.colors.process(user.color), color = t.site_chat.colors.process(user.color),
@ -222,11 +244,63 @@ export default class VideoChatHook extends Module {
u.staff = u.roles && u.roles.isStaff; u.staff = u.roles && u.roles.isStaff;
} }
let system_msg;
if ( msg.system_msg === true ) {
const params = msg.params || {},
msg_id = params['msg-id'];
if ( msg_id === 'resub' ) {
const setting = t.chat.context.get('chat.subs.show'),
raw_months = parseParamInt(params['msg-param-months']),
cumulative_months = parseParamInt(params['msg-param-cumulative-months']),
months = cumulative_months || raw_months;
t.log.info('resub-notice setting:', setting, 'months:', months, 'cumulative:', cumulative_months, 'raw:', raw_months);
t.log.info('-> params:', params);
if ( setting === 3 || (months > 1 && setting > 0) ) {
const share = parseParamInt(params['msg-param-should-share-streak']) === 1,
plan = params['msg-param-sub-plan'],
prime = plan === 'Prime',
tier = SUB_TIERS[plan] || 1;
t.log.info('-> share:', share, 'plan:', plan, 'tier:', tier);
system_msg = t.i18n.tList('chat.sub.main', '{user} subscribed {plan}. ', {
user: <span class="tw-c-text-base tw-strong">{user.displayName}</span>,
plan: prime ?
t.i18n.t('chat.sub.twitch-prime', 'with Prime Gaming') :
t.i18n.t('chat.sub.plan', 'at Tier {tier}', {tier})
});
if ( share && raw_months > 1 )
system_msg.push(t.i18n.t(
'chat.sub.cumulative-months',
"They've subscribed for {cumulative,number} months, currently on a {streak,number} month streak!",
{
cumulative: cumulative_months,
streak: raw_months
}
));
else if ( months > 1 )
system_msg.push(t.i18n.t(
'chat.sub.months',
"They've subscribed for {count,number} months!",
{
count: months
}
));
}
}
} else if ( msg.system_msg )
system_msg = msg.system_msg;
const tokens = msg.ffz_tokens = msg.ffz_tokens || t.chat.tokenizeMessage(msg, u), const tokens = msg.ffz_tokens = msg.ffz_tokens || t.chat.tokenizeMessage(msg, u),
rich_content = FFZRichContent && t.chat.pluckRichContent(tokens, msg); rich_content = FFZRichContent && t.chat.pluckRichContent(tokens, msg);
return (<div class="tw-align-items-start tw-flex tw-flex-nowrap tw-c-text-base"> let out = (<div class="tw-flex-grow-1" data-room-id={msg.roomID} data-room={msg.roomLogin} data-user-id={user.id} data-user={user.login}>
<div class="tw-flex-grow-1" data-room-id={msg.roomID} data-room={msg.roomLogin} data-user-id={user.id} data-user={user.login}>
<span class="chat-line__message--badges">{ <span class="chat-line__message--badges">{
t.chat.badges.render(msg, createElement) t.chat.badges.render(msg, createElement)
}</span> }</span>
@ -243,10 +317,26 @@ export default class VideoChatHook extends Module {
</a> </a>
<div data-test-selector="comment-message-selector" class="tw-inline video-chat__message"> <div data-test-selector="comment-message-selector" class="tw-inline video-chat__message">
<span>{is_action ? ' ' : ': '}</span> <span>{is_action ? ' ' : ': '}</span>
<span class="message" style={{color: is_action ? color : null}}>{ t.chat.renderTokens(tokens, createElement) }</span> <span
class={`message ${action_italic ? 'chat-line__message-body--italicized' : ''}`}
style={{color: action_color ? color : null}}
>
{ t.chat.renderTokens(tokens, createElement) }
</span>
{rich_content && createElement(FFZRichContent, rich_content)} {rich_content && createElement(FFZRichContent, rich_content)}
</div> </div>
</div>);
if ( system_msg )
out = (<div class="tw-flex-grow-1">
<div class="tw-flex tw-c-text-alt-2">
<div>{system_msg}</div>
</div> </div>
{out}
</div>);
return (<div class="tw-align-items-start tw-flex tw-flex-nowrap tw-c-text-base">
{ out }
{ reply ? (<t.MenuContainer { reply ? (<t.MenuContainer
context={reply} context={reply}
isCurrentUserModerator={this.props.isCurrentUserModerator} isCurrentUserModerator={this.props.isCurrentUserModerator}
@ -412,12 +502,29 @@ export default class VideoChatHook extends Module {
is_action: comment.message.isAction, is_action: comment.message.isAction,
more_replies: comment.moreReplies, more_replies: comment.moreReplies,
timestamp: comment.createdAt, timestamp: comment.createdAt,
ffz_context: 'video',
is_sub: msg_id === 'sub' || msg_id === 'resub', is_sub: msg_id === 'sub' || msg_id === 'resub',
highlight: msg_id === 'highlighted-message' highlight: msg_id === 'highlighted-message',
params
}; };
// TODO: We need to strip the sub message from chat messages // We need to strip the sub message from chat messages
// because Twitch is dumb. // because Twitch is dumb. This might need updating to
// handle system messages with different syntax.
if ( Array.isArray(out.messageParts) && out.messageParts.length && msg_id === 'resub' ) {
let content = out.messageParts[0].content;
if ( typeof content === 'string' ) {
const match = SUB_REGEX.exec(content);
if ( match ) {
content = content.slice(match[0].length).trimLeft();
if ( content.length ) {
out.messageParts[0].ffz_content = content;
out.system_msg = true;
}
}
}
}
this.chat.detokenizeMessage(out); this.chat.detokenizeMessage(out);

View file

@ -354,6 +354,11 @@ export class FFZEvent {
Object.assign(this, data); Object.assign(this, data);
} }
_reset() {
this.defaultPrevented = false;
this.propagationStopped = false;
}
stopPropagation() { stopPropagation() {
this.propagationStopped = true; this.propagationStopped = true;
} }