diff --git a/src/main.js b/src/main.js index 4f530949..8b8c5399 100644 --- a/src/main.js +++ b/src/main.js @@ -100,7 +100,7 @@ class FrankerFaceZ extends Module { FrankerFaceZ.Logger = Logger; const VER = FrankerFaceZ.version_info = { - major: 4, minor: 0, revision: 0, extra: '-rc13.3', + major: 4, minor: 0, revision: 0, extra: '-rc13.4', commit: __git_commit__, build: __webpack_hash__, toString: () => diff --git a/src/sites/twitch-twilight/modules/directory/browse_popular.gql b/src/sites/twitch-twilight/modules/directory/browse_popular.gql index 860cb07d..6ea15342 100644 --- a/src/sites/twitch-twilight/modules/directory/browse_popular.gql +++ b/src/sites/twitch-twilight/modules/directory/browse_popular.gql @@ -1,9 +1,5 @@ -query { - streams { - edges { - node { - createdAt - } - } +fragment browsePagePopularStreamsWithTagsEdge2 on StreamEdge { + node { + createdAt } } \ No newline at end of file diff --git a/src/sites/twitch-twilight/modules/directory/followed_channels.gql b/src/sites/twitch-twilight/modules/directory/followed_channels.gql index a4e359f4..730d5c2a 100644 --- a/src/sites/twitch-twilight/modules/directory/followed_channels.gql +++ b/src/sites/twitch-twilight/modules/directory/followed_channels.gql @@ -1,9 +1,18 @@ -query { +query FollowedChannels_RENAME2 { currentUser { + follows { + edges { + node { + stream { + createdAt + type + } + } + } + } followedLiveUsers { nodes { stream { - type createdAt } } diff --git a/src/sites/twitch-twilight/modules/directory/followed_hosts.gql b/src/sites/twitch-twilight/modules/directory/followed_hosts.gql index e6c12ffd..c5ddb257 100644 --- a/src/sites/twitch-twilight/modules/directory/followed_hosts.gql +++ b/src/sites/twitch-twilight/modules/directory/followed_hosts.gql @@ -1,4 +1,4 @@ -query { +query FollowingHosts_CurrentUser { currentUser { followedHosts { nodes { diff --git a/src/sites/twitch-twilight/modules/directory/followed_index.gql b/src/sites/twitch-twilight/modules/directory/followed_index.gql index 7d65d87f..c926bbad 100644 --- a/src/sites/twitch-twilight/modules/directory/followed_index.gql +++ b/src/sites/twitch-twilight/modules/directory/followed_index.gql @@ -1,4 +1,4 @@ -query { +query FollowedIndex_CurrentUser { currentUser { followedLiveUsers { nodes { diff --git a/src/sites/twitch-twilight/modules/directory/followed_live.gql b/src/sites/twitch-twilight/modules/directory/followed_live.gql index 49c66bfa..515bd990 100644 --- a/src/sites/twitch-twilight/modules/directory/followed_live.gql +++ b/src/sites/twitch-twilight/modules/directory/followed_live.gql @@ -1,4 +1,4 @@ -query { +query FollowingLive_CurrentUser { currentUser { followedLiveUsers { edges { diff --git a/src/sites/twitch-twilight/modules/directory/game.gql b/src/sites/twitch-twilight/modules/directory/game.gql index b007e73e..2d638123 100644 --- a/src/sites/twitch-twilight/modules/directory/game.gql +++ b/src/sites/twitch-twilight/modules/directory/game.gql @@ -1,24 +1,9 @@ query { - directory { - ... on Community { - streams { - edges { - node { - createdAt - type - } - } - } - } - ... on Game { - streams { - edges { - node { - createdAt - type - } - } - } - } + __type +} + +fragment directoryPageGameStreamWithTagsEdge on StreamEdge { + node { + createdAt } } \ No newline at end of file diff --git a/src/sites/twitch-twilight/modules/directory/game.jsx b/src/sites/twitch-twilight/modules/directory/game.jsx index 50fa8d7d..4d062232 100644 --- a/src/sites/twitch-twilight/modules/directory/game.jsx +++ b/src/sites/twitch-twilight/modules/directory/game.jsx @@ -21,71 +21,6 @@ export default class Game extends SiteModule { this.inject('i18n'); this.inject('settings'); - /*this.metadata.definitions.block_game = { - type: 'directory', - button(data) { - return `ffz-directory-toggle-block${data.blocked ? ' active' : ''}` - }, - - setup(data) { - if ( data.type !== 'GAMES' ) - return null; - - const blocked_games = this.settings.provider.get('directory.game.blocked-games', []), - blocked = blocked_games.includes(data.name); - - data.blocked = blocked; - return data; - }, - - label(data) { - if ( ! data ) - return null; - - return data.blocked ? - this.i18n.t('directory.unblock', 'Unblock') : - this.i18n.t('directory.block', 'Block') - }, - - tooltip() { - return this.i18n.t('directory.block-explain', 'This will let you block streams playing this game from showing up in the directory.'); - }, - - click: this.generateClickHandler('directory.game.blocked-games') - } - - this.metadata.definitions.hide_thumbnails = { - type: 'directory', - button(data) { - return `ffz-directory-toggle-thumbnail${data.hidden ? ' active' : ''}` - }, - - setup(data) { - if ( data.type !== 'GAMES' ) - return null; - - const hidden_games = this.settings.provider.get('directory.game.hidden-thumbnails', []); - - data.hidden = hidden_games.includes(data.name); - return data; - }, - - label(data) { - if ( ! data ) - return null; - - return data.hidden ? - this.i18n.t('directory.show-thumbnails', 'Show Thumbnails') : - this.i18n.t('directory.hide-thumbnails', 'Hide Thumbnails'); - }, - - tooltip() { - return this.i18n.t('directory.thumbnails-explain', 'Enabling this will hide thumbnails of this game everywhere in the directory.'); - }, - - click: this.generateClickHandler('directory.game.hidden-thumbnails') - }*/ - this.GameHeader = this.fine.define( 'game-header', n => n.props && n.props.data && n.renderDropsAvailable, @@ -93,6 +28,16 @@ export default class Game extends SiteModule { ); this.apollo.registerModifier('DirectoryPage_Game', GAME_QUERY); + this.apollo.registerModifier('DirectoryPage_Game', res => this.modifyStreams(res), false); + } + + modifyStreams(res) { // eslint-disable-line class-methods-use-this + const edges = get('data.game.streams.edges', res); + if ( ! edges || ! edges.length ) + return res; + + res.data.game.streams.edges = this.parent.processNodes(edges); + return res; } onEnable() { diff --git a/src/sites/twitch-twilight/modules/directory/index.jsx b/src/sites/twitch-twilight/modules/directory/index.jsx index 6796c095..37f9ea89 100644 --- a/src/sites/twitch-twilight/modules/directory/index.jsx +++ b/src/sites/twitch-twilight/modules/directory/index.jsx @@ -46,8 +46,6 @@ export default class Directory extends SiteModule { this.inject(Game); this.inject(BrowsePopular); - this.apollo.registerModifier('DirectoryPage_Game', res => this.modifyStreams(res), false); - this.DirectoryCard = this.fine.define( 'directory-card', n => n.renderTitles && n.renderIconicImage, @@ -217,7 +215,7 @@ export default class Directory extends SiteModule { // TODO: Better query handling. this.apollo.ensureQuery( 'DirectoryPage_Game', - 'data.directory.streams.edges.0.node.createdAt' + 'data.game.streams.edges.0.node.createdAt' ); //for(const inst of instances) @@ -294,18 +292,6 @@ export default class Directory extends SiteModule { } - modifyStreams(res) { // eslint-disable-line class-methods-use-this - const is_game_query = get('data.directory.__typename', res) === 'Game', - edges = get('data.directory.streams.edges', res); - - if ( ! edges || ! edges.length ) - return res; - - res.data.directory.streams.edges = this.processNodes(edges, is_game_query); - return res; - } - - clearUptime(inst) { // eslint-disable-line class-methods-use-this if ( inst.ffz_update_timer ) { clearInterval(inst.ffz_update_timer); diff --git a/src/sites/twitch-twilight/modules/directory/recommended_channels.gql b/src/sites/twitch-twilight/modules/directory/recommended_channels.gql index 09300ea9..a4242e72 100644 --- a/src/sites/twitch-twilight/modules/directory/recommended_channels.gql +++ b/src/sites/twitch-twilight/modules/directory/recommended_channels.gql @@ -1,4 +1,4 @@ -query { +query RecommendedChannels { currentUser { recommendations { liveRecommendations { diff --git a/src/utilities/compat/apollo.js b/src/utilities/compat/apollo.js index e6ba4f16..3ce1a3d9 100644 --- a/src/utilities/compat/apollo.js +++ b/src/utilities/compat/apollo.js @@ -6,10 +6,11 @@ // ============================================================================ import Module from 'utilities/module'; -import {has, get} from 'utilities/object'; +import {get, deep_copy} from 'utilities/object'; +import merge from 'utilities/graphql'; -const BAD_ERRORS = [ +/*const BAD_ERRORS = [ 'timeout', 'unable to load', 'error internal', @@ -31,7 +32,7 @@ function skip_error(err) { for(const m of BAD_ERRORS) if ( err.message.includes(m) ) return true; -} +}*/ export class GQLError extends Error { @@ -212,6 +213,9 @@ export default class Apollo extends Module { query = query_map && query_map.get(id), modifiers = this.modifiers[operation]; + + const pre_modification = deep_copy(request.query); + if ( modifiers ) { for(const mod of modifiers) { if ( typeof mod === 'function' ) @@ -245,6 +249,8 @@ export default class Apollo extends Module { this.log.info('Unable to find GQL Print. Clearing store for query:', operation); this.client.queryManager.queryStore.store[id] = null; } + + this.log.info('Query', operation, pre_modification, request.query, request.variables); } apolloPostFlight(response) { @@ -406,71 +412,3 @@ export default class Apollo extends Module { } } - - -// ============================================================================ -// Query Merging -// ============================================================================ - -function canMerge(a, b) { - return a.kind === b.kind && - a.kind !== 'FragmentDefinition' && - (a.selectionSet == null) === (b.selectionSet == null); -} - - -function merge(a, b) { - if ( ! canMerge(a, b) ) - return a; - - if ( a.definitions ) { - const a_def = a.definitions, - b_def = b.definitions; - - for(let i=0; i < a_def.length && i < b_def.length; i++) - a_def[i] = merge(a_def[i], b_def[i]); - } - - if ( a.selectionSet ) { - const s = a.selectionSet.selections, - selects = {}; - for(const sel of b.selectionSet.selections) { - const name = sel.kind === 'InlineFragment' ? - (sel.typeCondition.name ? - sel.typeCondition.name.value : null) : - (sel.name ? sel.name.value : null), - alias = sel.alias ? sel.alias.value : null, - key = `${name}:${alias}`; - - if ( name ) - selects[key] = sel; - } - - for(let i=0, l = s.length; i < l; i++) { - const sel = s[i], - name = sel.kind === 'InlineFragment' ? - (sel.typeCondition.name ? - sel.typeCondition.name.value : null) : - (sel.name ? sel.name.value : null), - alias = sel.alias ? sel.alias.value : null, - key = `${name}:${alias}`, - other = selects[key]; - - if ( other ) { - s[i] = merge(sel, other); - selects[key] = null; - } - } - - for(const key in selects) - if ( has(selects, key) ) { - const val = selects[key]; - if ( val ) - s.push(val); - } - } - - // TODO: Variables? - - return a; -} \ No newline at end of file diff --git a/src/utilities/graphql.js b/src/utilities/graphql.js new file mode 100644 index 00000000..08391441 --- /dev/null +++ b/src/utilities/graphql.js @@ -0,0 +1,113 @@ +import { deep_copy } from "./object"; + +'use strict'; + +// ============================================================================ +// GraphQL Document Manipulation +// ============================================================================ + +export const MERGE_METHODS = { + Document: (a, b) => { + if ( a.definitions && b.definitions ) + a.definitions = mergeList(a.definitions, b.definitions); + else if ( b.definitions ) + a.definitions = b.definitions; + + return a; + }, + + Field: (a, b) => { + if ( a.name && (! b.name || b.name.value !== a.name.value) ) + return a; + + // TODO: arguments + // TODO: directives + + if ( a.selectionSet && b.selectionSet ) + a.selectionSet = merge(a.selectionSet, b.selectionSet); + else if ( b.selectionSet ) + a.selectionSet = b.selectionSet; + + return a; + }, + + OperationDefinition: (a, b) => { + if ( a.operation !== b.operation ) + return a; + + // TODO: variableDefinitions + // TODO: directives + + if ( a.selectionSet && b.selectionSet ) + a.selectionSet = merge(a.selectionSet, b.selectionSet); + else if ( b.selectionSet ) + a.selectionSet = b.selectionSet; + + return a; + }, + + FragmentDefinition: (a, b) => { + if ( a.typeCondition && b.typeCondition ) { + if ( a.typeCondition.kind !== b.typeCondition.kind ) + return a; + + if ( a.typeCondition.name.value != b.typeCondition.name.value ) + return a; + } + + // TODO: directives + + if ( a.selectionSet && b.selectionSet ) + a.selectionSet = merge(a.selectionSet, b.selectionSet); + else if ( b.selectionSet ) + a.selectionSet = b.selectionSet; + + return a; + }, + + SelectionSet: (a, b) => { + if ( a.selections && b.selections ) + a.selections = mergeList(a.selections, b.selections); + else if ( b.selections ) + a.selections = b.selections; + + return a; + } +} + + +export function mergeList(a, b) { + const a_names = {}; + for(const item of a) { + if ( ! item || ! item.name || item.name.kind !== 'Name' ) + continue; + + a_names[item.name.value] = item; + } + + for(const item of b) { + if ( ! item || ! item.name || item.name.kind !== 'Name' ) + continue; + + const name = item.name.value, + idx = a_names[name] ? a.indexOf(a_names[name]) : -1; + + if ( idx !== -1 ) + a[idx] = merge(a[idx], item); + else + a.push(item); + } + + return a; +} + + +export default function merge(a, b) { + if ( a.kind !== b.kind ) + return a; + + if ( MERGE_METHODS[a.kind] ) + return MERGE_METHODS[a.kind](a, b); + + return a; +} \ No newline at end of file