- // ==UserScript==
- // @name YouTube Sub Feed Filter 2
- // @version 1.21
- // @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/1402875/%24Config.js
- // @grant GM.setValue
- // @grant GM.getValue
- // @grant GM.deleteValue
- // ==/UserScript==
-
- /* global $Config */
-
- // 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',
- PREMIERES: 'Premieres',
- NONE: 'None',
- },
- INDIVIDUALS: {
- STREAMS_SCHEDULED: 'Scheduled Streams',
- STREAMS_LIVE: 'Live Streams',
- STREAMS_FINISHED: 'Finished Streams',
- PREMIERES_SCHEDULED: 'Scheduled Premieres',
- PREMIERES_LIVE: 'Live Premieres',
- SHORTS: 'Shorts',
- FUNDRAISERS: 'Fundraisers',
- 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.PREMIERES:
- register(VIDEO_TYPE_IDS.INDIVIDUALS.PREMIERES_SCHEDULED);
- register(VIDEO_TYPE_IDS.INDIVIDUALS.PREMIERES_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;
- };
-
- const videoTypePredicate = Object.values({
- ...VIDEO_TYPE_IDS.GROUPS,
- ...VIDEO_TYPE_IDS.INDIVIDUALS,
- });
-
- 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: videoTypePredicate,
- },
- ],
- seed: {
- value: VIDEO_TYPE_IDS.GROUPS.NONE,
- predicate: videoTypePredicate,
- },
- 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,
- scrollbarColor: 'initial',
- },
- );
-
- 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.PREMIERES_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.PREMIERES_SCHEDULED]: (video) => {
- const metadataLine = getMetadataLine(video);
-
- return firstWordEquals(metadataLine, 'Premieres');
- },
- [VIDEO_TYPE_IDS.INDIVIDUALS.PREMIERES_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;
- },
- [VIDEO_TYPE_IDS.INDIVIDUALS.FUNDRAISERS]: (video) => {
- for (const badge of getVideoBadges(video)) {
- if (firstWordEquals(badge, 'Fundraiser')) {
- return true;
- }
- }
-
- return 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 = true;
-
- 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
-
- function hideFromMutations(isActive, mutations) {
- const rows = [];
- const sections = [];
-
- 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;
- isDormant = false;
-
- constructor() {
- this.element = (() => {
- const getSVG = () => {
- const svgNamespace = 'http://www.w3.org/2000/svg';
-
- const bottom = document.createElementNS(svgNamespace, 'path');
-
- bottom.setAttribute('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');
-
- const top = document.createElementNS(svgNamespace, 'rect');
-
- top.setAttribute('x', '13.95');
- top.setAttribute('width', '294');
- top.setAttribute('height', '45');
-
- const g = document.createElementNS(svgNamespace, 'g');
-
- g.appendChild(bottom);
- g.appendChild(top);
-
- const svg = document.createElementNS(svgNamespace, 'svg');
-
- svg.setAttribute('viewBox', '-50 -50 400 400');
- svg.setAttribute('focusable', 'false');
- svg.appendChild(g);
-
- return svg;
- };
-
- const getNewButton = () => {
- const {parentElement, 'children': [, openerTemplate]} = getButtonDock();
- const button = openerTemplate.cloneNode(false);
-
- if (openerTemplate.innerText) {
- throw new Error('too early');
- }
-
- parentElement.appendChild(button);
-
- button.innerHTML = openerTemplate.innerHTML;
-
- button.querySelector('yt-button-shape').innerHTML = openerTemplate.querySelector('yt-button-shape').innerHTML;
-
- button.querySelector('a').removeAttribute('href');
-
- button.querySelector('yt-icon').appendChild(getSVG());
-
- button.querySelector('tp-yt-paper-tooltip').remove();
-
- return button;
- };
-
- return getNewButton();
- })();
-
- this.element.addEventListener('mousedown', this.onMouseDown.bind(this));
-
- GM.getValue(KEY_IS_ACTIVE, true).then((isActive) => {
- this.isActive = isActive;
-
- this.update();
-
- const videoObserver = new MutationObserver(hideFromMutations.bind(null, () => this.isActive));
-
- videoObserver.observe(
- document.querySelector('ytd-browse[page-subtype="subscriptions"]').querySelector('div#contents'),
- {childList: true},
- );
-
- hideAll(isActive);
- });
-
- let resizeCount = 0;
-
- window.addEventListener('resize', () => {
- const resizeId = ++resizeCount;
-
- this.forceInactive();
-
- const listener = ({detail}) => {
- // column size changed
- if (detail.actionName === 'yt-window-resized') {
- window.setTimeout(() => {
- if (resizeId !== resizeCount) {
- return;
- }
-
- this.forceInactive(false);
-
- // Don't bother re-running filters if the sub page isn't shown
- if (this.isDormant) {
- return;
- }
-
- resetConfig();
-
- hideAll(this.isActive);
- }, 1000);
-
- document.body.removeEventListener('yt-action', listener);
- }
- };
-
- document.body.addEventListener('yt-action', listener);
- });
- }
-
- forceInactive(doForce = true) {
- if (doForce) {
- // if wasActive isn't undefined, forceInactive was already called
- if (this.wasActive === undefined) {
- // Saves a GM.getValue call later
- this.wasActive = this.isActive;
- this.isActive = false;
- }
- } else {
- this.isActive = this.wasActive;
- this.wasActive = undefined;
- }
- }
-
- update() {
- if (this.isActive) {
- this.setButtonActive();
- }
- }
-
- 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);
- }
- }
-
- async onLongClick() {
- await $config.edit();
-
- resetConfig();
-
- hideAll(this.isActive);
- }
-
- onMouseDown(event) {
- if (event.button === 0) {
- new ClickHandler(this.element, this.toggleActive.bind(this), this.onLongClick.bind(this));
- }
- }
- }
-
- // Main
-
- (() => {
- let button;
-
- const loadButton = async () => {
- if (button) {
- button.isDormant = false;
-
- hideAll(button.isActive);
-
- return;
- }
-
- try {
- await $config.ready();
- } catch (error) {
- if (!$config.reset) {
- throw error;
- }
-
- if (!window.confirm(`${error.message}\n\nWould you like to erase your data?`)) {
- return;
- }
-
- $config.reset();
- }
-
- 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);
- }
- };
-
- 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'),
- );
- };
-
- function onNavigate({detail}) {
- if (detail.endpoint.browseEndpoint) {
- const {params, browseId} = detail.endpoint.browseEndpoint;
-
- // Handle navigation to the sub feed
- if ((params === 'MAE%3D' || (!params && (!button || isGridView()))) && browseId === 'FEsubscriptions') {
- const emitter = document.querySelector('ytd-app');
- const event = 'yt-action';
-
- if (button || isGridView()) {
- loadButton();
- } else {
- const listener = ({detail}) => {
- if (detail.actionName === 'ytd-update-grid-state-action') {
- if (isGridView()) {
- loadButton();
- }
-
- emitter.removeEventListener(event, listener);
- }
- };
-
- emitter.addEventListener(event, listener);
- }
-
- return;
- }
- }
-
- // Handle navigation away from the sub feed
- if (button) {
- button.isDormant = true;
-
- hideList.revert();
- }
- }
-
- document.body.addEventListener('yt-navigate-finish', onNavigate);
- })();