mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-06-27 21:05:53 +00:00
4.77.2
* 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:
parent
43c80713e9
commit
ddfcf9e17c
10 changed files with 624 additions and 364 deletions
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "frankerfacez",
|
"name": "frankerfacez",
|
||||||
"author": "Dan Salvato LLC",
|
"author": "Dan Salvato LLC",
|
||||||
"version": "4.77.1",
|
"version": "4.77.2",
|
||||||
"description": "FrankerFaceZ is a Twitch enhancement suite.",
|
"description": "FrankerFaceZ is a Twitch enhancement suite.",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
|
|
@ -378,11 +378,8 @@ export default class Channel extends Module {
|
||||||
if ( event.ctrlKey || event.shiftKey || event.altKey )
|
if ( event.ctrlKey || event.shiftKey || event.altKey )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const history = this.router.history;
|
|
||||||
if ( history ) {
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
history.push(link);
|
this.router.push(link);
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return a;
|
return a;
|
||||||
|
|
|
@ -273,6 +273,12 @@ export default class ChatHook extends Module {
|
||||||
Twilight.CHAT_ROUTES
|
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.joined_raids = new Set;
|
||||||
|
|
||||||
this.RaidController = this.fine.define(
|
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('mount', this.connectorMounted, this);
|
||||||
this.ChatBufferConnector.on('update', this.connectorUpdated, this);
|
this.ChatBufferConnector.on('update', this.connectorUpdated, this);
|
||||||
this.ChatBufferConnector.on('unmount', this.connectorUnmounted, this);
|
this.ChatBufferConnector.on('unmount', this.connectorUnmounted, this);
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
query FFZ_BroadcastID($id: ID, $login: String) {
|
|
||||||
user(id: $id, login: $login) {
|
|
||||||
id
|
|
||||||
stream {
|
|
||||||
id
|
|
||||||
archiveVideo {
|
|
||||||
id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
query FFZ_LastBroadcast($id: ID, $login: String) {
|
|
||||||
user(id: $id, login: $login) {
|
|
||||||
id
|
|
||||||
lastBroadcast {
|
|
||||||
id
|
|
||||||
startedAt
|
|
||||||
title
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
25
src/utilities/data/recent-broadcasts.gql
Normal file
25
src/utilities/data/recent-broadcasts.gql
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,7 +12,9 @@ query FFZ_FetchUser($id: ID, $login: String) {
|
||||||
title
|
title
|
||||||
game {
|
game {
|
||||||
id
|
id
|
||||||
|
name
|
||||||
displayName
|
displayName
|
||||||
|
boxArtURL(width: 40, height: 56)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stream {
|
stream {
|
||||||
|
|
|
@ -5,7 +5,9 @@ query FFZ_UserGame($id: ID, $login: String) {
|
||||||
id
|
id
|
||||||
game {
|
game {
|
||||||
id
|
id
|
||||||
|
name
|
||||||
displayName
|
displayName
|
||||||
|
boxArtURL(width: 40, height: 56)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,7 @@ const SVG_TAGS = [
|
||||||
'font-face-name', 'font-face-src', 'font-face-uri', 'font-face', 'font', 'foreignObject',
|
'font-face-name', 'font-face-src', 'font-face-uri', 'font-face', 'font', 'foreignObject',
|
||||||
'g', 'glyph', 'glyphRef', 'hkern', 'image', 'line', 'linearGradient', 'marker', 'mask',
|
'g', 'glyph', 'glyphRef', 'hkern', 'image', 'line', 'linearGradient', 'marker', 'mask',
|
||||||
'metadata', 'missing-glyph', 'mpath', 'path', 'pattern', 'polygon', 'polyline', 'radialGradient',
|
'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'
|
'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 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<K extends keyof HTMLElementDeprecatedTagNameMap>(tag: K, props?: any, ...children: DomFragment[]): HTMLElementDeprecatedTagNameMap[K];
|
||||||
export function createElement(tag: string, props?: any, ...children: DomFragment[]): HTMLElement {
|
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)
|
if ( children.length === 0)
|
||||||
children = null as any;
|
children = null as any;
|
||||||
|
@ -207,14 +211,17 @@ export function createElement(tag: string, props?: any, ...children: DomFragment
|
||||||
children = children[0] as any;
|
children = children[0] as any;
|
||||||
|
|
||||||
if ( typeof props === 'string' )
|
if ( typeof props === 'string' )
|
||||||
el.className = props;
|
el.setAttribute('class', props);
|
||||||
else if ( props )
|
else if ( props )
|
||||||
for(const key in props)
|
for(const key in props)
|
||||||
if ( has(props, key) ) {
|
if ( has(props, key) ) {
|
||||||
const lk = key.toLowerCase(),
|
const lk = key.toLowerCase(),
|
||||||
prop = props[key];
|
prop = props[key];
|
||||||
|
|
||||||
if ( lk === 'style' ) {
|
if ( key === 'className' ) {
|
||||||
|
el.setAttribute('class', prop);
|
||||||
|
|
||||||
|
} else if ( lk === 'style' ) {
|
||||||
if ( typeof prop === 'string' )
|
if ( typeof prop === 'string' )
|
||||||
el.style.cssText = prop;
|
el.style.cssText = prop;
|
||||||
else if ( prop && typeof prop === 'object' )
|
else if ( prop && typeof prop === 'object' )
|
||||||
|
|
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue