Youtube compact sidebar+

Add more buttons in compact sidebar/mini guide

目前為 2024-06-11 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name                   Youtube compact sidebar+
// @name:pt-BR             Youtube barra lateral+
// @namespace              https://greasyfork.org/users/821661
// @match                  https://www.youtube.com/*
// @grant                  GM_xmlhttpRequest
// @grant                  GM_registerMenuCommand
// @grant                  GM_setValue
// @grant                  GM_getValue
// @grant                  GM_addStyle
// @grant                  GM_addStyle
// @version                0.3
// @author                 hdyzen
// @description            Add more buttons in compact sidebar/mini guide
// @description:pt-BR      Adiciona mais botões a barra lateral
// @license                GPL-3.0
// ==/UserScript==
'use strict';

let channelId; // Channel ID
const defaultLang = 'en'; // If the lang is not in the labels use this language
const lang = document.documentElement.lang; // Lang of HTML

// Labels
const labels = {
    'pt-BR': {
        yourChannel: 'Canal',
        history: 'Histórico',
        download: 'Download',
        playlists: 'Playlists',
        yourVideos: 'Videos',
        watchLater: 'Depois',
        liked: 'Gostei',
    },
    'en': {
        yourChannel: 'Channel',
        history: 'History',
        playlists: 'Playlists',
        download: 'Download',
        yourVideos: 'Videos',
        watchLater: 'Later',
        liked: 'Liked',
    },
};

// Get channel id
async function getChannelId() {
    const urlGet = 'https://studio.youtube.com/';

    GM_xmlhttpRequest({
        url: urlGet,
        onload: e => {
            if (e.finalUrl !== urlGet) {
                console.log('NOT FOUND ID:', e);
            }

            const id = e.responseText.match(/"CHANNEL_ID":"([a-zA-Z0-9]+)"/)?.[1];

            if (!id) {
                console.log('NOT FOUND ID:', e);
            }

            channelId = id;
        },
    });
}
getChannelId();

// Mini guide item template
function getTemplate(path, label, href, id) {
    return `
    <ytd-mini-guide-entry-renderer id="${id}" class="style-scope ytd-mini-guide-renderer" system-icons="" role="tab" tabindex="0" aria-selected="false" aria-label="${label}">
        <a id="endpoint" tabindex="-1" class="yt-simple-endpoint style-scope ytd-mini-guide-entry-renderer" title="Você" href="${href}">
        <svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24" focusable="false" style="color: inherit; fill: currentColor; pointer-events: none; display: inline-flexbox; display: -moz-inline-box; display: inline-flex; -moz-box-align: center; align-items: center; -moz-box-pack: center; justify-content: center; position: relative; vertical-align: middle; fill: var(--iron-icon-fill-color,currentcolor); stroke: var(--iron-icon-stroke-color,none); width: var(--iron-icon-width,24px); height: var(--iron-icon-height,24px); animation: var(--iron-icon-animation); margin-top: var(--iron-icon-margin-top); margin-right: var(--iron-icon-margin-right); margin-bottom: var(--iron-icon-margin-bottom); margin-left: var(--iron-icon-margin-left); padding: var(--iron-icon-padding);">${path}</svg>
            <span class="title style-scope ytd-mini-guide-entry-renderer">${label}</span>
        </a>
    </ytd-mini-guide-entry-renderer>
`;
}

