Export YouTube Subscriptions to RSS OPML

Adds an export button to the subscriptions section of the sidebar, which generates an OPML file of RSS feeds for your subscriptions.

当前为 2021-02-07 提交的版本,查看 最新版本

// ==UserScript==
// @name         Export YouTube Subscriptions to RSS OPML
// @namespace    https://github.com/theborg3of5/Userscripts/
// @version      1.2
// @description  Adds an export button to the subscriptions section of the sidebar, which generates an OPML file of RSS feeds for your subscriptions.
// @author       Gavin Borg
// @match        https://www.youtube.com/
// ==/UserScript==

function addButton() {
    var buttonParent = findSpotToPutButton();

    // Create and insert button
    var button = document.createElement("button");
    button.title = "Export subscriptions as OPML file for RSS readers. Make sure to expand subscriptions in sidebar first.";
    button.innerHTML = "Export";
    button.style.fontWeight = "bold";
    button.style.float = "right";
    button.style.marginBottom = "2px";
    button.style.marginRight = "30px";
    button.id = "exportOPMLButton";
    button.addEventListener("click", exportSubscriptions);
    buttonParent.appendChild(button);
}

function findSpotToPutButton() {
    var sectionLabels = document.querySelectorAll("yt-formatted-string#guide-section-title");

    // First try to find the "Subscriptions" section. Won't work outside of an en-US locale.
    var subscriptionsText = "Subscriptions";
    for(var i = 0; i < sectionLabels.length; i++) {
        if(sectionLabels[i].innerHTML === subscriptionsText) {
            return sectionLabels[i].parentElement;
        }
    }

    // If that fails, just put it in the second section (first one's header is hidden).
    return sectionLabels[1].parentElement;
}

function exportSubscriptions() {
    var channels = [];
    var inChannels = false;
    var channelURLFragment = "www.youtube.com/channel/";

    var links = document.querySelectorAll("div#items a#endpoint[href]"); // Sidebar links
    for(var i = 0; i < links.length; i++) {
        var href = links[i].href;
        var title = links[i].getAttribute("title");

        if(href.indexOf(channelURLFragment) > -1) { // Found our first channel
            inChannels = true;
        }

        if(inChannels) {
            if(href.indexOf(channelURLFragment) === -1) { // Once we're in the channels block, any link that's not to a channel means we've exited it and are finished.
                break;
            }

            var channelId = href.substring(href.lastIndexOf(channelURLFragment) + channelURLFragment.length, href.length);
            if(channelId.indexOf("/") > -1) {
                channelId = channelId.substring(0, channelId.indexOf("/")) // Sometimes the link doesn't end with the ID, but with other stuff like "/videos" - trim that off.
            }
            channels.push({"title":title, "id":channelId});
        }
    }

    // Build download link and click it
    var xml = buildXML(channels);
    var fileType = "text/plain";
    var blob = new Blob([xml], {type: fileType});
    var blobURL = window.URL.createObjectURL(blob);
    var filename = "youtubeSubscriptions.opml";
    var downloadLink = document.createElement("a");
    downloadLink.setAttribute("href", blobURL);
    downloadLink.setAttribute("download", filename);
    downloadLink.dataset.downloadurl = fileType + ":" + filename + ":" + blobURL;
    downloadLink.click();
}

function buildXML(channels) {
    // Goal structure:
    //  <opml version="1.0">
    //   <head>
    //    <title>YouTube Subscriptions as RSS</title>
    //   </head>
    //   <body>
    //    <outline text="YouTube Subscriptions" title="YouTube Subscriptions">
    //     <outline type="rss" text="" title="" xmlURL="" />
    //     ...
    //    </outline>
    //   </body>
    //  </opml>

    var xmlDoc = document.implementation.createDocument("", "", null);
    var opml = xmlDoc.createElement("opml");
    opml.setAttribute("version", "1.0");

    var head = xmlDoc.createElement("head");
    var title = xmlDoc.createElement("title");
    title.innerHTML = "YouTube Subscriptions as RSS";
    head.appendChild(title);
    opml.appendChild(head);

    var body = xmlDoc.createElement("body");
    var parentOutline = xmlDoc.createElement("outline");
    parentOutline.setAttribute("text", "YouTube Subscriptions")
    parentOutline.setAttribute("title", "YouTube Subscriptions")

    for(var j = 0; j < channels.length; j++) {
        var outline = xmlDoc.createElement("outline");
        outline.setAttribute("type", "rss");
        outline.setAttribute("text", channels[j].title);
        outline.setAttribute("title", channels[j].title);
        outline.setAttribute("xmlUrl", "https://www.youtube.com/feeds/videos.xml?channel_id=" + channels[j].id);
        parentOutline.appendChild(outline);
    }

    body.appendChild(parentOutline);

    opml.appendChild(body);
    xmlDoc.appendChild(opml);

    var s = new XMLSerializer();
    return s.serializeToString(xmlDoc);
}

window.onload = addButton;