diff options
Diffstat (limited to 'searx/favicons/resolvers.py')
-rw-r--r-- | searx/favicons/resolvers.py | 100 |
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, +} |