Google Infinite Scroll

-

  1. // ==UserScript==
  2. // @name:ko 구글 무한 스크롤
  3. // @name Google Infinite Scroll
  4.  
  5. // @description:ko -
  6. // @description -
  7.  
  8. // @namespace https://ndaesik.tistory.com/
  9. // @version 2025.01.10.08.44
  10. // @author ndaesik
  11. // @icon https://t1.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=https://www.google.com
  12.  
  13. // @match *://www.google.com/search*
  14. // @grant GM_xmlhttpRequest
  15. // @run-at document-end
  16. // @connect *
  17. // ==/UserScript==
  18.  
  19. if(new URL(window.location.href).searchParams.has('udm')) return;
  20.  
  21. document.head.appendChild(Object.assign(document.createElement('style'), {
  22. textContent: `
  23. #botstuff [role="navigation"] { display: none !important; }
  24. .youtube-thumbnail {
  25. object-fit: cover !important;
  26. width:100%;
  27. height:100%;
  28. }
  29. img[src="data:text/plain;base64,"] {
  30. opacity: 0 !important;
  31. }
  32. `
  33. }));
  34.  
  35. const PLACEHOLDER_SELECTOR = '[src="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="]';
  36.  
  37. const getYoutubeVideoId = url => {
  38. try {
  39. const urlObj = new URL(url);
  40. return urlObj.hostname.includes('youtube.com') ? urlObj.searchParams.get('v') : null;
  41. } catch (error) {
  42. return (console.error('Error parsing YouTube URL:', error), null);
  43. }
  44. };
  45.  
  46. async function replaceYoutubeThumbnail(imgElement) {
  47. try {
  48. const container = imgElement.closest('[data-curl*="youtube.com/watch"]');
  49. if (!container) return;
  50. const videoId = getYoutubeVideoId(container.getAttribute('data-curl'));
  51. if (!videoId) return;
  52. const dataUrl = await convertImageToDataUrl(`https://img.youtube.com/vi/${videoId}/sddefault.jpg`);
  53. dataUrl && (imgElement.src = dataUrl, imgElement.classList.add('youtube-thumbnail'));
  54. } catch (error) {
  55. console.error('Error replacing YouTube thumbnail:', error);
  56. imgElement.src.startsWith('data:text/plain;base64,') && (imgElement.style.opacity = '0');
  57. }
  58. }
  59.  
  60. async function convertImageToDataUrl(imageUrl) {
  61. return new Promise(resolve =>
  62. GM_xmlhttpRequest({
  63. method: "GET",
  64. url: imageUrl,
  65. responseType: "blob",
  66. onload: ({response}) => {
  67. const reader = new FileReader();
  68. reader.onloadend = () => resolve(reader.result);
  69. reader.readAsDataURL(response);
  70. },
  71. onerror: error => (console.error('Error fetching image:', error), resolve(null))
  72. })
  73. ).catch(error => (console.error('Error converting image:', error), null));
  74. }
  75.  
  76. async function getOGImage(url) {
  77. return new Promise((resolve) =>
  78. GM_xmlhttpRequest({
  79. method: "GET",
  80. url,
  81. onload: ({responseText}) => {
  82. const doc = new DOMParser().parseFromString(responseText, "text/html");
  83. resolve(doc.querySelector('meta[property="og:image"]')?.content || doc.querySelector('img[src^="http"]')?.src || null);
  84. },
  85. onerror: (error) => (console.error('Error fetching page:', error), resolve(null))
  86. })
  87. ).catch(() => null);
  88. }
  89.  
  90. async function replacePlaceholderImage(imgElement) {
  91. try {
  92. let url, ogImageUrl, dataUrl;
  93. const youtubeContainer = imgElement.closest('[data-curl*="youtube.com/watch"]');
  94. youtubeContainer ? (await replaceYoutubeThumbnail(imgElement), imgElement.classList.add('youtube-thumbnail')) :
  95. (url = imgElement.parentElement?.parentElement?.parentElement?.parentElement?.querySelector('a[data-ved]')?.href) ?
  96. (ogImageUrl = await getOGImage(url)) ?
  97. (dataUrl = await convertImageToDataUrl(ogImageUrl)) ? imgElement.src = dataUrl : null
  98. : null
  99. : null;
  100. } catch (error) { console.debug(error); }
  101. }
  102.  
  103. const fetchNextPage = async pageNumber => {
  104. const baseUrl = new URL(window.location.href);
  105. const text = await (await fetch(`${baseUrl.origin}${baseUrl.pathname}?q=${baseUrl.searchParams.get('q')}&start=${pageNumber * 10}`)).text();
  106. const newDoc = new DOMParser().parseFromString(text, 'text/html');
  107. const container = document.createElement('div');
  108. container.id = `page-${pageNumber}`;
  109. container.style.cssText = 'margin-top: 20px;';
  110. newDoc.querySelectorAll('#rso > div').forEach(result => container.appendChild(result.cloneNode(true)));
  111. const lastAddedPage = document.querySelector(`#page-${pageNumber - 1}`) || document.querySelector('#botstuff');
  112. lastAddedPage.after(container);
  113.  
  114. const newPlaceholders = container.querySelectorAll(PLACEHOLDER_SELECTOR);
  115. const youtubeResults = container.querySelectorAll('[data-curl*="youtube.com/watch"]');
  116.  
  117. if (youtubeResults.length > 0) {
  118. newPlaceholders.forEach(replacePlaceholderImage);
  119. }
  120.  
  121. return !!newDoc.querySelector('#pnnext');
  122. };
  123.  
  124. let [pageNumber, isLoading, hasMore] = [1, false, true];
  125.  
  126. window.addEventListener('scroll', async () => {
  127. if (!isLoading && hasMore && window.innerHeight + window.pageYOffset >= document.documentElement.offsetHeight - 1000) {
  128. isLoading = true;
  129. hasMore = await fetchNextPage(pageNumber);
  130. pageNumber += hasMore ? 1 : 0;
  131. isLoading = false;
  132. }
  133. });

QingJ © 2025

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