1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-07-05 18:48:31 +00:00

4.0.0-rc5

* Added: Remove messages entirely using Blocked Terms.
* Fixed: Position of emotes in locally echoed chat messages containing emoji.

Added a `chat:receive-message` event for processing incoming chat messages before they're added to the buffer.
This commit is contained in:
SirStendec 2018-07-14 14:13:28 -04:00
parent 84589231c8
commit f15d3c1d4f
7 changed files with 190 additions and 52 deletions

View file

@ -1,3 +1,9 @@
<div class="list-header">4.0.0-rc5<span>@a36c49ab78f754fcd1c6</span> <time datetime="2018-07-14">(2018-07-14)</time></div>
<ul class="chat-menu-content menu-side-padding">
<li>Added: Remove messages entirely using Blocked Terms.</li>
<li>Fixed: Position of emotes in locally echoed chat messages containing emoji.</li>
</ul>
<div class="list-header">4.0.0-rc4.7<span>@f9f030a275798072a22e</span> <time datetime="2018-07-13">(2018-07-13)</time></div> <div class="list-header">4.0.0-rc4.7<span>@f9f030a275798072a22e</span> <time datetime="2018-07-13">(2018-07-13)</time></div>
<ul class="chat-menu-content menu-side-padding"> <ul class="chat-menu-content menu-side-padding">
<li>Fixed: Handling of action messages in chat.</li> <li>Fixed: Handling of action messages in chat.</li>

View file

@ -100,7 +100,7 @@ class FrankerFaceZ extends Module {
FrankerFaceZ.Logger = Logger; FrankerFaceZ.Logger = Logger;
const VER = FrankerFaceZ.version_info = { const VER = FrankerFaceZ.version_info = {
major: 4, minor: 0, revision: 0, extra: '-rc4.7', major: 4, minor: 0, revision: 0, extra: '-rc5',
build: __webpack_hash__, build: __webpack_hash__,
toString: () => toString: () =>
`${VER.major}.${VER.minor}.${VER.revision}${VER.extra || ''}${DEBUG ? '-dev' : ''}` `${VER.major}.${VER.minor}.${VER.revision}${VER.extra || ''}${DEBUG ? '-dev' : ''}`

View file

@ -19,6 +19,8 @@ import * as RICH_PROVIDERS from './rich_providers';
import Actions from './actions'; 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 default class Chat extends Module { export default class Chat extends Module {
constructor(...args) { constructor(...args) {
@ -164,7 +166,7 @@ export default class Chat extends Module {
for(const [key, list] of colors) { for(const [key, list] of colors) {
if ( list[0].length ) if ( list[0].length )
list[1].push(`\\b(?:${list[0].join('|')})\\b`); list[1].push(`(^|.*?${SEPARATORS})(?:${list[0].join('|')})(?=$|${SEPARATORS})`);
colors.set(key, new RegExp(list[1].join('|'), 'gi')); colors.set(key, new RegExp(list[1].join('|'), 'gi'));
} }
@ -180,7 +182,8 @@ export default class Chat extends Module {
always_inherit: true, always_inherit: true,
ui: { ui: {
path: 'Chat > Filtering >> Blocked Terms', path: 'Chat > Filtering >> Blocked Terms',
component: 'basic-terms' component: 'basic-terms',
removable: true
} }
}); });
@ -192,7 +195,10 @@ export default class Chat extends Module {
if ( ! val || ! val.length ) if ( ! val || ! val.length )
return null; return null;
const out = [[], []]; const out = [
[[], []],
[[], []]
];
for(const item of val) { for(const item of val) {
const t = item.t; const t = item.t;
@ -210,16 +216,15 @@ export default class Chat extends Module {
if ( ! v || ! v.length ) if ( ! v || ! v.length )
continue; continue;
out[word ? 0 : 1].push(v); out[item.remove ? 1 : 0][word ? 0 : 1].push(v);
} }
if ( out[0].length ) return out.map(data => {
out[1].push(`\\b(?:${out[0].join('|')})\\b`); if ( data[0].length )
data[1].push(`(^|.*?${SEPARATORS})(?:${data[0].join('|')})(?=$|${SEPARATORS})`);
if ( ! out[1].length ) return data[1].length ? new RegExp(data[1].join('|'), 'gi') : null;
return; });
return new RegExp(out[1].join('|'), 'gi');
} }
}); });

View file

@ -295,7 +295,9 @@ export const CustomHighlights = {
let idx = 0, match; let idx = 0, match;
while((match = regex.exec(text))) { while((match = regex.exec(text))) {
const nix = match.index; const raw_nix = match.index,
offset = match[1] ? match[1].length : 0,
nix = raw_nix + offset;
if ( idx !== nix ) if ( idx !== nix )
out.push({type: 'text', text: text.slice(idx, nix)}); out.push({type: 'text', text: text.slice(idx, nix)});
@ -305,10 +307,10 @@ export const CustomHighlights = {
out.push({ out.push({
type: 'highlight', type: 'highlight',
text: match[0] text: match[0].slice(offset)
}); });
idx = nix + match[0].length; idx = raw_nix + match[0].length;
} }
if ( idx < text.length ) if ( idx < text.length )
@ -323,6 +325,45 @@ export const CustomHighlights = {
} }
function blocked_process(tokens, msg, regex, do_remove) {
const out = [];
for(const token of tokens) {
if ( token.type !== 'text' ) {
out.push(token);
continue;
}
regex.lastIndex = 0;
const text = token.text;
let idx = 0, match;
while((match = regex.exec(text))) {
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)});
out.push({
type: 'blocked',
text: match[0].slice(offset)
});
if ( do_remove )
msg.ffz_removed = true;
idx = raw_nix + match[0].length;
}
if ( idx < text.length )
out.push({type: 'text', text: text.slice(idx)});
}
return out;
}
export const BlockedTerms = { export const BlockedTerms = {
type: 'blocked', type: 'blocked',
priority: 99, priority: 99,
@ -349,44 +390,21 @@ export const BlockedTerms = {
] ]
}, },
process(tokens) { process(tokens, msg) {
if ( ! tokens || ! tokens.length ) if ( ! tokens || ! tokens.length )
return tokens; return tokens;
const regex = this.context.get('chat.filtering.highlight-basic-blocked--regex'); const regexes = this.context.get('chat.filtering.highlight-basic-blocked--regex');
if ( ! regex ) if ( ! regexes )
return tokens; return tokens;
const out = []; if ( regexes[0] )
for(const token of tokens) { tokens = blocked_process(tokens, msg, regexes[0], false);
if ( token.type !== 'text' ) {
out.push(token);
continue;
}
regex.lastIndex = 0; if ( regexes[1] )
const text = token.text; tokens = blocked_process(tokens, msg, regexes[1], true);
let idx = 0, match;
while((match = regex.exec(text))) { return tokens;
const nix = match.index;
if ( idx !== nix )
out.push({type: 'text', text: text.slice(idx, nix)});
out.push({
type: 'blocked',
text: match[0]
});
idx = nix + match[0].length;
}
if ( idx < text.length )
out.push({type: 'text', text: text.slice(idx)});
}
return out;
} }
} }

View file

@ -3,6 +3,7 @@
<term-editor <term-editor
:term="default_term" :term="default_term"
:colored="item.colored" :colored="item.colored"
:removable="item.removable"
:adding="true" :adding="true"
@save="new_term" @save="new_term"
/> />
@ -16,6 +17,7 @@
:key="term.id" :key="term.id"
:term="term.v" :term="term.v"
:colored="item.colored" :colored="item.colored"
:removable="item.removable"
@remove="remove(term)" @remove="remove(term)"
@save="save(term, $event)" @save="save(term, $event)"
/> />
@ -39,7 +41,8 @@ export default {
default_term: { default_term: {
v: '', v: '',
t: 'text', t: 'text',
c: '' c: '',
remove: false
} }
} }
}, },

View file

