YouTube: Subscriptions Watch Later

Adds unwatched videos from your YouTube subscriptions page to the "Watch later" playlist

目前為 2017-09-17 提交的版本,檢視 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         YouTube: Subscriptions Watch Later
// @namespace    https://github.com/ackoujens
// @version      1.0
// @description  Adds unwatched videos from your YouTube subscriptions page to the "Watch later" playlist
// @author       Jens Ackou
// @grant        GM_addStyle
// @include      http://*.youtube.com/*
// @include      http://youtube.com/*
// @include      https://*.youtube.com/*
// @include      https://youtube.com/*
// @require      http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js
// ==/UserScript==

// To submit bugs or submit revisions please see visit the repository at:
// https://github.com/ackoujens/youtube-subcriptions-watch-later
// You can open new issues at:
// https://github.com/ackoujens/youtube-subcriptions-watch-later/issues

// I highly recommend using the "YouTube: Hide Watched Videos" script
// created by Evguani Naverniouk. This is a flow that I use and find very useful.
// This script also helped me getting an idea of how to add a button to the YouTube AI.
// Probably the reason why I chose to make it look like the icons are part of eachother.

(function(undefined) {
    // Enable for debugging
    var __DEV__ = false;

    // Set defaults
    localStorage.YTSP = localStorage.SP || 'false';

    GM_addStyle(`
.YT-SP-BUTTON {
    background: transparent;
    border: 0;
    color: #888888;
    cursor: pointer;
    height: 40px;
    outline: 0;
    margin-right: 8px;
    padding: 0 8px;
    width: 40px;
}

.YT-SP-BUTTON svg {
    margin-top: 8px;
    height: 34px;
    width: 34px;
}

.YT-SP-BUTTON:focus,
.YT-SP-BUTTON:hover {
    color: #FFF;
}

.YT-SP-MENU {
    background: #F8F8F8;
    border: 1px solid #D3D3D3;
    box-shadow: 0 1px 0 rgba(0, 0, 0, 0.05);
    display: none;
    font-size: 12px;
    margin-top: -1px;
    padding: 10px;
    position: absolute;
    right: 0;
    text-align: center;
    top: 100%;
    white-space: normal;
    z-index: 9999;
}

.YT-SP-MENU-ON { display: block; }
.YT-SP-MENUBUTTON-ON span { transform: rotate(180deg) }
`);

    var counter = 0;
    var watchlaterIcon = '<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48"><g fill="currentColor"><path d="M32 4h-4v-4h-4v4h-4v4h4v4h4v-4h4z"></path><path d="M26.996 13.938c0.576 0.64 1.1 1.329 1.563 2.062-1.197 1.891-2.79 3.498-4.67 4.697-2.362 1.507-5.090 2.303-7.889 2.303s-5.527-0.796-7.889-2.303c-1.88-1.199-3.473-2.805-4.67-4.697 1.197-1.891 2.79-3.498 4.67-4.697 0.122-0.078 0.246-0.154 0.371-0.228-0.311 0.854-0.482 1.776-0.482 2.737 0 4.418 3.582 8 8 8s8-3.582 8-8c0-0.022-0.001-0.043-0.001-0.065-3.415-0.879-5.947-3.957-5.998-7.635-0.657-0.074-1.325-0.113-2.001-0.113-6.979 0-13.028 4.064-16 10 2.972 5.936 9.021 10 16 10s13.027-4.064 16-10c-0.551-1.101-1.209-2.137-1.958-3.095-0.915 0.537-1.946 0.897-3.046 1.034zM13 10c1.657 0 3 1.343 3 3s-1.343 3-3 3-3-1.343-3-3 1.343-3 3-3z"></path></g></svg>';


    // ===========================================================
    // Returns a function, that, as long as it continues to be invoked, will not
    // be triggered. The function will be called after it stops being called for
    // N milliseconds. If `immediate` is passed, trigger the function on the
    // leading edge, instead of the trailing.

    var debounce = function (func, wait, immediate) {
        var timeout;
        return function() {
            var context = this, args = arguments;
            var later = function() {
                timeout = null;
                if (!immediate) func.apply(context, args);
            };
            var callNow = immediate && !timeout;
            clearTimeout(timeout);
            timeout = setTimeout(later, wait);
            if (callNow) func.apply(context, args);
        };
    };

    // ===========================================================
    // Button will be injected into the main header menu
    var findButtonTarget = function () {
        return $('#container #end #buttons');
    };

    // ===========================================================
    // Check if button is already present
    var isButtonAlreadyThere = function () {
        return $('.YT-SP-BUTTON').length > 0;
    };

    // ===========================================================
    // Add button to page
    var addButton = function () {
        if (isButtonAlreadyThere()) return;

        // Find button target
        var target = findButtonTarget();
        if (!target) return;

        // Generate button DOM
        //var icon = localStorage.SP === 'true' ? visibilityIcon : visibilityOffIcon;
        var icon = localStorage.YTSP = watchlaterIcon;
        var button = $('<button class="YT-SP-BUTTON" title="Add Videos to Watch Later">' + icon + '</button>');

        // Attach button event
        button.on("click", function () {
            $('ytd-thumbnail-overlay-toggle-button-renderer').click();
        });

        // Insert button into DOM
        target.prepend(button);
 };

    var run = debounce(function () {
        if (__DEV__) console.log('[YT-SP] Running check for subcription videos');
        addButton();
    }, 500);

    // ===========================================================

    // Hijack all XHR calls
    var send = XMLHttpRequest.prototype.send;
    XMLHttpRequest.prototype.send = function (data) {
        this.addEventListener("readystatechange", function () {
            if (
                // Anytime more videos are fetched -- re-run script
                this.responseURL.indexOf('browse_ajax?action_continuation') > 0
            ) {
                setTimeout(function () {
                    run();
                }, 0);
            }
        }, false);
        send.call(this, data);
    };

    // ===========================================================

    var observeDOM = (function() {
        var MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
        var eventListenerSupported = window.addEventListener;

        return function(obj, callback) {
            if (__DEV__) console.log('[YT-SP] Attaching DOM listener');

            // Invalid `obj` given
            if (!obj) return;

            if (MutationObserver) {
                var obs = new MutationObserver(function (mutations, observer) {
                    if (mutations[0].addedNodes.length || mutations[0].removedNodes.length) {
                        callback(mutations);
                    }
                });

                obs.observe(obj, {childList: true, subtree: true});
            } else if (eventListenerSupported) {
                obj.addEventListener('DOMNodeInserted', callback, false);
                obj.addEventListener('DOMNodeRemoved', callback, false);
            }
        };
    })();

    // ===========================================================

    if (__DEV__) console.log('[YT-SP] Starting Script');

    // YouTube does navigation via history and also does a bunch
    // of AJAX video loading. In order to ensure we're always up
    // to date, we have to listen for ANY DOM change event, and
    // re-run our script.
    observeDOM(document.body, run);

    run();
})();