Skip to content

Instantly share code, notes, and snippets.

@qiwichupa
Last active January 7, 2024 12:43
Show Gist options
  • Save qiwichupa/733994d90c7b6ceaaf69067584cd6017 to your computer and use it in GitHub Desktop.
Save qiwichupa/733994d90c7b6ceaaf69067584cd6017 to your computer and use it in GitHub Desktop.
Userscript: YouTube RSS Feed
// ==UserScript==
// @name YouTube RSS Feed (qiwichupa fork)
// @namespace https://gist.github.com/qiwichupa/733994d90c7b6ceaaf69067584cd6017
// @homepage https://greasyfork.org/ru/scripts/484129-youtube-rss-feed-qiwichupa-fork
// @author Doodles + qiwichupa
// @version 20240107.1542
// @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);
$(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;
// 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;
}
}
//try get from Channel Videos url (2024.01.07)
let videosLink = $("#infocard-videos-button").children("ytd-button-renderer").children("yt-button-shape").children("a");
if (videosLink.length) {
let videosLinkHref = videosLink.attr("href");
channelId = videosLinkHref ? getChannelIdFromUrl(videosLinkHref) : null;
if (channelId) {
return channelId;
}
}
// give up
return null;
}
function getChannelIdFromUrl(url) {
if (url && url.indexOf("/channel/") !== -1) {
return url.split("/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">
let nameAttributes = ['property', 'name', 'itemprop'];
let metaProperty = null;
for (let i = 0; i < nameAttributes.length; i++) {
metaProperty = $("meta[" + nameAttributes[i] + "='" + metaPropertyKey + "']");
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", link)
.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 "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);
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment