§
    9qìj„6  ã                  ó  — d Z ddlmZ ddlZddlZddlZddlZddlZddlZddl	Z	ddl
ZddlZddlZdZdZh d£Zh d£Z G d„ d	e¦  «        Zd/d„Zd0d„Zd1d„Zd2d„Zd3d„Zd4d„Z	 	 	 d5d6d!„Zd7d"„Zd8d#„Zd9d%„Zd9d&„Zd:d;d*„Zd<d+„Z d=d,„Z!d>d.„Z"dS )?a   
Download of images (and of any other file) from hosts that don't like being scripted.

This module deliberately imports nothing from the project (no constants, no Environment): the bot
uses it through download_utils.py, and scripts/download_character_images.py uses it standing on its
own, outside the bot's environment.

A download goes through these ways, in order, until one of them works:
1. urllib, sending the headers of a browser that is loading an image (see get_browser_headers)
2. curl_cffi, if it is installed: the same request, but with the TLS and HTTP/2 handshake of a real
   Chrome. A CDN can tell a script from a browser by that handshake alone, whatever the User-Agent
   header says, and answer 403 to the first one
3. An image proxy, if the IMAGE_PROXY_URL environment variable is set: another server downloads the
   image instead of this one, which is the way around a CDN that blocks the IP address of this one

Why a failure happened is never lost: every way that failed is logged, with its reason.
é    )ÚannotationsNzoMozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36ÚIMAGE_PROXY_URL>   é�  é”  éš  >   é­  éö  é÷  éø  c                  ó   — e Zd ZdZdS )ÚNotAnImageErrorzYThe host answered successfully, but with something that isn't an image (e.g. a web page).N)Ú__name__Ú
__module__Ú__qualname__Ú__doc__© ó    ú/app/src/utils/image_fetch.pyr   r   4   s   € € € € € ØcÐcÐcÐcr   r   ÚurlÚstrÚreturnÚdictc                ó2  — t           j                             | ¦  «        }|j        › d|j        › d�}|j                             d¦  «        r;|j                             d¦  «                             d¦  «        d         }|rd|› d�}t          dd|d	d
dddddœ
S )a[  
    Get the headers of a browser loading an image embedded in a web page: not only the
    User-Agent but everything a real browser sends along with it, since some hosts tell a script
    from a browser by that combination (and, for hotlink protection, by the Referer)
    :param url: The url of the file to download
    :return: The headers
    z://ú/zwikia.nocookie.netr   zhttps://z.fandom.com/zBimage/webp,image/apng,image/png,image/jpeg,image/*;q=0.8,*/*;q=0.5zen-US,en;q=0.9zA"Google Chrome";v="125", "Chromium";v="125", "Not.A/Brand";v="24"z?0z	"Windows"Úimagezno-corsz
cross-site)
ú
User-AgentÚAcceptzAccept-LanguageÚRefererú	sec-ch-uaúsec-ch-ua-mobileúsec-ch-ua-platformzSec-Fetch-DestzSec-Fetch-ModezSec-Fetch-Site)
ÚurllibÚparseÚurlparseÚschemeÚnetlocÚendswithÚpathÚstripÚsplitÚBROWSER_USER_AGENT)r   Ú
parsed_urlÚrefererÚwikis       r   Úget_browser_headersr/   8   sÀ   € õ ”×&Ò& sÑ+Ô+€JØÔ"Ð;Ð; zÔ'8Ð;Ð;Ð;€Gð
 Ô×!Ò!Ð"6Ñ7Ô7ð 4ØŒ×$Ò$ SÑ)Ô)×/Ò/°Ñ4Ô4°QÔ7ˆØð 	4Ø3 Ð3Ð3Ð3ˆGõ )àVØ+ØØXØ Ø)Ø!Ø#Ø&ðð ð r   ÚheadÚbytesÚboolc                óþ   — |                       d¦  «        ph|                       d¦  «        pS|                       d¦  «        p>| dd…         dk    o| dd…         d	k    p"| dd…         d
k    p|                       d¦  «        S )a0  
    Check if the first bytes of a file are those of an image format (by its signature, not by the
    file name or the Content-Type, which a host that is blocking you can set to anything)
    :param head: The first bytes of the file, at least 12 of them
    :return: True if it starts like an image
    s   ÿØÿs   ‰PNG

)s   GIF87as   GIF89aNé   s   RIFFé   é   s   WEBPs   ftyps   BM)Ú
startswith)r0   s    r   Úlooks_like_imager8   [   s˜   € ð 	�Š˜Ñ(Ô(ð 	"Ø�?Š?Ð/Ñ0Ô0ð	"à�?Š?Ð1Ñ2Ô2ð	"ð ��!�ŒH˜ÒÐ9 D¨¨2¨¤J°'Ò$9ð	"ð ��!�Œ9˜Òð		"ð
 �?Š?˜5Ñ!Ô!ðr   c                óš  — d„ t           j                             | ¦  «        j                             d¦  «        D ¦   «         }d|v r|d|                     d¦  «        …         }|r%t           j                             |d         ¦  «        nd}d|v r|                     dd¦  «        d	         n|}t          j	        d
d|¦  «         
                    d¦  «        }||k    s|sit          j        |                     d¦  «        p|                      d¦  «        ¦  «                             ¦   «         }|r|› d|dd…         › �n	|dd…         }|› d�S )a  
    Get the name the file of an image gets when it is saved next to the bot (bundled with it)
    instead of being downloaded each time. It is made from the url alone, and not from the
    revision of the file the url may point to, so the same image always gets the same name
    (https://static.wikia.nocookie.net/onepiece/images/f/f1/Abdullah_Anime_Infobox.png/revision/latest?cb=1
    -> Abdullah_Anime_Infobox.jpg)
    :param url: The url of the image
    :return: The file name, safe on any file system
    c                ó   — g | ]}|¯|‘ŒS r   r   )Ú.0Úparts     r   ú
<listcomp>z/get_bundled_image_file_name.<locals>.<listcomp>x   s   € ÐQÐQÐQ�dÈDÐQˆTÐQÐQÐQr   r   ÚrevisionNéÿÿÿÿÚ ú.é   r   z[^A-Za-z0-9._-]+Ú_z._zutf-8ú-r5   é   z.jpg)r"   r#   r$   r(   r*   ÚindexÚunquoteÚrsplitÚreÚsubr)   ÚhashlibÚsha1ÚencodeÚ	hexdigest)r   ÚpartsÚoriginal_nameÚoriginal_stemÚstemÚdigests         r   Úget_bundled_image_file_namerT   m   sT  € ð RÐQ�fœl×3Ò3°CÑ8Ô8Ô=×CÒCÀCÑHÔHÐQÑQÔQ€EØ�UÐÐØÐ/˜Ÿš JÑ/Ô/Ð/Ô0ˆà7<ÐD•F”L×(Ò(¨¨r¬Ñ3Ô3Ð3À"€MØ7:¸mÐ7KÐ7K�M×(Ò(¨¨aÑ0Ô0°Ô3Ð3ÐQ^€MåŒ6Ð% s¨MÑ:Ô:×@Ò@ÀÑFÔF€Dð ˆ}ÒÐ DÐÝ”˜m×2Ò2°7Ñ;Ô;ÐR¸s¿zºzÈ'Ñ?RÔ?RÑSÔS×]Ò]Ñ_Ô_ˆØ)-Ð>�$Ð%Ð%˜   œÐ%Ð%Ð%°6¸#¸2¸#´;ˆàˆ=ˆ=ˆ=Ðr   ú
str | Nonec                 óÄ   — t           j                             t          ¦  «        pd                     ¦   «         } | sdS d| vrt          j        dt          › d�¦  «         dS | S )z~
    Get the image proxy url template that is set up, if there is one and it is usable
    :return: The template, or None
    r@   Nú{url}z	Ignoring zg: it has to contain {url}, where the url of the image goes (e.g. https://wsrv.nl/?url={url}&output=jpg))ÚosÚenvironÚgetÚIMAGE_PROXY_ENV_NAMEr)   ÚloggingÚwarning)Útemplates    r   Úget_image_proxy_urlr_   ‰   s|   € õ ”
—’Õ3Ñ4Ô4Ð:¸×AÒAÑCÔC€HØð Øˆtà�hÐÐÝŒðIÕ,ð Ið Ið Iñ	
ô 	
ð 	
ð ˆtà€Or   c                 ó2   — 	 ddl } dS # t          $ r Y dS w xY w)zi
    Check if curl_cffi, which downloads like a real Chrome, is installed
    :return: True if it is
    r   NTF)Ú	curl_cffiÚ	Exception)ra   s    r   Ú"is_browser_impersonation_availablerc   �   s:   € ðØÐÐÐàˆtøÝð ð ð Øˆuˆuðøøøs   ‚ ˆ
•ÚerrorÚBaseExceptionc                ó�   — t          | t          j        j        ¦  «        rt	          | ¦  «        S t          | ¦  «        j        › d| › �S )z\
    Describe an error in a line
    :param error: The error
    :return: What happened
    ú: )Ú
isinstancer"   rd   Ú	HTTPErrorr   Útyper   ©rd   s    r   Údescribe_errorrl   «   sD   € õ �%�œÔ/Ñ0Ô0ð Ý�5‰zŒzÐå�5‰kŒkÔ"Ð-Ð- eÐ-Ð-Ð-r   é   FÚ	file_pathÚtimeoutÚfloatÚexpect_imageÚ	proxy_urlc                ó4  — |€t          ¦   «         }d}g }t          | |||¦  «        D ]¼\  }}	  |¦   «          |rt          |¦  «         |r0t          j        d| › d|› dd                     |¦  «        › �¦  «         |c S # t          $ rY}	t          |¦  «         |                     |› dt          |	¦  «        › �¦  «         |€|	}t          |	¦  «        rY d}	~	 nY d}	~	Œµd}	~	ww xY wt          j        d| › dd                     |¦  «        › �¦  «         ||_        |‚)	a?  
    Download a file the way a browser would, trying every way there is (see the top of this file)
    until one works
    :param url: The url of the file
    :param file_path: Where to save it
    :param timeout: Seconds to wait for the host in every request
    :param expect_image: If the file has to be an image. A host that blocks you often answers with a
    web page instead of an error, which is then not taken as a download that worked
    :param proxy_url: Url template of the image proxy to use as the last way. Default: the one of
    the IMAGE_PROXY_URL environment variable, if any
    :return: The name of the way that worked
    :raises: The error of the first way (urllib) if none of them worked, so that whoever calls
    this can tell a dead link (HTTPError 404) from a blocked one (HTTPError 403) as always
    NzDownloaded z	 through z after: z; rg   zCould not download z - )r_   Ú
_iter_waysÚ_check_file_is_imager\   ÚinfoÚjoinrb   Ú_remove_quietlyÚappendrl   Ú_is_dead_linkr]   Údownload_failures)
r   rn   ro   rq   rr   Úfirst_errorÚfailuresÚway_nameÚwayÚes
             r   Úfetch_to_filer�   ¸   s…  € ð, ÐÝ'Ñ)Ô)ˆ	à€KØ€Hå# C¨°G¸YÑGÔGð ð ‰ˆ�#ð	ØˆC‰EŒEˆEØð 0Ý$ YÑ/Ô/Ð/àð bÝ”Ð`¨3Ð`Ð`¸Ð`Ð`È4Ï9Ê9ÐU]ÑK^ÔK^Ð`Ð`ÑaÔaÐaØˆOˆOˆOøåð 	ð 	ð 	Ý˜IÑ&Ô&Ð&Ø�OŠO˜xÐ>Ð>­>¸!Ñ+<Ô+<Ð>Ð>Ñ?Ô?Ð?ØÐ"Ø�õ ˜QÑÔð Ø�����ðð ð ð ð øøøøð	øøøõ „OÐG¨#ÐGÐG°$·)²)¸HÑ2EÔ2EÐGÐGÑHÔHÐHð %-€KÔ!Ø
Ðs   ­AA>Á>
C!ÂA	CÃC!c              #  óz   ‡ ‡‡‡K  — dˆˆˆ fd„fV — t          ¦   «         rdˆˆˆ fd„fV — ‰rdˆˆˆˆ fd„fV — dS dS )aè  
    Get the ways to download a file, in the order they are tried. It is a generator on purpose: the
    ones after urllib (curl_cffi isn't even loaded) are only looked at if urllib didn't work
    :param url: The url of the file
    :param file_path: Where to save it
    :param timeout: Seconds to wait for the host in every request
    :param proxy_url: The url template of the image proxy, if there is one
    :return: (name of the way, function that downloads the file that way)
    r"   c                 ó&   •— t          ‰‰ ‰¦  «        S ©N)Ú_fetch_with_urllib©rn   ro   r   s   €€€r   ú<lambda>z_iter_ways.<locals>.<lambda>ú   s   ø€ Õ.¨s°I¸wÑGÔG€ r   z!browser impersonation (curl_cffi)c                 ó&   •— t          ‰‰ ‰¦  «        S r„   )Ú!_fetch_with_browser_impersonationr†   s   €€€r   r‡   z_iter_ways.<locals>.<lambda>ÿ   s   ø€ Õ5°c¸9ÀgÑNÔN€ r   zimage proxyc                 ó(   •— t          ‰‰ ‰‰¦  «        S r„   )Ú_fetch_through_image_proxy)rn   rr   ro   r   s   €€€€r   r‡   z_iter_ways.<locals>.<lambda>  s   ø€ Õ.¨s°I¸wÈ	ÑRÔR€ r   N)rc   )r   rn   ro   rr   s   ````r   rt   rt   ï   s®   øøøøè è € ð ÐGÐGÐGÐGÐGÐGÐ
GÐGÐGÐGå)Ñ+Ô+ð 
à/ØNÐNÐNÐNÐNÐNð
ð 	
ð 	
ð 	
ð
 ð 
àØRÐRÐRÐRÐRÐRÐRð
ð 	
ð 	
ð 	
ð 	
ð 	
ð
ð 
r   c                ó\   — t          | t          j        j        ¦  «        o| j        t
          v S )zr
    Check if an error says that the file is not there
    :param error: The error
    :return: True if it is
    )rh   r"   rd   ri   ÚcodeÚDEAD_LINK_STATUS_CODESrk   s    r   rz   rz   	  s&   € õ �e�Vœ\Ô3Ñ4Ô4Ð]¸¼ÕG]Ð9]Ð]r   ÚNonec                óR   — 	 t          j        | ¦  «         dS # t          $ r Y dS w xY w)zo
    Remove a (partially) downloaded file, if it is there
    :param file_path: The file
    :return: None
    N)rX   ÚremoveÚOSError)rn   s    r   rx   rx     s?   € ðÝ
Œ	�)ÑÔÐÐÐøÝð ð ð Øˆˆðøøøs   ‚ ˜
&¥&c                óÒ   — t          | d¦  «        5 }|                     d¦  «        }ddd¦  «         n# 1 swxY w Y   t          |¦  «        st          d|dd…         ›�¦  «        ‚dS )z�
    Check that a downloaded file is an image
    :param file_path: The file
    :return: None
    :raises NotAnImageError: If it is not
    Úrbé    Nz.the host sent something that is not an image: é   )ÚopenÚreadr8   r   )rn   Úfiler0   s      r   ru   ru      s¶   € õ 
