ó
    ¡zˆj;  ã                   óN  • S r SSKrSSKrSSKrSSKrSSKrSSKrSSKrSSK	J
r
  SrSrSrSrSrS	rS
rSr1 SkrS\SS4S jrS!S\S\S-  S\S\S-  4S jjrS"S\S\S-  S\S-  4S jjrS\
S\4S jrS\
S\SS4S jrS\
S\\   S\S\S\
S\S\4S jrS\SS4S jr S\S\4S  jr!g)#aH  
Shared, dependency-free core for the manga downloaders.

Provides:
  - a polite HTTP fetcher (single sequential connection, fixed delays,
    exponential backoff, and a hard stop if the server keeps returning 429),
  - a resumable chapter download engine (page_NNN naming, self-healing
    reconciliation of existing files, download_state.json),
  - a Source adapter interface that site modules implement.

A Source adapter provides:
  name            str   identifier, e.g. "mangaread"
  site            str   origin used for links, e.g. "https://www.mangaread.org"
  output_dir(url) Path  archive directory for a manga URL
  chapter_dirname(ch_id) str  e.g. "chapter_12" or "chapter_71_1"
  get_chapters(manga_url) -> list[(ch_id, ch_url, label)]  in READING order
  get_pages(chapter_url) -> list[str]  page image URLs in reading order
é    N)ÚPathzjMozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0 Safari/124.0g       @g      è?é   g      @é   g       @é   >   ú.bmpú.gifú.jpgú.pngú.avifú.jpegú.webpÚmsgÚreturnc                 óF   • [        [        R                  " S5      U -   SS9  g )Nz[%H:%M:%S] T)Úflush)ÚprintÚtimeÚstrftime)r   s    Ú;/home/nathan/projects/ai/drake_scans_download/manga/core.pyÚlogr   .   s   € Ü	Œ$�-Š-˜Ó
&¨Ñ
,°DÓ9ó    ÚurlÚrefererÚtimeoutc                 ó’  • [         SS.nU(       a  XS'   [        S[        S-   5       HX  n [        R                  R                  XS9n[        R                  R                  XRS9 nUR                  5       sSSS5        s  $    [        S[         SU  35        g! , (       d  f       M�  = f! [        R                  R                   Ga  nUR                  S:X  an  U[        :¼  a!  [        S	5        [        R                  " S
5        [        S
U-  -  n[        SUS SU S[         S35        [         R"                  " U5         SnAGM!  SUR                  s=::  a  S:  aP  O  OM[        U-  n[        SUR                   SUS SU S[         S3	5        [         R"                  " U5         SnAGMˆ  [        SUR                   SU  S35         SnA  gSnAf[        R                  R$                  [&        [(        4 aH  n[        U-  n[        SU SUS SU S[         S3	5        [         R"                  " U5         SnAGM  SnAff = f)z¿GET a URL politely. Returns bytes, or None after repeated failure.

Backs off on 5xx/network errors; on 429 it backs off exponentially and
aborts the whole run if the rate limiting persists.
z*/*)z
User-AgentÚAcceptÚRefereré   )Úheaders)r   Ni­  u7   Server keeps returning 429 â€” stopping out of respect.é   z  429 rate-limited. Waiting z.0fzs (attempt Ú/z)...iô  iX  z  HTTP z. Retrying in z for u    â€” giving up on this URL.z  Network error (z). Retrying in z  Failed after z attempts: )Ú
USER_AGENTÚrangeÚMAX_RETRIESÚurllibÚrequestÚRequestÚurlopenÚreadÚerrorÚ	HTTPErrorÚcodeÚABORT_ON_429_RETRIESr   ÚsysÚexitÚRETRY_BACKOFF_BASEr   ÚsleepÚURLErrorÚTimeoutErrorÚOSError)	r   r   r   r   ÚattemptÚreqÚrespÚeÚwaits	            r   Úfetchr:   2   sê  € ô (°5Ñ9€GÞØ$�	ÑÜ˜œK¨!™OÖ,ˆð	Ü—.‘.×(Ñ(¨Ð(Ð>ˆCÜ—‘×'Ñ'¨Ð'Ñ=ÀØ—y‘y“{÷ >Ó=ñ -ô4 ˆ/œ+˜ k°#°Ð7Ô8Ø÷1 >Ö=ûä�|‰|×%Ñ%ô 	Ø�v‰v˜‹}ØÔ2Ó2ÜÐQÔRÜ—H’H˜Q”KÜ)¨Q°'©\Ñ:�ÜÐ2°4¸°*¸KÈÀyÐPQÔR]ÐQ^Ð^bÐcÔdÜ—
’
˜4Ô ÞØ�a—f‘fÕ"˜sÖ"Ü)¨GÑ3�Ü�g˜aŸf™f˜X ^°D¸°:¸[ÈÈ	ÐQRÔS^ÐR_Ð_cÐdÔeÜ—
’
˜4Ô ÞÜ�'˜!Ÿ&™&˜  s eÐ+FÐGÔHÝûÜ—‘×%Ñ%¤|´WÐ=ó 	Ü%¨Ñ/ˆDÜÐ# A 3 o°d¸3°Z¸{È7È)ÐSTÔU`ÐTaÐaeÐfÔgÜ�JŠJ�tÔÞûð		úsO   ¬:B,Á&BÁ6	B,Â
B)	Â$B,Â)B,Â,IÃA7GÅ	A GÆ0GÇ,IÇ><IÉIc                 ó>   • [        XS9nUb  UR                  SSS9$ S $ )N)r   zutf-8Úreplace)Úerrors)r:   Údecode)r   r   Údatas      r   Ú
fetch_htmlr@   Y   s)   € Ü�Ñ&€DØ59Ñ5Eˆ4�;‰;�w yˆ;Ð1ÐOÈ4ÐOr   Úpathc                 óž   • U R                  5       (       a%   [        R                  " U R                  5       5      $ 0 $ ! [         a    0 s $ f = f©N)ÚexistsÚjsonÚloadsÚ	read_textÚ	Exception)rA   s    r   Ú
load_staterI   ^   sE   € Ø‡{�{‡}�}ð	Ü—:’:˜dŸn™nÓ.Ó/Ð/ð €Iøô ó 	ØŠIð	ús   —#= ½AÁAÚstatec                 ó�   • U R                  S5      nUR                  [        R                  " USS95        UR	                  U 5        g )Nz.tmpr    )Úindent)Úwith_suffixÚ
write_textrE   Údumpsr<   )rA   rJ   Útmps      r   Ú
save_staterQ   g   s5   € Ø
×
Ñ
˜6Ó
"€CØ‡N�N”4—:’:˜e¨AÑ.Ô/Ø‡K�K�Õr   Údest_dirÚurlsÚ
state_fileÚlabelc           	      ó¸  • U R                  SSS9  U R                  5        Vs0 s HI  nUR                  5       (       d  M  UR                  R	                  S5      (       a  M<  UR                  U_MK     nn[        5       n/ n	[        US5       H±  u  p«[        R                  R                  U5      nSU
S [        R                  R                  U5      S    3nSnXÜ4 H,  nUR                  U5      nUc  M  [        U5      U;  d  M*  Un  O   Ub  UR                  [        U5      5        U	R                  XÞU45        M³     SnUR                  5        HP  u  nn[        U5      U;  d  M  UR                   R#                  5       [$        ;   d  M;  UR'                  5         US-  nMR     U(       a  [)        S	U S
35        Sn[        U	S5       GH  u  n
u  pÞnX-  nUb  UU:w  a  UR+                  U5        UR-                  5       (       a   UR/                  5       R0                  S:”  a  M]  [2        R4                  " [6        5        [9        X²SS9nUc  US-  nM‹  UR;                  UR                   S-   5      nUR=                  U5        UR?                  U5        U
[@        -  S:X  d  MÚ  U
[C        U	5      :  d  Më  [2        R4                  " [D        5        GM     U(       a  [)        U SU S[C        U5       S35        gU[C        U5      [2        RF                  " S5      S.URI                  S0 5      U'   [K        XC5        [)        U S[C        U5       S35        gs  snf )a=  Download one chapter's pages as page_001.ext, page_002.ext, ...

Self-healing: existing files are matched by the URL basename (old naming)
or the final page_NNN name and renamed into place; missing pages are
downloaded; stray image files that belong to neither are removed.
Returns True when the chapter is complete.
T©ÚparentsÚexist_okz.partr   Úpage_Ú03dNr   z
  removed z$ stray image files from chapter dir.g      N@)r   r   z: r!   u!    pages failed â€” retry next run.Fz%Y-%m-%dT%H:%M:%S)r   ÚimagesÚfinishedÚdonez: done (z pages).)&ÚmkdirÚiterdirÚis_fileÚnameÚendswithÚsetÚ	enumerateÚosrA   ÚbasenameÚsplitextÚgetÚidÚaddÚappendÚitemsÚsuffixÚlowerÚIMG_EXTSÚunlinkr   ÚrenamerD   ÚstatÚst_sizer   r1   ÚDELAY_IMAGEr:   rM   Úwrite_bytesr<   ÚPAUSE_EVERY_N_IMAGESÚlenÚ
PAUSE_LONGr   Ú
setdefaultrQ   )rR   rS   r   rJ   rT   rU   ÚpÚexistingÚassignedÚplanÚir   ÚbaseÚ	dest_nameÚsrcÚ	cand_nameÚcandÚremovedrb   ÚfailedÚdestr?   rP   s                          r   Údownload_chapterrˆ   m   sç  € ð ‡N�N˜4¨$€NÑ/à#+×#3Ñ#3Ô#5ó AÒ#5˜aØ—9‘9—;ó Ø'(§v¡v§¡°w×'?ó �—‘˜’	Ñ#5€Hð Aä‹u€HØ€DÜ˜D !Ö$‰ˆÜ�w‰w×Ñ Ó$ˆØ˜A˜c˜7¤2§7¡7×#3Ñ#3°DÓ#9¸!Ñ#<Ð"=Ð>ˆ	ØˆØ#Ó*ˆIØ—<‘< 	Ó*ˆDØÓ¤B t£H°HÕ$<Ø�Ùñ	 +ð
 ‰?Ø�L‰Lœ˜C›Ô!Ø�‰�Y SÐ)Ö*ñ %ð €GØ—>‘>Ö#‰ˆˆaÜˆa‹5˜Õ  Q§X¡X§^¡^Ó%5¼Õ%AØ�H‰HŒJØ�q‰LŠGñ $ö Üˆj˜˜	Ð!EÐFÔGà€FÜ$-¨d°A×$6Ñ ˆÑ ˆI˜CØÑ#ˆØ‰?˜s d›{Ø�J‰J�tÔØ�;‰;�=‰=˜TŸY™Y›[×0Ñ0°1Ó4ÙÜ�
Š
”;ÔÜ�S°4Ñ8ˆØ‰<Ø�a‰KˆFÙØ×Ñ˜tŸ{™{¨WÑ4Ó5ˆØ�‰˜ÔØ�‰�DÔØÔ#Ñ# qÕ(¨Q´°T³­]Ü�JŠJ”z×"ñ %7ö" Üˆuˆg�R˜�x˜q¤ T£ Ð+LÐMÔNØð Ü�d“)Ü—M’MÐ"5Ó6ñ+€E×Ñ�V˜RÓ  Ñ'ô
 ˆzÔ!Üˆ5ˆ'�œ#˜d›)˜ HÐ-Ô.ØùòmAs   £MÁ MÁ"MÚ	manga_urlc                 óh  • U R                  U5      nUR                  SSS9  US-  n[        U5      n[        SU 35        [        R
                  " [        5        U R                  U5      nU(       d  [        R                  " S5        [        S[        U5       S35        [        SUR                  5        35        S	=pg[        US
5       HŸ  u  nu  pšn[        SU SU S[        U5       S35        [        R
                  " [        5        U R                  U
5      nU(       d  [        U S35        US
-  nMn  X R                  U	5      -  n[        XÜX¤X;5      (       a  US
-  nMš  US
-  nM¡     [        SU SU S35        U(       a!  [        S5        [        R                  " S
5        [        S5        g)z;Download every chapter of a manga through a Source adapter.TrW   zdownload_state.jsonzFetching manga page: u8   No chapter links found â€” site layout may have changed.zFound z chapters (in reading order).zOutput dir: r   r   z
=== z (r!   z) ===u3   : could not determine page list â€” retry next run.z
All chapters processed: z ok, z incomplete.z4Re-run this script to retry the incomplete chapters.zKNext: run generate_html.py on this directory to (re)build the reader pages.N)Ú
output_dirr_   rI   r   r   r1   ÚDELAY_CHAPTER_PAGEÚget_chaptersr.   r/   rx   Úresolvere   Ú	get_pagesÚchapter_dirnamerˆ   )Úsourcer‰   ÚoutrT   rJ   ÚchaptersÚokÚ
incompleter   Úch_idÚch_urlrU   Úpagesr‡   s                 r   Úrunr™   ±   s�  € à
×
Ñ
˜IÓ
&€CØ‡I�I�d T€IÑ*ØÐ,Ñ,€JÜ�zÓ"€EäÐ
 	˜{Ð+Ô,Ü‡J‚JÔ!Ô"Ø×"Ñ" 9Ó-€HÞÜ�ŠÐKÔLÜˆ&”�X“�Ð<Ð=Ô>Üˆ,�s—{‘{“}�oÐ&Ô'àÐ€BÜ%.¨x¸Ö%;Ñ!ˆÑ!ˆE˜5Üˆf�U�G˜2˜a˜S ¤# h£- °Ð6Ô7Ü�
Š
Ô%Ô&Ø× Ñ  Ó(ˆÞÜ�5�'ÐLÐMÔNØ˜!‰OˆJÙØ×+Ñ+¨EÓ2Ñ2ˆÜ˜D¨¸
×JÑJØ�!‰GŠBà˜!‰OŠJñ &<ô Ð
$ R D¨¨j¨\¸ÐFÔGÞÜÐBÔCÜ�Š�ŒÜÐUÕVr   c                 ód   • [         R                  " SU 5      nU(       a  UR                  S5      $ S$ )z8Trailing non-empty path segment, minus a trailing slash.z([^/]+?)/?$r   Úmanga)ÚreÚsearchÚgroup)r   Úms     r   Úslug_from_urlr    Ö   s'   € ä
�	Š	�. #Ó&€AÞˆ1�7‰7�1‹:Ð' Ð'r   )Ng      >@rC   )"Ú__doc__rE   rf   rœ   r.   r   Úurllib.errorr%   Úurllib.requestÚpathlibr   r"   rŒ   ru   rw   ry   r$   r0   r-   rp   Ústrr   ÚfloatÚbytesr:   r@   ÚdictrI   rQ   ÚlistÚboolrˆ   r™   r    © r   r   Ú<module>r¬      s^  ðñó& Û 	Û 	Û 
Û Û Û Ý ð4ð ð Ð Ø€ØÐ Ø€
Ø€ØÐ ØÐ âF€ð:ˆSð :�Tô :ñ$ˆsð $˜S 4™Zð $¸ð $È%ÐRVÉ,õ $ñNP�Cð P #¨¡*ð P¸¸d¹
õ Pð
�Tð ˜dô ð�Tð  $ð ¨4ô ðA˜tð A¨4°©9ð A¸sð AØ ðAØ.2ðAØ;>ðAØCGôAðH"W˜3ð "W 4ô "WðJ(�sð (˜sõ (r   