2021-01-27 17:36:01 -05:00
|
|
|
'use strict';
|
|
|
|
|
|
|
|
// ============================================================================
|
|
|
|
// Mod View Module
|
|
|
|
// ============================================================================
|
|
|
|
|
|
|
|
import Module from 'utilities/module';
|
|
|
|
import {debounce} from 'utilities/object';
|
|
|
|
|
|
|
|
|
|
|
|
export default class ModView extends Module {
|
|
|
|
constructor(...args) {
|
|
|
|
super(...args);
|
|
|
|
|
|
|
|
this.inject('i18n');
|
|
|
|
this.inject('settings');
|
|
|
|
this.inject('site.channel');
|
|
|
|
this.inject('site.css_tweaks');
|
|
|
|
this.inject('site.fine');
|
|
|
|
this.inject('site.elemental');
|
|
|
|
this.inject('site.router');
|
|
|
|
this.inject('site.twitch_data');
|
|
|
|
this.inject('metadata');
|
|
|
|
this.inject('socket');
|
2023-09-26 17:40:25 -04:00
|
|
|
this.inject('pubsub');
|
2021-01-27 17:36:01 -05:00
|
|
|
|
|
|
|
this.should_enable = true;
|
|
|
|
|
2021-03-22 18:19:09 -04:00
|
|
|
this._cached_color = null;
|
2021-01-27 17:36:01 -05:00
|
|
|
this._cached_channel = null;
|
2021-02-03 16:34:22 -05:00
|
|
|
this._cached_id = null;
|
2021-01-27 17:36:01 -05:00
|
|
|
|
|
|
|
this.Root = this.elemental.define(
|
|
|
|
'mod-view-root', '.moderation-view-page',
|
|
|
|
['mod-view'],
|
|
|
|
{attributes: true}, 1
|
|
|
|
);
|
|
|
|
|
|
|
|
this.ModInfoBar = this.elemental.define(
|
2025-08-18 05:21:44 +02:00
|
|
|
'mod-info-bar', '.modview-player-widget__stream-info .scrollable-area',
|
2021-01-27 17:36:01 -05:00
|
|
|
['mod-view'],
|
2021-02-03 16:34:22 -05:00
|
|
|
{childNodes: true, subtree: true}, 1, 30000, false
|
2021-01-27 17:36:01 -05:00
|
|
|
);
|
|
|
|
|
|
|
|
this.checkRoot = debounce(this.checkRoot, 250);
|
2021-02-03 16:34:22 -05:00
|
|
|
this.checkBar = debounce(this.checkBar, 250);
|
2021-01-27 17:36:01 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
onEnable() {
|
|
|
|
this.Root.on('mount', this.updateRoot, this);
|
|
|
|
this.Root.on('mutate', this.updateRoot, this);
|
|
|
|
this.Root.on('unmount', this.removeRoot, this);
|
|
|
|
this.Root.each(el => this.updateRoot(el));
|
|
|
|
|
|
|
|
this.ModInfoBar.on('mount', this.updateBar, this);
|
|
|
|
this.ModInfoBar.on('mutate', this.updateBar, this);
|
|
|
|
this.ModInfoBar.on('unmount', this.removeBar, this);
|
|
|
|
this.ModInfoBar.each(el => this.updateBar(el));
|
|
|
|
|
|
|
|
this.router.on(':route', this.checkNavigation, this);
|
|
|
|
this.checkNavigation();
|
|
|
|
}
|
|
|
|
|
2023-09-26 17:40:25 -04:00
|
|
|
updateSubscription(id, login) {
|
|
|
|
if ( this._subbed_login === login && this._subbed_id === id )
|
2021-01-27 17:36:01 -05:00
|
|
|
return;
|
|
|
|
|
2023-09-26 17:40:25 -04:00
|
|
|
if ( this._subbed_id ) {
|
2023-10-30 14:01:28 -04:00
|
|
|
this.pubsub.unsubscribe(this, `twitch/${this._subbed_id}`);
|
2023-09-26 17:40:25 -04:00
|
|
|
this._subbed_id = null;
|
|
|
|
}
|
|
|
|
|
2021-01-27 17:36:01 -05:00
|
|
|
if ( this._subbed_login ) {
|
|
|
|
this.socket.unsubscribe(this, `channel.${this._subbed_login}`);
|
|
|
|
this._subbed_login = null;
|
|
|
|
}
|
|
|
|
|
2023-09-26 17:40:25 -04:00
|
|
|
if ( id ) {
|
2023-10-30 14:01:28 -04:00
|
|
|
this.pubsub.subscribe(this, `twitch/${id}`);
|
2023-09-26 17:40:25 -04:00
|
|
|
this._subbed_id = id;
|
|
|
|
}
|
|
|
|
|
2021-01-27 17:36:01 -05:00
|
|
|
if ( login ) {
|
|
|
|
this.socket.subscribe(this, `channel.${login}`);
|
|
|
|
this._subbed_login = login;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
checkNavigation() {
|
|
|
|
if ( this.router.current_name === 'mod-view' ) {
|
2021-03-22 18:19:09 -04:00
|
|
|
this.channel.updateChannelColor(this._cached_color);
|
2021-01-27 17:36:01 -05:00
|
|
|
this.checkRoot();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-03 16:34:22 -05:00
|
|
|
checkBar() {
|
|
|
|
this.ModInfoBar.clean();
|
|
|
|
}
|
|
|
|
|
2021-01-27 17:36:01 -05:00
|
|
|
checkRoot() {
|
|
|
|
this.Root.each(el => this.updateRoot(el));
|
|
|
|
}
|
|
|
|
|
|
|
|
updateRoot(el) {
|
|
|
|
const root = this.fine.getReactInstance(el);
|
|
|
|
|
2025-02-14 13:49:56 -05:00
|
|
|
let channel = null;
|
|
|
|
let node = this.fine.searchNode(root, n => {
|
|
|
|
let i = 0;
|
|
|
|
let state = n.memoizedState;
|
2021-03-22 18:19:09 -04:00
|
|
|
while(state != null && channel == null && i < 50) {
|
2023-12-14 17:52:35 -05:00
|
|
|
channel = state?.memoizedState?.current?.result?.data?.user ??
|
|
|
|
state?.memoizedState?.current?.previousData?.user;
|
|
|
|
|
2025-02-14 13:49:56 -05:00
|
|
|
if (!channel?.id || !channel?.login)
|
|
|
|
channel = null;
|
|
|
|
|
2021-03-22 18:19:09 -04:00
|
|
|
state = state?.next;
|
|
|
|
i++;
|
|
|
|
}
|
2025-02-14 13:49:56 -05:00
|
|
|
if (channel)
|
|
|
|
return true;
|
|
|
|
});
|
2021-01-27 17:36:01 -05:00
|
|
|
|
2021-03-22 18:19:09 -04:00
|
|
|
if ( channel?.id ) {
|
|
|
|
if ( this._cached_id != channel.id ) {
|
|
|
|
this._cached_id = channel.id;
|
|
|
|
this._cached_channel = channel;
|
|
|
|
this._cached_color = null;
|
2023-09-26 17:40:25 -04:00
|
|
|
this.updateSubscription(channel.id, channel.login);
|
2021-03-22 18:19:09 -04:00
|
|
|
|
|
|
|
this.getChannelColor(el, channel.id).then(color => {
|
|
|
|
if ( this._cached_id != channel.id )
|
|
|
|
return;
|
|
|
|
|
|
|
|
this._cached_color = color;
|
|
|
|
this.channel.updateChannelColor(color);
|
|
|
|
this.settings.updateContext({
|
|
|
|
channelColor: color
|
|
|
|
});
|
|
|
|
|
|
|
|
}).catch(() => {
|
|
|
|
if ( this._cached_id != channel.id )
|
|
|
|
return;
|
|
|
|
|
|
|
|
this._cached_color = null;
|
|
|
|
this.channel.updateChannelColor();
|
|
|
|
this.settings.updateContext({
|
|
|
|
channelColor: null
|
|
|
|
});
|
2021-01-27 17:36:01 -05:00
|
|
|
});
|
2021-03-22 18:19:09 -04:00
|
|
|
|
2021-01-27 17:36:01 -05:00
|
|
|
this.settings.updateContext({
|
2021-03-22 18:19:09 -04:00
|
|
|
channel: channel.login,
|
|
|
|
channelID: channel.id
|
2021-01-27 17:36:01 -05:00
|
|
|
});
|
2021-03-22 18:19:09 -04:00
|
|
|
}
|
2021-01-27 17:36:01 -05:00
|
|
|
} else
|
|
|
|
this.removeRoot();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
getChannelColor(el, channel_id, no_promise) {
|
|
|
|
const cache = el._ffz_color_cache = el._ffz_color_cache || {};
|
|
|
|
if ( channel_id === cache.channel_id ) {
|
|
|
|
if ( Date.now() - cache.saved < 60000 ) {
|
|
|
|
if ( no_promise )
|
|
|
|
return cache.data;
|
|
|
|
return Promise.resolve(cache.data);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return new Promise(async (s, f) => {
|
|
|
|
if ( cache.updating ) {
|
|
|
|
cache.updating.push([s, f]);
|
|
|
|
return ;
|
|
|
|
}
|
|
|
|
|
|
|
|
cache.channel_id = channel_id;
|
|
|
|
cache.updating = [[s,f]];
|
|
|
|
let data, err;
|
|
|
|
|
|
|
|
try {
|
|
|
|
data = await this.twitch_data.getChannelColor(channel_id);
|
|
|
|
} catch(error) {
|
|
|
|
data = null;
|
|
|
|
err = error;
|
|
|
|
}
|
|
|
|
|
|
|
|
const waiters = cache.updating;
|
|
|
|
cache.updating = null;
|
|
|
|
|
|
|
|
if ( cache.channel_id !== channel_id ) {
|
|
|
|
err = new Error('Outdated');
|
|
|
|
cache.channel_id = null;
|
|
|
|
cache.data = null;
|
|
|
|
cache.saved = 0;
|
|
|
|
for(const pair of waiters)
|
|
|
|
pair[1](err);
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
cache.data = data;
|
|
|
|
cache.saved = Date.now();
|
|
|
|
|
|
|
|
for(const pair of waiters)
|
|
|
|
err ? pair[1](err) : pair[0](data);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
removeRoot() {
|
2021-02-03 16:34:22 -05:00
|
|
|
this._cached_id = null;
|
2021-01-27 17:36:01 -05:00
|
|
|
this._cached_channel = null;
|
2021-03-22 18:19:09 -04:00
|
|
|
this._cached_color = null;
|
2021-01-27 17:36:01 -05:00
|
|
|
this.updateSubscription();
|
|
|
|
this.channel.updateChannelColor();
|
|
|
|
this.settings.updateContext({
|
|
|
|
channel: null,
|
|
|
|
channelID: null,
|
|
|
|
channelColor: null
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
updateBar(el) {
|
|
|
|
const container = el.closest('.modview-player-widget__stream-info'),
|
|
|
|
root = container && this.fine.getReactInstance(container);
|
|
|
|
|
|
|
|
let channel = null, state = root?.return?.memoizedState, i = 0;
|
|
|
|
while(state != null && channel == null && i < 50 ) {
|
2023-12-14 17:52:35 -05:00
|
|
|
channel = state?.memoizedState?.current?.result?.data?.channel ??
|
|
|
|
state?.memoizedState?.current?.previousData?.channel;
|
2021-01-27 17:36:01 -05:00
|
|
|
state = state?.next;
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
|
|
|
|
const bcast = channel?.lastBroadcast,
|
|
|
|
title = bcast?.title,
|
|
|
|
game = bcast?.game;
|
|
|
|
|
2021-03-22 18:19:09 -04:00
|
|
|
// This doesn't work because hosting in mod view.
|
|
|
|
//if ( channel?.id && channel.id != this._cached_id )
|
|
|
|
// this.checkRoot();
|
2021-01-27 17:36:01 -05:00
|
|
|
|
2023-09-06 16:10:47 -04:00
|
|
|
if ( this._cached_bar_id != channel?.id ) {
|
|
|
|
this._cached_bar_id = channel?.id;
|
|
|
|
this._cached_bar_channel = channel;
|
|
|
|
}
|
|
|
|
|
2021-01-27 17:36:01 -05:00
|
|
|
if ( title != el._cached_title || game?.id != el._cached_game ) {
|
|
|
|
el._cached_title = title;
|
|
|
|
el._cached_game = game?.id;
|
|
|
|
|
|
|
|
this.settings.updateContext({
|
|
|
|
category: game?.name,
|
|
|
|
categoryID: game?.id,
|
|
|
|
title
|
|
|
|
});
|
|
|
|
}
|
2021-02-03 16:34:22 -05:00
|
|
|
|
|
|
|
if ( container ) {
|
|
|
|
if ( ! container._ffz_cont ) {
|
|
|
|
const e = container._ffz_cont = container.querySelector('.modview-player-widget__viewcount');
|
|
|
|
if ( e )
|
|
|
|
e.classList.add('ffz--mod-tray');
|
|
|
|
}
|
|
|
|
|
|
|
|
this.updateMetadata(container);
|
|
|
|
}
|
2021-01-27 17:36:01 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
removeBar(el) {
|
|
|
|
this.settings.updateContext({
|
|
|
|
category: null,
|
|
|
|
categoryID: null,
|
|
|
|
title: null
|
|
|
|
});
|
|
|
|
|
2021-02-03 16:34:22 -05:00
|
|
|
const container = el.closest('.modview-player-widget__stream-info');
|
|
|
|
if ( ! container )
|
|
|
|
return;
|
|
|
|
|
|
|
|
if ( container._ffz_cont )
|
|
|
|
container._ffz_cont.classList.remove('ffz--mod-tray');
|
2021-01-27 17:36:01 -05:00
|
|
|
|
2021-02-03 16:34:22 -05:00
|
|
|
container._ffz_cont = null;
|
|
|
|
if ( container._ffz_meta_timers ) {
|
|
|
|
for(const val of Object.values(container._ffz_meta_timers))
|
2021-01-27 17:36:01 -05:00
|
|
|
clearTimeout(val);
|
|
|
|
|
2021-02-03 16:34:22 -05:00
|
|
|
container._ffz_meta_timers = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
container._ffz_update = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
updateMetadata(el, keys) {
|
|
|
|
const cont = el._ffz_cont,
|
2023-09-06 16:10:47 -04:00
|
|
|
channel = this._cached_bar_channel;
|
2021-02-03 16:34:22 -05:00
|
|
|
//root = this.fine.getReactInstance(el);
|
|
|
|
|
|
|
|
/*let channel = null, state = root?.return?.memoizedState, i = 0;
|
|
|
|
while(state != null && channel == null && i < 50 ) {
|
|
|
|
state = state?.next;
|
|
|
|
channel = state?.memoizedState?.current?.previousData?.result?.data?.channel;
|
|
|
|
i++;
|
|
|
|
}*/
|
|
|
|
|
|
|
|
if ( ! cont || ! document.contains(cont) ) {
|
|
|
|
this.checkBar();
|
|
|
|
return;
|
2021-01-27 17:36:01 -05:00
|
|
|
}
|
|
|
|
|
2021-02-03 16:34:22 -05:00
|
|
|
if ( ! channel?.id )
|
|
|
|
return;
|
|
|
|
|
|
|
|
if ( ! keys )
|
|
|
|
keys = this.metadata.keys;
|
|
|
|
else if ( ! Array.isArray(keys) )
|
|
|
|
keys = [keys];
|
|
|
|
|
|
|
|
const timers = el._ffz_meta_timers = el._ffz_meta_timers || {},
|
|
|
|
refresh_fn = key => this.updateMetadata(el, key),
|
|
|
|
data = {
|
|
|
|
channel: {
|
|
|
|
id: channel.id,
|
|
|
|
login: channel.login,
|
|
|
|
display_name: channel.displayName
|
|
|
|
},
|
|
|
|
el,
|
|
|
|
getViewerCount: () => 0,
|
|
|
|
getUserSelfImmediate: () => null,
|
|
|
|
getUserSelf: () => null,
|
|
|
|
getBroadcastID: () => null
|
|
|
|
};
|
|
|
|
|
|
|
|
for(const key of keys)
|
|
|
|
if ( this.metadata.definitions[key].modview )
|
|
|
|
this.metadata.renderLegacy(key, data, cont, timers, refresh_fn);
|
|
|
|
|
2021-01-27 17:36:01 -05:00
|
|
|
}
|
|
|
|
|
2023-12-14 17:52:35 -05:00
|
|
|
}
|