@ -48,6 +48,31 @@
<option value="raw">{{ t('setting.terms.type.regex', 'Regex') }}</option> <option value="raw">{{ t('setting.terms.type.regex', 'Regex') }}</option>
</select> </select>
</div> </div>
<div v-if="removable" class="tw-flex-shrink-0 tw-mg-r-05 tw-tooltip-wrapper">
<button
v-if="editing"
:class="{active: edit_data.remove}"
class="tw-button ffz-directory-toggle-block"
@click="toggleRemove"
>
<span
:class="edit_data.remove ? 'ffz-i-eye-off' : 'ffz-i-eye'"
class="tw-button__text"
/>
</button>
<span
v-else-if="term.remove"
class="ffz-i-eye-off tw-pd-x-1"
/>
<div class="tw-tooltip tw-tooltip--down tw-tooltip--align-right">
<span v-if="display.remove">
{{ t('setting.terms.remove.on', 'Remove matching messages from chat.') }}
</span>
<span v-else>
{{ t('setting.terms.remove.off', 'Do not remove matching messages from chat.') }}
</span>
</div>
</div>
<div v-if="adding" class="tw-flex-shrink-0"> <div v-if="adding" class="tw-flex-shrink-0">
<button class="tw-button" @click="save"> <button class="tw-button" @click="save">
<span class="tw-button__text"> <span class="tw-button__text">
@ -107,6 +132,8 @@ import safety from 'safe-regex';
import {deep_copy, glob_to_regex, escape_regex} from 'utilities/object'; import {deep_copy, glob_to_regex, escape_regex} from 'utilities/object';
let id = 0;
export default { export default {
props: { props: {
term: Object, term: Object,
@ -114,6 +141,10 @@ export default {
type: Boolean, type: Boolean,
default: false default: false
}, },
removable: {
type: Boolean,
default: false
},
adding: { adding: {
type: Boolean, type: Boolean,
default: false default: false
@ -123,12 +154,14 @@ export default {
data() { data() {
if ( this.adding ) if ( this.adding )
return { return {
editor_id: id++,
deleting: false, deleting: false,
editing: true, editing: true,
edit_data: deep_copy(this.term) edit_data: deep_copy(this.term)
}; };
return { return {
editor_id: id++,
deleting: false, deleting: false,
editing: false, editing: false,
edit_data: null edit_data: null
@ -136,8 +169,12 @@ export default {
}, },
computed: { computed: {
display() {
return this.editing ? this.edit_data : this.term;
},
is_valid() { is_valid() {
const data = this.editing ? this.edit_data : this.term, const data = this.display,
t = data.t; t = data.t;
let v = data.v; let v = data.v;
@ -157,7 +194,7 @@ export default {
}, },
is_safe() { is_safe() {
const data = this.editing ? this.edit_data : this.term, const data = this.display,
t = data.t; t = data.t;
let v = data.v; let v = data.v;
@ -195,6 +232,11 @@ export default {
this.edit_data = deep_copy(this.term); this.edit_data = deep_copy(this.term);
}, },
toggleRemove() {
if ( this.editing )
this.edit_data.remove = ! this.edit_data.remove;
},
cancel() { cancel() {
if ( this.adding ) { if ( this.adding ) {
this.edit_data = deep_copy(this.term); this.edit_data = deep_copy(this.term);

View file

@ -424,7 +424,7 @@ export default class ChatHook extends Module {
this.updateMentionCSS(); this.updateMentionCSS();
this.ChatController.on('mount', this.chatMounted, this); this.ChatController.on('mount', this.chatMounted, this);
this.ChatController.on('unmount', this.removeRoom, this); this.ChatController.on('unmount', this.chatUmounted, this);
this.ChatController.on('receive-props', this.chatUpdated, this); this.ChatController.on('receive-props', this.chatUpdated, this);
this.ChatController.ready((cls, instances) => { this.ChatController.ready((cls, instances) => {
@ -471,6 +471,9 @@ export default class ChatHook extends Module {
if ( ! buffer._ffz_was_here ) if ( ! buffer._ffz_was_here )
this.wrapChatBuffer(buffer.constructor); this.wrapChatBuffer(buffer.constructor);
buffer.consumeChatEvent = buffer.ffzConsumeChatEvent.bind(buffer);
buffer.ffzController = inst;
service.client.events.removeAll(); service.client.events.removeAll();
service.connectHandlers(); service.connectHandlers();
@ -530,10 +533,57 @@ export default class ChatHook extends Module {
wrapChatBuffer(cls) { wrapChatBuffer(cls) {
const t = this; const t = this,
old_consume = cls.prototype.consumeChatEvent;
cls.prototype._ffz_was_here = true; cls.prototype._ffz_was_here = true;
cls.prototype.ffzConsumeChatEvent = cls.prototype.consumeChatEvent = function(msg) {
if ( msg ) {
try {
const types = t.chat_types || {};
if ( msg.type === types.Message ) {
const m = t.chat.standardizeMessage(msg),
cont = this.ffzController;
let room = m.roomLogin = m.roomLogin ? m.roomLogin : m.channel ? m.channel.slice(1) : cont && cont.props.channelLogin,
room_id = cont && cont.props.channelID;
if ( ! room && room_id ) {
const r = t.chat.getRoom(room_id, null, true);
if ( r && r.login )
room = m.roomLogin = r.login;
}
const u = t.site.getUser(),
r = {id: room_id, login: room};
if ( u && cont ) {
u.moderator = cont.props.isCurrentUserModerator;
u.staff = cont.props.isStaff;
}
m.ffz_tokens = m.ffz_tokens || t.chat.tokenizeMessage(m, u, r);
const event = new FFZEvent({
message: m,
channel: room
});
t.emit('chat:receive-message', event);
if ( event.defaultPrevented || m.ffz_removed )
return;
}
} catch(err) {
t.log.capture(err, {extra: {msg}})
}
}
return old_consume.call(this, msg);
}
cls.prototype.toArray = function() { cls.prototype.toArray = function() {
const buf = this.buffer, const buf = this.buffer,
size = t.chat.context.get('chat.scrollback-length'), size = t.chat.context.get('chat.scrollback-length'),
@ -655,7 +705,7 @@ export default class ChatHook extends Module {
this.onChatMessageEvent = function(e) { this.onChatMessageEvent = function(e) {
if ( e && e.sentByCurrentUser ) { if ( e && e.sentByCurrentUser ) {
try { try {
e.message.ffz_emotes = findEmotes( e.message.user.emotes = findEmotes(
e.message.body, e.message.body,
i.ffzGetEmotes() i.ffzGetEmotes()
); );
@ -673,7 +723,7 @@ export default class ChatHook extends Module {
this.onChatActionEvent = function(e) { this.onChatActionEvent = function(e) {
if ( e && e.sentByCurrentUser ) { if ( e && e.sentByCurrentUser ) {
try { try {
e.message.ffz_emotes = findEmotes( e.message.user.emotes = findEmotes(
e.message.body.slice(8, -1), e.message.body.slice(8, -1),
i.ffzGetEmotes() i.ffzGetEmotes()
); );
@ -853,6 +903,9 @@ export default class ChatHook extends Module {
// ======================================================================== // ========================================================================
chatMounted(chat, props) { chatMounted(chat, props) {
if ( chat.chatBuffer )
chat.chatBuffer.ffzController = chat;
if ( ! props ) if ( ! props )
props = chat.props; props = chat.props;
@ -863,7 +916,18 @@ export default class ChatHook extends Module {
} }
chatUmounted(chat) {
if ( chat.chatBuffer && chat.chatBuffer.ffzController === this )
chat.chatBuffer.ffzController = null;
this.removeRoom(chat);
}
chatUpdated(chat, props) { chatUpdated(chat, props) {
if ( chat.chatBuffer )
chat.chatBuffer.ffzController = chat;
if ( props.channelID !== chat.props.channelID ) { if ( props.channelID !== chat.props.channelID ) {
this.removeRoom(chat); this.removeRoom(chat);
this.chatMounted(chat, props); this.chatMounted(chat, props);