YouTube View Count in Google Search Results

Displays YouTube video view counts directly in Google search results

// ==UserScript==
// @name         YouTube View Count in Google Search Results
// @namespace    https://github.com/Haris00911/GoogleSearchViewCounter/
// @version      1.0.0
// @description  Displays YouTube video view counts directly in Google search results
// @author       Haris00911
// @match        https://www.google.com/search*
// @grant        GM_xmlhttpRequest
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// @license      MIT
// @homepage     https://github.com/Haris00911/GoogleSearchViewCounter/
// ==/UserScript==

(function() {
    'use strict';

    let apiKey = GM_getValue('youtubeApiKey');

    if (!apiKey) {
        apiKey = prompt('Please enter your YouTube Data API key:');
        if (apiKey) {
            GM_setValue('youtubeApiKey', apiKey);
        } else {
            alert('You need to provide an API key for the script to work.');
            return;
        }
    }

    GM_registerMenuCommand('Change YouTube API Key', function() {
        const newKey = prompt('Please enter your YouTube Data API key:', apiKey);
        if (newKey) {
            GM_setValue('youtubeApiKey', newKey);
            apiKey = newKey;
            alert('API key updated. Please reload the page.');
        }
    });

    const processedVideoIDs = new Set();
    const videoIDToLinks = {};

    function getYouTubeVideoID(url) {
        try {
            const urlObj = new URL(url);
            if (urlObj.hostname === 'youtu.be') {
                return urlObj.pathname.substr(1);
            } else if (urlObj.hostname.includes('youtube.com')) {
                return urlObj.searchParams.get('v');
            }
        } catch (e) {
            return null;
        }
        return null;
    }

    function formatViewCount(viewCount) {
        const count = parseInt(viewCount, 10);
        if (count >= 1e9) {
            return (count / 1e9).toFixed(1) + 'B';
        } else if (count >= 1e6) {
            return (count / 1e6).toFixed(1) + 'M';
        } else if (count >= 1e3) {
            return (count / 1e3).toFixed(1) + 'K';
        } else {
            return count.toString();
        }
    }

    function insertViewCount(link, viewCount) {
        const formattedViewCount = formatViewCount(viewCount);
        const span = document.createElement('span');
        span.style.color = '#555';
        span.style.marginLeft = '5px';
        span.textContent = `(${formattedViewCount} views)`;

        // Insert the span after the link
        link.parentNode.insertBefore(span, link.nextSibling);
    }

    function fetchVideoStatistics(videoIDs) {
        const apiURL = 'https://www.googleapis.com/youtube/v3/videos';
        const params = new URLSearchParams();
        params.append('part', 'statistics');
        params.append('id', videoIDs.join(','));
        params.append('key', apiKey);

        const url = `${apiURL}?${params.toString()}`;

        GM_xmlhttpRequest({
            method: 'GET',
            url: url,
            onload: function(response) {
                if (response.status === 200) {
                    const data = JSON.parse(response.responseText);
                    if (data.items) {
                        data.items.forEach(function(item) {
                            const videoID = item.id;
                            const viewCount = item.statistics.viewCount;
                            const links = videoIDToLinks[videoID];
                            if (links) {
                                links.forEach(function(link) {
                                    insertViewCount(link, viewCount);
                                });
                            }
                        });
                    }
                } else {
                    console.error('Failed to fetch video statistics', response);
                }
            },
            onerror: function(error) {
                console.error('Error fetching video statistics', error);
            }
        });
    }

    function processPage() {
        const searchResults = document.getElementById('search');
        if (!searchResults) return;

        const youtubeLinks = searchResults.querySelectorAll('a[href*="youtube.com/watch?v="], a[href*="youtu.be/"]');

        const videoIDs = new Set();
        youtubeLinks.forEach(function(link) {
            const videoID = getYouTubeVideoID(link.href);
            if (videoID && !processedVideoIDs.has(videoID)) {
                videoIDs.add(videoID);
                processedVideoIDs.add(videoID);
                if (!videoIDToLinks[videoID]) {
                    videoIDToLinks[videoID] = [];
                }
                videoIDToLinks[videoID].push(link);
            }
        });

        const videoIDArray = Array.from(videoIDs);
        if (videoIDArray.length === 0) return;

        const batches = [];
        const batchSize = 50;
        for (let i = 0; i < videoIDArray.length; i += batchSize) {
            batches.push(videoIDArray.slice(i, i + batchSize));
        }

        batches.forEach(function(batch) {
            fetchVideoStatistics(batch);
        });
    }

    processPage();

    const observer = new MutationObserver(function(mutations) {
        mutations.forEach(function(mutation) {
            mutation.addedNodes.forEach(function(node) {
                if (node.nodeType === Node.ELEMENT_NODE) {
                    if (node.closest('#search')) {
                        processPage();
                    }
                }
            });
        });
    });

    observer.observe(document.body, { childList: true, subtree: true });
})();