ˆi˜Ñ	Ô	ð  $Ø�yŠy˜‰}Œ}ˆðð ð ñ ô ð ð ð ð ð ð øøøð ð ð ð õ ˜DÑ!Ô!ð ^ÝÐ\ÈtÐTWÐUWÐTWÌyÐ\Ð\Ñ]Ô]Ð]ð^ð ^s   ‘3³7º7é   ÚattemptsÚintc                óx  — t           j                             | t          | ¦  «        ¬¦  «        }t	          |¦  «        D ]û}	 t           j                             ||¬¦  «        5 }t          |d¦  «        5 }t          j        ||¦  «         ddd¦  «         n# 1 swxY w Y   ddd¦  «         n# 1 swxY w Y    dS # t           j	        j
        $ r6}||dz
  k     r'|j        t          v rt          j        d¦  «         Y d}~ŒÀ‚ d}~wt           j	        j        $ r! ||dz
  k     rt          j        d¦  «         Y Œ÷‚ w xY wdS )a2  
    Download a file with urllib, sending the headers of a browser
    :param url: The url of the file
    :param file_path: Where to save it
    :param timeout: Seconds to wait for the host in every request
    :param attempts: How many times to ask when the problem looks temporary
    :return: None
    )Úheaders)ro   ÚwbNrB   )r"   ÚrequestÚRequestr/   ÚrangeÚurlopenr—   ÚshutilÚcopyfileobjrd   ri   r�   ÚRETRYABLE_STATUS_CODESÚtimeÚsleepÚURLError)	r   rn   ro   r›   r    ÚattemptÚresponseÚout_filer€   s	            r   r…   r…   /  sÌ  € õ Œn×$Ò$ SÕ2EÀcÑ2JÔ2JÐ$ÑKÔK€Gå˜‘?”?ð ð ˆð	Ý”×'Ò'¨¸Ð'ÑAÔAð ;ÀXÝ˜) TÑ*Ô*ð ;¨hÝÔ& x°Ñ:Ô:Ð:ð;ð ;ð ;ñ ;ô ;ð ;ð ;ð ;ð ;ð ;ð ;øøøð ;ð ;ð ;ð ;ð;ð ;ð ;ñ ;ô ;ð ;ð ;ð ;ð ;ð ;ð ;øøøð ;ð ;ð ;ð ;ð ˆFˆFøåŒ|Ô%ð 	ð 	ð 	à˜ A™Ò%Ð%¨!¬&Õ4JÐ*JÐ*JÝ”
˜1‘”�Ø����ØøøøøåŒ|Ô$ð 	ð 	ð 	Ø˜ A™Ò%Ð%Ý”
˜1‘”�Ø�Øð		øøøðð sl   Á!B:Á"B,Á3BÂ	B,ÂBÂB,ÂBÂB,Â B:Â,B0	Â0B:Â3B0	Â4B:Â:D7Ã+C?Ã>C?Ã?4D7Ä5D7c                ó¨  — ddl m} t          | ¦  «        }dD ]}|                     |¦  «         Œ|                     | |d|d¬¦  «        }|j        dk    r:t          j                             | |j        t          |j        ¦  «        dd¦  «        ‚t          |d	¦  «        5 }|                     |j        ¦  «         ddd¦  «         dS # 1 swxY w Y   dS )
a=  
    Download a file with curl_cffi, which does the TLS and HTTP/2 handshake of a real Chrome (and
    sends the headers that go with it, so the User-Agent is left to it)
    :param url: The url of the file
    :param file_path: Where to save it
    :param timeout: Seconds to wait for the host
    :return: None
    r   )Úrequests)r   r   r    r!   ÚchromeT)rž   Úimpersonatero   Úallow_redirectséÈ   NrŸ   )ra   r®   r/   ÚpoprZ   Ústatus_coder"   rd   ri   Ú_get_status_phraser—   ÚwriteÚcontent)r   rn   ro   Úcurl_requestsrž   Úheaderr«   r¬   s           r   r‰   r‰   P  s;  € ð 4Ð3Ð3Ð3Ð3Ð3å! #Ñ&Ô&€GØWð ð ˆØ�Š�FÑÔÐÐà× Ò Ø�W¨(¸GÐUYð !ñ ô €Hð Ô˜sÒ"Ð"ÝŒl×$Ò$Ø�Ô%Õ'9¸(Ô:NÑ'OÔ'OÐQUÐW[ñ
