Greasy Fork

Greasy Fork is available in English.

YouTube All Videos Playlists (YAVP)

Adds buttons for various channel playlists: All Videos, Shorts, Streams, Members-Only

当前为 2024-04-10 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         YouTube All Videos Playlists (YAVP)
// @namespace    c0d3r
// @license      MIT
// @version      0.1
// @description  Adds buttons for various channel playlists: All Videos, Shorts, Streams, Members-Only
// @author       c0d3r
// @match        https://www.youtube.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=youtube.com
// @run-at       document-idle
// @grant        none
// ==/UserScript==

// custom buttons use native classes and tags so both Light and Dark theme is supported
function btnHtml(text, title, href) {
    return `
    <div class="yt-flexible-actions-view-model-wiz__action">
        <button-view-model class="yt-spec-button-view-model">
            <a href="${href}" title="${title}" class="yt-spec-button-shape-next
                yt-spec-button-shape-next--tonal yt-spec-button-shape-next--mono
                yt-spec-button-shape-next--size-m">${text}</a>
        </button-view-model>
    </div>`;
}

function btnBlock(data, chanId) {
    const root = 'https://www.youtube.com/playlist?list=';
    let html = '<div id="yavp-wrap" style="display: flex; flex: 0.8;">';

    // only add relevant buttons based on available channel tabs
    // for IDs see https://github.com/RobertWesner/YouTube-Play-All/blob/main/documentation/available-lists.md
    data.response.contents.twoColumnBrowseResultsRenderer.tabs.forEach(function (tab) {
        if (tab.hasOwnProperty('tabRenderer')) {
            // check for URLs instead of tab titles because languages might be different
            const url = tab.tabRenderer.endpoint.commandMetadata.webCommandMetadata.url;
            if (url.endsWith('/videos')) {
                html += btnHtml('VAll', 'All Videos', root + 'UULF' + chanId);
                html += btnHtml('VPop', 'Popular Videos', root + 'UULP' + chanId);
            } else if (url.endsWith('/shorts')) {
                html += btnHtml('SAll', 'All Shorts', root + 'UUSH' + chanId);
                html += btnHtml('SPop', 'Popular Shorts', root + 'UUPS' + chanId);
            } else if (url.endsWith('/streams')) {
                html += btnHtml('LAll', 'All Streams', root + 'UULV' + chanId);
                html += btnHtml('LPop', 'Popular Streams', root + 'UUPV' + chanId);
            }
        }
    });

    // add Members-Only button only if Join button exists
    let addMemberBtn = false;
    const head = data.response.header;

    // when not logged in
    if (head.hasOwnProperty('c4TabbedHeaderRenderer') &&
        head.c4TabbedHeaderRenderer.hasOwnProperty('sponsorButton')) {
        addMemberBtn = true;
    }

    // when logged in
    if (head.hasOwnProperty('pageHeaderRenderer')) {
        const actionRows = head.pageHeaderRenderer.content.pageHeaderViewModel.actions.flexibleActionsViewModel.actionsRows;
        actionRows.forEach(function (row) {
            row.actions.forEach(function (action) {
                if (action.hasOwnProperty('buttonViewModel') && action.buttonViewModel.targetId === 'sponsorships-button') {
                    addMemberBtn = true;
                }
            });
        });
    }

    if (addMemberBtn) {
        html += btnHtml('Mem', 'Members-Only Videos', root + 'UUMO' + chanId);
    }

    html += '</div>';
    return html;
}

(function () {
    'use strict';

    let oldChanId;

    // native event that fires on each page or tab change
    window.addEventListener('yt-navigate-finish', function () {
        const path = window.location.pathname;
        // run only on channel pages, see https://support.google.com/youtube/answer/6180214
        if (path.startsWith('/channel/') || path.startsWith('/@') ||
            path.startsWith('/c/') || path.startsWith('/user/')) {
            const pageMan = document.querySelector('#page-manager');
            // native function that returns useful data
            const pageData = pageMan.getCurrentData();
            // get channel ID, but remove UC from the start
            const chanId = pageData.response.metadata.channelMetadataRenderer.externalId.substring(2);

            // proceed only if a new channel is opened, don't react to tab changes
            if (oldChanId !== chanId) {
                // remove the wrapper if it exists
                const wrapper = document.querySelector('#yavp-wrap');
                if (wrapper) {
                    wrapper.remove();
                }

                const tabBlock = document.querySelector('#tabsContainer');
                if (tabBlock) {
                    // tabs container might already exist in DOM
                    tabBlock.insertAdjacentHTML('afterEnd', btnBlock(pageData, chanId));
                } else {
                    // Chrome needs to wait for this block to be added at first
                    let stopObserve = false;
                    const observer = new MutationObserver(function (mutList) {
                        for (let i = 0; i < mutList.length; i++) {
                            for (let j = 0; j < mutList[i].addedNodes.length; j++) {
                                if (mutList[i].addedNodes[j].tagName === 'TP-YT-APP-HEADER-LAYOUT') {
                                    const tabs = mutList[i].addedNodes[j].querySelector('#tabsContainer');
                                    tabs.insertAdjacentHTML('afterEnd', btnBlock(pageData, chanId));
                                    observer.disconnect();
                                    stopObserve = true;
                                    break;
                                }
                            }
                            if (stopObserve) {
                                break;
                            }
                        }
                    });

                    observer.observe(pageMan, {
                        subtree: true, childList: true
                    });
                }

                // save channel ID for future checks
                oldChanId = chanId;
            }
        }
    });
})();