1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-06-27 21:05:53 +00:00
* API Added: When adding a command to tab-completion with the `chat:get-tab-commands` event, you can now specify a `prefix` for your command. Valid options: `!` and `/`. Defaults to `/` if not specified.
* API Added: New `chat:update-line` event. Signature: `(messageId: string, clearTokens: boolean = true, clearBadges?: boolean)`. To re-render a chatline without re-tokenizing it, pass `false` as the second argument.
* API Changed: Whisper / video chat messages now have their message IDs correctly added to their standardized message objects.
This commit is contained in:
SirStendec 2024-03-16 20:34:27 -04:00
parent 3aeb70f0fb
commit 8807e09ea3
6 changed files with 133 additions and 5 deletions

View file

@ -1,7 +1,7 @@
{
"name": "frankerfacez",
"author": "Dan Salvato LLC",
"version": "4.68.2",
"version": "4.69.0",
"description": "FrankerFaceZ is a Twitch enhancement suite.",
"private": true,
"license": "Apache-2.0",

View file

@ -1895,6 +1895,7 @@ export default class Chat extends Module {
offset = is_action ? 4 : 0,
out = msg._ffz_message = {
id: msg.id,
user: {...msg.from}, // Apollo seals this~
message: msg.content.slice(offset),
is_action,

View file

@ -38,6 +38,7 @@ export default class Line extends Module {
onEnable() {
this.on('chat.overrides:changed', id => this.updateLinesByUser(id, null, false, false), this);
this.on('chat:update-lines-by-user', this.updateLinesByUser, this);
this.on('chat:update-line', this.updateLineById, this);
this.on('chat:update-lines', this.updateLines, this);
this.on('chat:rerender-lines', this.rerenderLines, this);
this.on('chat:update-line-tokens', this.updateLineTokens, this);
@ -174,6 +175,21 @@ export default class Line extends Module {
}
}
updateLineById(id, clear_tokens = true, clear_badges = null) {
if ( clear_badges == null )
clear_badges = clear_tokens;
for(const inst of this.ChatLine.instances) {
const msg = inst.props.node;
if ( msg?.id === id ) {
if ( clear_tokens || clear_badges )
this.messages.delete(msg);
inst.forceUpdate();
return;
}
}
}
maybeUpdateLines() {
if ( this.chat.context.get('chat.rich.all-links') )

View file

@ -12,6 +12,11 @@ import { TWITCH_POINTS_SETS, TWITCH_GLOBAL_SETS, TWITCH_PRIME_SETS, KNOWN_CODES,
import Twilight from 'site';
const COMMAND_KEYS = [
'!',
'/'
];
// Prefer using these statically-allocated collators to String.localeCompare
const locale = Intl.Collator();
const localeCaseInsensitive = Intl.Collator(undefined, {sensitivity: 'accent'});
@ -771,9 +776,22 @@ export default class Input extends Module {
inst._ffz_override = true;
inst.oldCommands = inst.getCommands;
inst.oldMatches = inst.getMatches;
inst.oldReplacement = inst.determineReplacement;
const t = this;
inst.getMatches = function(input, unknown, index) {
try {
return index === 0 && COMMAND_KEYS.includes(input[0])
? inst.getCommands(input) : null;
} catch(err) {
t.log.error('Error getting matches from command handler.', err);
return inst.oldCommands(input, unknown, index);
}
}
inst.getCommands = function(input) { try {
const commands = inst.props.getCommands(inst.props.permissionLevel, {
isEditor: inst.props.isCurrentUserEditor
@ -792,9 +810,13 @@ export default class Input extends Module {
return null;
// Trim off the starting /
const i = input.slice(1);
const prefix = input[0],
i = input.slice(1);
const sorted = commands
.filter(cmd => prefix === (cmd.prefix ?? '/') && inst.doesCommandMatchTerm(cmd, i))
.sort(inst.sortCommands);
const sorted = commands.filter(cmd => inst.doesCommandMatchTerm(cmd, i)).sort(inst.sortCommands);
const out = [];
for(const cmd of sorted) {
const arg = cmd.commandArgs?.[0];
@ -813,12 +835,44 @@ export default class Input extends Module {
});
}
// If we're working with a non-/ prefix, and have no items,
// return null so we don't display ANY UI.
if ( prefix !== '/' && ! out.length )
return null;
return out;
} catch(err) {
console.error(err);
return inst.oldCommands(input);
}}
} }
inst.determineReplacement = function(cmd) {
const out = inst.oldReplacement(cmd);
if ( (cmd.prefix ?? '/') !== '/' && out.startsWith('/') )
return cmd.prefix + out.slice(1);
return out;
}
const React = this.site.getReact(),
createElement = React?.createElement;
if ( createElement )
inst.renderCommandSuggestion = function(cmd, input) {
const args = Array.isArray(cmd?.commandArgs)
? cmd.commandArgs.map(arg => (<div class={`tw-mg-r-05${arg.isRequired ? '' : ' tw-c-text-alt'}`}>[{arg.name}]</div>))
: null;
return (<div class="tw-pd-05">
<div class="tw-flex">
<div class="tw-mg-r-05">
<span>{ cmd.prefix ?? '/' }</span>
<span class="tw-semibold">{ cmd.name }</span>
</div>
{args}
</div>
<p class="tw-c-text-alt-2 tw-font-size-7">{ cmd.description }</p>
</div>);
}
}

View file

@ -486,6 +486,7 @@ export default class ChatLine extends Module {
async onEnable() {
this.on('chat.overrides:changed', id => this.updateLinesByUser(id, null, false, false), this);
this.on('chat:update-lines-by-user', this.updateLinesByUser, this);
this.on('chat:update-line', this.updateLineById, this);
this.on('chat:update-lines', this.updateLines, this);
this.on('chat:rerender-lines', this.rerenderLines, this);
this.on('chat:update-line-tokens', this.updateLineTokens, this);
@ -1440,6 +1441,39 @@ other {# messages were deleted by a moderator.}
}
}
updateLineById(id, clear_tokens = true, clear_badges = null) {
if ( clear_badges == null )
clear_badges = clear_tokens;
for(const inst of this.ChatLine.instances) {
const msg = inst.props.message;
if ( msg?.id === id ) {
if ( clear_badges )
msg.ffz_badges = msg.ffz_badge_cache = null;
if ( clear_tokens ) {
msg.ffz_tokens = null;
msg.ffz_reply = null;
msg.highlights = msg.mentioned = msg.mention_color = msg.color_priority = null;
}
inst.forceUpdate();
return;
}
}
for(const inst of this.WhisperLine.instances) {
const msg = inst.props.message?._ffz_message;
if ( msg?.id === id ) {
// TODO: Better support for clear_tokens and clear_badges
if ( clear_badges || clear_tokens )
msg._ffz_message = null;
inst.forceUpdate();
return;
}
}
}
updateLinesByUser(id, login, clear_tokens = true, clear_badges = true) {
for(const inst of this.ChatLine.instances) {

View file

@ -99,6 +99,7 @@ export default class VideoChatHook extends Module {
this.chat.context.on('changed:chat.video-chat.timestamps', this.rerenderLines, this);
this.on('chat.overrides:changed', id => this.updateLinesByUser(id, null, false, false), this);
this.on('chat:update-lines-by-user', this.updateLinesByUser, this);
this.on('chat:update-line', this.updateLineById, this);
this.on('chat:update-lines', this.updateLines, this);
this.on('chat:rerender-lines', this.rerenderLines, this);
this.on('chat:update-line-tokens', this.updateLineTokens, this);
@ -488,6 +489,27 @@ export default class VideoChatHook extends Module {
}
updateLineById(id, clear_tokens = true, clear_badges = null) {
if ( clear_badges == null )
clear_badges = clear_tokens;
for(const inst of this.VideoChatLine.instances) {
const context = inst.props.messageContext;
if ( ! context.comment )
continue;
if ( context.comment?.id === id ) {
// TODO: Better support for clear_tokens and clear_badges
if ( clear_tokens || clear_badges )
context.comment._ffz_message = null;
inst.forceUpdate();
return;
}
}
}
checkEffects() {
for(const inst of this.VideoChatLine.instances) {
const context = inst.props.messageContext,
@ -516,6 +538,7 @@ export default class VideoChatHook extends Module {
msg_id = params && params['msg-id'];
const out = comment._ffz_message = {
id: comment.id,
user: {
color: comment.message.userColor,
id: author.id,
@ -668,4 +691,4 @@ export default class VideoChatHook extends Module {
room.updateBitsConfig(formatBitsConfig(config));
}
}
}