YouTube Enhancer (Reveal Channel ID)

Revealing the channel ID, displayed next to the channel handle.

目前为 2024-12-14 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name YouTube Enhancer (Reveal Channel ID)
  3. // @description Revealing the channel ID, displayed next to the channel handle.
  4. // @icon https://raw.githubusercontent.com/exyezed/youtube-enhancer/refs/heads/main/extras/youtube-enhancer.png
  5. // @version 1.3
  6. // @author exyezed
  7. // @namespace https://github.com/exyezed/youtube-enhancer/
  8. // @supportURL https://github.com/exyezed/youtube-enhancer/issues
  9. // @license MIT
  10. // @match https://www.youtube.com/*
  11. // @grant GM_xmlhttpRequest
  12. // ==/UserScript==
  13.  
  14. (function() {
  15. 'use strict';
  16. let lastProcessedChannelName = '';
  17. let isRequestInProgress = false;
  18. let channelIdCache = {};
  19.  
  20. const CACHE_DURATION = 7 * 24 * 60 * 60 * 1000;
  21.  
  22. function loadCacheFromStorage() {
  23. try {
  24. const cachedData = localStorage.getItem('youtubeEnhancerCache');
  25. if (cachedData) {
  26. const parsedCache = JSON.parse(cachedData);
  27. const now = Date.now();
  28. Object.keys(parsedCache).forEach(key => {
  29. if (now - parsedCache[key].timestamp > CACHE_DURATION) {
  30. delete parsedCache[key];
  31. }
  32. });
  33. channelIdCache = parsedCache;
  34. return parsedCache;
  35. }
  36. } catch (error) {
  37. console.error('Error loading cache:', error);
  38. }
  39. return {};
  40. }
  41.  
  42. function saveToCache(channelName, channelId) {
  43. try {
  44. channelIdCache[channelName] = {
  45. id: channelId,
  46. timestamp: Date.now()
  47. };
  48. localStorage.setItem('youtubeEnhancerCache', JSON.stringify(channelIdCache));
  49. } catch (error) {
  50. console.error('Error saving to cache:', error);
  51. }
  52. }
  53.  
  54. function getFromCache(channelName) {
  55. const cached = channelIdCache[channelName];
  56. if (cached) {
  57. const now = Date.now();
  58. if (now - cached.timestamp <= CACHE_DURATION) {
  59. return cached.id;
  60. } else {
  61. delete channelIdCache[channelName];
  62. localStorage.setItem('youtubeEnhancerCache', JSON.stringify(channelIdCache));
  63. }
  64. }
  65. return null;
  66. }
  67.  
  68. function getChannelNameElement() {
  69. const selectors = [
  70. 'yt-content-metadata-view-model .yt-core-attributed-string',
  71. '#channel-header #channel-name .ytd-channel-name',
  72. '#channel-header #text.ytd-channel-name',
  73. '#owner-name a',
  74. '#channel-name.ytd-video-owner-renderer'
  75. ];
  76.  
  77. for (const selector of selectors) {
  78. const element = document.querySelector(selector);
  79. if (element) return element;
  80. }
  81. return null;
  82. }
  83.  
  84. function isChannelPage() {
  85. const path = window.location.pathname;
  86. const channelTabs = [
  87. '/featured', '/videos', '/streams', '/shorts', '/courses',
  88. '/playlists', '/community', '/podcasts', '/store', '/about',
  89. '/membership', '/channels', '/search', '/@'
  90. ];
  91. return channelTabs.some(tab => path.includes(tab));
  92. }
  93.  
  94. function isWatchPage() {
  95. return window.location.pathname.startsWith('/watch');
  96. }
  97.  
  98. function waitForElement(selector, timeout = 5000) {
  99. return new Promise((resolve, reject) => {
  100. const element = getChannelNameElement();
  101. if (element) {
  102. return resolve(element);
  103. }
  104.  
  105. const observer = new MutationObserver((mutations, obs) => {
  106. const element = getChannelNameElement();
  107. if (element) {
  108. obs.disconnect();
  109. resolve(element);
  110. }
  111. });
  112.  
  113. observer.observe(document.body, {
  114. childList: true,
  115. subtree: true
  116. });
  117.  
  118. setTimeout(() => {
  119. observer.disconnect();
  120. reject('Timeout waiting for element');
  121. }, timeout);
  122. });
  123. }
  124.  
  125. function createLoadingElement() {
  126. const loadingSpan = document.createElement('span');
  127. loadingSpan.className = 'YouTubeEnhancerLoading';
  128. loadingSpan.textContent = ' (Loading...)';
  129. loadingSpan.style.fontSize = '1em';
  130. loadingSpan.style.color = '#aaaaaa';
  131. return loadingSpan;
  132. }
  133.  
  134. async function addChannelId() {
  135. if (isWatchPage()) {
  136. return;
  137. }
  138.  
  139. try {
  140. const channelNameElement = await waitForElement();
  141. if (!channelNameElement || !channelNameElement.textContent ||
  142. channelNameElement.querySelector('.YouTubeEnhancerRevealChannelID')) {
  143. return;
  144. }
  145.  
  146. const channelName = channelNameElement.textContent.trim().replace('@', '');
  147. if (channelName.length === 0) {
  148. return;
  149. }
  150.  
  151. const cachedChannelId = getFromCache(channelName);
  152. if (cachedChannelId) {
  153. appendChannelIdToElement(channelNameElement, cachedChannelId);
  154. return;
  155. }
  156.  
  157. if (channelName === lastProcessedChannelName || isRequestInProgress) {
  158. return;
  159. }
  160.  
  161. isRequestInProgress = true;
  162. lastProcessedChannelName = channelName;
  163. const loadingElement = createLoadingElement();
  164. channelNameElement.appendChild(loadingElement);
  165. GM_xmlhttpRequest({
  166. method: 'GET',
  167. url: `https://exyezed.vercel.app/api/channel/${channelName}`,
  168. onload: function(response) {
  169. isRequestInProgress = false;
  170. try {
  171. const loadingIndicator = channelNameElement.querySelector('.YouTubeEnhancerLoading');
  172. if (loadingIndicator) {
  173. loadingIndicator.remove();
  174. }
  175. const data = JSON.parse(response.responseText);
  176. const channelId = data.channel_id;
  177. saveToCache(channelName, channelId);
  178. appendChannelIdToElement(channelNameElement, channelId);
  179. } catch (error) {
  180. console.error('Error parsing API response:', error);
  181. const loadingIndicator = channelNameElement.querySelector('.YouTubeEnhancerLoading');
  182. if (loadingIndicator) {
  183. loadingIndicator.remove();
  184. }
  185. }
  186. },
  187. onerror: function(error) {
  188. isRequestInProgress = false;
  189. console.error('Error fetching channel ID:', error);
  190. const loadingIndicator = channelNameElement.querySelector('.YouTubeEnhancerLoading');
  191. if (loadingIndicator) {
  192. loadingIndicator.remove();
  193. }
  194. }
  195. });
  196. } catch (error) {
  197. console.error('Error in addChannelId:', error);
  198. }
  199. }
  200.  
  201. function appendChannelIdToElement(element, channelId) {
  202. if (!element.querySelector('.YouTubeEnhancerRevealChannelID')) {
  203. const channelIdLink = document.createElement('a');
  204. channelIdLink.className = 'YouTubeEnhancerRevealChannelID';
  205. channelIdLink.textContent = ` (${channelId})`;
  206. channelIdLink.href = `https://www.youtube.com/channel/${channelId}`;
  207. channelIdLink.style.fontSize = '1em';
  208. channelIdLink.style.color = '#3ea6ff';
  209. channelIdLink.style.textDecoration = 'none';
  210. channelIdLink.style.cursor = 'pointer';
  211. channelIdLink.addEventListener('mouseover', function() {
  212. this.style.textDecoration = 'none';
  213. });
  214. element.appendChild(channelIdLink);
  215. }
  216. }
  217.  
  218. function handleNavigation() {
  219. lastProcessedChannelName = '';
  220. isRequestInProgress = false;
  221. if (isChannelPage() && !isWatchPage()) {
  222. addChannelId();
  223. }
  224. }
  225.  
  226. loadCacheFromStorage();
  227.  
  228. const observer = new MutationObserver((mutations) => {
  229. for (const mutation of mutations) {
  230. if (mutation.target.nodeName === 'YTD-APP') {
  231. handleNavigation();
  232. }
  233. else if (isChannelPage() && !isWatchPage()) {
  234. addChannelId();
  235. }
  236. }
  237. });
  238.  
  239. const urlObserver = new MutationObserver((mutations) => {
  240. handleNavigation();
  241. });
  242.  
  243. observer.observe(document.body, {
  244. childList: true,
  245. subtree: true
  246. });
  247.  
  248. urlObserver.observe(document.querySelector('title'), {
  249. childList: true
  250. });
  251.  
  252. handleNavigation();
  253.  
  254. document.addEventListener('yt-navigate-start', handleNavigation);
  255. document.addEventListener('yt-navigate-finish', handleNavigation);
  256. console.log('YouTube Enhancer (Reveal Channel ID) is running');
  257. })();