🏠 Home 

YouTube RSS Feed (hacky fork)

Adds an RSS feed button to YouTube channels next to the subscribe button


Installer dette script?
  1. // ==UserScript==
  2. // @name YouTube RSS Feed (hacky fork)
  3. // @namespace https://greasyfork.org/en/users/4612-gdorn
  4. // @author Doodles (original) with hacky fixes by George Dorn
  5. // @version 21
  6. // @description Adds an RSS feed button to YouTube channels next to the subscribe button
  7. // @icon http://i.imgur.com/Ty5HNbT.png
  8. // @icon64 http://i.imgur.com/1FfVvNr.png
  9. // @match *://www.youtube.com/*
  10. // @match *://youtube.com/*
  11. // @grant none
  12. // @run-at document-end
  13. // @require https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js
  14. // @license MIT
  15. // ==/UserScript==
  16. this.$ = this.jQuery = jQuery.noConflict(true);
  17. window.safejQuery = this.$;
  18. window.getRSS = function () {
  19. "use strict";
  20. addRssFeedSupport(true);
  21. document.body.addEventListener("yt-navigate-finish", function (event) {
  22. addRssFeedSupport(false);
  23. });
  24. }
  25. function addRssFeedSupport(firstLoad) {
  26. if (isPlaylistPage()) {
  27. waitForElement("owner-container", function () {
  28. let playlistFeedLink = getPlaylistFeed(getPlatlistId());
  29. addRssLink(playlistFeedLink);
  30. addRssButtonPlaylist(playlistFeedLink);
  31. }, 330);
  32. } else if (isVideoPage()) {
  33. waitForElement("upload-info", function () {
  34. let channelFeedLink = getChannelFeed(getChannelIdFromPage());
  35. removeRssLink();
  36. addRssLink(channelFeedLink);
  37. addRssButton(channelFeedLink);
  38. }, 330);
  39. } else if (isChannelPage()) {
  40. waitForElement("subscribe-button", function () {
  41. let channelId = getChannelIdFromPage();
  42. if (channelId === null && firstLoad) {
  43. removeRssLink();
  44. addRefreshButton();
  45. } else {
  46. let channelFeedLink = getChannelFeed(channelId);
  47. removeRssLink();
  48. addRssLink(channelFeedLink);
  49. addRssButton(channelFeedLink);
  50. }
  51. }, 330);
  52. }
  53. }
  54. function isPlaylistPage() {
  55. return document.URL.indexOf("/playlist?list=") !== -1;
  56. }
  57. function isVideoPage() {
  58. return document.URL.indexOf("/watch") !== -1 && document.URL.indexOf("v=") !== -1;
  59. }
  60. function isChannelPage() {
  61. return $("#channel-header").length > 0;
  62. }
  63. function getPlatlistId() {
  64. let playlistId = document.URL.split("list=")[1].split("&")[0];
  65. if (!playlistId.startsWith("PL")) {
  66. playlistId = "PL" + playlistId;
  67. }
  68. return playlistId;
  69. }
  70. function getChannelIdFromPage() {
  71. let channelId = null;
  72. channelId = ytInitialPlayerResponse['videoDetails']['channelId'];
  73. if (channelId) {
  74. return channelId;
  75. }
  76. // try URL
  77. channelId = getChannelIdFromUrl(document.URL);
  78. if (channelId) {
  79. return channelId;
  80. }
  81. // try meta tags that are channel URLs
  82. let metaChannelUrlTags = [
  83. 'og:url',
  84. 'al:ios:url',
  85. 'al:android:url',
  86. 'al:web:url',
  87. 'twitter:url',
  88. 'twitter:app:url:iphone',
  89. 'twitter:app:url:ipad'
  90. ];
  91. for (let i = 0; i < metaChannelUrlTags.length; i++) {
  92. let metaPropertyValue = getMetaTagValue(metaChannelUrlTags[i]);
  93. channelId = metaPropertyValue ? getChannelIdFromUrl(metaPropertyValue) : null;
  94. if (channelId) {
  95. return channelId;
  96. }
  97. }
  98. // try meta tags that are channel IDs
  99. let metaChannelIdTags = [
  100. 'channelId'
  101. ];
  102. for (let i = 0; i < metaChannelIdTags.length; i++) {
  103. channelId = getMetaTagValue(metaChannelIdTags[i]);
  104. if (channelId) {
  105. return channelId;
  106. }
  107. }
  108. // try upload info box on video page
  109. let uploadInfoLink = $("#upload-info a[href*='/channel/']:first");
  110. if (uploadInfoLink.length) {
  111. let uploadInfoLinkHref = uploadInfoLink.attr("href");
  112. channelId = uploadInfoLinkHref ? getChannelIdFromUrl(uploadInfoLinkHref) : null;
  113. if (channelId) {
  114. return channelId;
  115. }
  116. }
  117. // give up
  118. return null;
  119. }
  120. function getChannelIdFromUrl(url) {
  121. if (url && url.indexOf("youtube.com/channel/") !== -1) {
  122. return url.split("youtube.com/channel/")[1].split("/")[0].split("?")[0];
  123. } else {
  124. return null;
  125. }
  126. }
  127. function getMetaTagValue(metaPropertyKey) {
  128. // <meta property="og:url" content="https://www.youtube.com/channel/UCC8VtutLDSrreNEZj8CU01A">
  129. // <meta name="twitter:url" content="https://www.youtube.com/channel/UCC8VtutLDSrreNEZj8CU01A">
  130. // <meta itemprop="channelId" content="UCC8VtutLDSrreNEZj8CU01A">
  131. console.log("Trying to get metatagvalue for", metaPropertyKey);
  132. let nameAttributes = ['property', 'name', 'itemprop'];
  133. let metaProperty = null;
  134. for (let i = 0; i < nameAttributes.length; i++) {
  135. metaProperty = $("meta[" + nameAttributes[i] + "='" + metaPropertyKey + "']");
  136. console.log("Looking in:", metaProperty);
  137. if (metaProperty.length === 1) {
  138. break;
  139. }
  140. metaProperty = null;
  141. }
  142. if (metaProperty !== null) {
  143. let value = metaProperty.attr("content");
  144. if (value) {
  145. return value;
  146. }
  147. }
  148. return null;
  149. }
  150. function getChannelFeed(channelId) {
  151. return "https://www.youtube.com/feeds/videos.xml?channel_id=" + channelId;
  152. }
  153. function getPlaylistFeed(playlistId) {
  154. return "https://www.youtube.com/feeds/videos.xml?playlist_id=" + playlistId;
  155. }
  156. function addRssLink(link) {
  157. $("head").append('<link rel="alternate" type="application/rss+xml" title="RSS" href="' +
  158. link + '" />');
  159. }
  160. function removeRssLink() {
  161. if ($("link[type='application/rss+xml']").length > 0) {
  162. $("link[type='application/rss+xml']").remove();
  163. }
  164. }
  165. function waitForElement(elementId, callbackFunction, intervalLength = 330) {
  166. var waitCount = 15000 / intervalLength; // wait 15 seconds maximum
  167. var wait = setInterval(function () {
  168. waitCount--;
  169. if ($("#" + elementId).length > 0) {
  170. callbackFunction();
  171. clearInterval(wait);
  172. } else if (waitCount <= 0) {
  173. console.log("YouTube RSS Feed UserScript - wait for element \"#" + elementId +
  174. "\" failed! Time limit (15 seconds) exceeded.");
  175. clearInterval(wait);
  176. }
  177. }, intervalLength);
  178. }
  179. function addRssButton(link) {
  180. if ($("#rssSubButton").length > 0) {
  181. $("#rssSubButton").remove();
  182. }
  183. $("#subscribe-button")
  184. .css({
  185. "display": "flex",
  186. "flex-flow": "nowrap",
  187. "height": "37px"
  188. })
  189. .prepend(makeRssButton(link));
  190. }
  191. function addRssButtonPlaylist(link) {
  192. if ($("#rssSubButton").length === 0) {
  193. $("#owner-container > #button")
  194. .css({
  195. "display": "flex",
  196. "flex-flow": "nowrap",
  197. "height": "37px"
  198. })
  199. .prepend(makeRssButton(link));
  200. }
  201. }
  202. function makeRssButton(link) {
  203. return $("<a>RSS</a>")
  204. .attr("id", "rssSubButton")
  205. .attr("target", "_blank")
  206. .attr("href", rssLinkToData(link))
  207. .attr("download", "feed.rss")
  208. .css({
  209. "background-color": "#fd9b12",
  210. "border-radius": "3px",
  211. "padding": "10px 16px",
  212. "color": "#ffffff",
  213. "font-size": "14px",
  214. "text-decoration": "none",
  215. "text-transform": "uppercase",
  216. "margin-right": "5px"
  217. });
  218. }
  219. function rssLinkToData(link) {
  220. return link;
  221. return "data:application/atom+xml,<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
  222. "<feed xmlns=\"http://www.w3.org/2005/Atom\">" +
  223. "<title type=\"text\">YouTube RSS Button</title>" +
  224. "<link rel=\"self\" href=\"" + link + "\" type=\"application/atom+xml\" />" +
  225. "</feed>";
  226. }
  227. function addRefreshButton() {
  228. let refreshButton = $("<a>Refresh</a>")
  229. .attr("id", "rssSubButton")
  230. .attr("href", "#")
  231. .css({
  232. "background-color": "#fd9b12",
  233. "border-radius": "3px",
  234. "padding": "10px 16px",
  235. "color": "#ffffff",
  236. "font-size": "14px",
  237. "text-decoration": "none",
  238. "text-transform": "uppercase",
  239. "margin-right": "5px"
  240. });
  241. $(refreshButton).click(function (e) {
  242. e.preventDefault();
  243. let r = confirm("Due to how YouTube load pages, there isn't a reliable way to get channel" +
  244. " IDs from channel pages if you've navigated to them from another YouTube page." +
  245. " The solution is to reload the page.\n\nWould you like to reload the page?");
  246. if (r === true) {
  247. window.location.reload();
  248. }
  249. });
  250. if ($("#rssSubButton").length > 0) {
  251. $("#rssSubButton").remove();
  252. }
  253. $("#subscribe-button")
  254. .css({
  255. "display": "flex",
  256. "flex-flow": "nowrap",
  257. "height": "37px"
  258. })
  259. .prepend(refreshButton);
  260. }
  261. $(window.getRSS());