resolvers.py 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. # SPDX-License-Identifier: AGPL-3.0-or-later
  2. """Implementations of the favicon *resolvers* that are available in the favicon
  3. proxy by default. A *resolver* is a function that obtains the favicon from an
  4. external source. The *resolver* function receives two arguments (``domain,
  5. timeout``) and returns a tuple ``(data, mime)``.
  6. """
  7. from __future__ import annotations
  8. __all__ = ["DEFAULT_RESOLVER_MAP", "allesedv", "duckduckgo", "google", "yandex"]
  9. from typing import Callable
  10. from searx import network
  11. from searx import logger
  12. DEFAULT_RESOLVER_MAP: dict[str, Callable]
  13. logger = logger.getChild('favicons.resolvers')
  14. def _req_args(**kwargs):
  15. # add the request arguments from the searx.network
  16. d = {"raise_for_httperror": False}
  17. d.update(kwargs)
  18. return d
  19. def allesedv(domain: str, timeout: int) -> tuple[None | bytes, None | str]:
  20. """Favicon Resolver from allesedv.com / https://favicon.allesedv.com/"""
  21. data, mime = (None, None)
  22. url = f"https://f1.allesedv.com/32/{domain}"
  23. logger.debug("fetch favicon from: %s", url)
  24. # will just return a 200 regardless of the favicon existing or not
  25. # sometimes will be correct size, sometimes not
  26. response = network.get(url, **_req_args(timeout=timeout))
  27. if response and response.status_code == 200:
  28. mime = response.headers['Content-Type']
  29. if mime != 'image/gif':
  30. data = response.content
  31. return data, mime
  32. def duckduckgo(domain: str, timeout: int) -> tuple[None | bytes, None | str]:
  33. """Favicon Resolver from duckduckgo.com / https://blog.jim-nielsen.com/2021/displaying-favicons-for-any-domain/"""
  34. data, mime = (None, None)
  35. url = f"https://icons.duckduckgo.com/ip2/{domain}.ico"
  36. logger.debug("fetch favicon from: %s", url)
  37. # will return a 404 if the favicon does not exist and a 200 if it does,
  38. response = network.get(url, **_req_args(timeout=timeout))
  39. if response and response.status_code == 200:
  40. # api will respond with a 32x32 png image
  41. mime = response.headers['Content-Type']
  42. data = response.content
  43. return data, mime
  44. def google(domain: str, timeout: int) -> tuple[None | bytes, None | str]:
  45. """Favicon Resolver from google.com"""
  46. data, mime = (None, None)
  47. # URL https://www.google.com/s2/favicons?sz=32&domain={domain}" will be
  48. # redirected (HTTP 301 Moved Permanently) to t1.gstatic.com/faviconV2:
  49. url = (
  50. f"https://t1.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL"
  51. f"&url=https://{domain}&size=32"
  52. )
  53. logger.debug("fetch favicon from: %s", url)
  54. # will return a 404 if the favicon does not exist and a 200 if it does,
  55. response = network.get(url, **_req_args(timeout=timeout))
  56. if response and response.status_code == 200:
  57. # api will respond with a 32x32 png image
  58. mime = response.headers['Content-Type']
  59. data = response.content
  60. return data, mime
  61. def yandex(domain: str, timeout: int) -> tuple[None | bytes, None | str]:
  62. """Favicon Resolver from yandex.com"""
  63. data, mime = (None, None)
  64. url = f"https://favicon.yandex.net/favicon/{domain}"
  65. logger.debug("fetch favicon from: %s", url)
  66. # api will respond with a 16x16 png image, if it doesn't exist, it will be a
  67. # 1x1 png image (70 bytes)
  68. response = network.get(url, **_req_args(timeout=timeout))
  69. if response and response.status_code == 200 and len(response.content) > 70:
  70. mime = response.headers['Content-Type']
  71. data = response.content
  72. return data, mime
  73. DEFAULT_RESOLVER_MAP = {
  74. "allesedv": allesedv,
  75. "duckduckgo": duckduckgo,
  76. "google": google,
  77. "yandex": yandex,
  78. }