1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-06-27 12:55:55 +00:00
* Fixed: Channel points reward messages not appearing in chat correctly for some users. (Closes #1650)
* Fixed: Clicking on a stream's up-time not being able to find the ID of the current broadcast.
* Maintenance: Update the `twitch_data` module to have better typing and better use of promises.
* API: Change the `createElement` method to create SVG elements with the proper namespace.
This commit is contained in:
SirStendec 2025-03-29 16:06:33 -04:00
parent 43c80713e9
commit ddfcf9e17c
10 changed files with 624 additions and 364 deletions

View file

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

View file

@ -378,11 +378,8 @@ export default class Channel extends Module {
if ( event.ctrlKey || event.shiftKey || event.altKey )
return;
const history = this.router.history;
if ( history ) {
event.preventDefault();
history.push(link);
}
event.preventDefault();
this.router.push(link);
});
return a;

View file

@ -273,6 +273,12 @@ export default class ChatHook extends Module {
Twilight.CHAT_ROUTES
);
this.ChatRewardEventHandler = this.fine.define(
'chat-reward-event-handler',
n => n.unsubscribe && n.handleMessage && n.props?.messageHandlerAPI && n.props?.rewardMap,
Twilight.CHAT_ROUTES
);
this.joined_raids = new Set;
this.RaidController = this.fine.define(
@ -1614,6 +1620,85 @@ export default class ChatHook extends Module {
}
});
this.ChatRewardEventHandler.ready((cls, instances) => {
const t = this,
old_subscribe = cls.prototype.subscribe;
cls.prototype.ffzInstall = function() {
if (this._ffz_installed)
return;
this._ffz_installed = true;
const inst = this;
const old_handle = this.handleMessage;
this.handleMessage = function(msg) {
//t.log.info('reward-message', msg, inst);
try {
if ( ! inst.props?.channelID || ! msg )
return;
// TODO: Refactor this code and the PubSub code to remove code dupe.
const type = msg.type,
data = msg.data?.redemption,
isAutomaticReward = type === 'automatic-reward-redeemed',
isRedeemed = 'reward-redeemed';
if (!data?.reward || (!isAutomaticReward && !isRedeemed))
return;
if ((isRedeemed && data.user_input) || (isAutomaticReward && data.reward.reward_type !== 'celebration'))
return;
let rewardID;
if (isAutomaticReward)
rewardID = `${inst.props.channelID}:${data.reward.reward_type}`;
else
rewardID = data.reward.id;
const reward = inst.props.rewardMap[rewardID];
if ( ! reward )
return;
if ( t.chat.context.get('chat.filtering.blocked-types').has('ChannelPointsReward') )
return;
inst.addMessage({
id: data.id,
type: t.chat_types.Message,
ffz_type: 'points',
ffz_reward: reward,
ffz_reward_highlight: isHighlightedReward(reward),
messageParts: [],
user: {
id: data.user.id,
login: data.user.login,
displayName: data.user.display_name
},
timestamp: new Date(msg.data.timestamp || data.redeemed_at).getTime()
});
return;
} catch(err) {
t.log.error('Error handling reward event:', err);
return old_handle.call(this, msg);
}
}
};
cls.prototype.subscribe = function(...args) {
try {
this.ffzInstall();
} catch(err) {
t.log.error('Error in subscribe for RewardEventHandler:', err);
}
return old_subscribe.call(this, ...args);
}
for(const inst of instances)
inst.subscribe();
});
this.ChatBufferConnector.on('mount', this.connectorMounted, this);
this.ChatBufferConnector.on('update', this.connectorUpdated, this);
this.ChatBufferConnector.on('unmount', this.connectorUnmounted, this);

View file

@ -1,11 +0,0 @@
query FFZ_BroadcastID($id: ID, $login: String) {
user(id: $id, login: $login) {
id
stream {
id
archiveVideo {
id
}
}
}
}

View file

@ -1,10 +0,0 @@
query FFZ_LastBroadcast($id: ID, $login: String) {
user(id: $id, login: $login) {
id
lastBroadcast {
id
startedAt
title
}
}
}

View file

@ -0,0 +1,25 @@
query FFZ_RecentBroadcasts($id: ID, $login: String, $limit: Int, $cursor: Cursor, $type: BroadcastType, $sort: VideoSort, $options: VideoConnectionOptionsInput) {
user(id: $id, login: $login) {
id
videos(
first: $limit
after: $cursor
type: $type
sort: $sort
options: $options
) {
edges {
cursor
node {
id
title
createdAt
publishedAt
}
}
pageInfo {
hasNextPage
}
}
}
}

View file

@ -12,7 +12,9 @@ query FFZ_FetchUser($id: ID, $login: String) {
title
game {
id
name
displayName
boxArtURL(width: 40, height: 56)
}
}
stream {
@ -28,4 +30,4 @@ query FFZ_FetchUser($id: ID, $login: String) {
isStaff
}
}
}
}

View file

@ -5,8 +5,10 @@ query FFZ_UserGame($id: ID, $login: String) {
id
game {
id
name
displayName
boxArtURL(width: 40, height: 56)
}
}
}
}
}

View file

@ -33,7 +33,7 @@ const SVG_TAGS = [
'font-face-name', 'font-face-src', 'font-face-uri', 'font-face', 'font', 'foreignObject',
'g', 'glyph', 'glyphRef', 'hkern', 'image', 'line', 'linearGradient', 'marker', 'mask',
'metadata', 'missing-glyph', 'mpath', 'path', 'pattern', 'polygon', 'polyline', 'radialGradient',
'rect', 'set', 'stop', 'svg', 'switch', 'symbol', 'text', 'textPath', 'tref',
'rect', 'set', 'stop', 'switch', 'symbol', 'text', 'textPath', 'tref',
'tspan', 'use', 'view', 'vkern'
];
@ -199,7 +199,11 @@ export function findReactFragment<TNode extends SimpleNodeLike>(
export function createElement<K extends keyof HTMLElementTagNameMap>(tag: K, props?: any, ...children: DomFragment[]): HTMLElementTagNameMap[K];
export function createElement<K extends keyof HTMLElementDeprecatedTagNameMap>(tag: K, props?: any, ...children: DomFragment[]): HTMLElementDeprecatedTagNameMap[K];
export function createElement(tag: string, props?: any, ...children: DomFragment[]): HTMLElement {
const el = document.createElement(tag);
const isSvg = SVG_TAGS.includes(tag);
const el = isSvg
// This is technically wrong. I do not really care.
? document.createElementNS('http://www.w3.org/2000/svg', tag) as unknown as HTMLElement
: document.createElement(tag);
if ( children.length === 0)
children = null as any;
@ -207,14 +211,17 @@ export function createElement(tag: string, props?: any, ...children: DomFragment
children = children[0] as any;
if ( typeof props === 'string' )
el.className = props;
el.setAttribute('class', props);
else if ( props )
for(const key in props)
if ( has(props, key) ) {
const lk = key.toLowerCase(),
prop = props[key];
if ( lk === 'style' ) {
if ( key === 'className' ) {
el.setAttribute('class', prop);
} else if ( lk === 'style' ) {
if ( typeof prop === 'string' )
el.style.cssText = prop;
else if ( prop && typeof prop === 'object' )

File diff suppressed because it is too large Load diff