2017-11-13 01:23:39 -05:00
|
|
|
'use strict';
|
|
|
|
|
|
|
|
// ============================================================================
|
|
|
|
// Default Tokenizers
|
|
|
|
// ============================================================================
|
|
|
|
|
2018-04-01 18:24:08 -04:00
|
|
|
import {sanitize, createElement} from 'utilities/dom';
|
2021-06-17 14:27:04 -04:00
|
|
|
import {has, getTwitchEmoteURL, split_chars, getTwitchEmoteSrcSet} from 'utilities/object';
|
2017-11-13 01:23:39 -05:00
|
|
|
|
2021-06-17 14:27:04 -04:00
|
|
|
import {EmoteTypes, REPLACEMENT_BASE, REPLACEMENTS} from 'utilities/constants';
|
2021-12-11 21:45:39 +01:00
|
|
|
import {CATEGORIES, JOINER_REPLACEMENT} from './emoji';
|
2018-04-09 19:57:05 -04:00
|
|
|
|
2023-03-06 17:08:47 -05:00
|
|
|
import { MODIFIER_FLAGS } from './emotes';
|
|
|
|
|
|
|
|
const SHRINK_X = MODIFIER_FLAGS.ShrinkX,
|
|
|
|
STRETCH_X = MODIFIER_FLAGS.GrowX,
|
|
|
|
SHRINK_Y = MODIFIER_FLAGS.ShrinkY,
|
|
|
|
STRETCH_Y = MODIFIER_FLAGS.GrowY,
|
|
|
|
ROTATE_45 = MODIFIER_FLAGS.Rotate45,
|
|
|
|
ROTATE_90 = MODIFIER_FLAGS.Rotate90;
|
|
|
|
|
2018-04-09 19:57:05 -04:00
|
|
|
|
2018-12-11 17:40:02 -05:00
|
|
|
const EMOTE_CLASS = 'chat-image chat-line__message--emote',
|
2021-02-22 20:11:35 -05:00
|
|
|
//WHITESPACE = /^\s*$/,
|
|
|
|
//LINK_REGEX = /([^\w@#%\-+=:~])?((?:(https?:\/\/)?(?:[\w@#%\-+=:~]+\.)+[a-z]{2,6}(?:\/[\w./@#%&()\-+=:?~]*)?))([^\w./@#%&()\-+=:?~]|\s|$)/g,
|
2021-02-10 16:53:10 -05:00
|
|
|
NEW_LINK_REGEX = /(?:(https?:\/\/)?((?:[\w#%\-+=:~]+\.)+[a-z]{2,10}(?:\/[\w./#%&@()\-+=:?~]*)?))/g,
|
2019-05-31 16:05:50 -04:00
|
|
|
//MENTION_REGEX = /([^\w@#%\-+=:~])?(@([^\u0000-\u007F]+|\w+)+)([^\w./@#%&()\-+=:?~]|\s|$)/g; // eslint-disable-line no-control-regex
|
2019-06-05 20:29:33 -04:00
|
|
|
MENTION_REGEX = /^(['"*([{<\\/]*)(@)((?:[^\u0000-\u007F]|[\w-])+)(?:\b|$)/; // eslint-disable-line no-control-regex
|
2017-11-13 01:23:39 -05:00
|
|
|
|
|
|
|
|
2021-05-03 15:33:03 -04:00
|
|
|
export const FilterTester = {
|
|
|
|
type: 'filter_test',
|
|
|
|
priority: 1000,
|
|
|
|
|
|
|
|
render(token, createElement) {
|
|
|
|
if ( ! token.msg.filters?.length )
|
|
|
|
return null;
|
|
|
|
|
|
|
|
return (<div class="ffz-pill tw-mg-l-1">
|
|
|
|
{ token.msg.filters.join(', ') }
|
|
|
|
</div>);
|
|
|
|
},
|
|
|
|
|
|
|
|
process(tokens, msg) {
|
|
|
|
if ( ! tokens || ! tokens.length || ! this.context.get('chat.filtering.debug') )
|
2021-12-01 16:48:10 -05:00
|
|
|
return;
|
2021-05-03 15:33:03 -04:00
|
|
|
|
|
|
|
msg.filters = [];
|
|
|
|
|
|
|
|
tokens.push({
|
|
|
|
type: 'filter_test',
|
|
|
|
msg
|
|
|
|
});
|
|
|
|
|
|
|
|
return tokens;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
// ============================================================================
|
|
|
|
// Links
|
|
|
|
// ============================================================================
|
|
|
|
|
2020-08-04 18:26:11 -04:00
|
|
|
function datasetBool(value) {
|
|
|
|
return value == null ? null : value === 'true';
|
|
|
|
}
|
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
export const Links = {
|
|
|
|
type: 'link',
|
|
|
|
priority: 50,
|
|
|
|
|
2018-05-10 19:56:39 -04:00
|
|
|
component: () => import(/* webpackChunkName: 'vue-chat' */ './components/chat-link.vue'),
|
|
|
|
|
2018-04-01 18:24:08 -04:00
|
|
|
render(token, createElement) {
|
|
|
|
return (<a
|
2019-05-16 14:46:26 -04:00
|
|
|
class="ffz-tooltip link-fragment"
|
2018-04-01 18:24:08 -04:00
|
|
|
data-tooltip-type="link"
|
|
|
|
data-url={token.url}
|
|
|
|
data-is-mail={token.is_mail}
|
|
|
|
rel="noopener noreferrer"
|
|
|
|
target="_blank"
|
|
|
|
href={token.url}
|
|
|
|
>{token.text}</a>);
|
2017-11-13 01:23:39 -05:00
|
|
|
},
|
|
|
|
|
|
|
|
tooltip(target, tip) {
|
2019-09-12 13:11:08 -04:00
|
|
|
if ( ! this.context.get('tooltip.rich-links') && ! target.dataset.forceTooltip )
|
2017-11-13 01:23:39 -05:00
|
|
|
return '';
|
|
|
|
|
|
|
|
if ( target.dataset.isMail === 'true' )
|
2019-05-03 19:30:46 -04:00
|
|
|
return [this.i18n.t('tooltip.email-link', 'E-Mail {address}', {address: target.textContent})];
|
2017-11-13 01:23:39 -05:00
|
|
|
|
2020-08-04 18:26:11 -04:00
|
|
|
const url = target.dataset.url || target.href,
|
|
|
|
show_images = datasetBool(target.dataset.forceMedia) ?? this.context.get('tooltip.link-images'),
|
|
|
|
show_unsafe = datasetBool(target.dataset.forceUnsafe) ?? this.context.get('tooltip.link-nsfw-images');
|
2020-07-29 02:22:45 -04:00
|
|
|
|
2020-08-04 18:26:11 -04:00
|
|
|
return Promise.all([
|
2020-08-07 01:32:18 -04:00
|
|
|
import(/* webpackChunkName: 'rich_tokens' */ 'utilities/rich_tokens'),
|
2020-08-04 18:26:11 -04:00
|
|
|
this.get_link_info(url)
|
|
|
|
]).then(([rich_tokens, data]) => {
|
2021-11-15 17:12:01 -05:00
|
|
|
if ( ! data || (data.v || 1) > rich_tokens.VERSION )
|
2017-11-13 01:23:39 -05:00
|
|
|
return '';
|
|
|
|
|
2020-08-04 18:26:11 -04:00
|
|
|
const ctx = {
|
|
|
|
tList: (...args) => this.i18n.tList(...args),
|
|
|
|
i18n: this.i18n,
|
2021-11-15 17:12:01 -05:00
|
|
|
|
|
|
|
fragments: data.fragments,
|
2023-01-19 17:00:09 -05:00
|
|
|
i18n_prefix: data.i18n_prefix,
|
2021-11-15 17:12:01 -05:00
|
|
|
|
2020-08-04 18:26:11 -04:00
|
|
|
allow_media: show_images,
|
|
|
|
allow_unsafe: show_unsafe,
|
2021-02-24 14:38:25 -05:00
|
|
|
onload: () => requestAnimationFrame(() => tip.update())
|
2020-08-04 18:26:11 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
let content;
|
|
|
|
if ( tip.element ) {
|
|
|
|
tip.element.classList.add('ffz-rich-tip');
|
|
|
|
tip.element.classList.add('tw-align-left');
|
|
|
|
}
|
2017-11-13 01:23:39 -05:00
|
|
|
|
2020-08-04 18:26:11 -04:00
|
|
|
if ( data.full ) {
|
|
|
|
content = rich_tokens.renderTokens(data.full, createElement, ctx);
|
2017-11-13 01:23:39 -05:00
|
|
|
|
2021-11-15 17:12:01 -05:00
|
|
|
} else if ( data.mid ) {
|
|
|
|
content = rich_tokens.renderTokens(data.mid, createElement, ctx);
|
|
|
|
|
|
|
|
} else if ( data.short ) {
|
|
|
|
content = rich_tokens.renderTokens(data.short, createElement, ctx);
|
|
|
|
|
|
|
|
} else
|
|
|
|
content = this.i18n.t('card.empty', 'No data was returned.');
|
2017-11-13 01:23:39 -05:00
|
|
|
|
2020-08-04 18:26:11 -04:00
|
|
|
if ( ! data.urls )
|
|
|
|
return content;
|
2017-11-13 01:23:39 -05:00
|
|
|
|
2020-08-04 18:26:11 -04:00
|
|
|
const url_table = [];
|
|
|
|
for(let i=0; i < data.urls.length; i++) {
|
|
|
|
const url = data.urls[i];
|
2017-11-13 01:23:39 -05:00
|
|
|
|
2020-08-04 18:26:11 -04:00
|
|
|
url_table.push(<tr>
|
|
|
|
<td>{this.i18n.formatNumber(i + 1)}.</td>
|
|
|
|
<td class="tw-c-text-alt-2 tw-pd-x-05 tw-word-break-all">{url.url}</td>
|
|
|
|
<td>{url.flags ? url.flags.map(flag => <span class="tw-pill">{flag.toLowerCase()}</span>) : null}</td>
|
|
|
|
</tr>);
|
|
|
|
}
|
2018-10-01 15:36:38 -04:00
|
|
|
|
2020-08-04 18:26:11 -04:00
|
|
|
let url_notice;
|
|
|
|
if ( data.unsafe ) {
|
|
|
|
const reasons = Array.from(new Set(data.urls.map(url => url.flags).flat())).join(', ');
|
|
|
|
url_notice = (<div class="ffz-i-attention">
|
|
|
|
{this.i18n.tList(
|
|
|
|
'tooltip.link-unsafe',
|
2021-09-06 16:48:48 -04:00
|
|
|
'Caution: This URL is has been flagged as potentially harmful by: {reasons}',
|
2020-08-04 18:26:11 -04:00
|
|
|
{reasons: reasons.toLowerCase()}
|
|
|
|
)}
|
|
|
|
</div>);
|
|
|
|
} else if ( data.urls.length > 1 )
|
|
|
|
url_notice = this.i18n.t('tooltip.link-destination', 'Destination: {url}', {
|
|
|
|
url: data.urls[data.urls.length-1].url
|
2017-11-13 01:23:39 -05:00
|
|
|
});
|
|
|
|
|
2020-08-04 18:26:11 -04:00
|
|
|
content = (<div>
|
|
|
|
<div class="ffz--shift-hide">
|
|
|
|
{content}
|
|
|
|
{url_notice ? <div class="tw-mg-t-05 tw-border-t tw-pd-t-05 tw-align-center">
|
|
|
|
{url_notice}
|
|
|
|
<div class=" tw-font-size-8">
|
|
|
|
{this.i18n.t('tooltip.shift-detail', '(Shift for Details)')}
|
|
|
|
</div>
|
|
|
|
</div> : null}
|
|
|
|
</div>
|
|
|
|
<div class="ffz--shift-show tw-align-left">
|
|
|
|
<div class="tw-semibold tw-mg-b-05 tw-align-center">
|
|
|
|
{this.i18n.t('tooltip.link.urls', 'Visited URLs')}
|
|
|
|
</div>
|
|
|
|
<table>{url_table}</table>
|
|
|
|
</div>
|
|
|
|
</div>);
|
2017-11-13 01:23:39 -05:00
|
|
|
|
|
|
|
return content;
|
|
|
|
|
2020-08-04 18:26:11 -04:00
|
|
|
}).catch(error => {
|
|
|
|
console.error(error);
|
|
|
|
return sanitize(this.i18n.t('tooltip.error', 'An error occurred. ({error})', {error}))
|
|
|
|
});
|
2017-11-13 01:23:39 -05:00
|
|
|
},
|
|
|
|
|
2018-04-02 03:30:22 -04:00
|
|
|
process(tokens) {
|
2017-11-13 01:23:39 -05:00
|
|
|
if ( ! tokens || ! tokens.length )
|
2021-12-01 16:48:10 -05:00
|
|
|
return;
|
2021-02-10 16:53:10 -05:00
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
const out = [];
|
|
|
|
for(const token of tokens) {
|
|
|
|
if ( token.type !== 'text' ) {
|
|
|
|
out.push(token);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2021-02-10 16:53:10 -05:00
|
|
|
NEW_LINK_REGEX.lastIndex = 0;
|
2017-11-13 01:23:39 -05:00
|
|
|
const text = token.text;
|
|
|
|
let idx = 0, match;
|
|
|
|
|
2021-02-22 20:11:35 -05:00
|
|
|
while((match = NEW_LINK_REGEX.exec(text))) {
|
|
|
|
const nix = match.index;
|
|
|
|
if ( idx !== nix )
|
|
|
|
out.push({type: 'text', text: text.slice(idx, nix)});
|
|
|
|
|
2021-03-23 18:24:09 -04:00
|
|
|
let url = match[0];
|
|
|
|
if ( url.endsWith(')') ) {
|
|
|
|
let open = 1, i = url.length - 1;
|
|
|
|
while(i--) {
|
|
|
|
const chr = url[i];
|
|
|
|
if ( chr === ')' )
|
|
|
|
open++;
|
|
|
|
else if ( chr === '(' )
|
|
|
|
open--;
|
|
|
|
|
|
|
|
if ( ! open )
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( open )
|
|
|
|
url = url.slice(0, url.length - 1);
|
|
|
|
}
|
|
|
|
|
2021-02-22 20:11:35 -05:00
|
|
|
out.push({
|
|
|
|
type: 'link',
|
2021-03-23 18:24:09 -04:00
|
|
|
url: `${match[1] ? '' : 'https://'}${url}`,
|
2021-02-22 20:11:35 -05:00
|
|
|
is_mail: false,
|
2021-03-23 18:24:09 -04:00
|
|
|
text: url
|
2021-02-22 20:11:35 -05:00
|
|
|
});
|
2017-11-13 01:23:39 -05:00
|
|
|
|
2021-03-23 18:24:09 -04:00
|
|
|
idx = nix + url.length;
|
2021-02-22 20:11:35 -05:00
|
|
|
}
|
2017-11-13 01:23:39 -05:00
|
|
|
|
|
|
|
if ( idx < text.length )
|
|
|
|
out.push({type: 'text', text: text.slice(idx)});
|
|
|
|
}
|
|
|
|
|
|
|
|
return out;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-02 03:30:22 -04:00
|
|
|
Links.tooltip.interactive = function(target) {
|
2017-11-14 04:11:43 -05:00
|
|
|
if ( ! this.context.get('tooltip.rich-links') || ! this.context.get('tooltip.link-interaction') || target.dataset.isMail === 'true' )
|
|
|
|
return false;
|
|
|
|
|
|
|
|
const info = this.get_link_info(target.dataset.url, true);
|
|
|
|
return info && info.interactive;
|
|
|
|
};
|
|
|
|
|
2018-04-02 03:30:22 -04:00
|
|
|
Links.tooltip.delayHide = function(target) {
|
2021-02-24 14:38:25 -05:00
|
|
|
if ( ! this.context.get('tooltip.rich-links') || target.dataset.isMail === 'true' )
|
2017-11-14 04:11:43 -05:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
return 64;
|
|
|
|
};
|
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
|
2020-08-12 16:10:06 -04:00
|
|
|
// ============================================================================
|
|
|
|
// Replies (Styled Like Mentions)
|
|
|
|
// ============================================================================
|
|
|
|
|
|
|
|
export const Replies = {
|
|
|
|
type: 'reply',
|
|
|
|
priority: 0,
|
|
|
|
|
|
|
|
component: () => null,
|
|
|
|
|
|
|
|
render(token, createElement) {
|
|
|
|
let color = token.color;
|
|
|
|
if ( color ) {
|
|
|
|
const chat = this.resolve('site.chat');
|
|
|
|
color = chat ? chat.colors.process(color) : color;
|
|
|
|
}
|
|
|
|
|
|
|
|
return (<strong
|
2020-11-23 18:12:07 -05:00
|
|
|
class={`chat-line__message-mention ffz--pointer-events ffz-tooltip ffz--reply-mention ffz-i-threads${token.me ? ' ffz--mention-me' : ''}`}
|
2020-08-12 16:10:06 -04:00
|
|
|
style={{color}}
|
|
|
|
data-tooltip-type="reply"
|
|
|
|
data-login={token.recipient}
|
|
|
|
onClick={this.handleReplyClick}
|
|
|
|
>
|
|
|
|
{token.text}
|
|
|
|
</strong>)
|
|
|
|
},
|
|
|
|
|
|
|
|
tooltip(target) {
|
|
|
|
const fine = this.resolve('site.fine');
|
|
|
|
if ( ! target || ! fine )
|
|
|
|
return null;
|
|
|
|
|
|
|
|
const chat = fine.searchParent(target, n => n.props && n.props.reply && n.setOPCardTray),
|
|
|
|
reply = chat?.props?.reply;
|
|
|
|
if ( ! reply )
|
|
|
|
return null;
|
|
|
|
|
|
|
|
return [
|
|
|
|
createElement('strong', {}, this.i18n.t('chat.reply-to', 'Replying To:')),
|
|
|
|
'\n\n',
|
|
|
|
createElement('div', {className: 'tw-align-left'}, [
|
|
|
|
createElement('strong', {}, reply.parentDisplayName),
|
|
|
|
': ',
|
|
|
|
reply.parentMessageBody
|
|
|
|
])
|
|
|
|
];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
// ============================================================================
|
|
|
|
// Mentions
|
|
|
|
// ============================================================================
|
|
|
|
|
|
|
|
export const Mentions = {
|
|
|
|
type: 'mention',
|
2018-04-07 17:59:16 -04:00
|
|
|
priority: 0,
|
2017-11-13 01:23:39 -05:00
|
|
|
|
2018-05-10 19:56:39 -04:00
|
|
|
component: () => import(/* webpackChunkName: 'vue-chat' */ './components/chat-mention.vue'),
|
|
|
|
|
2021-12-01 16:48:10 -05:00
|
|
|
/*oldRender(token, createElement) {
|
2018-04-01 18:24:08 -04:00
|
|
|
return (<strong class={`chat-line__message-mention${token.me ? ' ffz--mention-me' : ''}`}>
|
|
|
|
{token.text}
|
|
|
|
</strong>);
|
2021-12-01 16:48:10 -05:00
|
|
|
},*/
|
2017-11-13 01:23:39 -05:00
|
|
|
|
2019-05-31 16:05:50 -04:00
|
|
|
render(token, createElement) {
|
2020-07-24 17:55:11 -04:00
|
|
|
let color = token.color;
|
|
|
|
if ( color ) {
|
|
|
|
const chat = this.resolve('site.chat');
|
|
|
|
color = chat ? chat.colors.process(color) : color;
|
|
|
|
}
|
|
|
|
|
2019-06-01 13:58:12 -04:00
|
|
|
return (<strong
|
2020-08-13 14:00:47 -04:00
|
|
|
class={`chat-line__message-mention${token.me ? ' ffz--mention-me' : ''} ffz--pointer-events`}
|
2020-07-24 17:55:11 -04:00
|
|
|
style={{color}}
|
2019-05-31 16:05:50 -04:00
|
|
|
data-login={token.recipient}
|
|
|
|
onClick={this.handleMentionClick}
|
|
|
|
>
|
|
|
|
{token.text}
|
2019-06-01 13:58:12 -04:00
|
|
|
</strong>)
|
2019-05-31 16:05:50 -04:00
|
|
|
},
|
|
|
|
|
2017-11-17 14:59:46 -05:00
|
|
|
process(tokens, msg, user) {
|
2017-11-13 01:23:39 -05:00
|
|
|
if ( ! tokens || ! tokens.length )
|
2021-12-01 16:48:10 -05:00
|
|
|
return;
|
2017-11-13 01:23:39 -05:00
|
|
|
|
2021-04-30 17:38:49 -04:00
|
|
|
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');
|
2019-05-31 16:05:50 -04:00
|
|
|
|
2019-05-31 23:21:49 -04:00
|
|
|
let regex, login, display, mentionable = false;
|
|
|
|
if ( user && user.login && ! can_highlight_user ) {
|
2017-11-17 14:59:46 -05:00
|
|
|
login = user.login.toLowerCase();
|
2018-04-28 17:56:03 -04:00
|
|
|
display = user.displayName && user.displayName.toLowerCase();
|
2017-11-17 14:59:46 -05:00
|
|
|
if ( display === login )
|
|
|
|
display = null;
|
|
|
|
|
2019-05-31 23:21:49 -04:00
|
|
|
mentionable = true;
|
2019-06-05 20:29:33 -04:00
|
|
|
regex = new RegExp(`^(['"*([{<\\/]*)(?:(@?)(${user.login.toLowerCase()}${display ? `|${display}` : ''})|@((?:[^\u0000-\u007F]|[\\w-])+))(?:\\b|$)`, 'i');
|
2017-11-17 14:59:46 -05:00
|
|
|
} else
|
|
|
|
regex = MENTION_REGEX;
|
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
const out = [];
|
|
|
|
for(const token of tokens) {
|
|
|
|
if ( token.type !== 'text' ) {
|
|
|
|
out.push(token);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2019-05-31 16:05:50 -04:00
|
|
|
let text = [];
|
|
|
|
|
|
|
|
for(const segment of token.text.split(/ +/)) {
|
|
|
|
const match = regex.exec(segment);
|
|
|
|
if ( match ) {
|
|
|
|
// If we have pending text, join it together.
|
|
|
|
if ( text.length || match[1]) {
|
|
|
|
out.push({
|
|
|
|
type: 'text',
|
|
|
|
text: `${text.join(' ')} ${match[1] || ''}`
|
|
|
|
});
|
|
|
|
text = [];
|
|
|
|
}
|
2017-11-13 01:23:39 -05:00
|
|
|
|
2019-05-31 16:05:50 -04:00
|
|
|
let recipient,
|
|
|
|
mentioned = false,
|
|
|
|
at = match[2];
|
2017-11-17 14:59:46 -05:00
|
|
|
|
2019-05-31 16:05:50 -04:00
|
|
|
if ( match[4] ) {
|
|
|
|
recipient = match[4];
|
|
|
|
at = '@';
|
2017-11-13 01:23:39 -05:00
|
|
|
|
2019-05-31 16:05:50 -04:00
|
|
|
} else {
|
|
|
|
recipient = match[3];
|
2019-05-31 23:21:49 -04:00
|
|
|
mentioned = mentionable;
|
2019-05-31 16:05:50 -04:00
|
|
|
}
|
2017-11-17 14:59:46 -05:00
|
|
|
|
2020-07-24 17:55:11 -04:00
|
|
|
const rlower = recipient ? recipient.toLowerCase() : '',
|
|
|
|
color = this.color_cache ? this.color_cache.get(rlower) : null;
|
|
|
|
|
2019-05-31 16:05:50 -04:00
|
|
|
out.push({
|
|
|
|
type: 'mention',
|
|
|
|
text: `${at}${recipient}`,
|
|
|
|
me: mentioned,
|
2020-07-24 17:55:11 -04:00
|
|
|
color,
|
|
|
|
recipient: rlower
|
2019-05-31 16:05:50 -04:00
|
|
|
});
|
2017-11-13 01:23:39 -05:00
|
|
|
|
2021-05-03 15:33:03 -04:00
|
|
|
if ( mentioned )
|
|
|
|
this.applyHighlight(msg, priority, null, 'mention', true);
|
2019-05-31 16:05:50 -04:00
|
|
|
|
|
|
|
// Push the remaining text from the token.
|
|
|
|
text.push(segment.substr(match[0].length));
|
|
|
|
|
|
|
|
} else
|
|
|
|
text.push(segment);
|
2017-11-13 01:23:39 -05:00
|
|
|
}
|
|
|
|
|
2019-05-31 16:05:50 -04:00
|
|
|
if ( text.length > 1 || (text.length === 1 && text[0] !== '') )
|
|
|
|
out.push({type: 'text', text: text.join(' ')})
|
2017-11-13 01:23:39 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
return out;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-05-31 18:34:15 -04:00
|
|
|
// ============================================================================
|
|
|
|
// Custom Highlight Terms
|
|
|
|
// ============================================================================
|
|
|
|
|
2019-04-28 17:28:16 -04:00
|
|
|
export const UserHighlights = {
|
|
|
|
type: 'user_highlight',
|
|
|
|
priority: 90,
|
|
|
|
|
|
|
|
process(tokens, msg, user) {
|
|
|
|
if ( user && user.login && user.login == msg.user.login && ! this.context.get('chat.filtering.process-own') )
|
2021-12-01 16:48:10 -05:00
|
|
|
return;
|
2019-04-28 17:28:16 -04:00
|
|
|
|
2021-04-30 17:38:49 -04:00
|
|
|
const list = this.context.get('__filter:highlight-users');
|
|
|
|
if ( ! list || ! list.length )
|
2021-12-01 16:48:10 -05:00
|
|
|
return;
|
2019-04-28 17:28:16 -04:00
|
|
|
|
|
|
|
const u = msg.user;
|
2021-04-30 17:38:49 -04:00
|
|
|
for(const [priority, color, regex] of list) {
|
2021-05-03 15:33:03 -04:00
|
|
|
if ( regex.test(u.login) || regex.test(u.displayName) )
|
|
|
|
this.applyHighlight(msg, priority, color, 'user');
|
2019-04-28 17:28:16 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export const BlockedUsers = {
|
|
|
|
type: 'user_block',
|
|
|
|
priority: 100,
|
|
|
|
|
2021-03-22 18:19:09 -04:00
|
|
|
process(tokens, msg, user, haltable) {
|
2019-04-28 17:28:16 -04:00
|
|
|
if ( user && user.login && user.login == msg.user.login && ! this.context.get('chat.filtering.process-own') )
|
2021-12-01 16:48:10 -05:00
|
|
|
return;
|
2019-04-28 17:28:16 -04:00
|
|
|
|
|
|
|
const u = msg.user,
|
2021-04-30 17:38:49 -04:00
|
|
|
regexes = this.context.get('__filter:block-users');
|
2019-04-28 17:28:16 -04:00
|
|
|
if ( ! regexes )
|
2021-12-01 16:48:10 -05:00
|
|
|
return;
|
2019-04-28 17:28:16 -04:00
|
|
|
|
|
|
|
if ( regexes[1] && (regexes[1].test(u.login) || regexes[1].test(u.displayName)) ) {
|
|
|
|
msg.deleted = true;
|
|
|
|
msg.ffz_removed = true;
|
2021-03-22 18:19:09 -04:00
|
|
|
if ( haltable )
|
|
|
|
msg.ffz_halt_tokens = true;
|
2019-04-28 17:28:16 -04:00
|
|
|
|
2021-03-22 18:19:09 -04:00
|
|
|
} else if ( ! msg.deleted && regexes[0] && (regexes[0].test(u.login) || regexes[0].test(u.displayName)) )
|
2019-04-28 17:28:16 -04:00
|
|
|
msg.deleted = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-22 18:19:09 -04:00
|
|
|
function getBadgeIDs(msg) {
|
|
|
|
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',
|
2021-04-30 17:38:49 -04:00
|
|
|
priority: 97,
|
2019-04-28 17:28:16 -04:00
|
|
|
|
2021-03-22 18:19:09 -04:00
|
|
|
process(tokens, msg, user, haltable) {
|
2019-04-28 17:28:16 -04:00
|
|
|
if ( user && user.login && user.login == msg.user.login && ! this.context.get('chat.filtering.process-own') )
|
2021-12-01 16:48:10 -05:00
|
|
|
return;
|
2019-04-28 17:28:16 -04:00
|
|
|
|
2021-04-30 17:38:49 -04:00
|
|
|
const highlights = this.context.get('__filter:highlight-badges'),
|
|
|
|
list = this.context.get('__filter:block-badges');
|
2021-03-22 18:19:09 -04:00
|
|
|
|
2021-04-30 17:38:49 -04:00
|
|
|
if ( ! highlights && ! list )
|
2021-12-01 16:48:10 -05:00
|
|
|
return;
|
2019-04-28 17:28:16 -04:00
|
|
|
|
2021-03-22 18:19:09 -04:00
|
|
|
const keys = getBadgeIDs(msg);
|
|
|
|
if ( ! keys || ! keys.length )
|
2021-12-01 16:48:10 -05:00
|
|
|
return;
|
2019-04-28 17:28:16 -04:00
|
|
|
|
2021-03-22 18:19:09 -04:00
|
|
|
for(const badge of keys) {
|
|
|
|
if ( list && list[1].includes(badge) ) {
|
|
|
|
msg.deleted = true;
|
|
|
|
msg.ffz_removed = true;
|
|
|
|
if ( haltable )
|
|
|
|
msg.ffz_halt_tokens = true;
|
2021-12-01 16:48:10 -05:00
|
|
|
return;
|
2021-03-22 18:19:09 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
if ( list && ! msg.deleted && list[0].includes(badge) )
|
|
|
|
msg.deleted = true;
|
|
|
|
|
2021-04-30 17:38:49 -04:00
|
|
|
if ( highlights && highlights.has(badge) ) {
|
|
|
|
const details = highlights.get(badge);
|
2021-05-03 15:33:03 -04:00
|
|
|
if ( Array.isArray(details) && details.length > 1 )
|
|
|
|
this.applyHighlight(msg, details[0], details[1], 'badge');
|
2019-04-28 17:28:16 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-22 18:19:09 -04:00
|
|
|
/*export const BlockedBadges = {
|
2019-04-28 17:28:16 -04:00
|
|
|
type: 'badge_block',
|
|
|
|
priority: 100,
|
2021-03-22 18:19:09 -04:00
|
|
|
process(tokens, msg, user, haltable) {
|
2019-04-28 17:28:16 -04:00
|
|
|
if ( user && user.login && user.login == msg.user.login && ! this.context.get('chat.filtering.process-own') )
|
|
|
|
return tokens;
|
|
|
|
|
2021-04-30 17:38:49 -04:00
|
|
|
const list = this.context.get('__filter:block-badges');
|
2019-04-28 17:28:16 -04:00
|
|
|
if ( ! list || (! list[0].length && ! list[1].length) )
|
|
|
|
return tokens;
|
|
|
|
|
2021-03-22 18:19:09 -04:00
|
|
|
const keys = getBadgeIDs(msg);
|
|
|
|
if ( ! keys || ! keys.length )
|
|
|
|
return tokens;
|
|
|
|
|
|
|
|
for(const badge of keys) {
|
2019-04-28 17:28:16 -04:00
|
|
|
if ( list[1].includes(badge) ) {
|
|
|
|
msg.deleted = true;
|
|
|
|
msg.ffz_removed = true;
|
2021-03-22 18:19:09 -04:00
|
|
|
if ( haltable )
|
|
|
|
msg.ffz_halt_tokens = true;
|
2019-04-28 17:28:16 -04:00
|
|
|
return tokens;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( ! msg.deleted && list[0].includes(badge) )
|
|
|
|
msg.deleted = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return tokens;
|
|
|
|
}
|
2021-03-22 18:19:09 -04:00
|
|
|
}*/
|
2019-04-28 17:28:16 -04:00
|
|
|
|
2018-05-31 18:34:15 -04:00
|
|
|
export const CustomHighlights = {
|
|
|
|
type: 'highlight',
|
2019-06-08 17:35:48 -04:00
|
|
|
priority: 35,
|
2018-05-31 18:34:15 -04:00
|
|
|
|
|
|
|
component: () => import(/* webpackChunkName: 'vue-chat' */ './components/chat-highlight.vue'),
|
|
|
|
|
|
|
|
render(token, createElement) {
|
|
|
|
return (<strong class="ffz--highlight">{token.text}</strong>);
|
|
|
|
},
|
|
|
|
|
2018-08-02 14:29:18 -04:00
|
|
|
process(tokens, msg, user) {
|
2018-05-31 18:34:15 -04:00
|
|
|
if ( ! tokens || ! tokens.length )
|
2021-12-01 16:48:10 -05:00
|
|
|
return;
|
2018-05-31 18:34:15 -04:00
|
|
|
|
2018-08-02 14:29:18 -04:00
|
|
|
if ( user && user.login && user.login == msg.user.login && ! this.context.get('chat.filtering.process-own') )
|
2021-12-01 16:48:10 -05:00
|
|
|
return;
|
2018-08-02 14:29:18 -04:00
|
|
|
|
2021-04-30 17:38:49 -04:00
|
|
|
const data = this.context.get('__filter:highlight-terms');
|
2021-03-03 17:10:14 -05:00
|
|
|
if ( ! data )
|
2021-12-01 16:48:10 -05:00
|
|
|
return;
|
2018-05-31 18:34:15 -04:00
|
|
|
|
2021-04-30 17:38:49 -04:00
|
|
|
let had_match = false;
|
2021-03-03 17:10:14 -05:00
|
|
|
if ( data.non ) {
|
2021-04-30 17:38:49 -04:00
|
|
|
for(const [priority, color, regexes] of data.non) {
|
2021-05-03 15:33:03 -04:00
|
|
|
if ( had_match && msg.mention_priority != null && msg.mention_priority > priority )
|
2021-04-30 17:38:49 -04:00
|
|
|
break;
|
|
|
|
|
2021-03-03 17:10:14 -05:00
|
|
|
let matched = false;
|
|
|
|
if ( regexes[0] ) {
|
|
|
|
regexes[0].lastIndex = 0;
|
|
|
|
matched = regexes[0].test(msg.message);
|
|
|
|
}
|
|
|
|
if ( ! matched && regexes[1] ) {
|
|
|
|
regexes[1].lastIndex = 0;
|
|
|
|
matched = regexes[1].test(msg.message);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( matched ) {
|
2021-04-30 17:38:49 -04:00
|
|
|
had_match = true;
|
2021-05-03 15:33:03 -04:00
|
|
|
this.applyHighlight(msg, priority, color, 'term');
|
2021-03-03 17:10:14 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( ! data.hl )
|
|
|
|
return tokens;
|
|
|
|
|
2021-04-30 17:38:49 -04:00
|
|
|
for(const [priority, color, regexes] of data.hl) {
|
2018-05-31 18:34:15 -04:00
|
|
|
const out = [];
|
|
|
|
for(const token of tokens) {
|
|
|
|
if ( token.type !== 'text' ) {
|
|
|
|
out.push(token);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
const text = token.text;
|
|
|
|
let idx = 0, match;
|
|
|
|
|
2021-03-03 17:10:14 -05:00
|
|
|
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;
|
|
|
|
|
2018-07-14 14:13:28 -04:00
|
|
|
const raw_nix = match.index,
|
|
|
|
offset = match[1] ? match[1].length : 0,
|
|
|
|
nix = raw_nix + offset;
|
2018-05-31 18:34:15 -04:00
|
|
|
|
|
|
|
if ( idx !== nix )
|
|
|
|
out.push({type: 'text', text: text.slice(idx, nix)});
|
|
|
|
|
2021-05-03 15:33:03 -04:00
|
|
|
this.applyHighlight(msg, priority, color, 'term');
|
2018-05-31 18:34:15 -04:00
|
|
|
|
|
|
|
out.push({
|
|
|
|
type: 'highlight',
|
2018-07-14 14:13:28 -04:00
|
|
|
text: match[0].slice(offset)
|
2018-05-31 18:34:15 -04:00
|
|
|
});
|
|
|
|
|
2018-07-14 14:13:28 -04:00
|
|
|
idx = raw_nix + match[0].length;
|
2018-05-31 18:34:15 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
if ( idx < text.length )
|
|
|
|
out.push({type: 'text', text: text.slice(idx)});
|
|
|
|
}
|
|
|
|
|
|
|
|
tokens = out;
|
|
|
|
}
|
|
|
|
|
|
|
|
return tokens;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-03-22 18:19:09 -04:00
|
|
|
function blocked_process(tokens, msg, regexes, do_remove, haltable) {
|
2018-07-14 14:13:28 -04:00
|
|
|
const out = [];
|
|
|
|
for(const token of tokens) {
|
|
|
|
if ( token.type !== 'text' ) {
|
|
|
|
out.push(token);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
const text = token.text;
|
|
|
|
let idx = 0, match;
|
|
|
|
|
2021-03-22 18:19:09 -04:00
|
|
|
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;
|
|
|
|
|
2018-07-14 14:13:28 -04:00
|
|
|
const raw_nix = match.index,
|
|
|
|
offset = match[1] ? match[1].length : 0,
|
|
|
|
nix = raw_nix + offset;
|
|
|
|
|
|
|
|
if ( idx !== nix )
|
|
|
|
out.push({type: 'text', text: text.slice(idx, nix)});
|
|
|
|
|
2021-03-22 18:19:09 -04:00
|
|
|
if ( do_remove ) {
|
|
|
|
msg.ffz_removed = true;
|
|
|
|
if ( haltable )
|
|
|
|
return tokens;
|
|
|
|
}
|
|
|
|
|
2018-07-14 14:13:28 -04:00
|
|
|
out.push({
|
|
|
|
type: 'blocked',
|
|
|
|
text: match[0].slice(offset)
|
|
|
|
});
|
|
|
|
|
2021-03-22 18:19:09 -04:00
|
|
|
idx = raw_nix + match[0].length
|
2018-07-14 14:13:28 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
if ( idx < text.length )
|
|
|
|
out.push({type: 'text', text: text.slice(idx)});
|
|
|
|
}
|
|
|
|
|
|
|
|
return out;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-05-31 18:34:15 -04:00
|
|
|
export const BlockedTerms = {
|
|
|
|
type: 'blocked',
|
|
|
|
priority: 99,
|
|
|
|
|
|
|
|
component: () => import(/* webpackChunkName: 'vue-chat' */ './components/chat-blocked.vue'),
|
|
|
|
|
|
|
|
render(token, createElement) {
|
|
|
|
return (<strong
|
|
|
|
data-text={token.text}
|
|
|
|
data-tooltip-type="blocked"
|
2020-08-13 14:00:47 -04:00
|
|
|
class="ffz-tooltip ffz--blocked ffz--pointer-events"
|
2019-01-18 19:07:57 -05:00
|
|
|
onClick={this.clickToReveal}
|
2018-05-31 18:34:15 -04:00
|
|
|
>
|
|
|
|
×××
|
|
|
|
</strong>);
|
|
|
|
},
|
|
|
|
|
|
|
|
tooltip(target) {
|
|
|
|
const ds = target.dataset;
|
|
|
|
return [
|
|
|
|
(<div class="tw-border-b tw-mg-b-05">{ // eslint-disable-line react/jsx-key
|
|
|
|
this.i18n.t('chat.filtering.blocked-term', 'Blocked Term')
|
|
|
|
}</div>),
|
|
|
|
ds.text
|
|
|
|
]
|
|
|
|
},
|
|
|
|
|
2021-03-22 18:19:09 -04:00
|
|
|
process(tokens, msg, user, haltable) {
|
2018-05-31 18:34:15 -04:00
|
|
|
if ( ! tokens || ! tokens.length )
|
2021-12-01 16:48:10 -05:00
|
|
|
return;
|
2018-05-31 18:34:15 -04:00
|
|
|
|
2018-08-02 14:29:18 -04:00
|
|
|
if ( user && user.login && user.login == msg.user.login && ! this.context.get('chat.filtering.process-own') )
|
2021-12-01 16:48:10 -05:00
|
|
|
return;
|
2018-08-02 14:29:18 -04:00
|
|
|
|
2021-04-30 17:38:49 -04:00
|
|
|
const regexes = this.context.get('__filter:block-terms');
|
2018-07-14 14:13:28 -04:00
|
|
|
if ( ! regexes )
|
2021-12-01 16:48:10 -05:00
|
|
|
return;
|
2018-05-31 18:34:15 -04:00
|
|
|
|
2021-03-22 18:19:09 -04:00
|
|
|
if ( regexes.remove ) {
|
|
|
|
tokens = blocked_process(tokens, msg, regexes.remove, true, haltable);
|
|
|
|
if ( haltable && msg.ffz_removed ) {
|
|
|
|
msg.ffz_halt_tokens = true;
|
|
|
|
return tokens;
|
|
|
|
}
|
|
|
|
}
|
2018-05-31 18:34:15 -04:00
|
|
|
|
2021-03-22 18:19:09 -04:00
|
|
|
if ( regexes.non )
|
|
|
|
tokens = blocked_process(tokens, msg, regexes.non, false, haltable);
|
2018-05-31 18:34:15 -04:00
|
|
|
|
2018-07-14 14:13:28 -04:00
|
|
|
return tokens;
|
2018-05-31 18:34:15 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-01-18 19:07:57 -05:00
|
|
|
// ============================================================================
|
|
|
|
// AutoMod Filtering
|
|
|
|
// ============================================================================
|
|
|
|
|
|
|
|
const AM_DESCRIPTIONS = {
|
|
|
|
A: 'Hostility',
|
|
|
|
I: 'Discrimination',
|
|
|
|
P: 'Profanity',
|
|
|
|
S: 'Sexually Explicit Language'
|
|
|
|
};
|
|
|
|
|
|
|
|
export const AutomoddedTerms = {
|
|
|
|
type: 'amterm',
|
2021-04-30 17:38:49 -04:00
|
|
|
priority: 95,
|
2019-01-18 19:07:57 -05:00
|
|
|
|
|
|
|
component: () => import(/* webpackChunkName: 'vue-chat' */ './components/chat-automod-blocked.vue'),
|
|
|
|
|
|
|
|
render(token, createElement) {
|
|
|
|
return (<strong
|
|
|
|
data-text={token.text}
|
|
|
|
data-categories={JSON.stringify(token.categories)}
|
|
|
|
data-tooltip-type="amterm"
|
2020-08-13 14:00:47 -04:00
|
|
|
class="ffz-tooltip ffz--blocked ffz--pointer-events"
|
2019-01-18 19:07:57 -05:00
|
|
|
onClick={this.clickToReveal}
|
|
|
|
>
|
|
|
|
×××
|
|
|
|
</strong>);
|
|
|
|
},
|
|
|
|
|
|
|
|
tooltip(target) {
|
|
|
|
const ds = target.dataset,
|
|
|
|
flags = [];
|
|
|
|
|
|
|
|
let cats;
|
|
|
|
try {
|
|
|
|
cats = JSON.parse(ds.categories);
|
|
|
|
for(const key in cats) {
|
|
|
|
if ( cats[key] && AM_DESCRIPTIONS[key] )
|
|
|
|
flags.push(this.i18n.t(`chat.filtering.automod.${key}`, AM_DESCRIPTIONS[key]))
|
|
|
|
}
|
|
|
|
|
|
|
|
} catch(err) {
|
|
|
|
flags.push('Parse Error');
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return [
|
|
|
|
(<div class="tw-border-b tw-mg-b-05">{ // eslint-disable-line react/jsx-key
|
|
|
|
this.i18n.t('chat.filtering.automod-term', 'AutoMod Blocked Term')
|
|
|
|
}</div>),
|
|
|
|
this.i18n.t('chat.filtering.automod-why', 'This was flagged as: '),
|
|
|
|
flags.join(', ')
|
|
|
|
];
|
|
|
|
},
|
|
|
|
|
2021-03-22 18:19:09 -04:00
|
|
|
process(tokens, msg, user, haltable) {
|
2019-01-18 19:07:57 -05:00
|
|
|
if ( ! tokens || ! tokens.length || ! msg.flags || ! Array.isArray(msg.flags.list) )
|
2021-12-01 16:48:10 -05:00
|
|
|
return;
|
2019-01-18 19:07:57 -05:00
|
|
|
|
|
|
|
const cats = msg.flags.preferences,
|
|
|
|
flagged = msg.flags.list.filter(x => {
|
|
|
|
if ( ! x || x.startIndex == null || x.endIndex == null )
|
|
|
|
return false;
|
|
|
|
|
|
|
|
const y = x.categories;
|
|
|
|
if ( ! y )
|
|
|
|
return false;
|
|
|
|
|
|
|
|
for(const key in y) {
|
|
|
|
if ( y[key] && cats[key] )
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}),
|
|
|
|
f_length = flagged.length;
|
|
|
|
|
|
|
|
if ( ! f_length )
|
2021-12-01 16:48:10 -05:00
|
|
|
return;
|
2019-01-18 19:07:57 -05:00
|
|
|
|
|
|
|
const out = [];
|
|
|
|
let idx = 0,
|
|
|
|
fix = 0;
|
|
|
|
|
2019-10-11 17:41:07 -04:00
|
|
|
const remove = this.context.get('chat.automod.remove-messages');
|
|
|
|
const del = this.context.get('chat.automod.delete-messages');
|
|
|
|
|
|
|
|
if ( del )
|
|
|
|
msg.deleted = true;
|
|
|
|
|
|
|
|
if ( remove ) {
|
|
|
|
msg.ffz_removed = true;
|
2021-03-22 18:19:09 -04:00
|
|
|
if ( haltable )
|
|
|
|
msg.ffz_halt_tokens = true;
|
2021-12-01 16:48:10 -05:00
|
|
|
return;
|
2019-10-11 17:41:07 -04:00
|
|
|
}
|
|
|
|
|
2019-01-18 19:07:57 -05:00
|
|
|
for(const token of tokens) {
|
|
|
|
const length = token.length || (token.text && split_chars(token.text).length) || 0,
|
|
|
|
t_start = idx,
|
|
|
|
t_end = idx + length;
|
|
|
|
|
|
|
|
if ( token.type !== 'text' ) {
|
|
|
|
out.push(token);
|
|
|
|
idx = t_end;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
const text = split_chars(token.text);
|
|
|
|
|
|
|
|
while ( fix < f_length ) {
|
|
|
|
const flag = flagged[fix],
|
|
|
|
f_start = flag.startIndex,
|
|
|
|
f_end = flag.endIndex + 1;
|
|
|
|
|
|
|
|
// Did this flagged term already end? Skip it!
|
|
|
|
if ( f_end < t_start ) {
|
|
|
|
fix++;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Does this flagged term start after this token?
|
|
|
|
if ( f_start > t_end ) {
|
|
|
|
// Just dump this token and move on.
|
|
|
|
out.push(token);
|
|
|
|
idx = t_end;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If there's text at the beginning of the token that isn't part of
|
|
|
|
// this flagged term, output it.
|
|
|
|
if ( f_start > idx )
|
|
|
|
out.push({
|
|
|
|
type: 'text',
|
|
|
|
text: text.slice(idx - t_start, f_start - t_start).join('')
|
|
|
|
});
|
|
|
|
|
|
|
|
// Clamp the start of the filtered term to the start of this token.
|
|
|
|
let fs = f_start - t_start;
|
|
|
|
if ( fs < 0 )
|
|
|
|
fs = 0;
|
|
|
|
|
|
|
|
// Add the token.
|
|
|
|
out.push({
|
|
|
|
type: 'amterm',
|
|
|
|
categories: flag.categories,
|
|
|
|
text: text.slice(fs, f_end - t_start).join('')
|
|
|
|
});
|
|
|
|
|
|
|
|
// Does this flagged term extend past the end of this token?
|
|
|
|
if ( f_end > t_end ) {
|
|
|
|
// Don't go to the next term, just continue processing on the
|
|
|
|
// next token.
|
|
|
|
idx = t_end;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
idx = f_end;
|
|
|
|
fix++;
|
|
|
|
}
|
|
|
|
|
|
|
|
// We've finished processing terms. If there's any remaining
|
|
|
|
// text in the token, push it out.
|
|
|
|
if ( idx < t_end ) {
|
|
|
|
if ( t_start === idx )
|
|
|
|
out.push(token);
|
|
|
|
else
|
|
|
|
out.push({
|
|
|
|
type: 'text',
|
|
|
|
text: text.slice(idx - t_start).join('')
|
|
|
|
});
|
|
|
|
|
|
|
|
idx = t_end;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return out;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-05-31 18:34:15 -04:00
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
// ============================================================================
|
|
|
|
// Cheers
|
|
|
|
// ============================================================================
|
|
|
|
|
|
|
|
export const CheerEmotes = {
|
|
|
|
type: 'cheer',
|
2018-04-07 17:59:16 -04:00
|
|
|
priority: 40,
|
2017-11-13 01:23:39 -05:00
|
|
|
|
2018-05-10 19:56:39 -04:00
|
|
|
component: () => import(/* webpackChunkName: 'vue-chat' */ './components/chat-cheer.vue'),
|
|
|
|
|
2018-04-01 18:24:08 -04:00
|
|
|
render(token, createElement) {
|
|
|
|
return (<span
|
2020-08-13 14:00:47 -04:00
|
|
|
class="ffz-cheer ffz-tooltip ffz--pointer-events"
|
2018-04-01 18:24:08 -04:00
|
|
|
data-tooltip-type="cheer"
|
|
|
|
data-prefix={token.prefix}
|
|
|
|
data-amount={this.i18n.formatNumber(token.amount)}
|
|
|
|
data-tier={token.tier}
|
|
|
|
data-individuals={JSON.stringify(token.individuals || null)}
|
|
|
|
alt={token.text}
|
|
|
|
/>);
|
2017-11-13 01:23:39 -05:00
|
|
|
},
|
|
|
|
|
|
|
|
tooltip(target) {
|
|
|
|
const ds = target.dataset,
|
|
|
|
amount = parseInt(ds.amount.replace(/,/g, ''), 10),
|
|
|
|
prefix = ds.prefix,
|
|
|
|
tier = ds.tier,
|
|
|
|
individuals = ds.individuals && JSON.parse(ds.individuals),
|
|
|
|
length = individuals && individuals.length;
|
|
|
|
|
|
|
|
const out = [
|
2018-04-01 18:24:08 -04:00
|
|
|
this.context.get('tooltip.emote-images') && (<div
|
|
|
|
class="preview-image ffz-cheer-preview"
|
|
|
|
data-prefix={prefix}
|
|
|
|
data-tier={tier}
|
|
|
|
/>),
|
2019-05-03 19:30:46 -04:00
|
|
|
this.i18n.t('tooltip.bits', '{count,number} Bits', amount),
|
2017-11-13 01:23:39 -05:00
|
|
|
];
|
|
|
|
|
|
|
|
if ( length > 1 ) {
|
2018-04-01 18:24:08 -04:00
|
|
|
out.push(<br />);
|
2017-11-13 01:23:39 -05:00
|
|
|
|
|
|
|
individuals.sort(i => -i[0]);
|
|
|
|
|
|
|
|
for(let i=0; i < length && i < 12; i++) {
|
|
|
|
const [amount, tier, prefix] = individuals[i];
|
|
|
|
out.push(this.tokenizers.cheer.render.call(this, {
|
|
|
|
amount,
|
|
|
|
prefix,
|
|
|
|
tier
|
2018-04-01 18:24:08 -04:00
|
|
|
}, createElement));
|
2017-11-13 01:23:39 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
if ( length > 12 ) {
|
2018-04-01 18:24:08 -04:00
|
|
|
out.push(<br />);
|
2021-11-15 17:12:01 -05:00
|
|
|
out.push(this.i18n.t('tooltip.bits.more', '(and {count, number} more)', length-12));
|
2017-11-13 01:23:39 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return out;
|
|
|
|
},
|
|
|
|
|
|
|
|
process(tokens, msg) {
|
|
|
|
if ( ! tokens || ! tokens.length || ! msg.bits )
|
2021-12-01 16:48:10 -05:00
|
|
|
return;
|
2017-11-13 01:23:39 -05:00
|
|
|
|
2018-08-29 19:03:10 -04:00
|
|
|
const room = this.getRoom(msg.roomID, msg.roomLogin, true),
|
|
|
|
actions = room && room.bitsConfig;
|
2017-11-13 01:23:39 -05:00
|
|
|
|
2018-08-29 19:03:10 -04:00
|
|
|
if ( ! actions )
|
2021-12-01 16:48:10 -05:00
|
|
|
return;
|
2017-11-13 01:23:39 -05:00
|
|
|
|
2018-08-29 19:03:10 -04:00
|
|
|
const matcher = new RegExp(`^(${Object.keys(actions).join('|')})(\\d+)$`, 'i');
|
2017-11-13 01:23:39 -05:00
|
|
|
|
|
|
|
const out = [],
|
|
|
|
collected = {},
|
|
|
|
collect = this.context.get('chat.bits.stack');
|
|
|
|
|
|
|
|
for(const token of tokens) {
|
2017-12-01 15:33:06 -05:00
|
|
|
if ( ! token || token.type !== 'text' ) {
|
2017-11-13 01:23:39 -05:00
|
|
|
out.push(token);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2017-12-01 15:33:06 -05:00
|
|
|
let text = [];
|
|
|
|
for(const segment of token.text.split(/ +/)) {
|
|
|
|
const match = matcher.exec(segment);
|
|
|
|
if ( match ) {
|
|
|
|
const prefix = match[1].toLowerCase(),
|
|
|
|
cheer = actions[prefix];
|
2017-11-13 01:23:39 -05:00
|
|
|
|
2017-12-01 15:33:06 -05:00
|
|
|
if ( ! cheer ) {
|
|
|
|
text.push(segment);
|
|
|
|
continue;
|
|
|
|
}
|
2017-11-13 01:23:39 -05:00
|
|
|
|
2017-12-01 15:33:06 -05:00
|
|
|
const amount = parseInt(match[2], 10),
|
2018-08-29 19:03:10 -04:00
|
|
|
tiers = cheer.tiers;
|
2017-11-13 01:23:39 -05:00
|
|
|
|
2017-12-01 15:33:06 -05:00
|
|
|
let tier, token;
|
|
|
|
for(let i=0, l = tiers.length; i < l; i++)
|
2018-08-29 19:03:10 -04:00
|
|
|
if ( amount >= tiers[i].amount ) {
|
2017-12-01 15:33:06 -05:00
|
|
|
tier = i;
|
|
|
|
break;
|
|
|
|
}
|
2017-11-13 01:23:39 -05:00
|
|
|
|
2017-12-01 15:33:06 -05:00
|
|
|
if ( text.length ) {
|
|
|
|
// We have pending text. Join it together, with an extra space.
|
|
|
|
out.push({type: 'text', text: `${text.join(' ')} `});
|
|
|
|
text = [];
|
2017-11-13 01:23:39 -05:00
|
|
|
}
|
|
|
|
|
2017-12-01 15:33:06 -05:00
|
|
|
out.push(token = {
|
|
|
|
type: 'cheer',
|
|
|
|
prefix,
|
|
|
|
tier,
|
|
|
|
amount,
|
|
|
|
text: match[0]
|
|
|
|
});
|
2017-11-13 01:23:39 -05:00
|
|
|
|
2017-12-01 15:33:06 -05:00
|
|
|
if ( collect ) {
|
2018-03-25 22:16:16 -04:00
|
|
|
let pref = collect === 2 ? 'cheer' : prefix;
|
|
|
|
if ( ! actions[pref] )
|
|
|
|
pref = prefix;
|
|
|
|
|
|
|
|
const group = collected[pref] = collected[pref] || {total: 0, individuals: []};
|
2017-11-13 01:23:39 -05:00
|
|
|
|
2017-12-01 15:33:06 -05:00
|
|
|
group.total += amount;
|
|
|
|
group.individuals.push([amount, tier, prefix]);
|
|
|
|
token.hidden = true;
|
|
|
|
}
|
2017-11-13 01:23:39 -05:00
|
|
|
|
2018-04-15 17:19:22 -04:00
|
|
|
text.push('');
|
|
|
|
|
2017-12-01 15:33:06 -05:00
|
|
|
} else
|
|
|
|
text.push(segment);
|
2017-11-13 01:23:39 -05:00
|
|
|
}
|
|
|
|
|
2017-12-01 15:33:06 -05:00
|
|
|
if ( text.length > 1 || (text.length === 1 && text[0] !== '') )
|
|
|
|
out.push({type: 'text', text: text.join(' ')});
|
2017-11-13 01:23:39 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
if ( collect ) {
|
|
|
|
for(const prefix in collected)
|
|
|
|
if ( has(collected, prefix) ) {
|
|
|
|
const cheers = collected[prefix],
|
|
|
|
cheer = actions[prefix],
|
2018-08-29 19:03:10 -04:00
|
|
|
tiers = cheer.tiers;
|
2017-11-13 01:23:39 -05:00
|
|
|
|
|
|
|
let tier = 0;
|
|
|
|
for(let l = tiers.length; tier < l; tier++)
|
2018-08-29 19:03:10 -04:00
|
|
|
if ( cheers.total >= tiers[tier].amount )
|
2017-11-13 01:23:39 -05:00
|
|
|
break;
|
|
|
|
|
|
|
|
out.unshift({
|
|
|
|
type: 'cheer',
|
|
|
|
prefix,
|
|
|
|
tier,
|
|
|
|
amount: cheers.total,
|
|
|
|
individuals: cheers.individuals,
|
|
|
|
length: 0
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return out;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================================================
|
|
|
|
// Addon Emotes
|
|
|
|
// ============================================================================
|
|
|
|
|
2018-12-11 17:40:02 -05:00
|
|
|
const render_emote = (token, createElement, wrapped) => {
|
2021-03-24 13:03:29 -04:00
|
|
|
const hover = token.anim === 2,
|
|
|
|
big = token.big && token.can_big;
|
2021-03-20 18:47:12 -04:00
|
|
|
let src, srcSet, hoverSrc, hoverSrcSet, normalSrc, normalSrcSet;
|
|
|
|
|
|
|
|
if ( token.anim === 1 && token.animSrc ) {
|
2021-03-24 13:03:29 -04:00
|
|
|
src = big ? token.animSrc2 : token.animSrc;
|
|
|
|
srcSet = big ? token.animSrcSet2 : token.animSrcSet;
|
2021-03-20 18:47:12 -04:00
|
|
|
} else {
|
2021-03-24 13:03:29 -04:00
|
|
|
src = big ? token.src2 : token.src;
|
|
|
|
srcSet = big ? token.srcSet2 : token.srcSet;
|
2021-03-20 18:47:12 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
if ( hover && token.animSrc ) {
|
|
|
|
normalSrc = src;
|
|
|
|
normalSrcSet = srcSet;
|
2021-03-24 13:03:29 -04:00
|
|
|
hoverSrc = big ? token.animSrc2 : token.animSrc;
|
|
|
|
hoverSrcSet = big ? token.animSrcSet2 : token.animSrcSet;
|
2021-03-20 18:47:12 -04:00
|
|
|
}
|
|
|
|
|
2018-05-10 19:56:39 -04:00
|
|
|
const mods = token.modifiers || [], ml = mods.length,
|
|
|
|
emote = createElement('img', {
|
2022-03-05 15:15:27 -05:00
|
|
|
class: `${EMOTE_CLASS} ffz-tooltip${hoverSrc ? ' ffz-hover-emote' : ''}${token.provider === 'twitch' ? ' twitch-emote' : token.provider === 'ffz' ? ' ffz-emote' : token.provider === 'emoji' ? ' ffz-emoji' : ''}`,
|
2018-05-10 19:56:39 -04:00
|
|
|
attrs: {
|
2021-03-20 18:47:12 -04:00
|
|
|
src,
|
|
|
|
srcSet,
|
2018-05-10 19:56:39 -04:00
|
|
|
alt: token.text,
|
2021-02-15 17:48:30 -05:00
|
|
|
height: (token.big && ! token.can_big && token.height) ? `${token.height * 2}px` : undefined,
|
2018-05-10 19:56:39 -04:00
|
|
|
'data-tooltip-type': 'emote',
|
|
|
|
'data-provider': token.provider,
|
|
|
|
'data-id': token.id,
|
|
|
|
'data-set': token.set,
|
|
|
|
'data-code': token.code,
|
|
|
|
'data-variant': token.variant,
|
2021-03-20 18:47:12 -04:00
|
|
|
'data-normal-src': normalSrc,
|
|
|
|
'data-normal-src-set': normalSrcSet,
|
|
|
|
'data-hover-src': hoverSrc,
|
|
|
|
'data-hover-src-set': hoverSrcSet,
|
2018-05-10 19:56:39 -04:00
|
|
|
'data-modifiers': ml ? mods.map(x => x.id).join(' ') : null,
|
|
|
|
'data-modifier-info': ml ? JSON.stringify(mods.map(x => [x.set, x.id])) : null
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2018-12-11 17:40:02 -05:00
|
|
|
if ( ! ml ) {
|
|
|
|
if ( wrapped )
|
|
|
|
return emote;
|
|
|
|
|
2019-12-06 15:56:58 -05:00
|
|
|
return createElement('div', {
|
|
|
|
className: 'ffz--inline',
|
2018-12-11 17:40:02 -05:00
|
|
|
attrs: {
|
2019-12-06 15:56:58 -05:00
|
|
|
'data-test-selector': 'emote-button'
|
2018-12-11 17:40:02 -05:00
|
|
|
}
|
|
|
|
}, [emote]);
|
|
|
|
}
|
2018-05-10 19:56:39 -04:00
|
|
|
|
2019-12-06 15:56:58 -05:00
|
|
|
return createElement('div', {
|
|
|
|
class: 'ffz--inline modified-emote',
|
2018-05-10 19:56:39 -04:00
|
|
|
attrs: {
|
2019-12-06 15:56:58 -05:00
|
|
|
'data-test-selector': 'emote-button',
|
2018-05-10 19:56:39 -04:00
|
|
|
'data-provider': token.provider,
|
|
|
|
'data-id': token.id,
|
2018-12-18 03:46:33 -05:00
|
|
|
'data-set': token.set,
|
|
|
|
'data-modifiers': ml ? mods.map(x => x.id).join(' ') : null
|
2018-05-10 19:56:39 -04:00
|
|
|
}
|
2018-12-11 17:40:02 -05:00
|
|
|
}, [emote, mods.map(x => createElement('span', {key: x.text}, render_emote(x, createElement, true)))])
|
2018-05-10 19:56:39 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
export const AddonEmotes = {
|
|
|
|
type: 'emote',
|
2018-04-07 17:59:16 -04:00
|
|
|
priority: 10,
|
2017-11-13 01:23:39 -05:00
|
|
|
|
2018-05-10 19:56:39 -04:00
|
|
|
component: {
|
|
|
|
functional: true,
|
|
|
|
render(createElement, {props}) {
|
|
|
|
return render_emote(props.token, createElement);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2018-12-11 17:40:02 -05:00
|
|
|
render(token, createElement, wrapped) {
|
2021-03-24 13:03:29 -04:00
|
|
|
const hover = token.anim === 2,
|
|
|
|
big = token.big && token.can_big;
|
2021-03-20 18:47:12 -04:00
|
|
|
let src, srcSet, hoverSrc, hoverSrcSet, normalSrc, normalSrcSet;
|
|
|
|
|
|
|
|
if ( token.anim === 1 && token.animSrc ) {
|
2021-03-24 13:03:29 -04:00
|
|
|
src = big ? token.animSrc2 : token.animSrc;
|
|
|
|
srcSet = big ? token.animSrcSet2 : token.animSrcSet;
|
2021-03-20 18:47:12 -04:00
|
|
|
} else {
|
2021-03-24 13:03:29 -04:00
|
|
|
src = big ? token.src2 : token.src;
|
|
|
|
srcSet = big ? token.srcSet2 : token.srcSet;
|
2021-03-20 18:47:12 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
if ( hover && token.animSrc ) {
|
|
|
|
normalSrc = src;
|
|
|
|
normalSrcSet = srcSet;
|
2021-03-24 13:03:29 -04:00
|
|
|
hoverSrc = big ? token.animSrc2 : token.animSrc;
|
|
|
|
hoverSrcSet = big ? token.animSrcSet2 : token.animSrcSet;
|
2021-03-20 18:47:12 -04:00
|
|
|
}
|
|
|
|
|
2023-03-06 17:08:47 -05:00
|
|
|
let style = undefined;
|
|
|
|
const effects = token.modifier_flags,
|
|
|
|
is_big = (token.big && ! token.can_big && token.height);
|
|
|
|
|
|
|
|
if ( effects ) {
|
|
|
|
this.emotes.ensureEffect(effects);
|
|
|
|
style = {
|
|
|
|
width: is_big ? token.width * 2 : token.width,
|
|
|
|
height: is_big ? token.height * 2 : token.height
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( (effects & SHRINK_X) === SHRINK_X )
|
|
|
|
style.width *= 0.5;
|
|
|
|
if ( (effects & STRETCH_X) === STRETCH_X )
|
|
|
|
style.width *= 2;
|
|
|
|
if ( (effects & SHRINK_Y) === SHRINK_Y )
|
|
|
|
style.height *= 0.5;
|
|
|
|
if ( (effects & STRETCH_Y) === STRETCH_Y )
|
|
|
|
style.height *= 2;
|
|
|
|
|
|
|
|
if ( (effects & ROTATE_90) === ROTATE_90 ) {
|
|
|
|
const w = style.width;
|
|
|
|
style.width = style.height;
|
|
|
|
style.height = w;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( style.width > 128 )
|
|
|
|
style.width = 128;
|
|
|
|
if ( style.height > 40 )
|
|
|
|
style.height = 40;
|
|
|
|
}
|
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
const mods = token.modifiers || [], ml = mods.length,
|
2018-04-01 18:24:08 -04:00
|
|
|
emote = (<img
|
2022-03-05 15:15:27 -05:00
|
|
|
class={`${EMOTE_CLASS} ffz--pointer-events ffz-tooltip${hoverSrc ? ' ffz-hover-emote' : ''}${token.provider === 'twitch' ? ' twitch-emote' : token.provider === 'ffz' ? ' ffz-emote' : token.provider === 'emoji' ? ' ffz-emoji' : ''}`}
|
2021-03-20 18:47:12 -04:00
|
|
|
src={src}
|
|
|
|
srcSet={srcSet}
|
2023-03-06 17:08:47 -05:00
|
|
|
style={style}
|
|
|
|
height={style ? undefined : is_big ? `${token.height * 2}px` : undefined}
|
2018-04-01 18:24:08 -04:00
|
|
|
alt={token.text}
|
|
|
|
data-tooltip-type="emote"
|
|
|
|
data-provider={token.provider}
|
|
|
|
data-id={token.id}
|
|
|
|
data-set={token.set}
|
2018-04-12 20:30:00 -04:00
|
|
|
data-code={token.code}
|
|
|
|
data-variant={token.variant}
|
2021-03-20 18:47:12 -04:00
|
|
|
data-normal-src={normalSrc}
|
|
|
|
data-normal-src-set={normalSrcSet}
|
|
|
|
data-hover-src={hoverSrc}
|
|
|
|
data-hover-src-set={hoverSrcSet}
|
2018-04-01 18:24:08 -04:00
|
|
|
data-modifiers={ml ? mods.map(x => x.id).join(' ') : null}
|
|
|
|
data-modifier-info={ml ? JSON.stringify(mods.map(x => [x.set, x.id])) : null}
|
2018-04-09 19:57:05 -04:00
|
|
|
onClick={this.emotes.handleClick}
|
2018-04-01 18:24:08 -04:00
|
|
|
/>);
|
2017-11-13 01:23:39 -05:00
|
|
|
|
2023-03-06 17:08:47 -05:00
|
|
|
if ( ! ml && ! token.modifier_flags ) {
|
2018-12-11 17:40:02 -05:00
|
|
|
if ( wrapped )
|
|
|
|
return emote;
|
|
|
|
|
2019-12-06 15:56:58 -05:00
|
|
|
return (<div class="ffz--inline" data-test-selector="emote-button">{emote}</div>);
|
2018-12-11 17:40:02 -05:00
|
|
|
}
|
2017-11-13 01:23:39 -05:00
|
|
|
|
2019-12-06 15:56:58 -05:00
|
|
|
return (<div
|
2023-03-06 17:08:47 -05:00
|
|
|
class={`ffz--inline ffz--pointer-events modified-emote${style ? ' scaled-modified-emote' : ''}`}
|
2019-12-06 15:56:58 -05:00
|
|
|
data-test-selector="emote-button"
|
2018-04-01 18:24:08 -04:00
|
|
|
data-provider={token.provider}
|
|
|
|
data-id={token.id}
|
|
|
|
data-set={token.set}
|
2023-03-06 17:08:47 -05:00
|
|
|
style={style}
|
2018-12-18 03:46:33 -05:00
|
|
|
data-modifiers={ml ? mods.map(x => x.id).join(' ') : null}
|
2023-03-03 15:24:20 -05:00
|
|
|
data-effects={effects ? effects : undefined}
|
2018-04-09 19:57:05 -04:00
|
|
|
onClick={this.emotes.handleClick}
|
2018-04-01 18:24:08 -04:00
|
|
|
>
|
|
|
|
{emote}
|
2023-03-03 15:24:20 -05:00
|
|
|
{mods.map(t => {
|
|
|
|
if ( (t.source_modifier_flags & 1) === 1)
|
|
|
|
return null;
|
|
|
|
return <span key={t.text}>
|
|
|
|
{this.tokenizers.emote.render.call(this, t, createElement, true)}
|
|
|
|
</span>
|
|
|
|
})}
|
2019-12-06 15:56:58 -05:00
|
|
|
</div>);
|
2017-11-13 01:23:39 -05:00
|
|
|
},
|
|
|
|
|
2019-10-28 14:56:55 -04:00
|
|
|
async tooltip(target, tip) {
|
2018-04-06 21:12:12 -04:00
|
|
|
const ds = target.dataset,
|
|
|
|
provider = ds.provider,
|
|
|
|
modifiers = ds.modifierInfo;
|
2017-11-13 01:23:39 -05:00
|
|
|
|
2018-04-12 20:30:00 -04:00
|
|
|
let name, preview, source, owner, mods, fav_source, emote_id,
|
2019-06-20 15:15:54 -04:00
|
|
|
plain_name = false;
|
|
|
|
|
|
|
|
const hide_source = ds.noSource === 'true';
|
2017-11-13 01:23:39 -05:00
|
|
|
|
|
|
|
if ( modifiers && modifiers !== 'null' ) {
|
|
|
|
mods = JSON.parse(modifiers).map(([set_id, emote_id]) => {
|
|
|
|
const emote_set = this.emotes.emote_sets[set_id],
|
|
|
|
emote = emote_set && emote_set.emotes[emote_id];
|
|
|
|
|
|
|
|
if ( emote )
|
2018-12-18 03:46:33 -05:00
|
|
|
return (<span class="tw-mg-05">
|
2018-04-12 20:30:00 -04:00
|
|
|
{this.tokenizers.emote.render.call(this, emote.token, createElement)}
|
2018-04-01 18:24:08 -04:00
|
|
|
{` - ${emote.hidden ? '???' : emote.name}`}
|
|
|
|
</span>);
|
2017-11-13 01:23:39 -05:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( provider === 'twitch' ) {
|
2019-12-12 18:44:19 -05:00
|
|
|
emote_id = ds.id;
|
2019-10-28 14:56:55 -04:00
|
|
|
const set_id = hide_source ? null : await this.emotes.getTwitchEmoteSet(emote_id),
|
|
|
|
emote_set = set_id != null && await this.emotes.getTwitchSetChannel(set_id);
|
2017-11-13 01:23:39 -05:00
|
|
|
|
2021-06-17 14:27:04 -04:00
|
|
|
preview = `${getTwitchEmoteURL(ds.id, 4, true, true)}?_=preview`;
|
2018-04-09 19:57:05 -04:00
|
|
|
fav_source = 'twitch';
|
2017-11-13 01:23:39 -05:00
|
|
|
|
|
|
|
if ( emote_set ) {
|
2019-10-28 14:56:55 -04:00
|
|
|
const type = emote_set.type;
|
2021-06-18 14:27:14 -04:00
|
|
|
if ( type === EmoteTypes.Global ) {
|
|
|
|
if ( emote_set.owner?.login ) {
|
|
|
|
source = this.i18n.t('tooltip.channel', 'Channel: {source}', {
|
|
|
|
source: emote_set.owner.displayName || emote_set.owner.login
|
|
|
|
});
|
|
|
|
} else
|
|
|
|
source = this.i18n.t('emote.global', 'Twitch Global');
|
2017-11-13 01:23:39 -05:00
|
|
|
|
2021-06-18 14:27:14 -04:00
|
|
|
} else if ( type === EmoteTypes.BitsTier ) {
|
2019-12-12 18:44:19 -05:00
|
|
|
source = this.i18n.t('emote.bits', 'Twitch Bits Reward');
|
|
|
|
if ( emote_set.owner?.login )
|
|
|
|
source = this.i18n.t('emote.bits-owner', '{source}\nChannel: {channel}', {
|
|
|
|
source,
|
|
|
|
channel: emote_set.owner.displayName || emote_set.owner.login
|
|
|
|
});
|
|
|
|
|
|
|
|
} else if ( type === EmoteTypes.Prime || type === EmoteTypes.Turbo )
|
2020-11-15 17:33:55 -05:00
|
|
|
source = this.i18n.t('emote.prime', 'Prime Gaming');
|
2017-11-13 01:23:39 -05:00
|
|
|
|
2020-04-03 19:30:28 -04:00
|
|
|
else if ( type === EmoteTypes.TwoFactor )
|
|
|
|
source = this.i18n.t('emote.2fa', 'Twitch 2FA Emote');
|
|
|
|
|
2019-10-28 14:56:55 -04:00
|
|
|
else if ( type === EmoteTypes.LimitedTime )
|
|
|
|
source = this.i18n.t('emote.limited', 'Limited-Time Only Emote');
|
|
|
|
|
|
|
|
else if ( type === EmoteTypes.ChannelPoints )
|
|
|
|
source = this.i18n.t('emote.points', 'Channel Points Emote');
|
|
|
|
|
2021-06-30 15:51:37 -04:00
|
|
|
else if ( type === EmoteTypes.Follower && emote_set.owner?.login )
|
|
|
|
source = this.i18n.t('emote.follower', 'Follower Emote ({source})', {
|
|
|
|
source: emote_set.owner.displayName || emote_set.owner.login
|
|
|
|
});
|
|
|
|
|
2019-10-28 14:56:55 -04:00
|
|
|
else if ( type === EmoteTypes.Subscription && emote_set.owner?.login )
|
|
|
|
source = this.i18n.t('tooltip.channel', 'Channel: {source}', {
|
|
|
|
source: emote_set.owner.displayName || emote_set.owner.login
|
|
|
|
});
|
2017-11-13 01:23:39 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
} else if ( provider === 'ffz' ) {
|
2018-04-06 21:12:12 -04:00
|
|
|
const emote_set = this.emotes.emote_sets[ds.set],
|
|
|
|
emote = emote_set && emote_set.emotes[ds.id];
|
2017-11-13 01:23:39 -05:00
|
|
|
|
2018-04-09 19:57:05 -04:00
|
|
|
if ( emote_set ) {
|
2017-11-13 01:23:39 -05:00
|
|
|
source = emote_set.source_line || (`${emote_set.source || 'FFZ'} ${emote_set.title || 'Global'}`);
|
2018-04-09 19:57:05 -04:00
|
|
|
fav_source = emote_set.source || 'ffz';
|
|
|
|
}
|
2017-11-13 01:23:39 -05:00
|
|
|
|
|
|
|
if ( emote ) {
|
2018-04-09 19:57:05 -04:00
|
|
|
emote_id = emote.id;
|
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
if ( emote.owner )
|
|
|
|
owner = this.i18n.t(
|
2019-05-03 19:30:46 -04:00
|
|
|
'emote.owner', 'By: {owner}',
|
2017-11-13 01:23:39 -05:00
|
|
|
{owner: emote.owner.display_name});
|
|
|
|
|
2021-03-20 18:47:12 -04:00
|
|
|
const anim = this.context.get('tooltip.emote-images.animated');
|
|
|
|
if ( anim && emote.animated?.[1] ) {
|
|
|
|
if ( emote.animated[4] )
|
|
|
|
preview = emote.animated[4];
|
|
|
|
else if ( emote.animated[2] )
|
|
|
|
preview = emote.animated[2];
|
|
|
|
|
|
|
|
} else {
|
|
|
|
if ( emote.urls[4] )
|
|
|
|
preview = emote.urls[4];
|
|
|
|
else if ( emote.urls[2] )
|
|
|
|
preview = emote.urls[2];
|
|
|
|
}
|
2023-03-06 17:08:47 -05:00
|
|
|
|
|
|
|
if ( ds.effects && emote.modifier && emote.modifier_flags ) {
|
|
|
|
owner = null;
|
|
|
|
|
|
|
|
const effects = emote.modifier_flags;
|
|
|
|
this.emotes.ensureEffect(effects);
|
|
|
|
|
|
|
|
const target = this.emotes.getTargetEmote();
|
|
|
|
|
|
|
|
let style = {
|
|
|
|
width: (target.width ?? 28) * 2,
|
|
|
|
height: (target.height ?? 28) * 2
|
|
|
|
};
|
|
|
|
|
|
|
|
let changed = false;
|
|
|
|
|
|
|
|
if ( (effects & SHRINK_X) === SHRINK_X ) {
|
|
|
|
style.width *= 0.5;
|
|
|
|
changed = true;
|
|
|
|
}
|
|
|
|
if ( (effects & STRETCH_X) === STRETCH_X ) {
|
|
|
|
style.width *= 2;
|
|
|
|
changed = true;
|
|
|
|
}
|
|
|
|
if ( (effects & SHRINK_Y) === SHRINK_Y ) {
|
|
|
|
style.height *= 0.5;
|
|
|
|
changed = true;
|
|
|
|
}
|
|
|
|
if ( (effects & STRETCH_Y) === STRETCH_Y ) {
|
|
|
|
style.height *= 2;
|
|
|
|
changed = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( changed ) {
|
|
|
|
if ( style.width > 512 )
|
|
|
|
style.width = 512;
|
|
|
|
if ( style.height > 160 )
|
|
|
|
style.height = 160;
|
|
|
|
}
|
|
|
|
|
|
|
|
style.width = `${style.width}px`;
|
|
|
|
style.height = `${style.height}px`;
|
|
|
|
|
|
|
|
// Whip up a special preview.
|
|
|
|
preview = (<div class="ffz-effect-tip">
|
|
|
|
<img
|
|
|
|
src={target.src}
|
|
|
|
srcSet={target.srcSet}
|
|
|
|
width={(target.width ?? 28) * 2}
|
|
|
|
height={(target.height ?? 28) * 2}
|
|
|
|
onLoad={tip.update}
|
|
|
|
/>
|
|
|
|
<span class="ffz-i-right-open"></span>
|
|
|
|
<div
|
|
|
|
class={`ffz--inline ffz--pointer-events modified-emote${style ? ' scaled-modified-emote' : ''}`}
|
|
|
|
style={style}
|
|
|
|
data-modifiers={emote.id}
|
|
|
|
data-effects={effects}
|
|
|
|
>
|
|
|
|
<img
|
|
|
|
class={`${EMOTE_CLASS} ffz--pointer-events ffz-tooltip ffz-emote`}
|
|
|
|
src={target.src}
|
|
|
|
srcSet={target.srcSet}
|
|
|
|
style={style}
|
|
|
|
height={style ? undefined : `${target.height * 2}px`}
|
|
|
|
onLoad={tip.update}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
</div>);
|
|
|
|
}
|
2017-11-13 01:23:39 -05:00
|
|
|
}
|
2018-04-09 19:57:05 -04:00
|
|
|
|
2018-04-12 20:30:00 -04:00
|
|
|
} else if ( provider === 'emoji' ) {
|
|
|
|
const emoji = this.emoji.emoji[ds.code],
|
|
|
|
style = this.context.get('chat.emoji.style'),
|
|
|
|
variant = ds.variant ? emoji.variants[ds.variant] : emoji,
|
|
|
|
vcode = ds.variant ? this.emoji.emoji[ds.variant] : null;
|
|
|
|
|
|
|
|
fav_source = 'emoji';
|
|
|
|
emote_id = ds.code;
|
|
|
|
|
|
|
|
preview = (<img
|
|
|
|
class="preview-image ffz-emoji"
|
|
|
|
src={this.emoji.getFullImage(variant.image, style)}
|
|
|
|
srcSet={this.emoji.getFullImageSet(variant.image, style)}
|
|
|
|
onLoad={tip.update}
|
|
|
|
/>);
|
|
|
|
|
|
|
|
plain_name = true;
|
|
|
|
name = `:${emoji.names[0]}:${vcode ? `:${vcode.names[0]}:` : ''}`;
|
2019-10-11 17:41:07 -04:00
|
|
|
|
2020-08-15 15:45:50 -04:00
|
|
|
const category = emoji.category ? this.i18n.t(`emoji.category.${emoji.category.toSnakeCase()}`, CATEGORIES[emoji.category] || emoji.category) : null;
|
2019-10-11 17:41:07 -04:00
|
|
|
source = this.i18n.t('tooltip.emoji', 'Emoji - {category}', {category});
|
2018-04-12 20:30:00 -04:00
|
|
|
|
2018-04-09 19:57:05 -04:00
|
|
|
} else
|
|
|
|
return;
|
2017-11-13 01:23:39 -05:00
|
|
|
|
2018-04-12 20:30:00 -04:00
|
|
|
if ( ! name )
|
|
|
|
name = ds.name || target.alt;
|
|
|
|
|
|
|
|
const favorite = fav_source && this.emotes.isFavorite(fav_source, emote_id);
|
2018-04-06 21:12:12 -04:00
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
return [
|
2018-04-12 20:30:00 -04:00
|
|
|
preview && this.context.get('tooltip.emote-images') && (typeof preview === 'string' ? (<img
|
2018-04-01 18:24:08 -04:00
|
|
|
class="preview-image"
|
|
|
|
src={preview}
|
|
|
|
onLoad={tip.update}
|
2018-04-12 20:30:00 -04:00
|
|
|
/>) : preview),
|
2017-11-13 01:23:39 -05:00
|
|
|
|
2019-05-03 19:30:46 -04:00
|
|
|
plain_name || (hide_source && ! owner) ? name : this.i18n.t('tooltip.emote', 'Emote: {name}', {name}),
|
2017-11-13 01:23:39 -05:00
|
|
|
|
2018-04-06 21:12:12 -04:00
|
|
|
! hide_source && source && this.context.get('tooltip.emote-sources') && (<div class="tw-pd-t-05">
|
2018-04-01 18:24:08 -04:00
|
|
|
{source}
|
|
|
|
</div>),
|
2017-11-13 01:23:39 -05:00
|
|
|
|
2018-04-01 18:24:08 -04:00
|
|
|
owner && this.context.get('tooltip.emote-sources') && (<div class="tw-pd-t-05">
|
|
|
|
{owner}
|
|
|
|
</div>),
|
2017-11-13 01:23:39 -05:00
|
|
|
|
2018-04-06 21:12:12 -04:00
|
|
|
ds.sellout && (<div class="tw-mg-t-05 tw-border-t tw-pd-t-05">{ds.sellout}</div>),
|
|
|
|
|
2018-04-09 19:57:05 -04:00
|
|
|
mods && (<div class="tw-pd-t-1">{mods}</div>),
|
|
|
|
|
|
|
|
favorite && (<figure class="ffz--favorite ffz-i-star" />)
|
2017-11-13 01:23:39 -05:00
|
|
|
];
|
|
|
|
},
|
|
|
|
|
|
|
|
process(tokens, msg) {
|
|
|
|
if ( ! tokens || ! tokens.length )
|
2021-12-01 16:48:10 -05:00
|
|
|
return;
|
2017-11-13 01:23:39 -05:00
|
|
|
|
2021-04-27 16:23:19 -04:00
|
|
|
if ( this.context.get('chat.emotes.enabled') !== 2 )
|
2021-12-01 16:48:10 -05:00
|
|
|
return;
|
2021-04-27 16:23:19 -04:00
|
|
|
|
2017-11-22 15:39:38 -05:00
|
|
|
const emotes = this.emotes.getEmotes(
|
2021-02-15 17:48:30 -05:00
|
|
|
msg.user.id,
|
|
|
|
msg.user.login,
|
|
|
|
msg.roomID,
|
|
|
|
msg.roomLogin
|
|
|
|
);
|
2017-11-13 01:23:39 -05:00
|
|
|
|
2017-11-22 15:39:38 -05:00
|
|
|
if ( ! emotes )
|
2021-12-01 16:48:10 -05:00
|
|
|
return;
|
2017-11-13 01:23:39 -05:00
|
|
|
|
2022-02-11 15:17:32 -05:00
|
|
|
const big = this.context.get('chat.emotes.2x') > 0,
|
2021-03-20 18:47:12 -04:00
|
|
|
anim = this.context.get('chat.emotes.animated'),
|
2021-02-15 17:48:30 -05:00
|
|
|
out = [];
|
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
let last_token, emote;
|
|
|
|
for(const token of tokens) {
|
|
|
|
if ( ! token )
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if ( token.type !== 'text' ) {
|
2021-02-15 17:48:30 -05:00
|
|
|
if ( token.type === 'emote' ) {
|
2023-03-03 15:24:20 -05:00
|
|
|
if ( ! token.modifiers ) {
|
2021-02-15 17:48:30 -05:00
|
|
|
token.modifiers = [];
|
2023-03-03 15:24:20 -05:00
|
|
|
token.modifier_flags = 0;
|
|
|
|
}
|
2021-02-15 17:48:30 -05:00
|
|
|
}
|
2017-11-13 01:23:39 -05:00
|
|
|
|
|
|
|
out.push(token);
|
|
|
|
last_token = token;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
let text = [];
|
|
|
|
|
|
|
|
for(const segment of token.text.split(/ +/)) {
|
|
|
|
if ( has(emotes, segment) ) {
|
|
|
|
emote = emotes[segment];
|
|
|
|
|
|
|
|
// Is this emote a modifier?
|
|
|
|
if ( emote.modifier && last_token && last_token.modifiers && (!text.length || (text.length === 1 && text[0] === '')) ) {
|
2021-02-15 17:48:30 -05:00
|
|
|
if ( last_token.modifiers.indexOf(emote.token) === -1 ) {
|
2023-03-03 15:24:20 -05:00
|
|
|
if ( emote.modifier_flags )
|
|
|
|
last_token.modifier_flags |= emote.modifier_flags;
|
|
|
|
|
2021-12-11 22:01:47 +01:00
|
|
|
last_token.modifiers.push(
|
|
|
|
Object.assign({
|
|
|
|
big,
|
|
|
|
anim
|
|
|
|
},
|
|
|
|
emote.token
|
|
|
|
)
|
|
|
|
);
|
2021-02-15 17:48:30 -05:00
|
|
|
}
|
2017-11-13 01:23:39 -05:00
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( text.length ) {
|
|
|
|
// We have pending text. Join it together, with an extra space.
|
2017-11-22 15:39:38 -05:00
|
|
|
const t = {type: 'text', text: `${text.join(' ')} `};
|
2017-11-13 01:23:39 -05:00
|
|
|
out.push(t);
|
|
|
|
if ( t.text.trim().length )
|
|
|
|
last_token = t;
|
|
|
|
|
|
|
|
text = [];
|
|
|
|
}
|
|
|
|
|
2021-02-15 17:48:30 -05:00
|
|
|
const t = Object.assign({
|
|
|
|
modifiers: [],
|
2023-03-03 15:24:20 -05:00
|
|
|
modifier_flags: 0,
|
2021-03-20 18:47:12 -04:00
|
|
|
big,
|
|
|
|
anim
|
2021-02-15 17:48:30 -05:00
|
|
|
}, emote.token);
|
2017-11-13 01:23:39 -05:00
|
|
|
out.push(t);
|
|
|
|
last_token = t;
|
|
|
|
|
|
|
|
text.push('');
|
|
|
|
|
|
|
|
} else
|
|
|
|
text.push(segment);
|
|
|
|
}
|
|
|
|
|
2021-02-15 17:48:30 -05:00
|
|
|
if ( text.length > 1 || (text.length === 1 && text[0] !== '') ) {
|
|
|
|
const t = {type: 'text', text: text.join(' ')};
|
|
|
|
out.push(t);
|
|
|
|
}
|
2017-11-13 01:23:39 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
return out;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-23 18:24:09 -04:00
|
|
|
/*AddonEmotes.tooltip.interactive = function(target) {
|
|
|
|
const mods = target.dataset.modifiers;
|
|
|
|
return mods && mods.length > 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
AddonEmotes.tooltip.delayHide = function(target) {
|
|
|
|
const mods = target.dataset.modifiers;
|
|
|
|
return mods && mods.length > 0 ? 100 : 0;
|
|
|
|
}*/
|
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
|
2018-04-12 20:30:00 -04:00
|
|
|
// ============================================================================
|
|
|
|
// Emoji
|
|
|
|
// ============================================================================
|
|
|
|
|
|
|
|
export const Emoji = {
|
|
|
|
type: 'emoji',
|
|
|
|
priority: 15,
|
|
|
|
|
|
|
|
process(tokens) {
|
|
|
|
if ( ! tokens || ! tokens.length )
|
2021-12-01 16:48:10 -05:00
|
|
|
return;
|
2018-04-12 20:30:00 -04:00
|
|
|
|
|
|
|
const splitter = this.emoji.splitter,
|
2022-02-11 15:17:32 -05:00
|
|
|
replace = this.context.get('chat.emoji.replace-joiner') > 0,
|
2021-12-01 16:48:10 -05:00
|
|
|
style = this.context.get('chat.emoji.style');
|
2018-04-12 20:30:00 -04:00
|
|
|
|
|
|
|
if ( style === 0 )
|
2021-12-01 16:48:10 -05:00
|
|
|
return;
|
|
|
|
|
|
|
|
const out = [];
|
2018-04-12 20:30:00 -04:00
|
|
|
|
|
|
|
for(const token of tokens) {
|
|
|
|
if ( ! token )
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if ( token.type !== 'text' ) {
|
|
|
|
out.push(token);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2022-02-11 15:17:32 -05:00
|
|
|
const text = replace ?
|
|
|
|
token.text.replace(JOINER_REPLACEMENT, "\u200d") :
|
|
|
|
token.text;
|
2018-04-12 20:30:00 -04:00
|
|
|
|
|
|
|
splitter.lastIndex = 0;
|
|
|
|
let idx = 0, match;
|
|
|
|
|
|
|
|
while((match = splitter.exec(text))) {
|
|
|
|
const start = match.index,
|
|
|
|
key = this.emoji.chars.get(match[0]);
|
|
|
|
|
|
|
|
if ( ! key )
|
|
|
|
continue;
|
|
|
|
|
|
|
|
const emoji = this.emoji.emoji[key[0]],
|
|
|
|
variant = key[1] ? emoji.variants[key[1]] : emoji,
|
|
|
|
length = split_chars(match[0]).length;
|
|
|
|
|
|
|
|
if ( idx !== start )
|
|
|
|
out.push({type: 'text', text: text.slice(idx, start)});
|
|
|
|
|
|
|
|
out.push({
|
|
|
|
type: 'emote',
|
|
|
|
|
|
|
|
provider: 'emoji',
|
|
|
|
code: key[0],
|
|
|
|
variant: key[1],
|
|
|
|
|
|
|
|
src: this.emoji.getFullImage(variant.image, style),
|
|
|
|
srcSet: this.emoji.getFullImageSet(variant.image, style),
|
|
|
|
|
|
|
|
text: match[0],
|
|
|
|
length,
|
2023-03-03 15:24:20 -05:00
|
|
|
modifiers: [],
|
|
|
|
modifier_flags: 0
|
2018-04-12 20:30:00 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
idx = start + match[0].length;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( idx < text.length )
|
|
|
|
out.push({type: 'text', text: text.slice(idx)});
|
|
|
|
}
|
|
|
|
|
|
|
|
return out;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
// ============================================================================
|
|
|
|
// Twitch Emotes
|
|
|
|
// ============================================================================
|
|
|
|
|
|
|
|
export const TwitchEmotes = {
|
|
|
|
type: 'twitch-emote',
|
2018-04-07 17:59:16 -04:00
|
|
|
priority: 20,
|
2017-11-13 01:23:39 -05:00
|
|
|
|
|
|
|
process(tokens, msg) {
|
2018-07-13 17:02:40 -04:00
|
|
|
if ( ! msg.ffz_emotes )
|
2021-12-01 16:48:10 -05:00
|
|
|
return;
|
2021-04-27 16:23:19 -04:00
|
|
|
|
|
|
|
if ( this.context.get('chat.emotes.enabled') < 1 )
|
2021-12-01 16:48:10 -05:00
|
|
|
return;
|
2017-11-13 01:23:39 -05:00
|
|
|
|
2018-07-13 17:02:40 -04:00
|
|
|
const data = msg.ffz_emotes,
|
2021-06-17 14:27:04 -04:00
|
|
|
anim = this.context.get('chat.emotes.animated'),
|
2022-02-11 15:17:32 -05:00
|
|
|
big = this.context.get('chat.emotes.2x') > 0,
|
2021-02-15 17:48:30 -05:00
|
|
|
use_replacements = this.context.get('chat.fix-bad-emotes'),
|
2017-11-13 01:23:39 -05:00
|
|
|
emotes = [];
|
|
|
|
|
|
|
|
for(const emote_id in data)
|
2018-07-13 14:32:12 -04:00
|
|
|
// Disable fix for now so we can see what Twitch is sending for emote data.
|
|
|
|
if ( has(data, emote_id) ) { // && Array.isArray(data[emote_id]) ) {
|
2017-11-13 01:23:39 -05:00
|
|
|
for(const match of data[emote_id])
|
|
|
|
emotes.push([emote_id, match.startIndex, match.endIndex + 1]);
|
|
|
|
}
|
|
|
|
|
2021-12-01 16:48:10 -05:00
|
|
|
const e_length = emotes.length;
|
2017-11-13 01:23:39 -05:00
|
|
|
|
|
|
|
if ( ! e_length )
|
2021-12-01 16:48:10 -05:00
|
|
|
return;
|
|
|
|
|
|
|
|
const out = [];
|
2017-11-13 01:23:39 -05:00
|
|
|
|
2017-11-14 22:11:58 -05:00
|
|
|
emotes.sort((a,b) => a[1] !== b[1] ? a[1] - b[1] : b[0] - a[0]);
|
2017-11-13 01:23:39 -05:00
|
|
|
|
|
|
|
let idx = 0,
|
|
|
|
eix = 0;
|
|
|
|
|
|
|
|
for(const token of tokens) {
|
|
|
|
const length = token.length || (token.text && split_chars(token.text).length) || 0,
|
|
|
|
t_start = idx,
|
|
|
|
t_end = idx + length;
|
|
|
|
|
|
|
|
if ( token.type !== 'text' ) {
|
|
|
|
out.push(token);
|
|
|
|
idx = t_end;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
const text = split_chars(token.text);
|
|
|
|
|
|
|
|
while( eix < e_length ) {
|
|
|
|
const [e_id, e_start, e_end] = emotes[eix];
|
|
|
|
|
|
|
|
// Does this emote go outside the bounds of this token?
|
|
|
|
if ( e_start > t_end || e_end > t_end ) {
|
|
|
|
// Output the remainder of this token.
|
|
|
|
if ( t_start === idx )
|
|
|
|
out.push(token);
|
|
|
|
else
|
|
|
|
out.push({
|
|
|
|
type: 'text',
|
|
|
|
text: text.slice(idx - t_start).join('')
|
|
|
|
});
|
|
|
|
|
|
|
|
// If this emote goes across token boundaries,
|
|
|
|
// skip it.
|
|
|
|
if ( e_start < t_end && e_end > t_end )
|
|
|
|
eix++;
|
|
|
|
|
|
|
|
idx = t_end;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2017-11-14 22:11:58 -05:00
|
|
|
// If this emote starts before the current index, skip it.
|
|
|
|
if ( e_start < idx ) {
|
|
|
|
eix++;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
// If there's text at the beginning of the token that
|
|
|
|
// isn't part of this emote, output it.
|
|
|
|
if ( e_start > idx )
|
|
|
|
out.push({
|
|
|
|
type: 'text',
|
|
|
|
text: text.slice(idx - t_start, e_start - t_start).join('')
|
|
|
|
});
|
|
|
|
|
2021-06-17 14:27:04 -04:00
|
|
|
let src, srcSet, animSrc, animSrcSet;
|
|
|
|
let src2, srcSet2, animSrc2, animSrcSet2;
|
2021-04-14 16:53:15 -04:00
|
|
|
let can_big = true;
|
2017-12-13 17:35:20 -05:00
|
|
|
|
|
|
|
const replacement = REPLACEMENTS[e_id];
|
2021-02-15 17:48:30 -05:00
|
|
|
if ( replacement && use_replacements ) {
|
2017-12-13 17:35:20 -05:00
|
|
|
src = `${REPLACEMENT_BASE}${replacement}`;
|
|
|
|
srcSet = '';
|
2021-04-14 16:53:15 -04:00
|
|
|
can_big = false;
|
2017-12-13 17:35:20 -05:00
|
|
|
|
|
|
|
} else {
|
2021-06-17 14:27:04 -04:00
|
|
|
src = getTwitchEmoteURL(e_id, 1, false);
|
|
|
|
srcSet = getTwitchEmoteSrcSet(e_id, false);
|
|
|
|
|
|
|
|
if ( anim > 0 ) {
|
|
|
|
animSrc = getTwitchEmoteURL(e_id, 1, true);
|
|
|
|
animSrcSet = getTwitchEmoteSrcSet(e_id, true);
|
|
|
|
}
|
2021-02-15 17:48:30 -05:00
|
|
|
|
|
|
|
if ( big ) {
|
2021-06-17 14:27:04 -04:00
|
|
|
src2 = getTwitchEmoteURL(e_id, 2, false);
|
|
|
|
srcSet2 = getTwitchEmoteSrcSet(e_id, false, true, true);
|
|
|
|
|
|
|
|
if ( anim > 0 ) {
|
|
|
|
animSrc2 = getTwitchEmoteURL(e_id, 2, true);
|
|
|
|
animSrcSet2 = getTwitchEmoteSrcSet(e_id, true, true, true);
|
|
|
|
}
|
2021-02-15 17:48:30 -05:00
|
|
|
}
|
2017-12-13 17:35:20 -05:00
|
|
|
}
|
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
out.push({
|
|
|
|
type: 'emote',
|
|
|
|
id: e_id,
|
|
|
|
provider: 'twitch',
|
2017-12-13 17:35:20 -05:00
|
|
|
src,
|
|
|
|
srcSet,
|
2021-02-15 17:48:30 -05:00
|
|
|
src2,
|
|
|
|
srcSet2,
|
2021-06-17 14:27:04 -04:00
|
|
|
animSrc,
|
|
|
|
animSrc2,
|
|
|
|
animSrcSet,
|
|
|
|
animSrcSet2,
|
|
|
|
anim,
|
2021-02-15 17:48:30 -05:00
|
|
|
big,
|
2021-04-14 16:53:15 -04:00
|
|
|
can_big,
|
2023-03-06 17:08:47 -05:00
|
|
|
width: 28,
|
2021-04-14 16:53:15 -04:00
|
|
|
height: 28, // Not always accurate but close enough.
|
2017-11-13 01:23:39 -05:00
|
|
|
text: text.slice(e_start - t_start, e_end - t_start).join(''),
|
2023-03-03 15:24:20 -05:00
|
|
|
modifiers: [],
|
|
|
|
modifier_flags: 0
|
2017-11-13 01:23:39 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
idx = e_end;
|
|
|
|
eix++;
|
|
|
|
}
|
|
|
|
|
|
|
|
// We've finished processing emotes. If there is any
|
|
|
|
// remaining text in the token, push it out.
|
|
|
|
if ( idx < t_end ) {
|
|
|
|
if ( t_start === idx )
|
|
|
|
out.push(token);
|
|
|
|
else
|
|
|
|
out.push({
|
|
|
|
type: 'text',
|
|
|
|
text: text.slice(idx - t_start).join('')
|
|
|
|
});
|
|
|
|
|
|
|
|
idx = t_end;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return out;
|
|
|
|
}
|
|
|
|
}
|