YouTube View Count in Google Search Results

Displays YouTube video view counts directly in Google search results

  1. // ==UserScript==
  2. // @name YouTube View Count in Google Search Results
  3. // @namespace https://github.com/Haris00911/GoogleSearchViewCounter/
  4. // @version 1.0.0
  5. // @description Displays YouTube video view counts directly in Google search results
  6. // @author Haris00911
  7. // @match https://www.google.com/search*
  8. // @grant GM_xmlhttpRequest
  9. // @grant GM_setValue
  10. // @grant GM_getValue
  11. // @grant GM_registerMenuCommand
  12. // @license MIT
  13. // @homepage https://github.com/Haris00911/GoogleSearchViewCounter/
  14. // ==/UserScript==
  15.  
  16. (function() {
  17. 'use strict';
  18.  
  19. let apiKey = GM_getValue('youtubeApiKey');
  20.  
  21. if (!apiKey) {
  22. apiKey = prompt('Please enter your YouTube Data API key:');
  23. if (apiKey) {
  24. GM_setValue('youtubeApiKey', apiKey);
  25. } else {
  26. alert('You need to provide an API key for the script to work.');
  27. return;
  28. }
  29. }
  30.  
  31. GM_registerMenuCommand('Change YouTube API Key', function() {
  32. const newKey = prompt('Please enter your YouTube Data API key:', apiKey);
  33. if (newKey) {
  34. GM_setValue('youtubeApiKey', newKey);
  35. apiKey = newKey;
  36. alert('API key updated. Please reload the page.');
  37. }
  38. });
  39.  
  40. const processedVideoIDs = new Set();
  41. const videoIDToLinks = {};
  42.  
  43. function getYouTubeVideoID(url) {
  44. try {
  45. const urlObj = new URL(url);
  46. if (urlObj.hostname === 'youtu.be') {
  47. return urlObj.pathname.substr(1);
  48. } else if (urlObj.hostname.includes('youtube.com')) {
  49. return urlObj.searchParams.get('v');
  50. }
  51. } catch (e) {
  52. return null;
  53. }
  54. return null;
  55. }
  56.  
  57. function formatViewCount(viewCount) {
  58. const count = parseInt(viewCount, 10);
  59. if (count >= 1e9) {
  60. return (count / 1e9).toFixed(1) + 'B';
  61. } else if (count >= 1e6) {
  62. return (count / 1e6).toFixed(1) + 'M';
  63. } else if (count >= 1e3) {
  64. return (count / 1e3).toFixed(1) + 'K';
  65. } else {
  66. return count.toString();
  67. }
  68. }
  69.  
  70. function insertViewCount(link, viewCount) {
  71. const formattedViewCount = formatViewCount(viewCount);
  72. const span = document.createElement('span');
  73. span.style.color = '#555';
  74. span.style.marginLeft = '5px';
  75. span.textContent = `(${formattedViewCount} views)`;
  76.  
  77. // Insert the span after the link
  78. link.parentNode.insertBefore(span, link.nextSibling);
  79. }
  80.  
  81. function fetchVideoStatistics(videoIDs) {
  82. const apiURL = 'https://www.googleapis.com/youtube/v3/videos';
  83. const params = new URLSearchParams();
  84. params.append('part', 'statistics');
  85. params.append('id', videoIDs.join(','));
  86. params.append('key', apiKey);
  87.  
  88. const url = `${apiURL}?${params.toString()}`;
  89.  
  90. GM_xmlhttpRequest({
  91. method: 'GET',
  92. url: url,
  93. onload: function(response) {
  94. if (response.status === 200) {
  95. const data = JSON.parse(response.responseText);
  96. if (data.items) {
  97. data.items.forEach(function(item) {
  98. const videoID = item.id;
  99. const viewCount = item.statistics.viewCount;
  100. const links = videoIDToLinks[videoID];
  101. if (links) {
  102. links.forEach(function(link) {
  103. insertViewCount(link, viewCount);
  104. });
  105. }
  106. });
  107. }
  108. } else {
  109. console.error('Failed to fetch video statistics', response);
  110. }
  111. },
  112. onerror: function(error) {
  113. console.error('Error fetching video statistics', error);
  114. }
  115. });
  116. }
  117.  
  118. function processPage() {
  119. const searchResults = document.getElementById('search');
  120. if (!searchResults) return;
  121.  
  122. const youtubeLinks = searchResults.querySelectorAll('a[href*="youtube.com/watch?v="], a[href*="youtu.be/"]');
  123.  
  124. const videoIDs = new Set();
  125. youtubeLinks.forEach(function(link) {
  126. const videoID = getYouTubeVideoID(link.href);
  127. if (videoID && !processedVideoIDs.has(videoID)) {
  128. videoIDs.add(videoID);
  129. processedVideoIDs.add(videoID);
  130. if (!videoIDToLinks[videoID]) {
  131. videoIDToLinks[videoID] = [];
  132. }
  133. videoIDToLinks[videoID].push(link);
  134. }
  135. });
  136.  
  137. const videoIDArray = Array.from(videoIDs);
  138. if (videoIDArray.length === 0) return;
  139.  
  140. const batches = [];
  141. const batchSize = 50;
  142. for (let i = 0; i < videoIDArray.length; i += batchSize) {
  143. batches.push(videoIDArray.slice(i, i + batchSize));
  144. }
  145.  
  146. batches.forEach(function(batch) {
  147. fetchVideoStatistics(batch);
  148. });
  149. }
  150.  
  151. processPage();
  152.  
  153. const observer = new MutationObserver(function(mutations) {
  154. mutations.forEach(function(mutation) {
  155. mutation.addedNodes.forEach(function(node) {
  156. if (node.nodeType === Node.ELEMENT_NODE) {
  157. if (node.closest('#search')) {
  158. processPage();
  159. }
  160. }
  161. });
  162. });
  163. });
  164.  
  165. observer.observe(document.body, { childList: true, subtree: true });
  166. })();