101 lines
		
	
	
		
			3.6 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
		
		
			
		
	
	
			101 lines
		
	
	
		
			3.6 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| 
								 | 
							
								# 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,
							 | 
						||
| 
								 | 
							
								}
							 |