YouTube Channel RSS Link with Retry Logic

Add an RSS feed link to the video owner section on YouTube video pages, with retry logic when owner box isn't found and handling video ID changes.

当前为 2025-02-16 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name YouTube Channel RSS Link with Retry Logic
  3. // @namespace https://greasyfork.org/en/users/4612-gdorn
  4. // @version 1.0
  5. // @description Add an RSS feed link to the video owner section on YouTube video pages, with retry logic when owner box isn't found and handling video ID changes.
  6. // @author GDorn
  7. // @license MIT
  8. // @match https://www.youtube.com/watch*
  9. // @grant none
  10. // ==/UserScript==
  11.  
  12. (function () {
  13. 'use strict';
  14.  
  15. let currentVideoId = null; // Tracks the video ID for which the RSS link was added
  16. let isRunning = false; // Semaphore to prevent concurrent execution
  17.  
  18. /**
  19. * Extracts the channel ID from available data sources.
  20. */
  21. const getChannelId = () => {
  22. let channelId = null;
  23.  
  24. // First, try to get it from ytInitialPlayerResponse
  25. if (window.ytInitialPlayerResponse && window.ytInitialPlayerResponse.videoDetails) {
  26. channelId = window.ytInitialPlayerResponse.videoDetails.channelId;
  27. }
  28.  
  29. // If not found, try to get it from ytInitialData
  30. if (!channelId && window.ytInitialData) {
  31. const data = window.ytInitialData;
  32. if (data && data.contents && data.contents.twoColumnWatchNextResults) {
  33. const owner = data.contents.twoColumnWatchNextResults.results.contents[0].videoOwnerRenderer;
  34. if (owner && owner.ownerEndpoint) {
  35. channelId = owner.ownerEndpoint.browseEndpoint.browseId.replace('UC', '');
  36. }
  37. }
  38. }
  39.  
  40. // If no channelId found yet, fall back to script tag parsing
  41. if (!channelId) {
  42. const scriptTags = Array.from(document.querySelectorAll('script'));
  43. for (const script of scriptTags) {
  44. if (script.innerHTML.includes('channelId":"UC')) {
  45. const match = script.innerHTML.match(/"channelId":"(UC[0-9A-Za-z-_]+)"/);
  46. if (match) {
  47. channelId = match[1].replace('UC', '');
  48. break;
  49. }
  50. }
  51. }
  52. }
  53.  
  54. return channelId;
  55. };
  56.  
  57. /**
  58. * Adds an RSS link to the video owner's section, with retry logic for when the owner box isn't found.
  59. */
  60. const addRssLink = () => {
  61. const ownerBox = document.querySelector('#owner');
  62. if (ownerBox) {
  63. const existingLink = ownerBox.querySelector('.rss-link');
  64. if (existingLink) existingLink.remove();
  65.  
  66. // Extract channel ID dynamically
  67. const channelId = getChannelId();
  68. if (!channelId) {
  69. console.log("Failed to find channel ID.");
  70. setTimeout(addRssLink, 500); // Retry if channelId is not found
  71. return;
  72. }
  73.  
  74. const rssLink = `https://www.youtube.com/feeds/videos.xml?channel_id=${channelId}`;
  75. const rssElement = document.createElement('a');
  76. rssElement.href = rssLink;
  77. rssElement.textContent = 'RSS Feed';
  78. rssElement.target = '_blank';
  79. rssElement.className = 'rss-link';
  80. rssElement.style.display = 'block';
  81. rssElement.style.marginTop = '10px';
  82.  
  83. ownerBox.appendChild(rssElement);
  84. isRunning = false;
  85. console.log("Added RSS link");
  86. } else {
  87. // Retry after 500ms if owner box is not found
  88. console.log("Owner box not found, retrying...");
  89. setTimeout(addRssLink, 500);
  90. }
  91. };
  92.  
  93. /**
  94. * Replaces the existing RSS link with a reload link.
  95. */
  96. const replaceWithReloadLink = () => {
  97. const ownerBox = document.querySelector('#owner');
  98. if (ownerBox) {
  99. const existingLink = ownerBox.querySelector('.rss-link');
  100. if (existingLink) existingLink.remove();
  101.  
  102. const reloadElement = document.createElement('a');
  103. reloadElement.href = location.href;
  104. reloadElement.textContent = 'Reload to update RSS link';
  105. reloadElement.target = '_self';
  106. reloadElement.className = 'rss-link';
  107. reloadElement.style.display = 'block';
  108. reloadElement.style.marginTop = '10px';
  109. ownerBox.appendChild(reloadElement);
  110. isRunning = false;
  111.  
  112. console.log("Added reload link because video ID changed.");
  113. } else {
  114. // Retry after 500ms if owner box is not found
  115. console.log("Owner box not found, retrying...");
  116. setTimeout(replaceWithReloadLink, 500);
  117. }
  118.  
  119. };
  120.  
  121. /**
  122. * Processes the current video page and handles RSS/reload link updates.
  123. * If the owner box is not yet present, it retries every 500ms.
  124. */
  125. const processVideo = () => {
  126. const videoId = new URLSearchParams(window.location.search).get('v');
  127. if (!videoId) return; // No valid video ID
  128.  
  129. // If the video ID has changed, replace the RSS link with the reload link.
  130. if (videoId !== currentVideoId) {
  131. if (currentVideoId !== null) {
  132. console.log("Video ID changed. Replacing RSS link with reload link.");
  133. replaceWithReloadLink(); // Replace the RSS link with a reload link
  134. currentVideoId = videoId;
  135. return;
  136. }
  137.  
  138. currentVideoId = videoId;
  139. isRunning = true;
  140.  
  141. addRssLink(); // Start the retry logic to wait for the #owner box and add the RSS link
  142. }
  143. };
  144.  
  145. // Observe DOM changes to detect navigation to a new video
  146. const observer = new MutationObserver(() => {
  147. if (!isRunning) {
  148. processVideo(); // Process the video when necessary
  149. }
  150. });
  151.  
  152. observer.observe(document.body, { childList: true, subtree: true });
  153.  
  154. console.log("YouTube Channel RSS Link script initialized.");
  155. processVideo(); // Initial run
  156. })();