- // ==UserScript==
- // @name YouTube Sub Feed Filter 2
- // @version 1.13
- // @description Filters your YouTube subscriptions feed.
- // @author Callum Latham
- // @namespace https://greasyfork.org/users/696211-ctl2
- // @license MIT
- // @match *://www.youtube.com/*
- // @match *://youtube.com/*
- // @require https://update.greasyfork.org/scripts/446506/1284830/%24Config.js
- // @grant GM.setValue
- // @grant GM.getValue
- // @grant GM.deleteValue
- // ==/UserScript==
-
- // Don't run in frames (e.g. stream chat frame)
- if (window.parent !== window) {
- // noinspection JSAnnotator
- return;
- }
-
- // User config
-
- const LONG_PRESS_TIME = 400;
- const REGEXP_FLAGS = 'i';
-
- // Dev config
-
- const VIDEO_TYPE_IDS = {
- 'GROUPS': {
- 'ALL': 'All',
- 'STREAMS': 'Streams',
- 'PREMIERS': 'Premiers',
- 'NONE': 'None'
- },
- 'INDIVIDUALS': {
- 'STREAMS_SCHEDULED': 'Scheduled Streams',
- 'STREAMS_LIVE': 'Live Streams',
- 'STREAMS_FINISHED': 'Finished Streams',
- 'PREMIERS_SCHEDULED': 'Scheduled Premiers',
- 'PREMIERS_LIVE': 'Live Premiers',
- 'SHORTS': 'Shorts',
- 'NORMAL': 'Basic Videos'
- }
- };
-
- const CUTOFF_VALUES = [
- 'Minimum',
- 'Maximum'
- ];
-
- const BADGE_VALUES = [
- 'Exclude',
- 'Include',
- 'Require'
- ];
-
- const TITLE = 'YouTube Sub Feed Filter';
-
- function getVideoTypes(children) {
- const registry = new Set();
- const register = (value) => {
- if (registry.has(value)) {
- throw new Error(`Overlap found at '${value}'.`);
- }
-
- registry.add(value);
- };
-
- for (const {value} of children) {
- switch (value) {
- case VIDEO_TYPE_IDS.GROUPS.ALL:
- Object.values(VIDEO_TYPE_IDS.INDIVIDUALS).forEach(register);
- break;
-
- case VIDEO_TYPE_IDS.GROUPS.STREAMS:
- register(VIDEO_TYPE_IDS.INDIVIDUALS.STREAMS_SCHEDULED);
- register(VIDEO_TYPE_IDS.INDIVIDUALS.STREAMS_LIVE);
- register(VIDEO_TYPE_IDS.INDIVIDUALS.STREAMS_FINISHED);
- break;
-
- case VIDEO_TYPE_IDS.GROUPS.PREMIERS:
- register(VIDEO_TYPE_IDS.INDIVIDUALS.PREMIERS_SCHEDULED);
- register(VIDEO_TYPE_IDS.INDIVIDUALS.PREMIERS_LIVE);
- break;
-
- default:
- register(value);
- }
- }
-
- return registry;
- }
-
- const $config = new $Config(
- 'YTSFF_TREE',
- (() => {
- const regexPredicate = (value) => {
- try {
- RegExp(value);
- } catch (_) {
- return 'Value must be a valid regular expression.';
- }
-
- return true;
- };
-
- return {
- 'children': [
- {
- 'label': 'Filters',
- 'children': [],
- 'seed': {
- 'label': 'Filter Name',
- 'value': '',
- 'children': [
- {
- 'label': 'Channel Regex',
- 'children': [],
- 'seed': {
- 'value': '^',
- 'predicate': regexPredicate
- }
- },
- {
- 'label': 'Video Regex',
- 'children': [],
- 'seed': {
- 'value': '^',
- 'predicate': regexPredicate
- }
- },
- {
- 'label': 'Video Types',
- 'children': [{
- 'value': VIDEO_TYPE_IDS.GROUPS.ALL,
- 'predicate': Object.values(VIDEO_TYPE_IDS.GROUPS).concat(Object.values(VIDEO_TYPE_IDS.INDIVIDUALS))
- }],
- 'seed': {
- 'value': VIDEO_TYPE_IDS.GROUPS.NONE,
- 'predicate': Object.values(VIDEO_TYPE_IDS.GROUPS).concat(Object.values(VIDEO_TYPE_IDS.INDIVIDUALS))
- },
- 'childPredicate': (children) => {
- try {
- getVideoTypes(children);
- } catch ({message}) {
- return message;
- }
-
- return true;
- }
- }
- ]
- }
- },
- {
- 'label': 'Cutoffs',
- 'children': [
- {
- 'label': 'Watched (%)',
- 'children': [],
- 'seed': {
- 'childPredicate': ([{'value': boundary}, {value}]) => {
- if (boundary === CUTOFF_VALUES[0]) {
- return value < 100 ? true : 'Minimum must be less than 100%';
- }
-
- return value > 0 ? true : 'Maximum must be greater than 0%';
- },
- 'children': [
- {
- 'value': CUTOFF_VALUES[1],
- 'predicate': CUTOFF_VALUES
- },
- {
- 'value': 100
- }
- ]
- }
- },
- {
- 'label': 'View Count',
- 'children': [],
- 'seed': {
- 'childPredicate': ([{'value': boundary}, {value}]) => {
- if (boundary === CUTOFF_VALUES[1]) {
- return value > 0 ? true : 'Maximum must be greater than 0';
- }
-
- return true;
- },
- 'children': [
- {
- 'value': CUTOFF_VALUES[0],
- 'predicate': CUTOFF_VALUES
- },
- {
- 'value': 0,
- 'predicate': (value) => Math.floor(value) === value ? true : 'Value must be an integer'
- }
- ]
- }
- },
- {
- 'label': 'Duration (Minutes)',
- 'children': [],
- 'seed': {
- 'childPredicate': ([{'value': boundary}, {value}]) => {
- if (boundary === CUTOFF_VALUES[1]) {
- return value > 0 ? true : 'Maximum must be greater than 0';
- }
-
- return true;
- },
- 'children': [
- {
- 'value': CUTOFF_VALUES[0],
- 'predicate': CUTOFF_VALUES
- },
- {
- 'value': 0
- }
- ]
- }
- }
- ]
- },
- {
- 'label': 'Badges',
- 'children': [
- {
- 'label': 'Verified',
- 'value': BADGE_VALUES[1],
- 'predicate': BADGE_VALUES
- },
- {
- 'label': 'Official Artist',
- 'value': BADGE_VALUES[1],
- 'predicate': BADGE_VALUES
- }
- ]
- }
- ]
- };
- })(),
- ([filters, cutoffs, badges]) => ({
- 'filters': (() => {
- const getRegex = ({children}) => new RegExp(children.length === 0 ? '' :
- children.map(({value}) => `(${value})`).join('|'), REGEXP_FLAGS);
-
- return filters.children.map(({'children': [channel, video, type]}) => ({
- 'channels': getRegex(channel),
- 'videos': getRegex(video),
- 'types': type.children.length === 0 ? Object.values(VIDEO_TYPE_IDS.INDIVIDUALS) : getVideoTypes(type.children)
- }));
- })(),
- 'cutoffs': cutoffs.children.map(({children}) => {
- const boundaries = [Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY];
-
- for (const {'children': [{'value': boundary}, {value}]} of children) {
- boundaries[boundary === CUTOFF_VALUES[0] ? 0 : 1] = value;
- }
-
- return boundaries;
- }),
- 'badges': badges.children.map(({value}) => BADGE_VALUES.indexOf(value))
- }),
- TITLE,
- {
- 'headBase': '#ff0000',
- 'headButtonExit': '#000000',
- 'borderHead': '#ffffff',
- 'nodeBase': ['#222222', '#111111'],
- 'borderTooltip': '#570000'
- },
- {'zIndex': 10000}
- );
-
- const KEY_IS_ACTIVE = 'YTSFF_IS_ACTIVE';
-
- // Removing row styling
- (() => {
- const styleElement = document.createElement('style');
- document.head.appendChild(styleElement);
- const styleSheet = styleElement.sheet;
-
- const rules = [
- ['ytd-rich-grid-row #contents.ytd-rich-grid-row', [
- ['display', 'contents']
- ]],
- ['ytd-rich-grid-row', [
- ['display', 'contents']
- ]]
- ];
-
- for (let rule of rules) {
- styleSheet.insertRule(`${rule[0]}{${rule[1].map(([property, value]) => `${property}:${value} !important;`).join('')}}`);
- }
- })();
-
- // Video element helpers
-
- function getSubPage() {
- return document.querySelector('.ytd-page-manager[page-subtype="subscriptions"]');
- }
-
- function getAllRows() {
- const subPage = getSubPage();
-
- return subPage ? [...subPage.querySelectorAll('ytd-rich-grid-row')] : [];
- }
-
- function getAllSections() {
- const subPage = getSubPage();
-
- return subPage ? [...subPage.querySelectorAll('ytd-rich-section-renderer:not(:first-child)')] : [];
- }
-
- function getAllVideos(row) {
- return [...row.querySelectorAll('ytd-rich-item-renderer')];
- }
-
- function firstWordEquals(element, word) {
- return element.innerText.split(' ')[0] === word;
- }
-
- function getVideoBadges(video) {
- return video.querySelectorAll('.video-badge');
- }
-
- function getChannelBadges(video) {
- const container = video.querySelector('ytd-badge-supported-renderer.ytd-channel-name');
-
- return container ? [...container.querySelectorAll('.badge')] : [];
- }
-
- function getMetadataLine(video) {
- return video.querySelector('#metadata-line');
- }
-
- function isScheduled(video) {
- return VIDEO_PREDICATES[VIDEO_TYPE_IDS.INDIVIDUALS.STREAMS_SCHEDULED](video) ||
- VIDEO_PREDICATES[VIDEO_TYPE_IDS.INDIVIDUALS.PREMIERS_SCHEDULED](video);
- }
-
- function getUploadTimeNode(video) {
- const children = [...getMetadataLine(video).children].filter((child) => child.matches('.inline-metadata-item'));
-
- return children.length > 1 ? children[1] : null;
- }
-
- // Config testers
-
- const VIDEO_PREDICATES = {
- [VIDEO_TYPE_IDS.INDIVIDUALS.STREAMS_SCHEDULED]: (video) => {
- const metadataLine = getMetadataLine(video);
-
- return firstWordEquals(metadataLine, 'Scheduled');
- },
- [VIDEO_TYPE_IDS.INDIVIDUALS.STREAMS_LIVE]: (video) => {
- for (const badge of getVideoBadges(video)) {
- if (firstWordEquals(badge, 'LIVE')) {
- return true;
- }
- }
-
- return false;
- },
- [VIDEO_TYPE_IDS.INDIVIDUALS.STREAMS_FINISHED]: (video) => {
- const uploadTimeNode = getUploadTimeNode(video);
-
- return uploadTimeNode && firstWordEquals(uploadTimeNode, 'Streamed');
- },
- [VIDEO_TYPE_IDS.INDIVIDUALS.PREMIERS_SCHEDULED]: (video) => {
- const metadataLine = getMetadataLine(video);
-
- return firstWordEquals(metadataLine, 'Premieres');
- },
- [VIDEO_TYPE_IDS.INDIVIDUALS.PREMIERS_LIVE]: (video) => {
- for (const badge of getVideoBadges(video)) {
- if (firstWordEquals(badge, 'PREMIERING') || firstWordEquals(badge, 'PREMIERE')) {
- return true;
- }
- }
-
- return false;
- },
- [VIDEO_TYPE_IDS.INDIVIDUALS.SHORTS]: (video) => {
- return video.querySelector('ytd-rich-grid-slim-media')?.isShort ?? false;
- },
- [VIDEO_TYPE_IDS.INDIVIDUALS.NORMAL]: (video) => {
- const uploadTimeNode = getUploadTimeNode(video);
-
- return uploadTimeNode ? new RegExp('^\\d+ .+ ago$').test(uploadTimeNode.innerText) : false;
- }
- };
-
- const CUTOFF_GETTERS = [
- // Watched %
- (video) => {
- const progressBar = video.querySelector('#progress');
-
- if (!progressBar) {
- return 0;
- }
-
- return Number.parseInt(progressBar.style.width.slice(0, -1));
- },
- // View count
- (video) => {
- if (isScheduled(video)) {
- return 0;
- }
-
- const {innerText} = [...getMetadataLine(video).children].find((child) => child.matches('.inline-metadata-item'));
- const [valueString] = innerText.split(' ');
- const lastChar = valueString.slice(-1);
-
- if (/\d/.test(lastChar)) {
- return Number.parseInt(valueString);
- }
-
- const valueNumber = Number.parseFloat(valueString.slice(0, -1));
-
- switch (lastChar) {
- case 'B':
- return valueNumber * 1000000000;
- case 'M':
- return valueNumber * 1000000;
- case 'K':
- return valueNumber * 1000;
- }
-
- return valueNumber;
- },
- // Duration (minutes)
- (video) => {
- const timeElement = video.querySelector('ytd-thumbnail-overlay-time-status-renderer');
-
- let minutes = 0;
-
- if (timeElement) {
- const timeParts = timeElement.innerText.split(':').map((_) => Number.parseInt(_));
-
- let timeValue = 1 / 60;
-
- for (let i = timeParts.length - 1; i >= 0; --i) {
- minutes += timeParts[i] * timeValue;
-
- timeValue *= 60;
- }
- }
-
- return Number.isNaN(minutes) ? 0 : minutes;
- }
- ];
-
- const BADGE_PREDICATES = [
- // Verified
- (video) => getChannelBadges(video)
- .some((badge) => badge.classList.contains('badge-style-type-verified')),
- // Official Artist
- (video) => getChannelBadges(video)
- .some((badge) => badge.classList.contains('badge-style-type-verified-artist'))
- ];
-
- // Hider functions
-
- function loadVideo(video) {
- return new Promise((resolve) => {
- const test = () => {
- if (video.querySelector('#interaction.yt-icon-button')) {
- observer.disconnect();
-
- resolve();
- }
- };
-
- const observer = new MutationObserver(test);
-
- observer.observe(video, {
- 'childList': true,
- 'subtree': true,
- 'attributes': true,
- 'attributeOldValue': true
- });
-
- test();
- });
- }
-
- function shouldHide({filters, cutoffs, badges}, video) {
- for (let i = 0; i < BADGE_PREDICATES.length; ++i) {
- if (badges[i] !== 1 && Boolean(badges[i]) !== BADGE_PREDICATES[i](video)) {
- return true;
- }
- }
-
- for (let i = 0; i < CUTOFF_GETTERS.length; ++i) {
- const [lowerBound, upperBound] = cutoffs[i];
- const value = CUTOFF_GETTERS[i](video);
-
- if (value < lowerBound || value > upperBound) {
- return true;
- }
- }
-
- const channelName = video.querySelector('ytd-channel-name#channel-name')?.innerText;
- const videoName = video.querySelector('#video-title').innerText;
-
- for (const {'channels': channelRegex, 'videos': videoRegex, types} of filters) {
- if (
- (!channelName || channelRegex.test(channelName)) &&
- videoRegex.test(videoName)
- ) {
- for (const type of types) {
- if (VIDEO_PREDICATES[type](video)) {
- return true;
- }
- }
- }
- }
-
- return false;
- }
-
- const hideList = (() => {
- const list = [];
-
- let hasReverted = false;
-
- function hide(element, doHide) {
- element.hidden = false;
-
- if (doHide) {
- element.style.display = 'none';
- } else {
- element.style.removeProperty('display');
- }
- }
-
- return {
- 'add'(doAct, element, doHide = true) {
- if (doAct) {
- hasReverted = false;
- }
-
- list.push({element, doHide, 'wasHidden': element.hidden});
-
- if (doAct) {
- hide(element, doHide);
- }
- },
- 'revert'(doErase) {
- if (!hasReverted) {
- hasReverted = true;
-
- for (const {element, doHide, wasHidden} of list) {
- hide(element, !doHide);
-
- element.hidden = wasHidden;
- }
- }
-
- if (doErase) {
- list.length = 0;
- }
- },
- 'ensure'() {
- if (!hasReverted) {
- return;
- }
-
- hasReverted = false;
-
- for (const {element, doHide} of list) {
- hide(element, doHide);
- }
- }
- };
- })();
-
- async function hideFromRows(config, doAct, groups = getAllRows()) {
- for (const group of groups) {
- const videos = getAllVideos(group);
-
- // Process all videos in the row in parallel
- await Promise.all(videos.map((video) => new Promise(async (resolve) => {
- await loadVideo(video);
-
- if (shouldHide(config, video)) {
- hideList.add(doAct, video);
- }
-
- resolve();
- })));
-
- // Allow the page to update visually before moving on to the next row
- await new Promise((resolve) => {
- window.setTimeout(resolve, 0);
- });
- }
- }
-
- const hideFromSections = (() => {
- return async (config, doAct, groups = getAllSections()) => {
- for (const group of groups) {
- const shownVideos = [];
- const backupVideos = [];
-
- for (const video of getAllVideos(group)) {
- await loadVideo(video);
-
- if (video.hidden) {
- if (!shouldHide(config, video)) {
- backupVideos.push(video);
- }
- } else {
- shownVideos.push(video);
- }
- }
-
- let lossCount = 0;
-
- // Process all videos in the row in parallel
- await Promise.all(shownVideos.map((video) => new Promise(async (resolve) => {
- await loadVideo(video);
-
- if (shouldHide(config, video)) {
- hideList.add(doAct, video);
-
- if (backupVideos.length > 0) {
- hideList.add(doAct, backupVideos.shift(), false);
- } else {
- lossCount++;
- }
- }
-
- resolve();
- })));
-
- if (lossCount >= shownVideos.length) {
- hideList.add(doAct, group);
- }
-
- // Allow the page to update visually before moving on to the next row
- await new Promise((resolve) => {
- window.setTimeout(resolve, 0);
- });
- }
- };
- })();
-
- function hideAll(doAct = true, rows, sections, config = $config.get()) {
- return Promise.all([
- hideFromRows(config, doAct, rows),
- hideFromSections(config, doAct, sections)
- ]);
- }
-
- // Helpers
-
- async function hideFromMutations(isActive, mutations) {
- const rows = [];
- const sections = [];
-
- if (isActive()) {
- hideList.ensure();
- }
-
- for (const {addedNodes} of mutations) {
- for (const node of addedNodes) {
- switch (node.tagName) {
- case 'YTD-RICH-GRID-ROW':
- rows.push(node);
- break;
-
- case 'YTD-RICH-SECTION-RENDERER':
- sections.push(node);
- }
- }
- }
-
- hideAll(isActive(), rows, sections);
- }
-
- function resetConfig(fullReset = true) {
- hideList.revert(fullReset);
- }
-
- function getButtonDock() {
- return document
- .querySelector('ytd-browse[page-subtype="subscriptions"]')
- .querySelector('#contents')
- .querySelector('#title-container')
- .querySelector('#top-level-buttons-computed');
- }
-
- // Button
-
- class ClickHandler {
- constructor(button, onShortClick, onLongClick) {
- this.onShortClick = (function() {
- onShortClick();
-
- window.clearTimeout(this.longClickTimeout);
-
- window.removeEventListener('mouseup', this.onShortClick);
- }).bind(this);
-
- this.onLongClick = (function() {
- window.removeEventListener('mouseup', this.onShortClick);
-
- onLongClick();
- }).bind(this);
-
- this.longClickTimeout = window.setTimeout(this.onLongClick, LONG_PRESS_TIME);
-
- window.addEventListener('mouseup', this.onShortClick);
- }
- }
-
- class Button {
- wasActive;
- isActive = false;
-
- constructor() {
- this.element = this.getNewButton();
-
- this.element.addEventListener('mousedown', this.onMouseDown.bind(this));
-
- GM.getValue(KEY_IS_ACTIVE, true).then((isActive) => {
- this.isActive = isActive;
-
- const videoObserver = new MutationObserver(hideFromMutations.bind(null, () => this.isActive));
-
- $config.init()
- .catch(({message}) => {
- window.alert(message);
- })
- .then(() => {
- videoObserver.observe(
- document.querySelector('ytd-browse[page-subtype="subscriptions"]').querySelector('div#contents'),
- {childList: true}
- );
-
- hideAll(isActive);
- });
-
- this.update();
- });
-
- let resizeCount = 0;
-
- window.addEventListener('resize', () => {
- const resizeId = ++resizeCount;
-
- this.forceInactive();
-
- resetConfig();
-
- const listener = ({detail}) => {
- // column size changed
- if (detail.actionName === 'yt-window-resized') {
- window.setTimeout(() => {
- if (resizeId !== resizeCount) {
- return;
- }
-
- this.forceInactive(false);
-
- resetConfig();
-
- hideAll(this.isActive);
- }, 1000);
-
- document.body.removeEventListener('yt-action', listener);
- }
- };
-
- document.body.addEventListener('yt-action', listener);
- });
-
- document.body.addEventListener('yt-action', (x) => {
- const {detail} = x;
- if (detail.actionName === 'yt-store-grafted-ve-action') {
- hideList.revert(false);
- }
- });
- }
-
- forceInactive(doForce = true) {
- if (doForce) {
- // if wasActive isn't undefined, forceInactive was already called
- if (this.wasActive === undefined) {
- // Saves an async call later
- this.wasActive = this.isActive;
- this.isActive = false;
- }
- } else {
- this.isActive = this.wasActive;
- this.wasActive = undefined;
- }
- }
-
- update() {
- if (this.isActive) {
- this.setButtonActive();
- }
- }
-
- addToDOM(button = this.element) {
- const {parentElement} = getButtonDock();
-
- parentElement.appendChild(button);
- }
-
- getNewButton() {
- const openerTemplate = getButtonDock().children[1];
- const button = openerTemplate.cloneNode(false);
-
- if (openerTemplate.innerText) {
- throw new Error('too early');
- }
-
- this.addToDOM(button);
-
- button.innerHTML = openerTemplate.innerHTML;
-
- button.querySelector('yt-button-shape').innerHTML = openerTemplate.querySelector('yt-button-shape').innerHTML;
-
- button.querySelector('a').removeAttribute('href');
-
- // TODO Build the svg via javascript
- button.querySelector('yt-icon').innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" focusable="false" viewBox="-50 -50 400 400"><g><path d="M128.25,175.6c1.7,1.8,2.7,4.1,2.7,6.6v139.7l60-51.3v-88.4c0-2.5,1-4.8,2.7-6.6L295.15,65H26.75L128.25,175.6z"/><rect x="13.95" y="0" width="294" height="45"/></g></svg>';
-
- button.querySelector('tp-yt-paper-tooltip').remove();
-
- return button;
- }
-
- hide() {
- this.element.style.display = 'none';
- }
-
- show() {
- this.element.parentElement.appendChild(this.element);
- this.element.style.removeProperty('display');
- }
-
- setButtonActive() {
- if (this.isActive) {
- this.element.querySelector('svg').style.setProperty('fill', 'var(--yt-spec-call-to-action)');
- } else {
- this.element.querySelector('svg').style.setProperty('fill', 'currentcolor');
- }
- }
-
- toggleActive() {
- this.isActive = !this.isActive;
-
- this.setButtonActive();
-
- GM.setValue(KEY_IS_ACTIVE, this.isActive);
-
- if (this.isActive) {
- hideList.ensure();
- } else {
- hideList.revert(false);
- }
- }
-
- onLongClick() {
- $config.edit()
- .then(() => {
- resetConfig();
-
- hideAll(this.isActive);
- })
- .catch((error) => {
- console.error(error);
-
- if (window.confirm(
- `[${TITLE}]` +
- '\n\nYour config\'s structure is invalid.' +
- '\nThis could be due to a script update or your data being corrupted.' +
- '\n\nError Message:' +
- `\n${error}` +
- '\n\nWould you like to erase your data?'
- )) {
- $config.reset();
- }
- });
- }
-
- async onMouseDown(event) {
- if (event.button === 0) {
- new ClickHandler(this.element, this.toggleActive.bind(this), this.onLongClick.bind(this));
- }
- }
- }
-
- // Main
-
- (() => {
- let button;
-
- const loadButton = () => {
- if (!button) {
- try {
- getButtonDock();
-
- button = new Button();
- } catch (e) {
- const emitter = document.getElementById('page-manager');
- const bound = () => {
- loadButton();
-
- emitter.removeEventListener('yt-action', bound);
- };
-
- emitter.addEventListener('yt-action', bound);
-
- return;
- }
- } else if (button.isActive) {
- hideList.ensure();
- }
-
- button.show();
- };
-
- const isGridView = () => {
- return Boolean(
- document.querySelector('ytd-browse[page-subtype="subscriptions"]:not([hidden])') &&
- document.querySelector('ytd-browse > ytd-two-column-browse-results-renderer ytd-rich-grid-row ytd-rich-item-renderer ytd-rich-grid-media')
- );
- };
-
- const onNavigate = ({browseEndpoint}) => {
- if (browseEndpoint) {
- const {params, browseId} = browseEndpoint;
-
- if ((params === 'MAE%3D' || (!params && (!button || isGridView()))) && browseId === 'FEsubscriptions') {
- const emitter = document.querySelector('ytd-app');
- const event = 'yt-action';
-
- if (button || isGridView()) {
- const listener = ({detail}) => {
- if (detail.actionName === 'ytd-update-elements-per-row-action') {
- loadButton();
-
- emitter.removeEventListener(event, listener);
- }
- };
-
- emitter.addEventListener(event, listener);
- } else {
- const listener = ({detail}) => {
- if (detail.actionName === 'ytd-update-grid-state-action') {
- if (isGridView()) {
- loadButton();
- }
-
- emitter.removeEventListener(event, listener);
- }
- };
-
- emitter.addEventListener(event, listener);
- }
-
- return;
- }
- }
-
- if (button) {
- button.hide();
-
- if (button.isActive) {
- hideList.revert(false);
- }
- }
- };
-
- document.body.addEventListener('yt-navigate-finish', ({detail}) => {
- onNavigate(detail.endpoint);
- });
- })();