Google Search Link Checker

Adds 'no access' or safety warnings to Google search results

  1. // ==UserScript==
  2. // @name Google Search Link Checker
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.4
  5. // @description Adds 'no access' or safety warnings to Google search results
  6. // @author Bui Quoc Dung
  7. // @match https://www.google.com/search*
  8. // @grant GM_xmlhttpRequest
  9. // @connect *
  10. // ==/UserScript==
  11.  
  12. (function () {
  13. 'use strict';
  14.  
  15. async function checkLinkStatus(url) {
  16. return new Promise((resolve) => {
  17. GM_xmlhttpRequest({
  18. method: "HEAD",
  19. url,
  20. timeout: 5000,
  21. onload: res => {
  22. resolve((res.status >= 200 && res.status < 400) || [0, 403, 405, 429].includes(res.status));
  23. },
  24. onerror: (error) => {
  25. resolve(!!(error && error.status === 0 && error.finalUrl));
  26. },
  27. ontimeout: () => {
  28. resolve(false);
  29. }
  30. });
  31. });
  32. }
  33.  
  34. async function checkSafetyStatus(url) {
  35. try {
  36. const hostname = new URL(url).hostname;
  37. const apiUrl = `https://check.getsafeonline.org/check/${hostname}?inputUrl=${hostname}`;
  38.  
  39. return new Promise((resolve) => {
  40. GM_xmlhttpRequest({
  41. method: "GET",
  42. url: apiUrl,
  43. timeout: 10000,
  44. onload: res => {
  45. if (res.status >= 200 && res.status < 300) {
  46. const parser = new DOMParser();
  47. const doc = parser.parseFromString(res.responseText, 'text/html');
  48. const warningElement = doc.querySelector('div.font-light.text-2xl.mt-6 span.text-primary-red.font-bold');
  49. if (warningElement) {
  50. resolve(warningElement.textContent.trim());
  51. } else {
  52. resolve(null);
  53. }
  54. } else {
  55. resolve(null);
  56. }
  57. },
  58. onerror: () => resolve(null),
  59. ontimeout: () => resolve(null)
  60. });
  61. });
  62. } catch (e) {
  63. console.error("Could not parse URL for safety check:", url, e);
  64. return Promise.resolve(null);
  65. }
  66. }
  67.  
  68. function processLink(link) {
  69. if (!link || link.dataset.statusChecked) return;
  70. link.dataset.statusChecked = 'true';
  71.  
  72. const url = link.href;
  73. if (!url || url.startsWith('javascript:') || url.startsWith('#')) return;
  74.  
  75. const container = link.closest('.MjjYud');
  76. if (!container || container.querySelector('.access-safety-info')) return;
  77.  
  78. const description = container.querySelector('.VwiC3b');
  79. if (!description) return;
  80.  
  81. const infoDiv = document.createElement('div');
  82. infoDiv.className = 'access-safety-info';
  83. infoDiv.style.marginTop = '4px';
  84. infoDiv.style.fontSize = '1em';
  85. infoDiv.style.display = 'flex';
  86. infoDiv.style.flexWrap = 'wrap';
  87. infoDiv.style.gap = '8px';
  88.  
  89. description.parentElement.appendChild(infoDiv);
  90.  
  91. checkLinkStatus(url).then(accessible => {
  92. if (!accessible) {
  93. const tag = document.createElement('span');
  94. tag.textContent = '[no access]';
  95. tag.style.color = 'orange';
  96. infoDiv.appendChild(tag);
  97. }
  98. });
  99.  
  100. checkSafetyStatus(url).then(safetyText => {
  101. if (safetyText) {
  102. const tag = document.createElement('span');
  103. tag.textContent = `[${safetyText}]`;
  104. tag.style.color = 'red';
  105. infoDiv.appendChild(tag);
  106. }
  107. });
  108. }
  109.  
  110. let scanTimeoutId = null;
  111. function scanLinksDebounced() {
  112. clearTimeout(scanTimeoutId);
  113. scanTimeoutId = setTimeout(() => {
  114. document.querySelectorAll('div.MjjYud a:has(h3)').forEach(processLink);
  115. }, 300);
  116. }
  117.  
  118. const observer = new MutationObserver(mutations => {
  119. for (const m of mutations) {
  120. for (const node of m.addedNodes) {
  121. if (node.nodeType === 1 &&
  122. (node.querySelector?.('div.MjjYud a:has(h3)') || node.matches?.('.MjjYud'))) {
  123. scanLinksDebounced();
  124. return;
  125. }
  126. }
  127. }
  128. });
  129.  
  130. const container = document.getElementById('center_col') || document.getElementById('rcnt') || document.body;
  131. observer.observe(container, { childList: true, subtree: true });
  132.  
  133. let lastURL = location.href;
  134. setInterval(() => {
  135. if (location.href !== lastURL) {
  136. lastURL = location.href;
  137. setTimeout(scanLinksDebounced, 1000);
  138. }
  139. }, 1000);
  140.  
  141.  
  142. if (document.readyState === 'loading') {
  143. window.addEventListener('DOMContentLoaded', () => setTimeout(scanLinksDebounced, 1000));
  144. } else {
  145. setTimeout(scanLinksDebounced, 1000);
  146. }
  147. })();

QingJ © 2025

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