1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-09-16 18:06:55 +00:00

Add Browse -> Popular support for avatar and uptime (#359)

* Add Browse -> Popular support for avatar and uptime

* Fix potential null-erroring

* Fix Twitch's mess once again...

Also add actual decent support for the popular page and various other fixes, such as hiding blocked games and whatnot

* Fix...
This commit is contained in:
Lordmau5 2017-12-16 08:00:45 +01:00 committed by Mike
parent cc1705c0c8
commit bbf158aee1
3 changed files with 181 additions and 45 deletions

View file

@ -0,0 +1,104 @@
'use strict';
// ============================================================================
// Directory (Following, for now)
// ============================================================================
import {SiteModule} from 'utilities/module';
import {get} from 'utilities/object';
export default class BrowsePopular extends SiteModule {
constructor(...args) {
super(...args);
this.inject('site.apollo');
this.inject('site.fine');
this.inject('settings');
this.apollo.registerModifier('BrowsePage_Popular', `query {
streams {
edges {
node {
createdAt
broadcaster {
profileImageURL(width: 70)
}
}
}
}
}`);
this.ChannelCard = this.fine.define(
'browse-all-channel-card',
n => n.props && n.props.channelName && n.props.linkTo && n.props.linkTo.state && n.props.linkTo.state.medium === 'twitch_browse_directory'
);
this.apollo.registerModifier('BrowsePage_Popular', res => this.modifyStreams(res), false);
}
onEnable() {
this.ChannelCard.ready((cls, instances) => {
// Popular Directory Channel Cards
this.apollo.ensureQuery(
'BrowsePage_Popular',
'data.streams.edges.node.0.createdAt'
);
for(const inst of instances) this.updateChannelCard(inst);
});
this.ChannelCard.on('update', this.updateChannelCard, this);
this.ChannelCard.on('mount', this.updateChannelCard, this);
this.ChannelCard.on('unmount', this.parent.clearUptime, this);
}
modifyStreams(res) { // eslint-disable-line class-methods-use-this
const blockedGames = this.settings.provider.get('directory.game.blocked-games') || [];
const newStreams = [];
const edges = get('data.streams.edges', res);
if (!edges) return res;
for (let i = 0; i < edges.length; i++) {
const edge = edges[i];
const node = edge.node;
const s = node.viewersCount = new Number(node.viewersCount || 0);
s.profileImageURL = node.broadcaster.profileImageURL;
s.createdAt = node.createdAt;
s.login = node.broadcaster.login;
s.displayName = node.broadcaster.displayName;
if (!node.game || node.game && !blockedGames.includes(node.game.name)) newStreams.push(edge);
}
res.data.streams.edges = newStreams;
return res;
}
updateChannelCard(inst) {
const container = this.fine.getHostNode(inst);
if (!container) return;
if (container.classList.contains('ffz-modified-channel-card')) return;
container.classList.add('ffz-modified-channel-card');
this.parent.updateUptime(inst, 'props.viewerCount.createdAt', '.tw-card-img');
this.parent.addCardAvatar(inst, 'props.viewerCount', '.tw-card');
const hiddenThumbnails = this.settings.provider.get('directory.game.hidden-thumbnails') || [];
const hiddenPreview = 'https://static-cdn.jtvnw.net/ttv-static/404_preview-320x180.jpg';
if (inst.props.type === 'watch_party')
container.classList.toggle('tw-hide', this.settings.get('directory.hide-vodcasts'));
const img = container.querySelector && container.querySelector('.tw-card-img img');
if (img == null) return;
if (hiddenThumbnails.includes(inst.props.gameTitle)) {
img.src = hiddenPreview;
} else {
img.src = inst.props.imageSrc;
}
}
}

View file

@ -86,7 +86,8 @@ export default class Following extends SiteModule {
this.apollo.registerModifier('FollowingLive_CurrentUser', `query {
currentUser {
followedLiveUsers {
nodes {
edges {
node {
profileImageURL(width: 70)
stream {
createdAt
@ -94,6 +95,7 @@ export default class Following extends SiteModule {
}
}
}
}
}`);
this.apollo.registerModifier('FollowingHosts_CurrentUser', `query {
@ -155,20 +157,28 @@ export default class Following extends SiteModule {
const hiddenThumbnails = this.settings.provider.get('directory.game.hidden-thumbnails') || [];
const blockedGames = this.settings.provider.get('directory.game.blocked-games') || [];
const newLiveNodes = [];
const newStreams = [];
const nodes = res.data.currentUser.followedLiveUsers.nodes;
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i];
const followedLiveUsers = get('data.currentUser.followedLiveUsers', res);
if (!followedLiveUsers)
return res;
const oldMode = !!followedLiveUsers.nodes;
const edgesOrNodes = followedLiveUsers.nodes || followedLiveUsers.edges;
for (let i = 0; i < edgesOrNodes.length; i++) {
const edge = edgesOrNodes[i];
const node = edge.node || edge;
const s = node.stream.viewersCount = new Number(node.stream.viewersCount || 0);
s.profileImageURL = node.profileImageURL;
s.createdAt = node.stream.createdAt;
if (node.stream.game && hiddenThumbnails.includes(node.stream.game.name)) node.stream.previewImageURL = 'https://static-cdn.jtvnw.net/ttv-static/404_preview-320x180.jpg';
if (!node.stream.game || node.stream.game && !blockedGames.includes(node.stream.game.name)) newLiveNodes.push(node);
if (!node.stream.game || node.stream.game && !blockedGames.includes(node.stream.game.name)) newStreams.push(edge);
}
res.data.currentUser.followedLiveUsers.nodes = newLiveNodes;
res.data.currentUser.followedLiveUsers[oldMode ? 'nodes' : 'edges'] = newStreams;
return res;
}
@ -176,12 +186,19 @@ export default class Following extends SiteModule {
const hiddenThumbnails = this.settings.provider.get('directory.game.hidden-thumbnails') || [];
const blockedGames = this.settings.provider.get('directory.game.blocked-games') || [];
const nodes = res.data.currentUser.followedHosts.nodes;
const followedHosts = get('data.currentUser.followedHosts', res);
if (!followedHosts)
return res;
this.hosts = {};
const newHostNodes = [];
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i];
const oldMode = !!followedHosts.nodes;
const edgesOrNodes = followedHosts.nodes || followedHosts.edges;
for (let i = 0; i < edgesOrNodes.length; i++) {
const edge = edgesOrNodes[i];
const node = edge.node || edge;
const s = node.hosting.stream.viewersCount = new Number(node.hosting.stream.viewersCount || 0);
s.profileImageURL = node.hosting.profileImageURL;
@ -194,7 +211,7 @@ export default class Following extends SiteModule {
channels: [node.displayName]
};
if (node.hosting.stream.game && hiddenThumbnails.includes(node.hosting.stream.game.name)) node.hosting.stream.previewImageURL = 'https://static-cdn.jtvnw.net/ttv-static/404_preview-320x180.jpg';
if (!node.hosting.stream.game || node.hosting.stream.game && !blockedGames.includes(node.hosting.stream.game.name)) newHostNodes.push(node);
if (!node.hosting.stream.game || node.hosting.stream.game && !blockedGames.includes(node.hosting.stream.game.name)) newHostNodes.push(edge);
} else {
this.hosts[node.hosting.displayName].nodes.push(node);
this.hosts[node.hosting.displayName].channels.push(node.displayName);
@ -202,7 +219,7 @@ export default class Following extends SiteModule {
}
if (this.settings.get('directory.following.group-hosts')) {
res.data.currentUser.followedHosts.nodes = newHostNodes;
res.data.currentUser.followedHosts[oldMode ? 'nodes' : 'edges'] = newHostNodes;
}
return res;
}
@ -220,12 +237,14 @@ export default class Following extends SiteModule {
n =>
get('data.currentUser.followedLiveUsers.nodes.0.profileImageURL', n) !== undefined
||
get('data.currentUser.followedLiveUsers.edges.0.node.profileImageURL', n) !== undefined
||
get('data.currentUser.followedHosts.nodes.0.hosting.profileImageURL', n) !== undefined
);
} else if (this.router.match[1] === 'live') {
this.apollo.ensureQuery(
'FollowingLive_CurrentUser',
'data.currentUser.followedLiveUsers.nodes.0.profileImageURL'
'data.currentUser.followedLiveUsers.nodes.0.profileImageURL' || 'data.currentUser.followedLiveUsers.edges.0.node.profileImageURL'
);
} else if (this.router.match[1] === 'hosts') {
this.apollo.ensureQuery(
@ -353,9 +372,10 @@ export default class Following extends SiteModule {
)
);
(document.body.querySelector('.twilight-root') || document.body).appendChild(this.hostMenu);
const root = (document.body.querySelector('.twilight-root') || document.body);
root.appendChild(this.hostMenu);
this.hostMenuPopper = new Popper(document.body, this.hostMenu, {
this.hostMenuPopper = new Popper(root, this.hostMenu, {
placement: 'bottom-start',
modifiers: {
flip: {

View file

@ -12,6 +12,7 @@ import {get} from 'utilities/object';
import Following from './following';
import Game from './game';
import Community from './community';
import BrowsePopular from './browse_popular';
export default class Directory extends SiteModule {
constructor(...args) {
@ -30,6 +31,7 @@ export default class Directory extends SiteModule {
this.inject(Following);
this.inject(Game);
this.inject(Community);
this.inject(BrowsePopular);
this.apollo.registerModifier('GamePage_Game', res => this.modifyStreams(res), false);
@ -134,6 +136,7 @@ export default class Directory extends SiteModule {
this.css_tweaks.toggleHide('boxart-hover', boxart === 1);
this.ChannelCard.ready((cls, instances) => {
// Game Directory Channel Cards
this.apollo.ensureQuery(
'GamePage_Game',
'data.directory.streams.edges.0.node.createdAt'
@ -149,37 +152,40 @@ export default class Directory extends SiteModule {
updateChannelCard(inst) {
const uptimeSel = inst.props.directoryType === 'GAMES' ? '.tw-thumbnail-card .tw-card-img' : '.tw-card .tw-aspect > div';
const avatarSel = inst.props.directoryType === 'GAMES' ? '.tw-thumbnail-card' : '.tw-card';
const container = this.fine.getHostNode(inst);
if (!container) return;
this.updateUptime(inst, 'props.streamNode.viewersCount.createdAt', uptimeSel);
this.addCardAvatar(inst, avatarSel);
this.updateUptime(inst, 'props.streamNode.viewersCount.createdAt', '.tw-card-img');
this.addCardAvatar(inst, 'props.streamNode.viewersCount', '.tw-card');
const type = inst.props.directoryType;
const type = get('props.directoryType', inst);
const hiddenThumbnails = this.settings.provider.get('directory.game.hidden-thumbnails') || [];
const hiddenPreview = 'https://static-cdn.jtvnw.net/ttv-static/404_preview-320x180.jpg';
const container = this.fine.getHostNode(inst);
if (inst.props.streamNode.type === 'watch_party')
if (get('props.streamNode.type', inst) === 'watch_party' || get('props.type', inst) === 'watch_party')
container.classList.toggle('tw-hide', this.settings.get('directory.hide-vodcasts'));
const img = container && container.querySelector && container.querySelector(`${uptimeSel} img`);
if (img === null) return;
const img = container.querySelector && container.querySelector('.tw-card-img img');
if (img == null) return;
if (type === 'GAMES' && hiddenThumbnails.includes(inst.props.directoryName) ||
type === 'COMMUNITIES' && hiddenThumbnails.includes(inst.props.streamNode.game.name)) {
if (type === 'GAMES' && hiddenThumbnails.includes(get('props.directoryName', inst)) ||
type === 'COMMUNITIES' && hiddenThumbnails.includes(get('props.streamNode.game.name', inst))) {
img.src = hiddenPreview;
} else {
img.src = inst.props.streamNode.previewImageURL;
img.src = get('props.streamNode.previewImageURL', inst) || get('props.imageSrc', inst);
}
}
modifyStreams(res) { // eslint-disable-line class-methods-use-this
const blockedGames = this.settings.provider.get('directory.game.blocked-games') || [];
const gamePage = get('data.directory.__typename', res) === 'Game';
const newStreams = [];
const edges = res.data.directory.streams.edges;
const edges = get('data.directory.streams.edges', res);
if (!edges) return res;
for (let i = 0; i < edges.length; i++) {
const edge = edges[i];
const node = edge.node;
@ -187,8 +193,10 @@ export default class Directory extends SiteModule {
const s = node.viewersCount = new Number(node.viewersCount || 0);
s.profileImageURL = node.broadcaster.profileImageURL;
s.createdAt = node.createdAt;
s.login = node.broadcaster.login;
s.displayName = node.broadcaster.displayName;
newStreams.push(edge);
if (gamePage || (!node.game || node.game && !blockedGames.includes(node.game.name))) newStreams.push(edge);
}
res.data.directory.streams.edges = newStreams;
return res;
@ -256,10 +264,14 @@ export default class Directory extends SiteModule {
}
addCardAvatar(inst, selector) {
addCardAvatar(inst, created_path, selector) {
const container = this.fine.getHostNode(inst),
card = container && container.querySelector && container.querySelector(selector),
setting = this.settings.get('directory.show-channel-avatars');
setting = this.settings.get('directory.show-channel-avatars'),
data = get(created_path, inst);
if ( ! card )
return;
// Remove old elements
const hiddenBodyCard = card.querySelector('.tw-card-body.tw-hide');
@ -274,10 +286,10 @@ export default class Directory extends SiteModule {
if (channelAvatar !== null)
channelAvatar.remove();
if ( ! card || setting === 0 )
if ( setting === 0 )
return;
if (inst.props.streamNode.viewersCount.profileImageURL) {
if (data) {
if (setting === 1) {
const cardDiv = card.querySelector('.tw-card-body');
const modifiedDiv = e('div', {
@ -286,11 +298,11 @@ export default class Directory extends SiteModule {
const avatarDiv = e('a', {
className: 'ffz-channel-avatar tw-mg-r-05 tw-mg-t-05',
href: `/${inst.props.streamNode.broadcaster.login}`,
onclick: event => this.hijackUserClick(event, inst.props.streamNode.broadcaster.login)
href: `/${data.login}`,
onclick: event => this.hijackUserClick(event, data.login)
}, e('img', {
title: inst.props.streamNode.broadcaster.displayName,
src: inst.props.streamNode.viewersCount.profileImageURL
title: data.displayName,
src: data.profileImageURL
}));
const cardDivParent = cardDiv.parentElement;
@ -306,13 +318,13 @@ export default class Directory extends SiteModule {
} else if (setting === 2 || setting === 3) {
const avatarElement = e('a', {
className: 'ffz-channel-avatar',
href: `/${inst.props.streamNode.broadcaster.login}`,
onclick: event => this.hijackUserClick(event, inst.props.streamNode.broadcaster.login)
href: `/${data.login}`,
onclick: event => this.hijackUserClick(event, data.login)
}, e('div', 'live-channel-card__boxart tw-bottom-0 tw-absolute',
e('figure', 'tw-aspect tw-aspect--align-top',
e('img', {
title: inst.props.streamNode.broadcaster.displayName,
src: inst.props.streamNode.viewersCount.profileImageURL
title: data.displayName,
src: data.profileImageURL
})
)
));