ô 
ð 	
õ 
ˆi˜Ñ	Ô	ð ) (Ø�Š�xÔ'Ñ(Ô(Ð(ð)ð )ð )ñ )ô )ð )ð )ð )ð )ð )ð )ð )øøøð )ð )ð )ð )ð )ð )s   ÂCÃCÃCc                ó–   — |                      dt          j                             | d¬¦  «        ¦  «        }t	          |||d¬¦  «         dS )aO  
    Download a file through an image proxy: the proxy asks the host for it, not this server
    :param url: The url of the file
    :param file_path: Where to save it
    :param timeout: Seconds to wait for the proxy
    :param proxy_url: The url template of the proxy, with {url} where the url of the file goes
    :return: None
    rW   r@   )Úsafeé   )r›   N)Úreplacer"   r#   Úquoter…   )r   rn   ro   rr   Úproxied_urls        r   r‹   r‹   m  sK   € ð ×#Ò# G­V¬\×-?Ò-?ÀÈ"Ð-?Ñ-MÔ-MÑNÔN€KÝ�{ I¨wÀÐCÑCÔCÐCÐCÐCr   r´   c                óX   — 	 t          j        | ¦  «        j        S # t          $ r Y dS w xY w)z‰
    Get the standard phrase of an HTTP status code
    :param status_code: The status code
    :return: The phrase (e.g. Forbidden)
    zUnknown status)ÚhttpÚ
HTTPStatusÚphraseÚ
ValueError)r´   s    r   rµ   rµ   {  s>   € ð ÝŒ˜{Ñ+Ô+Ô2Ð2øÝð  ð  ð  ØÐÐð øøøs   ‚ ›
)¨))r   r   r   r   )r0   r1   r   r2   )r   r   r   r   )r   rU   )r   r2   )rd   re   r   r   )rm   FN)r   r   rn   r   ro   rp   rq   r2   rr   rU   r   r   )r   r   rn   r   ro   rp   rr   rU   )rd   re   r   r2   )rn   r   r   r�   )rš   )
r   r   rn   r   ro   rp   r›   rœ   r   r�   )r   r   rn   r   ro   rp   r   r�   )
r   r   rn   r   ro   rp   rr   r   r   r�   )r´   rœ   r   r   )#r   Ú
__future__r   rK   rÁ   r\   rX   rI   r¤   r§   Úurllib.errorr"   Úurllib.parseÚurllib.requestr+   r[   rŽ   r¦   rb   r   r/   r8   rT   r_   rc   rl   r�   rt   rz   rx   ru   r…   r‰   r‹   rµ   r   r   r   ú<module>rÉ      s>  ððð ð$ #Ð "Ð "Ð "Ð "Ð "à €€€Ø €€€Ø €€€Ø 	€	€	€	Ø 	€	€	€	Ø €€€Ø €€€Ø Ð Ð Ð Ø Ð Ð Ð Ø Ð Ð Ð ð%ð ð )Ð ð )˜˜Ð ð .Ð-Ð-Ð ðdð dð dð dð d�iñ dô dð dð ð  ð  ð  ðFð ð ð ð$ð ð ð ð8ð ð ð ð(ð ð ð ð
.ð 
.ð 
.ð 
.ð  ØØ ð4ð 4ð 4ð 4ð 4ðn
ð 
ð 
ð 
ð4^ð ^ð ^ð ^ð
ð 
ð 
ð 
ð^ð ^ð ^ð ^ðð ð ð ð ðB)ð )ð )ð )ð:Dð Dð Dð Dð
 ð 
 ð 
 ð 
 ð 
 ð 
 r   