Google Infinite Scroll

-

目前為 2025-01-09 提交的版本,檢視 最新版本

// ==UserScript==
// @name:ko           구글 무한 스크롤
// @name              Google Infinite Scroll

// @description:ko    -
// @description       -

// @namespace         https://ndaesik.tistory.com/
// @version           2025.01.09.10.50
// @author            ndaesik
// @icon              https://t1.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=https://www.google.com

// @match             *://www.google.com/search*
// @grant             GM_xmlhttpRequest
// @run-at            document-end
// @connect           *
// ==/UserScript==

if(new URL(window.location.href).searchParams.has('udm')) return;

document.head.appendChild(Object.assign(document.createElement('style'), {
    textContent: `
        #botstuff [role="navigation"] { display: none !important; }
        .youtube-thumbnail {
            object-fit: cover !important;
        }
        img[src="data:text/plain;base64,"] {
            opacity: 0 !important;
        }
    `
}));

const PLACEHOLDER_SELECTOR = '[src="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="]';

function getYoutubeVideoId(url) {
    try {
        const urlObj = new URL(url);
        if (urlObj.hostname.includes('youtube.com')) {
            return urlObj.searchParams.get('v');
        }
        return null;
    } catch (error) {
        console.error('Error parsing YouTube URL:', error);
        return null;
    }
}

async function replaceYoutubeThumbnail(imgElement) {
    try {
        const container = imgElement.closest('[data-curl*="youtube.com/watch"]');
        if (!container) return;

        const youtubeUrl = container.getAttribute('data-curl');
        const videoId = getYoutubeVideoId(youtubeUrl);
        if (!videoId) return;
        const thumbnailUrl = `https://img.youtube.com/vi/${videoId}/sddefault.jpg`;
        const dataUrl = await convertImageToDataUrl(thumbnailUrl);

        if (dataUrl) {
            imgElement.src = dataUrl;
            imgElement.classList.add('youtube-thumbnail');
        }
    } catch (error) {
        console.error('Error replacing YouTube thumbnail:', error);
        if (imgElement.src.startsWith('data:text/plain;base64,')) {
            imgElement.style.opacity = '0';
        }
    }
}

async function convertImageToDataUrl(imageUrl) {
    try {
        return new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                method: "GET",
                url: imageUrl,
                responseType: "blob",
                onload: function(response) {
                    const reader = new FileReader();
                    reader.onloadend = function() {
                        resolve(reader.result);
                    };
                    reader.readAsDataURL(response.response);
                },
                onerror: function(error) {
                    console.error('Error fetching image:', error);
                    resolve(null);
                }
            });
        });
    } catch (error) {
        console.error('Error converting image:', error);
        return null;
    }
}

async function getOGImage(url) {
    try {
        return new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                method: "GET",
                url: url,
                onload: function(response) {
                    const parser = new DOMParser();
                    const doc = parser.parseFromString(response.responseText, "text/html");

                    const ogImage = doc.querySelector('meta[property="og:image"]')?.content;
                    if (ogImage) {
                        resolve(ogImage);
                        return;
                    }

                    const firstImage = doc.querySelector('img[src^="http"]')?.src;
                    resolve(firstImage || null);
                },
                onerror: function(error) {
                    console.error('Error fetching page:', error);
                    resolve(null);
                }
            });
        });
    } catch (error) {
        console.error('Error in getOGImage:', error);
        return null;
    }
}

async function replacePlaceholderImage(imgElement) {
    try {
        const youtubeContainer = imgElement.closest('[data-curl*="youtube.com/watch"]');
        if (youtubeContainer) {
            await replaceYoutubeThumbnail(imgElement);
            imgElement.classList.add('youtube-thumbnail');
            return;
        }

        const url = imgElement.parentElement?.parentElement?.parentElement?.parentElement?.querySelector('a[data-ved]')?.href;
        if (!url) return;

        const ogImageUrl = await getOGImage(url);
        if (!ogImageUrl) return;

        const dataUrl = await convertImageToDataUrl(ogImageUrl);
        if (dataUrl) {
            imgElement.src = dataUrl;
        }
    } catch (error) {
        console.error('Error replacing placeholder:', error);
    }
}

const fetchNextPage = async pageNumber => {
    const baseUrl = new URL(window.location.href);
    const text = await (await fetch(`${baseUrl.origin}${baseUrl.pathname}?q=${baseUrl.searchParams.get('q')}&start=${pageNumber * 10}`)).text();
    const newDoc = new DOMParser().parseFromString(text, 'text/html');
    const container = document.createElement('div');
    container.id = `page-${pageNumber}`;
    container.style.cssText = 'margin-top: 20px;';
    newDoc.querySelectorAll('#rso > div').forEach(result => container.appendChild(result.cloneNode(true)));
    const lastAddedPage = document.querySelector(`#page-${pageNumber - 1}`) || document.querySelector('#botstuff');
    lastAddedPage.after(container);

    // 새로 추가된 페이지의 이미지만 처리
    const newPlaceholders = container.querySelectorAll(PLACEHOLDER_SELECTOR);
    const youtubeResults = container.querySelectorAll('[data-curl*="youtube.com/watch"]');

    // 유튜브 결과가 있는 경우에만 이미지 처리 수행
    if (youtubeResults.length > 0) {
        newPlaceholders.forEach(replacePlaceholderImage);
    }

    return !!newDoc.querySelector('#pnnext');
};

let [pageNumber, isLoading, hasMore] = [1, false, true];

window.addEventListener('scroll', async () => {
    if (!isLoading && hasMore && window.innerHeight + window.pageYOffset >= document.documentElement.offsetHeight - 1000) {
        isLoading = true;
        hasMore = await fetchNextPage(pageNumber);
        pageNumber += hasMore ? 1 : 0;
        isLoading = false;
    }
});

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址