YouTube Enhancer (Secret Stats)

Integrating "Secret Stats" and "Stream Stats" buttons into the channel page, directing users to detailed analytics pages for insights into the channel.

当前为 2024-11-07 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name YouTube Enhancer (Secret Stats)
  3. // @description Integrating "Secret Stats" and "Stream Stats" buttons into the channel page, directing users to detailed analytics pages for insights into the channel.
  4. // @icon https://raw.githubusercontent.com/exyezed/youtube-enhancer/refs/heads/main/extras/youtube-enhancer.png
  5. // @version 1.1
  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 none
  12. // ==/UserScript==
  13.  
  14. (function() {
  15. 'use strict';
  16.  
  17. function createSVGIcon(type) {
  18. const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
  19. const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
  20. svg.setAttribute("viewBox", type === 'stream' ? "0 0 576 512" : "0 0 448 512");
  21. svg.style.width = "20px";
  22. svg.style.height = "20px";
  23. path.setAttribute("fill", "currentColor");
  24. path.setAttribute("d", type === 'stream'
  25. ? "M108.2 71c13.8 11.1 16 31.2 5 45C82.4 154.4 64 203 64 256s18.4 101.6 49.1 140c11.1 13.8 8.8 33.9-5 45s-33.9 8.8-45-5C23.7 386.7 0 324.1 0 256S23.7 125.3 63.2 76c11.1-13.8 31.2-16 45-5zm359.7 0c13.8-11.1 33.9-8.8 45 5C552.3 125.3 576 187.9 576 256s-23.7 130.7-63.2 180c-11.1 13.8-31.2 16-45 5s-16-31.2-5-45c30.7-38.4 49.1-87 49.1-140s-18.4-101.6-49.1-140c-11.1-13.8-8.8-33.9 5-45zM232 256a56 56 0 1 1 112 0 56 56 0 1 1 -112 0zm-27.5-74.7c-17.8 19.8-28.5 46-28.5 74.7s10.8 54.8 28.5 74.7c11.8 13.2 10.7 33.4-2.5 45.2s-33.4 10.7-45.2-2.5C129 342.2 112 301.1 112 256s17-86.2 44.8-117.3c11.8-13.2 32-14.3 45.2-2.5s14.3 32 2.5 45.2zm214.7-42.7C447 169.8 464 210.9 464 256s-17 86.2-44.8 117.3c-11.8 13.2-32 14.3-45.2 2.5s-14.3-32-2.5-45.2c17.8-19.8 28.5-46 28.5-74.7s-10.8-54.8-28.5-74.7c-11.8-13.2-10.7-33.4 2.5-45.2s33.4-10.7 45.2 2.5z"
  26. : "M224 16c-6.7 0-10.8-2.8-15.5-6.1C201.9 5.4 194 0 176 0c-30.5 0-52 43.7-66 89.4C62.7 98.1 32 112.2 32 128c0 14.3 25 27.1 64.6 35.9c-.4 4-.6 8-.6 12.1c0 17 3.3 33.2 9.3 48l-59.9 0C38 224 32 230 32 237.4c0 1.7 .3 3.4 1 5l38.8 96.9C28.2 371.8 0 423.8 0 482.3C0 498.7 13.3 512 29.7 512l388.6 0c16.4 0 29.7-13.3 29.7-29.7c0-58.5-28.2-110.4-71.7-143L415 242.4c.6-1.6 1-3.3 1-5c0-7.4-6-13.4-13.4-13.4l-59.9 0c6-14.8 9.3-31 9.3-48c0-4.1-.2-8.1-.6-12.1C391 155.1 416 142.3 416 128c0-15.8-30.7-29.9-78-38.6C324 43.7 302.5 0 272 0c-18 0-25.9 5.4-32.5 9.9c-4.8 3.3-8.8 6.1-15.5 6.1zm56 208l-12.4 0c-16.5 0-31.1-10.6-36.3-26.2c-2.3-7-12.2-7-14.5 0c-5.2 15.6-19.9 26.2-36.3 26.2L168 224c-22.1 0-40-17.9-40-40l0-14.4c28.2 4.1 61 6.4 96 6.4s67.8-2.3 96-6.4l0 14.4c0 22.1-17.9 40-40 40zm-88 96l16 32L176 480 128 288l64 32zm128-32L272 480 240 352l16-32 64-32z");
  27. svg.appendChild(path);
  28. return svg;
  29. }
  30.  
  31. function createButton(text, iconType, id) {
  32. const button = document.createElement('button');
  33. button.id = id;
  34. button.className = 'yt-spec-button-shape-next yt-spec-button-shape-next--tonal yt-spec-button-shape-next--mono yt-spec-button-shape-next--size-m';
  35. Object.assign(button.style, {
  36. display: 'inline-flex',
  37. alignItems: 'center',
  38. justifyContent: 'center',
  39. minWidth: 'auto',
  40. padding: '0 12px',
  41. height: '36px',
  42. fontSize: '14px',
  43. lineHeight: '36px',
  44. fontWeight: '500',
  45. gap: '6px'
  46. });
  47.  
  48. const svgIcon = createSVGIcon(iconType);
  49. const buttonText = document.createTextNode(text);
  50.  
  51. button.appendChild(svgIcon);
  52. button.appendChild(buttonText);
  53.  
  54. return button;
  55. }
  56.  
  57. function createButtonWrapper(button) {
  58. const buttonWrapper = document.createElement('div');
  59. buttonWrapper.className = 'yt-flexible-actions-view-model-wiz__action';
  60. buttonWrapper.appendChild(button);
  61. return buttonWrapper;
  62. }
  63.  
  64. function addStatsButtons() {
  65. const targetElement = document.querySelector('yt-flexible-actions-view-model');
  66. const lastAction = targetElement ? targetElement.querySelector('div.yt-flexible-actions-view-model-wiz__action:last-child') : null;
  67.  
  68. if (lastAction && !document.querySelector('#YouTubeEnhancerChannelSecretStats')) {
  69. const secretStatsButton = createButton('Secret Stats', 'secret', 'YouTubeEnhancerChannelSecretStats');
  70. const streamStatsButton = createButton('Stream Stats', 'stream', 'YouTubeEnhancerStreamStats');
  71.  
  72. secretStatsButton.addEventListener('click', function() {
  73. const channelIdentifier = getChannelIdentifier();
  74. if (channelIdentifier) {
  75. const statsUrl = `https://exyezed.vercel.app/stats/secret/${channelIdentifier}`;
  76. window.open(statsUrl, '_blank');
  77. } else {
  78. alert('Could not determine channel identifier. Please try again on a channel page.');
  79. }
  80. });
  81.  
  82. streamStatsButton.addEventListener('click', function() {
  83. const channelIdentifier = getChannelIdentifier();
  84. if (channelIdentifier) {
  85. const statsUrl = `https://exyezed.vercel.app/stats/stream/${channelIdentifier}`;
  86. window.open(statsUrl, '_blank');
  87. } else {
  88. alert('Could not determine channel identifier. Please try again on a channel page.');
  89. }
  90. });
  91.  
  92. const secretStatsWrapper = createButtonWrapper(secretStatsButton);
  93. const streamStatsWrapper = createButtonWrapper(streamStatsButton);
  94.  
  95. lastAction.insertAdjacentElement('afterend', streamStatsWrapper);
  96. lastAction.insertAdjacentElement('afterend', secretStatsWrapper);
  97. }
  98. }
  99.  
  100. function getChannelIdentifier() {
  101. const channelId = getChannelId();
  102. if (channelId) return channelId;
  103. return getChannelHandle();
  104. }
  105.  
  106. function getChannelId() {
  107. const channelIdMeta = document.querySelector('meta[itemprop="channelId"]');
  108. if (channelIdMeta) {
  109. return channelIdMeta.content;
  110. }
  111.  
  112. const urlParams = new URLSearchParams(window.location.search);
  113. return urlParams.get('channel') || extractChannelIdFromUrl();
  114. }
  115.  
  116. function getChannelHandle() {
  117. const path = window.location.pathname;
  118. const matches = path.match(/\/@([^/]+)/);
  119. return matches ? matches[1] : null;
  120. }
  121.  
  122. function extractChannelIdFromUrl() {
  123. const path = window.location.pathname;
  124. const matches = path.match(/\/(channel|user|c)\/([^/]+)/);
  125. return matches ? matches[2] : null;
  126. }
  127.  
  128. function init() {
  129. if (window.location.pathname.includes('/channel/') ||
  130. window.location.pathname.includes('/@') ||
  131. window.location.pathname.includes('/c/') ||
  132. window.location.pathname.includes('/user/')) {
  133. observePageChanges();
  134. }
  135. }
  136.  
  137. function observePageChanges() {
  138. const observer = new MutationObserver((mutations) => {
  139. const targetElement = document.querySelector('yt-flexible-actions-view-model');
  140. if (targetElement && targetElement.querySelector('div.yt-flexible-actions-view-model-wiz__action')) {
  141. addStatsButtons();
  142. observer.disconnect();
  143. }
  144. });
  145.  
  146. observer.observe(document.body, {
  147. childList: true,
  148. subtree: true
  149. });
  150. }
  151.  
  152. init();
  153.  
  154. window.addEventListener('yt-navigate-finish', init);
  155. console.log('YouTube Enhancer (Secret Stats) is running');
  156. })();