Adds an RSS feed button to YouTube channels next to the subscribe button
// ==UserScript== // @name YouTube RSS Feed (hacky fork) // @namespace https://greasyfork.org/en/users/4612-gdorn // @author Doodles (original) with hacky fixes by George Dorn // @version 21 // @description Adds an RSS feed button to YouTube channels next to the subscribe button // @icon http://i.imgur.com/Ty5HNbT.png // @icon64 http://i.imgur.com/1FfVvNr.png // @match *://www.youtube.com/* // @match *://youtube.com/* // @grant none // @run-at document-end // @require https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js // @license MIT // ==/UserScript== this.$ = this.jQuery = jQuery.noConflict(true); window.safejQuery = this.$; window.getRSS = function () { "use strict"; addRssFeedSupport(true); document.body.addEventListener("yt-navigate-finish", function (event) { addRssFeedSupport(false); }); } function addRssFeedSupport(firstLoad) { if (isPlaylistPage()) { waitForElement("owner-container", function () { let playlistFeedLink = getPlaylistFeed(getPlatlistId()); addRssLink(playlistFeedLink); addRssButtonPlaylist(playlistFeedLink); }, 330); } else if (isVideoPage()) { waitForElement("upload-info", function () { let channelFeedLink = getChannelFeed(getChannelIdFromPage()); removeRssLink(); addRssLink(channelFeedLink); addRssButton(channelFeedLink); }, 330); } else if (isChannelPage()) { waitForElement("subscribe-button", function () { let channelId = getChannelIdFromPage(); if (channelId === null && firstLoad) { removeRssLink(); addRefreshButton(); } else { let channelFeedLink = getChannelFeed(channelId); removeRssLink(); addRssLink(channelFeedLink); addRssButton(channelFeedLink); } }, 330); } } function isPlaylistPage() { return document.URL.indexOf("/playlist?list=") !== -1; } function isVideoPage() { return document.URL.indexOf("/watch") !== -1 && document.URL.indexOf("v=") !== -1; } function isChannelPage() { return $("#channel-header").length > 0; } function getPlatlistId() { let playlistId = document.URL.split("list=")[1].split("&")[0]; if (!playlistId.startsWith("PL")) { playlistId = "PL" + playlistId; } return playlistId; } function getChannelIdFromPage() { let channelId = null; channelId = ytInitialPlayerResponse['videoDetails']['channelId']; if (channelId) { return channelId; } // try URL channelId = getChannelIdFromUrl(document.URL); if (channelId) { return channelId; } // try meta tags that are channel URLs let metaChannelUrlTags = [ 'og:url', 'al:ios:url', 'al:android:url', 'al:web:url', 'twitter:url', 'twitter:app:url:iphone', 'twitter:app:url:ipad' ]; for (let i = 0; i < metaChannelUrlTags.length; i++) { let metaPropertyValue = getMetaTagValue(metaChannelUrlTags[i]); channelId = metaPropertyValue ? getChannelIdFromUrl(metaPropertyValue) : null; if (channelId) { return channelId; } } // try meta tags that are channel IDs let metaChannelIdTags = [ 'channelId' ]; for (let i = 0; i < metaChannelIdTags.length; i++) { channelId = getMetaTagValue(metaChannelIdTags[i]); if (channelId) { return channelId; } } // try upload info box on video page let uploadInfoLink = $("#upload-info a[href*='/channel/']:first"); if (uploadInfoLink.length) { let uploadInfoLinkHref = uploadInfoLink.attr("href"); channelId = uploadInfoLinkHref ? getChannelIdFromUrl(uploadInfoLinkHref) : null; if (channelId) { return channelId; } } // give up return null; } function getChannelIdFromUrl(url) { if (url && url.indexOf("youtube.com/channel/") !== -1) { return url.split("youtube.com/channel/")[1].split("/")[0].split("?")[0]; } else { return null; } } function getMetaTagValue(metaPropertyKey) { // <meta property="og:url" content="https://www.youtube.com/channel/UCC8VtutLDSrreNEZj8CU01A"> // <meta name="twitter:url" content="https://www.youtube.com/channel/UCC8VtutLDSrreNEZj8CU01A"> // <meta itemprop="channelId" content="UCC8VtutLDSrreNEZj8CU01A"> console.log("Trying to get metatagvalue for", metaPropertyKey); let nameAttributes = ['property', 'name', 'itemprop']; let metaProperty = null; for (let i = 0; i < nameAttributes.length; i++) { metaProperty = $("meta[" + nameAttributes[i] + "='" + metaPropertyKey + "']"); console.log("Looking in:", metaProperty); if (metaProperty.length === 1) { break; } metaProperty = null; } if (metaProperty !== null) { let value = metaProperty.attr("content"); if (value) { return value; } } return null; } function getChannelFeed(channelId) { return "https://www.youtube.com/feeds/videos.xml?channel_id=" + channelId; } function getPlaylistFeed(playlistId) { return "https://www.youtube.com/feeds/videos.xml?playlist_id=" + playlistId; } function addRssLink(link) { $("head").append('<link rel="alternate" type="application/rss+xml" title="RSS" href="' + link + '" />'); } function removeRssLink() { if ($("link[type='application/rss+xml']").length > 0) { $("link[type='application/rss+xml']").remove(); } } function waitForElement(elementId, callbackFunction, intervalLength = 330) { var waitCount = 15000 / intervalLength; // wait 15 seconds maximum var wait = setInterval(function () { waitCount--; if ($("#" + elementId).length > 0) { callbackFunction(); clearInterval(wait); } else if (waitCount <= 0) { console.log("YouTube RSS Feed UserScript - wait for element \"#" + elementId + "\" failed! Time limit (15 seconds) exceeded."); clearInterval(wait); } }, intervalLength); } function addRssButton(link) { if ($("#rssSubButton").length > 0) { $("#rssSubButton").remove(); } $("#subscribe-button") .css({ "display": "flex", "flex-flow": "nowrap", "height": "37px" }) .prepend(makeRssButton(link)); } function addRssButtonPlaylist(link) { if ($("#rssSubButton").length === 0) { $("#owner-container > #button") .css({ "display": "flex", "flex-flow": "nowrap", "height": "37px" }) .prepend(makeRssButton(link)); } } function makeRssButton(link) { return $("<a>RSS</a>") .attr("id", "rssSubButton") .attr("target", "_blank") .attr("href", rssLinkToData(link)) .attr("download", "feed.rss") .css({ "background-color": "#fd9b12", "border-radius": "3px", "padding": "10px 16px", "color": "#ffffff", "font-size": "14px", "text-decoration": "none", "text-transform": "uppercase", "margin-right": "5px" }); } function rssLinkToData(link) { return link; return "data:application/atom+xml,<?xml version=\"1.0\" encoding=\"utf-8\"?>" + "<feed xmlns=\"http://www.w3.org/2005/Atom\">" + "<title type=\"text\">YouTube RSS Button</title>" + "<link rel=\"self\" href=\"" + link + "\" type=\"application/atom+xml\" />" + "</feed>"; } function addRefreshButton() { let refreshButton = $("<a>Refresh</a>") .attr("id", "rssSubButton") .attr("href", "#") .css({ "background-color": "#fd9b12", "border-radius": "3px", "padding": "10px 16px", "color": "#ffffff", "font-size": "14px", "text-decoration": "none", "text-transform": "uppercase", "margin-right": "5px" }); $(refreshButton).click(function (e) { e.preventDefault(); let r = confirm("Due to how YouTube load pages, there isn't a reliable way to get channel" + " IDs from channel pages if you've navigated to them from another YouTube page." + " The solution is to reload the page.\n\nWould you like to reload the page?"); if (r === true) { window.location.reload(); } }); if ($("#rssSubButton").length > 0) { $("#rssSubButton").remove(); } $("#subscribe-button") .css({ "display": "flex", "flex-flow": "nowrap", "height": "37px" }) .prepend(refreshButton); } $(window.getRSS());