summaryrefslogtreecommitdiff
path: root/searx/favicons/resolvers.py
diff options
context:
space:
mode:
Diffstat (limited to 'searx/favicons/resolvers.py')
-rw-r--r--searx/favicons/resolvers.py100
1 files changed, 100 insertions, 0 deletions
diff --git a/searx/favicons/resolvers.py b/searx/favicons/resolvers.py
new file mode 100644
index 000000000..bde5ae2b8
--- /dev/null
+++ b/searx/favicons/resolvers.py
@@ -0,0 +1,100 @@
+# SPDX-License-Identifier: AGPL-3.0-or-later
+"""Implementations of the favicon *resolvers* that are available in the favicon
+proxy by default. A *resolver* is a function that obtains the favicon from an
+external source. The *resolver* function receives two arguments (``domain,
+timeout``) and returns a tuple ``(data, mime)``.
+
+"""
+
+from __future__ import annotations
+
+__all__ = ["DEFAULT_RESOLVER_MAP", "allesedv", "duckduckgo", "google", "yandex"]
+
+from typing import Callable
+from searx import network
+from searx import logger
+
+DEFAULT_RESOLVER_MAP: dict[str, Callable]
+logger = logger.getChild('favicons.resolvers')
+
+
+def _req_args(**kwargs):
+ # add the request arguments from the searx.network
+ d = {"raise_for_httperror": False}
+ d.update(kwargs)
+ return d
+
+
+def allesedv(domain: str, timeout: int) -> tuple[None | bytes, None | str]:
+ """Favicon Resolver from allesedv.com / https://favicon.allesedv.com/"""
+ data, mime = (None, None)
+ url = f"https://f1.allesedv.com/32/{domain}"
+ logger.debug("fetch favicon from: %s", url)
+
+ # will just return a 200 regardless of the favicon existing or not
+ # sometimes will be correct size, sometimes not
+ response = network.get(url, **_req_args(timeout=timeout))
+ if response and response.status_code == 200:
+ mime = response.headers['Content-Type']
+ if mime != 'image/gif':
+ data = response.content
+ return data, mime
+
+
+def duckduckgo(domain: str, timeout: int) -> tuple[None | bytes, None | str]:
+ """Favicon Resolver from duckduckgo.com / https://blog.jim-nielsen.com/2021/displaying-favicons-for-any-domain/"""
+ data, mime = (None, None)
+ url = f"https://icons.duckduckgo.com/ip2/{domain}.ico"
+ logger.debug("fetch favicon from: %s", url)
+
+ # will return a 404 if the favicon does not exist and a 200 if it does,
+ response = network.get(url, **_req_args(timeout=timeout))
+ if response and response.status_code == 200:
+ # api will respond with a 32x32 png image
+ mime = response.headers['Content-Type']
+ data = response.content
+ return data, mime
+
+
+def google(domain: str, timeout: int) -> tuple[None | bytes, None | str]:
+ """Favicon Resolver from google.com"""
+ data, mime = (None, None)
+
+ # URL https://www.google.com/s2/favicons?sz=32&domain={domain}" will be
+ # redirected (HTTP 301 Moved Permanently) to t1.gstatic.com/faviconV2:
+ url = (
+ f"https://t1.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL"
+ f"&url=https://{domain}&size=32"
+ )
+ logger.debug("fetch favicon from: %s", url)
+
+ # will return a 404 if the favicon does not exist and a 200 if it does,
+ response = network.get(url, **_req_args(timeout=timeout))
+ if response and response.status_code == 200:
+ # api will respond with a 32x32 png image
+ mime = response.headers['Content-Type']
+ data = response.content
+ return data, mime
+
+
+def yandex(domain: str, timeout: int) -> tuple[None | bytes, None | str]:
+ """Favicon Resolver from yandex.com"""
+ data, mime = (None, None)
+ url = f"https://favicon.yandex.net/favicon/{domain}"
+ logger.debug("fetch favicon from: %s", url)
+
+ # api will respond with a 16x16 png image, if it doesn't exist, it will be a
+ # 1x1 png image (70 bytes)
+ response = network.get(url, **_req_args(timeout=timeout))
+ if response and response.status_code == 200 and len(response.content) > 70:
+ mime = response.headers['Content-Type']
+ data = response.content
+ return data, mime
+
+
+DEFAULT_RESOLVER_MAP = {
+ "allesedv": allesedv,
+ "duckduckgo": duckduckgo,
+ "google": google,
+ "yandex": yandex,
+}