Greasy Fork镜像 支持简体中文。

FaviconizeGoogle

Adds favicons next to Google search results. Also works for Ecosia, StartPage and Searx.

  1. // ==UserScript==
  2. // @name FaviconizeGoogle
  3. // @namespace http://userscripts.org/users/89794 (joeytwiddle)
  4. // @description Adds favicons next to Google search results. Also works for Ecosia, StartPage and Searx.
  5. // @homepage https://gf.qytechs.cn/en/scripts/7664-faviconizegoogle
  6. // @downstreamURL http://userscripts.org/scripts/source/48636.user.js
  7. // @license ISC
  8. // @version 1.8.7
  9. // @include /https?:\/\/((www\.)?|encrypted\.)google\.[a-z]{2,3}(\.[a-z]{2})?\/(search|webhp|\?gws_rd|\?gfe_rd)?.*/
  10. // @include /https?:\/\/(www\.|[a-z0-9-]*\.)?startpage\.com\/.*/
  11. // @include /https?:\/\/(www\.)?ecosia\.org\/(search|news|videos)?.*/
  12. // @include /https?:\/\/searx\..*\/.*
  13. // @exclude /https?:\/\/((www\.)?|encrypted\.)google\.[a-z]{2,3}(\.[a-z]{2})?\/maps[/?#]*.*/
  14. // These are some popular searx sites which don't have 'searx' in the domain:
  15. // @match https://spot.ecloud.global/*
  16. // @match https://search.disroot.org/*
  17. // @match https://search.stinpriza.org/*
  18. // @match https://zoek.anchel.nl/*
  19. // @match https://search.mdosch.de/*
  20. // @match https://metasearch.nl/*
  21. // @match https://suche.uferwerk.org/*
  22. // @match https://search.snopyta.org/*
  23. // @match https://www.gruble.de/*
  24. // End popular searx sites.
  25. // @grant GM_xmlhttpRequest
  26. // @grant GM_addStyle
  27. // @connect *
  28. // ==/UserScript==
  29.  
  30. // Google's favicon service now redirects to load-balanced domains: t0, t1, t2, t3, ...
  31. // - https://t3.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=http://docs.nginx.com&size=16
  32.  
  33. var placeFaviconByUrl = false; // The little green link below the article title
  34. var placeFaviconAfter = false; // Display after the link instead of before it
  35. var placeFaviconInsideLink = false; // Makes the favicon clickable but may also get underlined
  36. var placeFaviconOffTheLeft = true; // Makes the favicon sit out to the left of the main column (not on startpage)
  37. var iconSize = 1.3;
  38. var centraliseIconVertically = iconSize < 2; // For smaller icon sizes, we center-align with the text, for larger we top-align
  39.  
  40. // With thanks to:
  41. // - NV
  42. // - darkred
  43. // - SirCumference
  44.  
  45. // Some alternatives/remixes:
  46. // - https://github.com/NV/faviconize-google.js (Chrome extension)
  47. // - https://gf.qytechs.cn/en/scripts/12395-google-favicons (works with Endless Google)
  48. // - https://gist.github.com/Sir-Cumference/223d36cbec6473b0e6927e5c50c11568 (very short code, @match works with Greasemonkey)
  49.  
  50. // 2020-10-25 Added support for some of the searx sites (provided they have 'searx' in the domain).
  51. // 2020-04-21 Re-enabled for Ecosia and StartPage, bypass CSP by fetching the images using GM_xmlhttpRequest.
  52. // 2020-02-02 Reenabled Google again, because Google has stopped showing favicons.
  53. // 2020-01-22 Disabled Google, because Google is now displaying favicons itself.
  54. // 2020-01-22 Disabled Ecosia, because their CSP is blocking images
  55. // 2018-10-14 Added support for ecosia.org!
  56. // 2018-10-14 Disabled startpage.com, because their CSP has blocked favicons from loading.
  57. // 2018-07-31 Dropped support for news.google.com, because it is now linking to local URLs, instead of to external websites.
  58.  
  59. // TODO: The relative positioning of the icon appears a bit off for sub-links of the main result.
  60.  
  61. // DONE: Provided more options where to place favicon: by the link or by the
  62. // url, before or after, inside or outside the link. However in my opinion
  63. // they all suck except the default. ;)
  64.  
  65. // Broken images would be messy, but Firefox seems to hide them after a while
  66. // anyway. We do still see the gap from the image's padding though!
  67. // It might be desirable to check each image actually exists/loads, or remove it.
  68. // Is that possible, without making an http request ourselves?
  69.  
  70. // Third-party host URL detection is implemented leniently, and accordingly
  71. // hostname extraction implemented aggressively, which results in favicons
  72. // being given to unexpected things like bookmarklets which contain a site url.
  73.  
  74. var isEcosia = document.location.hostname === 'www.ecosia.org' || document.location.hostname === 'ecosia.org';
  75. var isStartpage = document.location.host.match(/\bstartpage\b/);
  76. var isSearx = document.location.host.match(/\bsearx\b/) || document.querySelector('.searx-navbar') || document.querySelector('script[src*=searx]');
  77.  
  78. //var bypassCSP = isEcosia || isStartpage;
  79. // Enable it for all sites. I think it's more secure for us to fetch the icon by GM_xhr rather than injecting it into the page.
  80. var bypassCSP = true && typeof GM_xmlhttpRequest !== 'undefined';
  81.  
  82. if (isEcosia) {
  83. iconSize = iconSize * 0.6;
  84. }
  85.  
  86. if (isStartpage) {
  87. // This feature doesn't work on startpage!
  88. placeFaviconOffTheLeft = false;
  89. }
  90.  
  91. if (document.location.search.match(/&tbm=nws/)) {
  92. // This feature doesn't work on Google News search
  93. placeFaviconOffTheLeft = false;
  94. // The layout is a bit too cramped for large favicons
  95. iconSize = 0.9;
  96. }
  97.  
  98. if ((isEcosia || isStartpage) && (window.innerWidth < 1000)) {
  99. // At lower widths, these website collapse their layout so there is no margin on the left, in which case we don't want to float the favicon out of view.
  100. // We dare not do this for Google, as this isn't working right now!
  101. placeFaviconOffTheLeft = false;
  102. }
  103.  
  104. if (!this.GM_addStyle) {
  105. this.GM_addStyle = function(css) {
  106. var s = document.createElement("style");
  107. s.type = 'text/css';
  108. s.innerHTML = css;
  109. document.getElementsByTagName("head")[0].appendChild(s);
  110. };
  111. }
  112.  
  113. function findClosest (elem, className) {
  114. // eslint-disable-next-line no-cond-assign
  115. while (elem = elem.parentNode) {
  116. //if (elem.tagName && elem.tagName.toLowerCase() === tagName.toLowerCase()) {
  117. if ((' ' + elem.className + ' ').indexOf(' ' + className + ' ') >= 0) {
  118. return elem;
  119. }
  120. }
  121. return null;
  122. }
  123.  
  124. function alreadyContainsFavicon(link) {
  125. // Feburuary 2023: On Google Search, they now (sometimes?) show favicons
  126. const img = link.querySelector('span > div > img[data-atf]:not([data-atf="0"])');
  127. if (img) return true;
  128. // On Google's News tab, they already show favicons
  129. // We will try to detect that
  130. const images = Array.from(link.querySelectorAll('g-img img'));
  131. // RIP semantic web. The classes for these favicons are "rISBZc zr758c"
  132. //const looksLikeFavicon = (img) => img.getAttribute('width') === '16' && img.getAttribute('height') === '16';
  133. const looksLikeFavicon = (img) => img.width == 16 && img.height == 16 || img.className == "rISBZc zr758c";
  134. return images.some(looksLikeFavicon);
  135. }
  136.  
  137. function createFaviconFor (url) {
  138. var host = url.replace(/^[^/]*:\/\//, '').replace(/\/.*$/, '');
  139. // if (host == document.location.host) {
  140. // return null;
  141. // }
  142. // Use protocol (http/https) of current page, to avoid mixed-content warnings/failures.
  143. //var protocol = document.location.protocol.replace(/:$/, '');
  144. //console.log("[FaviconizeGoogle.user.js] protocol:" ,protocol);
  145. // We may fail to access favicons, e.g. if the site only offers http, or due to CORS restrictions.
  146. // But it is still worth tryiing, because the fallback (google's s2) offers only low-res icons.
  147. var urlsToTry = [
  148. // Hitting each site directly means exposing our IP to them, even if we don't visit their site.
  149. // I think it would be preferable to use DuckDuckGo's service, because they claim to discard our data.
  150. // However, it doesn't provide good results for all websites. So until it improves, I don't plan to make it the default.
  151. // TODO: We could try DDG first, but if they return their fallback icon, then we could try the other URLs.
  152. '//' + host + '/favicon.ico',
  153. '//' + host + '/favicon.png',
  154. '//www.google.com/s2/favicons?domain=' + host,
  155. '//icons.duckduckgo.com/ip3/' + host + '.ico',
  156. ];
  157. // Google's cache will sometimes provide a favicon we would have missed, e.g. if the site uses .png instead of .ico. Thanks to NV for suggesting this, and to Google.
  158. var img = document.createElement('IMG');
  159. //img.src = protocol + '://'+host+'/favicon.ico';
  160. //img.src = '//g.etfv.co/http://" + host; // As suggested by decembre
  161. //img.src = 'http://api.byi.pw/favicon?url=' + protocol + "://" + host; // Another service suggested by decembre
  162. //img.width = '16';
  163. //img.height = '16';
  164. img.className = 'favicon';
  165. img.border = 0;
  166. img.style.display = 'none';
  167. var showImage = function () {
  168. img.style.display = '';
  169. if (typeof removeListeners === 'function') {
  170. removeListeners();
  171. }
  172. };
  173. if (bypassCSP) {
  174. var blobToDataURL = (blob, callback) => {
  175. var a = new FileReader();
  176. a.onload = function(e) {
  177. callback(e.target.result);
  178. };
  179. a.readAsDataURL(blob);
  180. };
  181. var tryNextUrlNoCors = () => {
  182. if (urlsToTry.length === 0) {
  183. return;
  184. }
  185. var url = 'https:' + urlsToTry.shift();
  186. //console.log('url:', url);
  187. GM_xmlhttpRequest({
  188. method: 'GET',
  189. url: url,
  190. responseType: 'blob',
  191. onload: (res) => {
  192. //console.log('response:', res.response);
  193. if (!res.response) {
  194. console.warn(`No content returned for url=${url}`);
  195. tryNextUrlNoCors();
  196. }
  197. blobToDataURL(res.response, (dataUrl) => {
  198. //console.log('dataUrl:', dataUrl);
  199. const isImage = dataUrl.startsWith('data:application/octet-stream;') || dataUrl.startsWith('data:image/');
  200. if (isImage) {
  201. img.src = dataUrl;
  202. showImage();
  203. } else {
  204. tryNextUrlNoCors();
  205. }
  206. });
  207. },
  208. });
  209. };
  210. tryNextUrlNoCors();
  211. } else {
  212. var tryNextUrl = function () {
  213. if (urlsToTry.length === 0) {
  214. removeListeners();
  215. return;
  216. }
  217. img.src = urlsToTry.shift();
  218. };
  219. var removeListeners = function () {
  220. img.removeEventListener('load', showImage);
  221. img.removeEventListener('error', tryNextUrl);
  222. };
  223. img.addEventListener('load', showImage, false);
  224. img.addEventListener('error', tryNextUrl, false);
  225. tryNextUrl();
  226. }
  227. return img;
  228. }
  229.  
  230. function getGoogleResultsLinks () {
  231. // var links = document.evaluate("//a[@class='l']",document,null,6,null);
  232. // var links = filterListBy(document.links, function(x){ return x.className=='l'; } );
  233. // var links = document.links.filter( function(x){ return x.className=='l'; } );
  234.  
  235. /*
  236. return filterListBy(document.getElementsByTagName('a'), function (x) {
  237. // Most pages show links with class 'l'
  238. // But on pages where the first result has an indented block of sub-pages,
  239. // the indented links have class 'l' but all the other links have no class but parent class 'r'
  240. return x.className === 'l' || x.parentNode.className === 'r';
  241. });
  242. */
  243.  
  244. // For Google search
  245. // a.l
  246. // a.fl are small one-line sub-results. Search "squeak" and see the Wikipedia result.
  247. // a.B0IOdd are lists of images which appear underneath a section on the News tab (inside .card-section)
  248. var links = document.querySelectorAll('.g a:not(.fl):not(.B0IOdd)');
  249.  
  250. // For news.google.com
  251. if (links.length === 0) {
  252. //links = document.querySelectorAll('a.article:not(.esc-thumbnail-link)');
  253. links = document.querySelectorAll('article > a');
  254. }
  255.  
  256. // For startpage.com
  257. if (links.length === 0) {
  258. //links = document.querySelectorAll('.clk > a');
  259. links = document.querySelectorAll('.w-gl__result-title');
  260. }
  261.  
  262. // For ecosia.org
  263. if (links.length === 0) {
  264. // We use .result-body to avoid getting deep links (which were producing an unwanted second large favicon)
  265. links = document.querySelectorAll('.result-body .result-title');
  266. }
  267.  
  268. // For searx
  269. if (links.length === 0) {
  270. links = document.querySelectorAll('.result_header a');
  271. }
  272.  
  273. // Remove any links which contain only one image
  274. links = [...links].filter(link => {
  275. if (link.childNodes.length === 1 && link.childNodes[0].tagName === 'IMG') {
  276. return false;
  277. }
  278. return true;
  279. });
  280.  
  281. // Only keep links which are not really links (e.g. ignore 'javascript:void(0)' and anchor links)
  282. links = [...links].filter(link => {
  283. return (link.protocol === 'http:' || link.protocol === 'https:') && link.href.slice(0, 1) !== '#';
  284. });
  285.  
  286. return links;
  287. }
  288.  
  289. var marginSide = (placeFaviconAfter ? 'left' : 'right');
  290. var leftPadding = 1.2 * iconSize + 0.6 - 0.3 * isEcosia;
  291. // We can try to centralise the icon with the text
  292. // Or we can top-align the icon with the text (better for larger icon sizes)
  293. var topPadding = centraliseIconVertically ? 0.87 - iconSize / 2 : 0.35;
  294. var extra = placeFaviconOffTheLeft ? 'position: absolute; left: -' + leftPadding + 'em; top: ' + topPadding + 'em;' : '';
  295. if (isEcosia) {
  296. var topMargin = 0.3 + centraliseIconVertically * iconSize / 13;
  297. // Use margin for vertical positioning
  298. extra += ' margin-top: ' + (-topMargin) + 'em;';
  299. }
  300. // If we are using placeFaviconOffTheLeft, then we don't need the paddings or alignment here
  301. const style = ".favicon { box-sizing: content-box; margin-" + marginSide + ": 0.3em; vertical-align: middle; width: " + iconSize + "em; height: " + iconSize + "em; padding-bottom: 0.2em; " + extra + "}";
  302. GM_addStyle(style);
  303.  
  304. // TODO: On search.disroot.org Chrome says: Refused to apply inline style because it violates the following Content Security Policy directive: "default-src 'self'"
  305. // This happens even if I use GM_addStyle
  306. // At present, the only workaround I can imagine is to apply the styles directly to the elements, rather than using CSS.
  307.  
  308. function updateFavicons () {
  309. // On Google's News and Images tabs, do nothing
  310. if (document.location.search.match(/[&?]tbm=isch\b/) || document.location.search.match(/[&?]tbm=nws\b/)) {
  311. return;
  312. }
  313.  
  314. var links = getGoogleResultsLinks();
  315.  
  316. // Allows it to work on any sites:
  317. if (links.length === 0) {
  318. links = document.getElementsByTagName("A");
  319. }
  320. //console.log("Got links:", links);
  321.  
  322. // for (var i=0;i<links.snapshotLength;i++) {
  323. // var link = links.snapshotItem(i);
  324. for (var i = 0; i < links.length; i++) {
  325. var link = links[i];
  326. // if (link.href.match('^javascript:') || link.href.match('^#')) {
  327. // continue;
  328. // }
  329. if (alreadyContainsFavicon(link)) {
  330. continue;
  331. }
  332.  
  333. var targetUrl = link.getAttribute('data-href') || link.href;
  334.  
  335. //// Skip relative and same-host links:
  336. if (targetUrl.match(/^[/]/) || targetUrl.match("://" + document.location.host)) {
  337. continue;
  338. }
  339.  
  340. //console.log("[faviconizegoogle.user.js] link.getAttribute(data-faviconized):" ,link.getAttribute("data-faviconized"));
  341. if (link.getAttribute("data-faviconized")) {
  342. // Already faviconized
  343. //console.log("[faviconizegoogle.user.js] Skipping");
  344. continue;
  345. }
  346.  
  347. // Sometimes Google shows multiple results from one side, side-by-side in a table
  348. // In this case, adding a favicon to the later links is redundant, and tends to overlap the earlier link
  349. // So let's not do that
  350. // Presumably this no longer works, since findClosest() works by className now
  351. /*
  352. var tableCell = findClosest(link, 'td');
  353. if (tableCell && tableCell.parentNode.firstChild !== tableCell) {
  354. // We are in a table and we are not the first cell; don't add favicon
  355. continue;
  356. }
  357. */
  358.  
  359. link.setAttribute("data-faviconized", "yes");
  360. var img = createFaviconFor(targetUrl);
  361. // <cite> is for google, .url is for startpage
  362. // For Google 2022, putting the img inside the container but pushed out to the left makes it invisible. So we go up to the container, and put it just inside that
  363. // After finding the parent '.g' we actually want the first 'div' inside it. (Not the firstChild, since that might be a favicon we created earlier!)
  364. var targetNode =
  365. placeFaviconByUrl && link.parentNode.parentNode.querySelector('cite, .url')
  366. || findClosest(link, 'g') && findClosest(link, 'g').querySelector('div')
  367. || link;
  368.  
  369. if (isEcosia && !placeFaviconOffTheLeft) {
  370. // With the default `display: block` the link will break onto a separate line from the favicon, so we do this
  371. link.style.display = 'inline-block';
  372. link.parentNode.style.whiteSpace = 'nowrap';
  373. }
  374.  
  375. if (isSearx && placeFaviconOffTheLeft) {
  376. //findClosest(link, 'h4').style.position = 'relative';
  377. link.parentNode.style.position = 'relative';
  378. }
  379.  
  380. // Some result blocks contain multiple links.
  381. // But now we are placing the favicon on the parent '.g' block, we don't want multiple favicons on top of each other!
  382. // (Of course it would be more efficient if we didn't generate an <img> at all for these sub-links.)
  383. if (targetNode.getAttribute("data-target-faviconized") || targetNode.classList.contains("favicon") || alreadyContainsFavicon(targetNode)) {
  384. continue;
  385. }
  386. targetNode.setAttribute("data-target-faviconized", "yes");
  387.  
  388. //console.log(`Placing favicon %o by %o`, img, targetNode);
  389. if (placeFaviconOffTheLeft) {
  390. const realTargetNode = placeFaviconInsideLink ? targetNode : targetNode.parentNode;
  391. realTargetNode.style.position = 'relative';
  392. }
  393. if (placeFaviconInsideLink) {
  394. if (placeFaviconAfter) {
  395. targetNode.appendChild(img);
  396. } else {
  397. targetNode.insertBefore(img, targetNode.firstChild);
  398. }
  399. } else {
  400. if (placeFaviconAfter) {
  401. targetNode.parentNode.insertBefore(img, targetNode.nextSibling);
  402. } else {
  403. targetNode.parentNode.insertBefore(img, targetNode);
  404. }
  405. }
  406. }
  407. }
  408.  
  409. // TODO: Use MutationObserver instead?
  410.  
  411. var last_srg = null;
  412. var results_count = -1;
  413.  
  414. function checkForUpdate () {
  415. // #ires was needed for the News tab, which doesn't have a .srg. Perhaps we could use ires for all tabs.
  416. var new_srg = document.querySelector('.srg') || document.querySelector('#ires');
  417. // startpage
  418. new_srg = new_srg || document.querySelector('.w-gl--default');
  419. // ecosia
  420. new_srg = new_srg || document.querySelector('.mainline');
  421. //console.log("[FaviconizeGoogle.user.js] last_srg:" ,last_srg);
  422. //console.log("[faviconizegoogle.user.js] new_srg:" ,new_srg);
  423. var new_results_count = getGoogleResultsLinks().length;
  424. if (new_srg !== last_srg || new_results_count !== results_count) {
  425. //console.log("Page change detected!");
  426. updateFavicons();
  427. last_srg = new_srg;
  428. results_count = new_results_count;
  429. } else {
  430. //console.log("Pages are the same:", last_srg, new_srg);
  431. }
  432. setTimeout(checkForUpdate, 1000);
  433. }
  434.  
  435. setTimeout(checkForUpdate, 100);

QingJ © 2025

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