// Render item in mini guide
function renderItem(element) {
    const items = {
        yourChannel: { href: `/channel/${channelId}`, path: '<path d="M4 20h14v1H3V6h1v14zM6 3v15h15V3H6zm2.02 14c.36-2.13 1.93-4.1 5.48-4.1s5.12 1.97 5.48 4.1H8.02zM11 8.5a2.5 2.5 0 015 0 2.5 2.5 0 01-5 0zm3.21 3.43A3.507 3.507 0 0017 8.5C17 6.57 15.43 5 13.5 5S10 6.57 10 8.5c0 1.69 1.2 3.1 2.79 3.43-3.48.26-5.4 2.42-5.78 5.07H7V4h13v13h-.01c-.38-2.65-2.31-4.81-5.78-5.07z"></path>' },
        history: { href: '/feed/history', path: '<g><path d="M14.97 16.95 10 13.87V7h2v5.76l4.03 2.49-1.06 1.7zM22 12c0 5.51-4.49 10-10 10S2 17.51 2 12h1c0 4.96 4.04 9 9 9s9-4.04 9-9-4.04-9-9-9C8.81 3 5.92 4.64 4.28 7.38c-.11.18-.22.37-.31.56L3.94 8H8v1H1.96V3h1v4.74c.04-.09.07-.17.11-.25.11-.22.23-.42.35-.63C5.22 3.86 8.51 2 12 2c5.51 0 10 4.49 10 10z"></path></g>' },
        playlists: { href: '/feed/playlists', path: '<path d="M22 7H2v1h20V7zm-9 5H2v-1h11v1zm0 4H2v-1h11v1zm2 3v-8l7 4-7 4z"></path>' },
        yourVideos: { href: `https://studio.youtube.com/channel/${channelId}/videos`, path: '<path d="m10 8 6 4-6 4V8zm11-5v18H3V3h18zm-1 1H4v16h16V4z"></path>' },
        watchLater: { href: '/playlist?list=WL', path: '<path d="M14.97 16.95 10 13.87V7h2v5.76l4.03 2.49-1.06 1.7zM12 3c-4.96 0-9 4.04-9 9s4.04 9 9 9 9-4.04 9-9-4.04-9-9-9m0-1c5.52 0 10 4.48 10 10s-4.48 10-10 10S2 17.52 2 12 6.48 2 12 2z"></path>' },
        liked: { href: '/playlist?list=LL', path: '<path d="M18.77,11h-4.23l1.52-4.94C16.38,5.03,15.54,4,14.38,4c-0.58,0-1.14,0.24-1.52,0.65L7,11H3v10h4h1h9.43 c1.06,0,1.98-0.67,2.19-1.61l1.34-6C21.23,12.15,20.18,11,18.77,11z M7,20H4v-8h3V20z M19.98,13.17l-1.34,6 C18.54,19.65,18.03,20,17.43,20H8v-8.61l5.6-6.06C13.79,5.12,14.08,5,14.38,5c0.26,0,0.5,0.11,0.63,0.3 c0.07,0.1,0.15,0.26,0.09,0.47l-1.52,4.94L13.18,12h1.35h4.23c0.41,0,0.8,0.17,1.03,0.46C19.92,12.61,20.05,12.86,19.98,13.17z"></path>' },
        download: { href: '/feed/downloads', path: '<path d="M17 18v1H6v-1h11zm-.5-6.6-.7-.7-3.8 3.7V4h-1v10.4l-3.8-3.8-.7.7 5 5 5-4.9z"></path>' },
    };

    Object.entries(items).forEach(([key, value]) => {
        const template = getTemplate(value.path, labels[lang] ? labels[lang][key] : labels[defaultLang][key], value.href, key);
        element[element.length - 1].insertAdjacentHTML('afterend', template);
        displayItems(key, getStates(key));
    });
}

function getStates(key) {
    const states = GM_getValue('states');
    const statesDefault = {
        download: true,
        liked: true,
        watchLater: true,
        yourVideos: true,
        playlists: true,
        history: true,
        yourChannel: true,
    };

    if (key) return states?.[key] !== undefined ? states[key] : statesDefault[key];
    else return statesDefault;
}

// Render commands menu
function renderMenuCommands() {
    const states = GM_getValue('states');

    Object.entries(labels[lang] ? labels[lang] : labels[defaultLang]).forEach(([key, label]) => {
        const state = getStates(key);

        GM_registerMenuCommand(`${label}: ${state ? '✅' : '❌'}`, e => stateToggle(key, state), { id: key, autoClose: false });
    });
}
renderMenuCommands();

// States toggle
function stateToggle(key, state) {
    const statesNew = getStates();
    statesNew[key] = !state;
    GM_setValue('states', statesNew);
    renderMenuCommands();
    displayItems(key, statesNew[key]);
}

// Display items
function displayItems(id, state) {
    const item = document.getElementById(id);

    if (state) {
        item.style.display = 'unset';
    } else {
        item.style.display = 'none';
    }
}

// Wait mini guide childs
const observer = new MutationObserver(mutations => {
    const items = document.getElementsByTagName('ytd-mini-guide-renderer')?.[0]?.children?.[0].children;

    if (items.length) {
        renderItem(items);
        observer.disconnect();
    }
});

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

// Scroll in mini guide
GM_addStyle(`
ytd-mini-guide-renderer.ytd-app {
    overflow: auto;
} 
`);