mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-06-28 05:15:54 +00:00
4.14.2
* Added: Additional settings for controlling the behavior of Twitch's native Chat Filters. * Added: Upload strings directly from the Translation Tester UI. (This is for me only, but I like it.) * Changed: Allow resizing text entry boxes in the Translation Tester UI. * Fixed: Emoji categories were not being localized.
This commit is contained in:
parent
b53e0e6427
commit
3b7e99e5a3
10 changed files with 198 additions and 14 deletions
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "frankerfacez",
|
"name": "frankerfacez",
|
||||||
"author": "Dan Salvato LLC",
|
"author": "Dan Salvato LLC",
|
||||||
"version": "4.14.1",
|
"version": "4.14.2",
|
||||||
"description": "FrankerFaceZ is a Twitch enhancement suite.",
|
"description": "FrankerFaceZ is a Twitch enhancement suite.",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
|
@ -317,8 +317,8 @@ export class TranslationManager extends Module {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async loadStrings() {
|
async loadStrings(ignore_loaded = false) {
|
||||||
if ( this.strings_loaded )
|
if ( this.strings_loaded && ! ignore_loaded )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if ( this.strings_loading )
|
if ( this.strings_loading )
|
||||||
|
|
|
@ -232,6 +232,34 @@ export default class Chat extends Module {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.settings.add('chat.automod.delete-messages', {
|
||||||
|
default: true,
|
||||||
|
ui: {
|
||||||
|
path: 'Chat > Filtering >> AutoMod Filters @{"description": "Extra configuration for Twitch\'s native `Chat Filters`."}',
|
||||||
|
title: 'Mark messages as deleted if they contain filtered phrases.',
|
||||||
|
component: 'setting-check-box'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.settings.add('chat.automod.remove-messages', {
|
||||||
|
default: true,
|
||||||
|
ui: {
|
||||||
|
path: 'Chat > Filtering >> AutoMod Filters',
|
||||||
|
title: 'Remove messages entirely if they contain filtered phrases.',
|
||||||
|
component: 'setting-check-box'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.settings.add('chat.automod.run-as-mod', {
|
||||||
|
default: false,
|
||||||
|
ui: {
|
||||||
|
path: 'Chat > Filtering >> AutoMod Filters',
|
||||||
|
title: 'Use Chat Filters as a moderator.',
|
||||||
|
description: 'By default, Twitch\'s Chat Filters feature does not function for moderators. This overrides that behavior.',
|
||||||
|
component: 'setting-check-box'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
this.settings.add('chat.filtering.process-own', {
|
this.settings.add('chat.filtering.process-own', {
|
||||||
default: false,
|
default: false,
|
||||||
ui: {
|
ui: {
|
||||||
|
|
|
@ -647,6 +647,17 @@ export const AutomoddedTerms = {
|
||||||
let idx = 0,
|
let idx = 0,
|
||||||
fix = 0;
|
fix = 0;
|
||||||
|
|
||||||
|
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;
|
||||||
|
return tokens;
|
||||||
|
}
|
||||||
|
|
||||||
for(const token of tokens) {
|
for(const token of tokens) {
|
||||||
const length = token.length || (token.text && split_chars(token.text).length) || 0,
|
const length = token.length || (token.text && split_chars(token.text).length) || 0,
|
||||||
t_start = idx,
|
t_start = idx,
|
||||||
|
@ -1085,7 +1096,9 @@ export const AddonEmotes = {
|
||||||
|
|
||||||
plain_name = true;
|
plain_name = true;
|
||||||
name = `:${emoji.names[0]}:${vcode ? `:${vcode.names[0]}:` : ''}`;
|
name = `:${emoji.names[0]}:${vcode ? `:${vcode.names[0]}:` : ''}`;
|
||||||
source = this.i18n.t('tooltip.emoji', 'Emoji - {category}', emoji);
|
|
||||||
|
const category = emoji.category ? this.i18n.t(`emoji.category.${emoji.category.toSnakeCase()}`, emoji.category) : null;
|
||||||
|
source = this.i18n.t('tooltip.emoji', 'Emoji - {category}', {category});
|
||||||
|
|
||||||
} else
|
} else
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
ref="editor"
|
ref="editor"
|
||||||
v-model="value"
|
v-model="value"
|
||||||
:class="{'tw-textarea--error': ! valid}"
|
:class="{'tw-textarea--error': ! valid}"
|
||||||
class="tw-block tw-font-size-6 tw-full-width tw-full-height tw-textarea"
|
class="tw-block tw-font-size-6 tw-full-width tw-textarea"
|
||||||
@input="onInput"
|
@input="onInput"
|
||||||
@blur="onBlur"
|
@blur="onBlur"
|
||||||
@focus="open = true"
|
@focus="open = true"
|
||||||
|
@ -38,10 +38,15 @@
|
||||||
<div
|
<div
|
||||||
v-for="(line, idx) in source"
|
v-for="(line, idx) in source"
|
||||||
:key="idx"
|
:key="idx"
|
||||||
:title="line"
|
:title="Array.isArray(line) ? `${line[0]} (${line[1]})` : line"
|
||||||
class="tw-font-size-7 tw-c-text-alt-2 tw-ellipsis tw-full-width"
|
class="tw-font-size-7 tw-c-text-alt-2 tw-ellipsis tw-full-width"
|
||||||
>
|
>
|
||||||
{{ line }}
|
<span v-if="Array.isArray(line)">
|
||||||
|
{{ line[0] }} (<a :href="line[2]" rel="noopener noreferrer" target="_blank">{{ line[1] }}</a>)
|
||||||
|
</span>
|
||||||
|
<span v-else>
|
||||||
|
{{ line }}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="context_str && ! open">
|
<div v-if="context_str && ! open">
|
||||||
|
@ -187,7 +192,22 @@ export default {
|
||||||
if ( ! Array.isArray(calls) || ! calls.length )
|
if ( ! Array.isArray(calls) || ! calls.length )
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
return calls.join('\n').split(/\n/);
|
const lines = calls.join('\n').split(/\n/),
|
||||||
|
out = [];
|
||||||
|
|
||||||
|
for(const line of lines) {
|
||||||
|
const match = /^(?:(.*?) \()?(\/[^:\)]+):(\d+):(\d+)\)?$/.exec(line);
|
||||||
|
if ( match )
|
||||||
|
out.push([
|
||||||
|
match[1] || '???',
|
||||||
|
`${match[2]}:${match[3]}:${match[4]}`,
|
||||||
|
`https://www.github.com/FrankerFaceZ/FrankerFaceZ/blob/master${match[2]}#L${match[3]}`
|
||||||
|
]);
|
||||||
|
else
|
||||||
|
out.push(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
},
|
},
|
||||||
|
|
||||||
preview() {
|
preview() {
|
||||||
|
|
|
@ -34,6 +34,14 @@
|
||||||
{{ t('i18n.ui.save', 'Generate Change Blob') }}
|
{{ t('i18n.ui.save', 'Generate Change Blob') }}
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
<button v-if="can_upload" class="tw-button-icon tw-mg-x-05 tw-relative tw-tooltip-wrapper" @click="uploadBlob">
|
||||||
|
<span class="tw-button-icon__icon">
|
||||||
|
<figure class="ffz-i-upload-cloud" />
|
||||||
|
</span>
|
||||||
|
<div class="tw-tooltip tw-tooltip--down tw-tooltip--align-right">
|
||||||
|
{{ t('i18n.ui.upload', 'Upload Changes') }}
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
<button class="tw-button-icon tw-mg-x-05 tw-relative tw-tooltip-wrapper" @click="requestKeys">
|
<button class="tw-button-icon tw-mg-x-05 tw-relative tw-tooltip-wrapper" @click="requestKeys">
|
||||||
<span class="tw-button-icon__icon">
|
<span class="tw-button-icon__icon">
|
||||||
<figure class="ffz-i-arrows-cw" />
|
<figure class="ffz-i-arrows-cw" />
|
||||||
|
@ -222,7 +230,7 @@ import displace from 'displacejs';
|
||||||
import Parser from '@ffz/icu-msgparser';
|
import Parser from '@ffz/icu-msgparser';
|
||||||
import { saveAs } from 'file-saver';
|
import { saveAs } from 'file-saver';
|
||||||
|
|
||||||
import { deep_equals, deep_copy } from 'utilities/object';
|
import { deep_equals, deep_copy, sleep } from 'utilities/object';
|
||||||
|
|
||||||
const parser = new Parser();
|
const parser = new Parser();
|
||||||
const PER_PAGE = 20;
|
const PER_PAGE = 20;
|
||||||
|
@ -236,6 +244,9 @@ export default {
|
||||||
data.page = 1;
|
data.page = 1;
|
||||||
data.page_open = false;
|
data.page_open = false;
|
||||||
|
|
||||||
|
data.can_upload = false;
|
||||||
|
data.uploading = false;
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -327,6 +338,7 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
created() {
|
created() {
|
||||||
|
this.checkUpload();
|
||||||
this.requestKeys();
|
this.requestKeys();
|
||||||
this.grabKeys();
|
this.grabKeys();
|
||||||
|
|
||||||
|
@ -356,7 +368,7 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
saveBlob() {
|
getBlob() {
|
||||||
const out = [];
|
const out = [];
|
||||||
|
|
||||||
for(const entry of this.phrases) {
|
for(const entry of this.phrases) {
|
||||||
|
@ -371,6 +383,12 @@ export default {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
},
|
||||||
|
|
||||||
|
saveBlob() {
|
||||||
|
const out = this.getBlob();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const blob = new Blob([JSON.stringify(out, null, '\t')], {type: 'application/json;charset=utf-8'});
|
const blob = new Blob([JSON.stringify(out, null, '\t')], {type: 'application/json;charset=utf-8'});
|
||||||
saveAs(blob, 'ffz-strings.json');
|
saveAs(blob, 'ffz-strings.json');
|
||||||
|
@ -379,6 +397,55 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async uploadBlob() {
|
||||||
|
if ( this.uploading || ! this.can_upload )
|
||||||
|
return;
|
||||||
|
|
||||||
|
const blob = JSON.stringify(this.getBlob());
|
||||||
|
const socket = this.getI18n().resolve('socket');
|
||||||
|
if ( ! socket )
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.uploading = true;
|
||||||
|
const token = await socket.getAPIToken();
|
||||||
|
if ( ! token?.token )
|
||||||
|
return;
|
||||||
|
|
||||||
|
const data = await fetch(`https://api-test.frankerfacez.com/v2/i18n/strings`, {
|
||||||
|
method: 'PATCH',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: `Bearer ${token.token}`
|
||||||
|
},
|
||||||
|
body: blob
|
||||||
|
}).then(r => r.json());
|
||||||
|
|
||||||
|
alert(`Uploaded ${data?.added || 0} new strings and ${data?.changed || 0} changed strings.`); // eslint-disable-line no-alert
|
||||||
|
this.uploading = false;
|
||||||
|
this.getI18n().loadStrings(true);
|
||||||
|
},
|
||||||
|
|
||||||
|
async checkUpload() {
|
||||||
|
this.can_upload = false;
|
||||||
|
const socket = this.getI18n().resolve('socket');
|
||||||
|
if ( ! socket )
|
||||||
|
return;
|
||||||
|
|
||||||
|
const token = await socket.getAPIToken();
|
||||||
|
if ( ! token?.token )
|
||||||
|
return;
|
||||||
|
|
||||||
|
const data = await fetch(`https://api-test.frankerfacez.com/v2/user/${token.id}/role/strings_upload`, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${token.token}`
|
||||||
|
}
|
||||||
|
}).then(r => r.json());
|
||||||
|
if ( ! data?.has_role )
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.can_upload = true;
|
||||||
|
},
|
||||||
|
|
||||||
openPage() {
|
openPage() {
|
||||||
this.page_open = true;
|
this.page_open = true;
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
|
|
|
@ -579,7 +579,7 @@ export default class EmoteMenu extends Module {
|
||||||
{show_heading ? (<heading class="tw-pd-1 tw-border-b tw-flex tw-flex-nowrap" onClick={this.clickHeading}>
|
{show_heading ? (<heading class="tw-pd-1 tw-border-b tw-flex tw-flex-nowrap" onClick={this.clickHeading}>
|
||||||
{image}
|
{image}
|
||||||
<div class="tw-pd-l-05">
|
<div class="tw-pd-l-05">
|
||||||
{data.title || t.i18n.t('emote-menu.unknown', 'Unknown Source')}
|
{(data.i18n ? t.i18n.t(data.i18n, data.title) : data.title) || t.i18n.t('emote-menu.unknown', 'Unknown Source')}
|
||||||
{calendar && (<span
|
{calendar && (<span
|
||||||
class={`tw-mg-x-05 ffz--expiry-info ffz-tooltip ffz-i-${calendar.icon}`}
|
class={`tw-mg-x-05 ffz--expiry-info ffz-tooltip ffz-i-${calendar.icon}`}
|
||||||
data-tooltip-type="html"
|
data-tooltip-type="html"
|
||||||
|
@ -587,7 +587,7 @@ export default class EmoteMenu extends Module {
|
||||||
/>)}
|
/>)}
|
||||||
</div>
|
</div>
|
||||||
<div class="tw-flex-grow-1" />
|
<div class="tw-flex-grow-1" />
|
||||||
{data.source || 'FrankerFaceZ'}
|
{(data.source_i18n ? t.i18n.t(data.source_i18n, data.source) : data.source) || 'FrankerFaceZ'}
|
||||||
{filtered ? '' : <figure class={`tw-pd-l-05 ffz-i-${collapsed ? 'left' : 'down'}-dir`} />}
|
{filtered ? '' : <figure class={`tw-pd-l-05 ffz-i-${collapsed ? 'left' : 'down'}-dir`} />}
|
||||||
</heading>) : null}
|
</heading>) : null}
|
||||||
{collapsed || this.renderBody(show_heading)}
|
{collapsed || this.renderBody(show_heading)}
|
||||||
|
@ -1108,8 +1108,10 @@ export default class EmoteMenu extends Module {
|
||||||
key: `emoji-${emoji.category}`,
|
key: `emoji-${emoji.category}`,
|
||||||
emoji: true,
|
emoji: true,
|
||||||
image: t.emoji.getFullImage(emoji.image),
|
image: t.emoji.getFullImage(emoji.image),
|
||||||
|
i18n: `emoji.category.${emoji.category.toSnakeCase()}`,
|
||||||
title: emoji.category,
|
title: emoji.category,
|
||||||
source: t.i18n.t('emote-menu.emoji', 'Emoji'),
|
source: 'Emoji',
|
||||||
|
source_i18n: 'emote-menu.emoji',
|
||||||
emotes: cat
|
emotes: cat
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1790,8 +1790,14 @@ export default class ChatHook extends Module {
|
||||||
if ( user )
|
if ( user )
|
||||||
message.emotes = user.emotes;
|
message.emotes = user.emotes;
|
||||||
|
|
||||||
if ( flags && this.getFilterFlagOptions )
|
if ( flags && this.getFilterFlagOptions ) {
|
||||||
|
const clear_mod = this.props.isCurrentUserModerator && t.chat.context.get('chat.automod.run-as-mod');
|
||||||
|
if ( clear_mod )
|
||||||
|
this.props.isCurrentUserModerator = false;
|
||||||
message.flags = this.getFilterFlagOptions(flags);
|
message.flags = this.getFilterFlagOptions(flags);
|
||||||
|
if ( clear_mod )
|
||||||
|
this.props.isCurrentUserModerator = true;
|
||||||
|
}
|
||||||
|
|
||||||
if ( typeof original.action === 'string' )
|
if ( typeof original.action === 'string' )
|
||||||
message.message = original.action;
|
message.message = original.action;
|
||||||
|
|
|
@ -110,6 +110,50 @@ export default class SocketClient extends Module {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// FFZ API Helpers
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
getAPIToken() {
|
||||||
|
if ( this._cached_token ) {
|
||||||
|
if ( this._cached_token.expires > (Date.now() + 15000) )
|
||||||
|
return Promise.resolve(this._cached_token);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( this._token_waiters )
|
||||||
|
return new Promise((s, f) => this._token_waiters.push([s, f]));
|
||||||
|
|
||||||
|
this._token_waiters = [];
|
||||||
|
|
||||||
|
return new Promise((s, f) => {
|
||||||
|
this._token_waiters.push([s, f]);
|
||||||
|
|
||||||
|
this.call('get_api_token').then(token => {
|
||||||
|
token.expires = (new Date(token.expires)).getTime();
|
||||||
|
this._cached_token = token;
|
||||||
|
|
||||||
|
const waiters = this._token_waiters;
|
||||||
|
this._token_waiters = null;
|
||||||
|
|
||||||
|
for(const pair of waiters)
|
||||||
|
pair[0](token);
|
||||||
|
|
||||||
|
}).catch(err => {
|
||||||
|
this.log.error('Unable to get API token.', err);
|
||||||
|
const waiters = this._token_waiters;
|
||||||
|
this._token_waiters = null;
|
||||||
|
|
||||||
|
for(const pair of waiters)
|
||||||
|
pair[1](err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getBareAPIToken() {
|
||||||
|
return (await this.getAPIToken())?.token;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
// Connection Logic
|
// Connection Logic
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
|
|
|
@ -36,6 +36,10 @@
|
||||||
width: 33%;
|
width: 33%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
min-height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
code {
|
code {
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue