From d5a73f342ec49796a008160ddc6a98633a2d06d8 Mon Sep 17 00:00:00 2001 From: ithillad Date: Thu, 19 Dec 2024 13:58:12 +0100 Subject: [PATCH] Initial commit: Add webtoon downloader --- MyWebtoon.bat | 2 + __pycache__/decrypt.cpython-312.pyc | Bin 0 -> 2969 bytes __pycache__/kakao_webtoon.cpython-312.pyc | Bin 0 -> 10171 bytes __pycache__/main.cpython-312.pyc | Bin 0 -> 1626 bytes __pycache__/prerequisite.cpython-312.pyc | Bin 0 -> 4163 bytes __pycache__/progress.cpython-312.pyc | Bin 0 -> 4199 bytes converter/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 140 bytes .../__pycache__/converter.cpython-312.pyc | Bin 0 -> 9766 bytes .../delete_declaration_img.cpython-312.pyc | Bin 0 -> 1660 bytes converter/converter.py | 178 ++++++++++++++++++ data/__init__.py | 0 data/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 139 bytes data/__pycache__/folder.cpython-312.pyc | Bin 0 -> 388 bytes .../__pycache__/headers_kakao.cpython-312.pyc | Bin 0 -> 1848 bytes data/__pycache__/kakao_cookie.cpython-312.pyc | Bin 0 -> 1312 bytes .../__pycache__/kakao_request.cpython-312.pyc | Bin 0 -> 2465 bytes .../__pycache__/not_processed.cpython-312.pyc | Bin 0 -> 341 bytes .../__pycache__/path_constant.cpython-312.pyc | Bin 0 -> 744 bytes data/__pycache__/special_list.cpython-312.pyc | Bin 0 -> 976 bytes .../webtoon_request.cpython-312.pyc | Bin 0 -> 2148 bytes data/kakao_cookie.py | 25 +++ data/kakao_request.py | 42 +++++ data/path_constant.py | 11 ++ data/special_list.py | 62 ++++++ data/webtoon_request.py | 39 ++++ downloaders/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 142 bytes .../__pycache__/bomtoom.cpython-312.pyc | Bin 0 -> 6919 bytes .../__pycache__/bomtoon.cpython-312.pyc | Bin 0 -> 3114 bytes .../__pycache__/decrypt.cpython-312.pyc | Bin 0 -> 2981 bytes .../__pycache__/downloader.cpython-312.pyc | Bin 0 -> 9119 bytes .../__pycache__/kakao_tw.cpython-312.pyc | Bin 0 -> 9761 bytes .../__pycache__/kakao_webtoon.cpython-312.pyc | Bin 0 -> 9378 bytes .../kakao_webtoon_use_b4s.cpython-312.pyc | Bin 0 -> 7960 bytes .../__pycache__/prerequisite.cpython-312.pyc | Bin 0 -> 1947 bytes .../__pycache__/webtoon_com.cpython-312.pyc | Bin 0 -> 7538 bytes downloaders/bomtoon.py | 62 ++++++ downloaders/decrypt.py | 43 +++++ downloaders/downloader.py | 168 +++++++++++++++++ downloaders/kakao_webtoon.py | 141 ++++++++++++++ downloaders/webtoon_com.py | 122 ++++++++++++ helper.py | 23 +++ helper/__init__.py | 0 helper/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 137 bytes .../missing_episode.cpython-312.pyc | Bin 0 -> 1250 bytes .../missing_images.cpython-312.pyc | Bin 0 -> 2084 bytes helper/missing_episode.py | 18 ++ helper/missing_images.py | 44 +++++ main.py | 99 ++++++++++ prerequisite.py | 62 ++++++ rename.py | 17 ++ rename_drawable.py | 15 ++ 53 files changed, 1173 insertions(+) create mode 100644 MyWebtoon.bat create mode 100644 __pycache__/decrypt.cpython-312.pyc create mode 100644 __pycache__/kakao_webtoon.cpython-312.pyc create mode 100644 __pycache__/main.cpython-312.pyc create mode 100644 __pycache__/prerequisite.cpython-312.pyc create mode 100644 __pycache__/progress.cpython-312.pyc create mode 100644 converter/__init__.py create mode 100644 converter/__pycache__/__init__.cpython-312.pyc create mode 100644 converter/__pycache__/converter.cpython-312.pyc create mode 100644 converter/__pycache__/delete_declaration_img.cpython-312.pyc create mode 100644 converter/converter.py create mode 100644 data/__init__.py create mode 100644 data/__pycache__/__init__.cpython-312.pyc create mode 100644 data/__pycache__/folder.cpython-312.pyc create mode 100644 data/__pycache__/headers_kakao.cpython-312.pyc create mode 100644 data/__pycache__/kakao_cookie.cpython-312.pyc create mode 100644 data/__pycache__/kakao_request.cpython-312.pyc create mode 100644 data/__pycache__/not_processed.cpython-312.pyc create mode 100644 data/__pycache__/path_constant.cpython-312.pyc create mode 100644 data/__pycache__/special_list.cpython-312.pyc create mode 100644 data/__pycache__/webtoon_request.cpython-312.pyc create mode 100644 data/kakao_cookie.py create mode 100644 data/kakao_request.py create mode 100644 data/path_constant.py create mode 100644 data/special_list.py create mode 100644 data/webtoon_request.py create mode 100644 downloaders/__init__.py create mode 100644 downloaders/__pycache__/__init__.cpython-312.pyc create mode 100644 downloaders/__pycache__/bomtoom.cpython-312.pyc create mode 100644 downloaders/__pycache__/bomtoon.cpython-312.pyc create mode 100644 downloaders/__pycache__/decrypt.cpython-312.pyc create mode 100644 downloaders/__pycache__/downloader.cpython-312.pyc create mode 100644 downloaders/__pycache__/kakao_tw.cpython-312.pyc create mode 100644 downloaders/__pycache__/kakao_webtoon.cpython-312.pyc create mode 100644 downloaders/__pycache__/kakao_webtoon_use_b4s.cpython-312.pyc create mode 100644 downloaders/__pycache__/prerequisite.cpython-312.pyc create mode 100644 downloaders/__pycache__/webtoon_com.cpython-312.pyc create mode 100644 downloaders/bomtoon.py create mode 100644 downloaders/decrypt.py create mode 100644 downloaders/downloader.py create mode 100644 downloaders/kakao_webtoon.py create mode 100644 downloaders/webtoon_com.py create mode 100644 helper.py create mode 100644 helper/__init__.py create mode 100644 helper/__pycache__/__init__.cpython-312.pyc create mode 100644 helper/__pycache__/missing_episode.cpython-312.pyc create mode 100644 helper/__pycache__/missing_images.cpython-312.pyc create mode 100644 helper/missing_episode.py create mode 100644 helper/missing_images.py create mode 100644 main.py create mode 100644 prerequisite.py create mode 100644 rename.py create mode 100644 rename_drawable.py diff --git a/MyWebtoon.bat b/MyWebtoon.bat new file mode 100644 index 0000000..4847285 --- /dev/null +++ b/MyWebtoon.bat @@ -0,0 +1,2 @@ +@echo off +python "%~dp0main.py" %* \ No newline at end of file diff --git a/__pycache__/decrypt.cpython-312.pyc b/__pycache__/decrypt.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c8fe9b41943e504c9c79c260246c36f9d0afbff2 GIT binary patch literal 2969 zcmbtWO>9(E6u$3wX39)Ur=`;tq0<(HNwi?)#~{D`iD{5REQp;Hp40bk=^N(H``%Ed zObIbvOchg_n6!o%%SMKFc~nE9E^t+-3ChZI-_J}XMHk-5oO{l> z=iGOHzH`p~(dVl`(Ej@U;~`E&=oczDjklTABfv}}1u0Ang{__mGa$1uHqM1PhU&N& z9~Z&`gIM%7QuuMC2zOX(hOn4nLgFJT^oAJ2t)){bf;A2Jt;bbOQxoS;q;xfzh{U>Z z3MUktkTczcB#CTIU{Q}PtcHP^Mi|jGgjt0dM_~^03OkPO*d}2CWDbi84}SsvA{K|- ziff!NS-3#w##GNXfY%VKxX1ahC*uiK7_KfXlT1pNUG{vmqnqL2zxqCqX_RHMNCM7k zSr#OwByk|wn@1?igG4>emK8v9juvG_kOXHegKjguA<^Kah^iPB5>BaFQo%ix*VQ=I z^hi8qh*Bb%kg?&C(i$c($V;QD5^@>5hGRnpM}0kIS5MhfupC9(j{`T2ZZT-JXTk3M z11Cu`ie+6JIG#C!2lZq!F`!td(U!^>Zb?!TsxC>R8|^b{b2M(4NP88?RkTza$nn=a zOO372RXJVVW{@^41-3xvHBCPCtkSK^rGoqm4)l(#+k+UCMbXk> zf`i>;77z(P2Z0RgL#^m_q%k8bgZjabK?&{>*N;XR58xV*%*EL;2DJ69NT*vd+creJ z4ALFs0m_|kf5eO+%ijb-X>IGTrw@hrUP6rk^W4$yzR`Xg_`#%&{Gd{TesFs#W8yEV zB;)wf_O{^37>;N-s3rgbu~<;mgL*Ouw;0riRV_G_PRLgHsCuR?BoGQVqw=V(6Lm1H zV>6UUBUM&+Qe2z!wsoqhVN8q-J@HhM=%!JKH8?q@nYJ2TZZJsdsGsXR)};v6Mys4_ zw-p;9a)9_wr-Jcs4K9)J^Z3+V_+L{wY;Fq2$>L&v`C0KYL&MbGfRK zsxC9j{w;;AnWBHR;2m87^UKV$6%p0c<+y9Er7HjA$izslQmckfT=%}heI@L4eJ$=s z-)uTs#TmR3(IWuHjZ*CK)jml?KKfPt_@(GsFSVK*l$jPY4MS_)NX z6hfE<{n}aWp)7aDwjL9*LUe7qa4Z-HF@1e>g?z=Ty~I! zM6wlzNsy9eN?Ir}FHe-s8`EfrSP^Soj$5*w*94`lc?nFlb|6>L!^-N(3lkSUiRZYb z>e}hRRA8oNM*cGPc`V;itlpUum#Q{RUYWQuqZF%J3+`4@4{rYm&C*c@0UB#VGfJWI zTw6a7ZqN&$>wm79`cl}H2XCM+xafwm>~PM7IfondIV0uXu^D5^N#q49tt@*v+`p`d z{l6=M)WkHRF;=IjHe1y1aOECOs zPN-Px+VbnzicsI6q(Z`Ddmqu{5b_zW;fOXIQwI%E8;-Q^*-PkU5&G~T^lA+e>OGV> zLsZoBP~(W#R1QMM+q>KF4DQ_xV1{u*(=ee|MM6MAE>cAe-8zn^B$@(TCI@-I2NTjf zhZR%rEsN^Ka!{=;eO4d^Xi~Ey2kP8VG9ICMRU2*UvP<9#3}}pM4}d@xtZAG+G<9h9 z)UAP=1GBwzyYC;qcev2r`EA$tC%!vT=sZ(;TG$7T=C)!uKq*HqYbw9wqK*xa+w+*52mR`kEK=Y3}$q!nb&6s zdO8)uWG`6WM&{(^B@mh&OhU7%_5qM_^n?|K`jw4HtbWXTgup5X+zRDZ>P(Ikw#{^} zAn5Xf+0E@SyZO^ksM59vZlwa8@|*JMIh@Zfgbw_HK(;x2%GBcP+DN81bp3LV2d YK)wg4@+Z{v8|r$>Z({;a5D?S(Z-HNotpET3 literal 0 HcmV?d00001 diff --git a/__pycache__/kakao_webtoon.cpython-312.pyc b/__pycache__/kakao_webtoon.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..01a0c1d873d812897c0f50f47990d684f9e97558 GIT binary patch literal 10171 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 D5w(Sq literal 0 HcmV?d00001 diff --git a/__pycache__/prerequisite.cpython-312.pyc b/__pycache__/prerequisite.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e650d75ca95d593ee754954510e28e4c103aff99 GIT binary patch literal 4163 zcmb_fO;8)j74G@dk0lwg8eYt!6MHgGT8Y zjA7*zl{l46g#*qZ;Bsx5R1S&Dl`0N7^ydG&J z;kCh)q*Z#|)BXDO>*??7_vQ~C&kh8o>GoGcP8ULdArDrei;eXmXv`uJiIj{6NlnYt zC_PA1g+6AGfj&lNN4Y_6)HY~?epaG~Xpy_e4%$WAJv8Vj_Sm7vS!_F?y`$K6LfbV# z1$KN+7L5cbgYAoH!vWfGhkLI_dV0IU(Qx-Q!xcF{aJ~21W%Geefl8bzx=AWK{2BPK zw?gkMN~4j=QKA9mNufev^uZ4hx=W?05i;k6Vri+=G!GhE+lhi5I!$k9N8_wTKz9p^ zPl{uhnht7YPi7IRH2n~LLR+I0(wx@VhR3E3sp`;}EKdXj zHp8CKBn;CHTSASJnu#SR3@(KeNzJgS!(&=PHf(rQ!;`|5MP!x`Vm-rj2PGWhLBMz*C zS13+|Rme*+!41R%aBC$1jaW>zG@-WO3m_i`a1tt%z*knYv#BH_8bsAZ=DuFFB#EQsng6oJ0RW7oL`^4Sj@L%r)aT!r2 z|383JHW^-eS>*+-&Ht2|r6@FALyZ248K(ji+&`H33fB)EJZi82rcsr=WI@0_s5YFP zx1^+WCxuT>o~%IFcmaiKbUdLAD`Q#-Y{5=hiN|F1bg(p$z)!6K@i+9(DfG-gu)xo0 zk3M?v(WAQ=|G>Xg7KXl8TSs9BG$vhPi;6-cCae$GF*#36cyIcO$t%SkB6bo{1tLJ> zYN!l1utm5@)9H7HW_gXj@ntli!?;6Cq-_YFfV9e z!`lD|2C&K8Mu2+9%P;>lvlUy)IGsMkA}5K0@UvE;P@3vT2hlOGKMqvA0Z=UT-C=H^ zaq1w_h~H+ZN7%O=+HaWlw9*9+bN1e4D|p7G+08t=0p8qYzsnLo3axQ!92Hg%*dhjn zCmq9@mQq8_&6DndVM!QD$dVA#gvlylJf;dsMH7aUv7{KB^mQc#31bBdN<2P>u_T5B zxZhbNB_$18ECrQlaH@tADWm!Vn29Lb1v2$KnPlS8aGt*tmr`0nNd~yWRqq(Xa#T*J zn!%E#4F;YKN>T}=;dU?5%&5L4X;D!bPs&ORq5$aQL!hw_{_0~8aBEjpPhDD}9b919 zQ$15Z$NV_Df*9wqc{aa$-x{Jl$7Yzk@6CDNGhcn4ubb_?-#h=#FS>u${qxI9{Lxho z6uw|Uf5nU3{8aB3)#dh=&z;Eq<{F~7)@6rh#yvMMcVWI+-`TR{IKE;t)t__7y^ClB zKtuN)obQ@HuJ3HjIS%QLLwT2Ps`twk4n{ZB)8OqrcNdRlc68a9hi`%SodX`9+PSW3 z`%^bV+SR7y_nh-GPn#V?p4@rP#r)pILHp&vKLGH4jXdYy#anQTEB&?doIiq{ZSox4 zNAeW7p}$scBYdtLWGb_hm+Oy|vYWCgkrHW(Xm^=3m1ZX4q6Ru_9a`u#Ys~^GZkMNN z5OiL#19(f{|DX53h$U}e56nyV$%!`0a;o{q}YYth%VP;aJQeVI^SrlpT0R<(y0 z`6GFMbI#wI@wevuof&`UZw5X+p7mdxxtQPIywI50-#&9`nXj4czTd6;n-}&h@}bIf zZlOW%=*e|l&2(IS)^hcgHsAd;ob`8uO@G59<$>}`X`w&sZ-3PqOMGZ`H~7EG!6v^R z%}O6Hh5T;Yj~Ds>MCuFMliJJ=OZg*^qZGm5_=@GFFs7_nY1JbA?JyiWHQiaJY~@uj zeF;FDG(pQm@HfEr=Ols$Y!v`yWGRV{KraNIl$_8CS&tVwtj_~8OEc<7_^|SQGbf9V zV+x)7Fz~yOwFYruV;>ZM&Xr zd0+q5&1}of0)UqIBP8N9t#Yt}dHw-64#Mh&oqX=di4QTMrf zmeer0R10Yz$sWzL%nXPooiN$ZV`SPbWT}qMQAHe+rPH_rhDZjic7s@9D2n@dy~aDJUTTGRQT!ZqLA6r5lj@kOUq?`_9N?&?dF?rZ%3SI{ Df0kyi literal 0 HcmV?d00001 diff --git a/__pycache__/progress.cpython-312.pyc b/__pycache__/progress.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..22128c7417b1b8cf5d0ff0425dba58522e44560c GIT binary patch literal 4199 zcmb^!TTC0-^^V_dpM?U)9pZ%$0sI8HstknLbAJvAmKly3Tz4I{1CaqTO zmFCRxJ?G3l_q_dCAmBr=K6~)@59e8g{z873%Vr?Y{|Lw(Bp`v2kj(H5LvWj9lkL2n z;&#a)J9($<;$5bOU7$-$48k!S^G? z#{@sX4uJ*ODFgs^2@L?_tHA{Klw2yoXxwG-x|+}D#`C#l5vwAGJ@8}vA*yL1zmk*k86hoY6?MS~7W#4kw8QiK z7Xa=cm2MI+fv%oKXblz7q8VKl4I!1t(Y4fzkiu+6RJXbFdWn0CPG^wnF*z6gCRTqJ zQJr&h%z;)R1d<6CCI@PcTt*ggn4r!A&gN7tu$+;yLV6*eU&_L5U712b%n4vsm$5~T zjlJj)hp)`w{Gzy^Dp#gg4U1nX;QS3ND#}1%RpS&e)WNUeBOuxUk1_;c9sRYjWgpp{ zi49lPANt9ekIwwMuk1fsV?$frclti=+hq^e*p><#E3vWd<~_FenH!j&gD6@s?oMz- ztVu1t3CJDDhc!lEif9>OZ;`3n|8@YZ&j>b)VtK)!*e#0X3xndYC@yj!Sq8;vQ7j+m zlp-^W60W4ioY0teG-gO+WQ{2#oY)J(m=qpNe1)O#APkxz2s&3bSGc8u&B21VBvl~_ z#$xy|ytD=qbxS19)3>ms-~?uduP( zm?(LOE9x?^afte6eHzE$n3v+5Sb|!88`Ok2w>+X+b~WQpV3sBj+G23O>I5meRLhbk z7sN5KjZUSNnV=?>4OdNzA zN71eZO)-@-F;i}tWMPv^a)Yj-z38}S+-=QPv@H_zzp^0S>i05u8MJxIfpV*Av&oEy z1t-;g@C~}VOdSNbp*wleRkZH))pW?67CiLKci(WP^@GD#HQbY)Gkw^{{tMP^Ze?X)Q>4a$RpY1L}%~5{;T&5_6c?2ouL*64^LH z0Eqz2sT8EFO4AgRK*1!wG|uW9#xeLULJJWvMSDnzRoS$Y-mnCIiuKp;j_df|fWsOI)m1m*{cy0{Id<1wjUK5)kC&py?}tB? z_M+oZ*FoT)9@NkzGDr&X&)9-Ig2^{>9W#z2K25&6n8&xCsR-T$i9O|{V! z2OYov1|3Q8)3wY1*N^|NIzFJ2j!n<|grDZ8=FY;jD`YV~4#(oJ1AwNhq{$Acur$3e z@u%h!@-PF@`52R{U^2Hrk7msbD`eE~X_RPL5VL%wbk1nfZ7M6y52+0>Z0 zQg3A~2PyjGYTAqT!pK3onN`q^X*bjyaweP8GmlJQdY>eXRAUQR#Ny3cS%oAs?Y*?^ zQn!+nrhhZ^&P%!+K1%>`(eDtoAZ>*&JnzYQ;g%$R15X2qba`b8z!!FgVIHI7kI~U5 zC|gF^CkR&%evEn^qmz$O-xD-hMw0+P=Ni`o)nI6y-FG;d#C`+IH14-WnCSjVAJg?b J>SA<%{Tt4Mha3O^ literal 0 HcmV?d00001 diff --git a/converter/__init__.py b/converter/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/converter/__pycache__/__init__.cpython-312.pyc b/converter/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d9ada636672d91246b5a95d5666ed819e4779f33 GIT binary patch literal 140 zcmX@j%ge<81j22-X(0MBh(HIQS%4zb87dhx8U0o=6fpsLpFwJVX{TDn1Qg|Gr6!jY z$M{x;rzVx;=jX*F=jWBB7L}wH#l**F=4F<|$LkeT{^GF7%}*)KNwq6t1?pu4;$jfv NBQql-V-Yiu1pp7>AdLV3 literal 0 HcmV?d00001 diff --git a/converter/__pycache__/converter.cpython-312.pyc b/converter/__pycache__/converter.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..41dc3fa98aa426943763b5436a519c91159d93ef GIT binary patch literal 9766 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 W{cqGUg*tYRIcJ~)cPPxsApZrP%b%M7 literal 0 HcmV?d00001 diff --git a/converter/__pycache__/delete_declaration_img.cpython-312.pyc b/converter/__pycache__/delete_declaration_img.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3f7bb847e837bc8f7440051ada0ee62df003a4fd GIT binary patch literal 1660 zcmah}O-vg{6n?Wm#*4GYP@51@U?sar-AE*9l+Y4DB7p=DL1|j4ibZ9)-VH3Vy=Zq+ zuxm%Cs#K7ufRkNCQGHCsg-fKWhbnN*#SuhBTldsldUGLCD!BB`+8ZadRo}{Q-uTUY z^XBdM?GsUK1T^x^x3@za!0&Y87*h-OLm1>i2AL!vLPsV+l1zjlAhTaXgq1n;T$+UW zKj@Z;FtPa2WNb=_lNtma8t}*BZ@+{t4+5C4vNO(6m~ku%>$Us^$dN3W!SasUB<{;G zIX274)v%w*v{ zUM|bshXlyb2YRuKcPx^yNrKW*EL{_Y zPOooqG-q*!no%racs{PonyQwv_*q>|nPJ{)wD&}3W9GEQs>vyfpHpRwUDJv>HEmi% zF?ht0s2zN$^iJN;wHYOD8k5)4pDLf5nwFZ3YpE|3-Bk2RSxG3S5|x#BBBsaaNm0b? zo=sa~R5dWh`l274J*zh%CT+1Xjcx%djEfJH#g>)GQl!whEe0#V2wg>E_wBClN4^_b zy1qEPdU5xyw!-JDGH;8*}d1aONbU#J)4+9nhG{WA~P`nylR&R2Tw5vwCnwhqB(Hz_y&ry|c0 z5S(Tq%Q*bav8L#($iAw@Aj@aoP>>s|lr7 z${fEM!$6iyO{y~1Ni=p#H4^P2eZ{7Tp|3{$>z&_{8k^2F@Ck0F>SFoM#t^%m)&0b z)<3{LX29;xfx9oM$#)aVfZm1w(J!~riVlw; bool: + self.webtoon_path_network.mkdir(parents=True, exist_ok=True) + return self._count_episodes(self.webtoon_path) > self._count_episodes(self.webtoon_path_network) + + def _count_episodes(self, path: Path): + episodes = [d for d in path.iterdir() if d.is_dir()] + return len(episodes) + + def copy_information(self): + info_path_local = self.webtoon_path / 'information.json' + info_path_network = self.webtoon_path_network / 'information.json' + + self.updateTag(info_path_local) + + copy_necessary = True + if info_path_network.exists(): + with open(info_path_local, "r", encoding='utf-8') as json_file: + local_information = json.load(json_file) + with open(info_path_network, "r", encoding='utf-8') as json_file: + network_information = json.load(json_file) + + if ( + local_information["title"] == network_information["title"] and + local_information["author"] == network_information["author"] and + local_information["tag"] == network_information["tag"] and + local_information["description"] == network_information["description"] and + local_information["thumbnail_name"] == network_information["thumbnail_name"] + ): + copy_necessary = False + + if (copy_necessary): + try: + shutil.copyfile(info_path_local, info_path_network) + print(f"File '{info_path_local}' copied to '{info_path_network}'.") + except FileNotFoundError: + print(f"Source file '{info_path_local}' not found.") + + def updateTag(self, path: Path): + update_necessary = False + if path.exists(): + with open(path, "r", encoding='utf-8') as json_file: + existing_information = json.load(json_file) + tag = existing_information["tag"] + print(tag) + if '冒險' in tag and tag != '冒險': + tag = '冒險' + update_necessary = True + elif '愛情' in tag and tag != '愛情': + tag = '愛情' + update_necessary = True + elif 'BL' in tag and tag != 'BL': + tag = 'BL' + update_necessary = True + elif '武俠' in tag: + tag = '冒險' + update_necessary = True + elif '大人系' in tag: + tag = '愛情' + update_necessary = True + elif '驚悚' in tag: + tag = '劇情' + update_necessary = True + elif '奇幻' in tag: + tag = '劇情' + update_necessary = True + elif '宮廷' in tag: + tag = '劇情' + update_necessary = True + elif '懸疑' in tag: + tag = '劇情' + update_necessary = True + + if update_necessary: + information = { + "title": existing_information["title"], + "author": existing_information["author"], + "tag": tag, + "description": existing_information["description"], + "thumbnail_name": existing_information["thumbnail_name"] + } + + with open(path, 'w', encoding='utf-8') as json_file: + json.dump(information, json_file, ensure_ascii=False, indent=2) + print(f"{path} is saved.") + + def copy_thumbnail(self, thumbnail_path: Path): + assets_path = ANDROID_ASSETS / thumbnail_path.name + if (not assets_path.exists()): + try: + shutil.copyfile(thumbnail_path, assets_path) + print(f"File '{thumbnail_path}' copied to '{assets_path}'.") + except FileNotFoundError: + print(f"Source file '{thumbnail_path}' not found.") + + def delete_over_width_image(self, episode_path: Path): + for img_path in episode_path.iterdir(): + if self._is_image_800(img_path): + img_path.unlink() + print(f"delete {img_path}") + + def _is_image_800(self, image_path: Path) -> bool: + try: + with Image.open(image_path) as img: + return img.width >= 800 + except Exception as e: + print(f"Error opening image {image_path}: {e}") + return False + + def is_new_episode(self, episode_path: Path) -> bool: + episode_path_network = self.webtoon_path_network / episode_path.name + return not episode_path_network.exists() + + + def concat_images(self, episode_path: Path): + episode_path_network = self.webtoon_path_network / episode_path.name + episode_path_network.mkdir(parents=True, exist_ok=True) + + result_images = [] + total_height = 0 + result_index = 1 + + for img_path in episode_path.iterdir(): + if img_path.suffix.lower() in self.img_extensions: + + with open(img_path, 'rb') as img_file: + img = Image.open(img_file) + img.load() + if total_height + img.height > 28800: + self.save_concatenated_image(result_images, episode_path_network, result_index) + result_index += 1 + result_images = [] + total_height = 0 + + result_images.append(img) + total_height += img.height + if result_images: + self.save_concatenated_image(result_images, episode_path_network, result_index) + + def save_concatenated_image(self, images: list, output_episode_path: Path, index: int): + img_width = images[0].width # Assuming all images have the same width + total_height = sum(img.height for img in images) + result_image = Image.new('RGB', (img_width, total_height)) + y_offset = 0 + for img in images: + result_image.paste(img, (0, y_offset)) + y_offset += img.height + + output_file = output_episode_path / f"{index}.jpg" + print(f"saving '{output_file}'") + result_image.save(output_file) \ No newline at end of file diff --git a/data/__init__.py b/data/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/data/__pycache__/__init__.cpython-312.pyc b/data/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..de673cdd4b19903dc9bf84e0c0921b5e01d314e4 GIT binary patch literal 139 zcmX@j%ge<81VPif(m?cM5P=Rpvj9b=GgLBYGWxA#C}INgK7-W!(sH$m2`I|XN=+^) zj`6JwPfaSx&(DiV&d)0@Nz5yWiI30B%PfhH*DI*}#bJ}1pHiBWYFESx)XE6N#URE< MW=2NFB4!{900WyKN&o-= literal 0 HcmV?d00001 diff --git a/data/__pycache__/folder.cpython-312.pyc b/data/__pycache__/folder.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fd215cb4477f05d44f6d9da172a6b5b64c2b26f5 GIT binary patch literal 388 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 literal 0 HcmV?d00001 diff --git a/data/__pycache__/not_processed.cpython-312.pyc b/data/__pycache__/not_processed.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..90dd2988926f10e844e924db45d296c8a65a15d1 GIT binary patch literal 341 zcmX@j%ge<81ni$?q}>P7k3k$5V1hC}^8gvs8PXY27#1-`F;p^YGHG&@f&^dgp7?a{ z#wRMx~%DG`>rQD8eZ(}eLkz@>C(+lTPKyWft1eN{&L2~QX!C{))mj^ zFMYAD;YCC1i)GVZET2@$4-)QQ^}K1?S3G#PJ+hPyh2`1||C z`}v2&2L$;$y9Nilx~ydQ478Bpmv*XEOh8e7R%&udag1+ecxqBfetup|N@7W3OkRFT zd_hrua%ypLYKmS#`r)v_xgEuzJuLiSXzsau!bw6UXZ(SrT7% z06cf??NNN`bdkg#yZ$VF$63sc(#y`nHIiO)K=K?W2Ke~auuovdd9X_0>UV>{O=oL| az#ZoWCJ;M&I)S?Lx<_E|onR*2NdEy0F4naG literal 0 HcmV?d00001 diff --git a/data/__pycache__/special_list.cpython-312.pyc b/data/__pycache__/special_list.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f1d13c740bf128ad0afe3e68346e2fd4c0c6fb0b GIT binary patch literal 976 zcmX|9OHUI~6rRpdpeTX?LJSEp#!WRLbf&b$7?XmS7^Fi4V!)aPnTB9cNTCaM15(>k zM_PH7lqj#zDkV0Eeew7KZd{t#+B=VpsSr1;JTnDvX3l))JLi1&+;iu9d3hNIi~I9n z@OLqW{ZgEZg`a~{6M!Xb5M!`)O&`W+1~q*e@Nn>Oh5(OXiog~Drv*;S6f-4*cwg~E zsa~hAa)T}=hm-Ph!d(T>B7R+wGZ#c;t{aXt>imjmStjvbxMOK{uAWY9b zHCS#xbJr`eU7r+BqOHALDl2WjM{`^7h%3{=Vj6VZHIO15`k@LPy_I%n1YZzs#soj- zu270SF~kPM^nu_%&Sf&HpfI(OOU>j`9P(!5$vN~TAw*sa@hoVHOCD8FoL*CUoSQ_U zJ%yf6{x1$K=TZSQd59KOTI~r^Bc`1@Ro>Hd+BNnGJ&>aj#=~ul7m-5T#ukeJSi3DRZ^8`r(B8`A3 z1yNKZnC51lFp@@|XtF?{soB62X37M{0-RalEGCl2jlffll)lJy6?8$+06AnMD3BDT z7(h)1lTBa3*F0%!>85F$!$x;IIv>-mZCzb$_xZ{T+F)_C(6*i~zP8=kZlxWx?NP6z z!}`$Xu(r0+J+^MXq!2J%0O|smd0ipYY3+?+r_c#@cFTFk-su{7;T#wrvv*89aXud( z8F^)A`p5h2W24T2q5fgV@X*+J!|23W>D`wjjCEzh)9Qe{gtI02GE7@`j!#OpDpd*8&mvu3OO)f2I@fg& F{RgImpW@UC z2f_33_n%9o3!y(n=lF@V$=hBaFA+vRpzjfm07ed^Y=?2|C%&W5I-tGX19*un@Wawi z5!`{JfHLj`?7}g?ZrlTS2FC#tI0=}-y?}kVA8-H<0;cg0UmUR-H*E*b`@di+O^?0bc>SFv+?wQ5_2?pf3+>HM{vRnbjS(D$rTo7Txr_1Ic<%-mFADsY{DFRW{s4#67PH_EzWl7+pSJF|h? z)mAD@NW1446kCqT`%G?Ca|$LUTlYv#*b4okN}_DrP`y_+%9dWMI3EotvxSs z!pP08RZ=31FrF!UUiEsRP_NhXkgrFnC)HS`NGpqfRti*>mswUGh(Y|q9V$>MXR>`v(tj=xq zh}5)26BhUzVuL~jprMvVu5UeI^eHhscWe0ok`T4Ig>_HgY8UD@3xnjV2h9PKc-zPJ zGJ(06%Q6s1g68@F&yg<;#-&Cy4!zWOlHUQn*U|J*X3Cd=`khD~I?CMeyF@33Ra>Ah)x{I=@+J-5Il+Gvxh@|O7LDJs}T9ZlH~V^em{~khf#k(@IeUu z!t>LD48e^m{U!07D?N3J{QF5LqFyLSTiw3UL+EiW9HmAc)Boe*0e~ zEp%}A%s>~z-qeZE@ythpl~cIuVLlJVaQ9vglYdaD94~8kRsx42@PJ%tMyyb@+_q}% z(2A&xyjL&$N2Mx$PN|A2>xQjiX8@XVuo~XwP|cvsiZgL= zOuQ(WEV7F|VHRbUcfdM@4c@+p#RP~qGK&jH3J`B!HXuM;tq??Eupxl(*0eWp`}o$3 z!N@Q)cNX9|dfmU?7@cd3ZZrlnje%Svz1bL38spi<$V6jgvN7@n6kg_`^w)4A-sz)+ y+!++!N>&Q49~03&n6?$WQK7hIldsu17#05%+!TP{5s5_JMAH%JZ9W+Z84u61Qg|Gr6!jY z$M{x;rzVx;=jX+wu*uC&Da}c>D`ExeW(4A5 P5aS~=BO_xGGmr%UblD+) literal 0 HcmV?d00001 diff --git a/downloaders/__pycache__/bomtoom.cpython-312.pyc b/downloaders/__pycache__/bomtoom.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..534ac34e2c4aeb4f9a4ff8be9c69535063875268 GIT binary patch literal 6919 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 bqJ0n1OApchuPx&gR literal 0 HcmV?d00001 diff --git a/downloaders/__pycache__/bomtoon.cpython-312.pyc b/downloaders/__pycache__/bomtoon.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..17c1b5965c2e633ec1d0af8ea73ba3131a02e9a9 GIT binary patch literal 3114 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 AkN^Mx literal 0 HcmV?d00001 diff --git a/downloaders/__pycache__/kakao_tw.cpython-312.pyc b/downloaders/__pycache__/kakao_tw.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..98a586c0fba37b6dc8b36630b72ca688aef8331c GIT binary patch literal 9761 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 R-`A$g26E410;$sH{{aFZpb-E7 literal 0 HcmV?d00001 diff --git a/downloaders/__pycache__/kakao_webtoon.cpython-312.pyc b/downloaders/__pycache__/kakao_webtoon.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1229d126d6ee2d617e761f03dafa341137ff4388 GIT binary patch literal 9378 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

a`T>CvrlgJWt&yCC+}&=d3t0|PtJ2l_8iK3 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 M{tXtDEKK760zu?iumAu6 literal 0 HcmV?d00001 diff --git a/downloaders/__pycache__/prerequisite.cpython-312.pyc b/downloaders/__pycache__/prerequisite.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fee6b21318d5217b11e31c5c84f323dd10ad0088 GIT binary patch literal 1947 zcmah}%}*Og6rcU_M{EPeAVX`y5=ii>EyPMfT2eVN9|@=wR|ScJs9e{(Bu0zZogIhR zGAV~B>VZnBR4J;WD6V=aRiyp}m0Gy=vMxckwn#nn)SCm5QjtsFEUY1I)jrE_-n@D5 z_hxqH_kQ;IP9TuEE1xg?#3S@Oo!G)28hc?d?jsq=jEd&y$g0c|JI5|@a~y+Y?hcyc zWgfJUXC&bdnl>r1rgtPhGdU5DjU-0K-#7jB*~IwNbYl90>C_>YUu8_AAl5b@b-GE?Lpz%@MNj#k%Z zd9?N!kMgJ+kp?@m2jR9A{#Yhg`~q85~}=v>I+bcUGD_>`3?31-tm8tWvXDmN810qILGd$LJ#!}RM)O3TQ{ zgI;!7QH-L~2Ov_}kBkn^zlXI&IDx$(?)W4J24Rgq$4DYhjI?;@u^VzivDhTk+UO_akE)DDI#!Txfv ze>)iYq1%YYE76&9bY?3$yFEBpWBK;TPUz&Lv9HFA(*sXJ7k5HkM$ZLf;F@t^%os}; zr#{*aC7*f#_o)*$^5?T=B=+tihNg9uuC2$`&z0V&gwB>jXJ6DfaPRAFaE6V6o}o^D zv(r6{oSWf>p-axqO9I#_SgVTzEPAbb0~SHA1MT4P)@e%WG?kghhxP@Fh(M(?lu9yN zWN#v%o~wxdjY>s!u}+|R)jHLW9_(6}hsw}};AdB2P!UbR!;b*|GRvS@#K4dG4xTbl z!ewIuC4xXEXcGq_b4PmzQt`)0HEUrIuN@1y?JL}|T!9ktA9UhwbF?-P3an#dOs70d6xOfGs&NZPsS9oFvIU+GY~aiFA_85Ub-^cVe{%>q~uy_8=Nr sUvuB}_L3&&R3(Z}fsfKgZv_P`WSC!(`!^Kbb$FP_-YXua1$V*9Uw>1=?*IS* literal 0 HcmV?d00001 diff --git a/downloaders/__pycache__/webtoon_com.cpython-312.pyc b/downloaders/__pycache__/webtoon_com.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5eaf951958292e1a80ab94329a1aec77b56a2596 GIT binary patch literal 7538 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 list[str]: + pass + + async def _download_image( + self, + episode_path: Path, + url: str, + image_no: int + ) -> None: + pass \ No newline at end of file diff --git a/downloaders/decrypt.py b/downloaders/decrypt.py new file mode 100644 index 0000000..2527438 --- /dev/null +++ b/downloaders/decrypt.py @@ -0,0 +1,43 @@ +import base64 +import hashlib +from contextlib import suppress + +from WebtoonScraper.exceptions import MissingOptionalDependencyError + +class Decrypt : + def __init__(self, aid, episodeId, timestamp, nonce, userId, zid): + self._aid = aid + self._episodeId = episodeId + self._timestamp = timestamp + self._nonce = nonce + self._userId = userId + self._zid = zid + + @classmethod + def get_aes(cls): + with suppress(AttributeError): + return cls.AES + try: + from Cryptodome.Cipher import AES + except ImportError: + raise ImportError("Missing optional dependency 'pycryptodomex'. Please install it to use this functionality.") + + cls.AES = AES + return cls.AES + + @classmethod + def _decrypt(cls, data: bytes, key: bytes, iv: bytes) -> bytes: + AES = cls.get_aes() + cipher = AES.new(key, AES.MODE_CBC, iv) + return cipher.decrypt(data) + + def get_decrypt_infomations(self) -> tuple[bytes, bytes]: + + temp_key = hashlib.sha256(f"{self._userId}{self._episodeId}{self._timestamp}".encode()).digest() + temp_iv = hashlib.sha256(f"{self._nonce}{self._timestamp}".encode()).digest()[:16] + encrypted_key = base64.b64decode(self._aid) + encrypted_iv = base64.b64decode(self._zid) + + key = self._decrypt(encrypted_key, temp_key, temp_iv)[:16] + iv = self._decrypt(encrypted_iv, temp_key, temp_iv)[:16] + return key, iv diff --git a/downloaders/downloader.py b/downloaders/downloader.py new file mode 100644 index 0000000..cbdd9ad --- /dev/null +++ b/downloaders/downloader.py @@ -0,0 +1,168 @@ +import asyncio +import html +import json +from pathlib import Path +import pyfilename as pf +import shutil +import time +from httpx import AsyncClient +import requests + +from data.special_list import WEBTOON_18_BONUS + + +class Downloader: + def __init__(self, webtoon_id: any) -> None: + self.webtoon_id = webtoon_id + self.client = AsyncClient() + self.lately_downloaded_episode: list[Path] = [] + self.new_webtoon = "" + + + def download_webtoon(self, url, path:Path) -> None: + self._fetch_information(url) + self.webtoon_path = path / self.title + self.webtoon_path.mkdir(parents=True, exist_ok=True) + + self._save_information() + if self.thumbnail_url != "": + self._download_thumbnail() + + self._fetch_episode_information() + unobtained_episodes = self._get_unobtained_episodes() + + if len(unobtained_episodes) > 0: + self.new_webtoon = self.title + + try: + asyncio.run( + self._download_episodes(unobtained_episodes) + ) + except Exception as e: + print(f"Error _download_episodes: {e}") + + + + def _fetch_information(self, url) -> None: + pass + + def _save_information(self) -> None: + information_path = self.webtoon_path / 'information.json' + save_necessary = True + + if information_path.exists(): + with open(information_path, "r", encoding='utf-8') as json_file: + existing_information = json.load(json_file) + if ( + existing_information["title"] == self.title and + existing_information["author"] == self.author and + existing_information["description"] == self.description and + existing_information["thumbnail_name"] == self.thumbnail_name + ): + save_necessary = False + if (save_necessary): + information = { + "title": self.title, + "author": self.author, + "tag": self.tag, + "description": self.description, + "thumbnail_name": self.thumbnail_name + } + + with open(information_path, 'w', encoding='utf-8') as json_file: + json.dump(information, json_file, ensure_ascii=False, indent=2) + print(f"{information_path} is saved.") + + + def _download_thumbnail(self) -> None: + thumbnail_path = self.webtoon_path / self.thumbnail_name + if not thumbnail_path.exists(): + response = requests.get(self.thumbnail_url) + if response.status_code == 200: + image_raw = response.content + thumbnail_path.write_bytes(image_raw) + print(f"{thumbnail_path} is saved.") + else: + print(response.status_code) + + + def _fetch_episode_information(self) -> None: + pass + + def _get_unobtained_episodes(self) -> list[int]: + downloaded_episodes = [] + + for dir in self.webtoon_path.glob('*'): + if dir.is_dir(): + downloaded_episodes.append(int(dir.name.split('.')[0])) + + if self.title in WEBTOON_18_BONUS: + count = len(self.readablities_index_list) - len(downloaded_episodes) + if count > 0: + episodes = self.readablities_index_list[-count:] + + else : + diffrence = set(self.readablities_index_list) - set(downloaded_episodes) + episodes = list(diffrence) + + print(f"{self.title} unobtained episodes: {episodes}") + + return episodes + + async def _download_episodes(self, episode_index_list: list[int]) -> None: + async with self.client: + for episode_index in episode_index_list: + episode_name = self.episode_titles[episode_index] + episode_title = self._get_safe_file_name(episode_index, episode_name) + # episode_title = self._get_safe_file_name(f"{episode_index}.{self.episode_titles[episode_index]}") + print(episode_title) + episode_path = self.webtoon_path / episode_title + episode_path.mkdir(parents=True, exist_ok=True) + time.sleep(2) + is_download_sucessful = await self._download_episode(episode_index, episode_path) + if is_download_sucessful: + self.lately_downloaded_episode.append(episode_path) + print(f"Download {self.episode_titles[episode_index]} sucessful.") + else: + print(f"Error _download_episode: {self.episode_titles[episode_index]}") + break + + + async def _download_episode(self, episode_index: int, episode_path: Path) -> bool: + episode_images_url = self._get_episode_image_urls(episode_index) + + if not episode_images_url: + print(f"Failed get image url for: {episode_path}") + return False + + try: + await asyncio.gather( + *( + self._download_image(episode_path, element, i) + for i, element in enumerate(episode_images_url) + ) + ) + except Exception as e: + shutil.rmtree(episode_path) + raise + + return True + + + def _get_episode_image_urls(self, episode_index: int) -> list[str] | None: + pass + + async def _download_image(self, episode_path: Path, url: str, image_no: int) -> None: + pass + + def _get_safe_file_name(self, episode_index: int, episode_name: str) -> str: + if self.title == '全知讀者視角': + episode_name = f"Ep{episode_name.split('.')[2]}" + episode_name = episode_name.replace("(", " (") + episode_name = episode_name.replace(")", ")") + elif self.title == '怪力亂神': + episode_name = episode_name.replace('話. ', '話 ') + + episode_title = f"{episode_index}.{episode_name}" + + return pf.convert(html.unescape(episode_title)) \ No newline at end of file diff --git a/downloaders/kakao_webtoon.py b/downloaders/kakao_webtoon.py new file mode 100644 index 0000000..8025370 --- /dev/null +++ b/downloaders/kakao_webtoon.py @@ -0,0 +1,141 @@ +from pathlib import Path +import random +import sys +import time + +from bs4 import BeautifulSoup +from httpx import AsyncClient, RequestError, HTTPStatusError +import httpx +import requests +from data.path_constant import DOWNLOAD_DIR, DOWNLOAD_LIST_TXT +from data.kakao_cookie import Cookie +from data.kakao_request import KakaoRequest +from downloaders.decrypt import Decrypt +from downloaders.downloader import Downloader + + +class KakaoWebtoon(Downloader): + def __init__(self, webtoon_id: int, cookie: Cookie): + super().__init__(webtoon_id) + self._timestamp = int(time.time() * 1000) + chars = [*range(0x30, 0x3A), *range(0x61, 0x7B)] + self._nonce = "".join(chr(i) for i in random.choices(chars, k=10)) + + self.kakaoRequest = KakaoRequest(self._timestamp, self._nonce) + self.cookie = cookie + self.episode_headers = self.kakaoRequest.get_episode_headers(self.cookie.ant) + self.post_headers = self.kakaoRequest.get_post_headers(self.cookie.ant) + + def verify_cookie(self) -> bool: + url = f"https://gateway.tw.kakaowebtoon.com/episode/v2/views/content-home/contents/{self.webtoon_id}/episodes?sort=-NO&offset=0&limit=30" + res = requests.get(url, headers=self.episode_headers) + return res.status_code == 200 + + def _fetch_information(self, url): + res = requests.get(url, headers=self.episode_headers) + + if res.status_code == 200: + soup = BeautifulSoup(res.content, 'html.parser') + description = soup.find('meta', attrs={'name': 'description'}) + if description: + self.description = description.get('content') + thumbnail_url = soup.find('meta', attrs={'property': 'og:image'}) + if thumbnail_url: + self.thumbnail_url = thumbnail_url.get('content') + + all_p = soup.find_all('p') + + self.title = all_p[0].get_text() + self.author = all_p[1].get_text() + self.tag = all_p[2].get_text() + self.thumbnail_name = self.webtoon_id + '.' + self.thumbnail_url.split('.')[-1] + + def _fetch_episode_information(self): + offset = 0 + limit = 30 + is_last: bool = False + webtoon_episodes_data = [] + while not is_last: + url = f"https://gateway.tw.kakaowebtoon.com/episode/v2/views/content-home/contents/{self.webtoon_id}/episodes?sort=-NO&offset={offset}&limit={limit}" + res = requests.get(url, headers=self.episode_headers) + if res.status_code == 200: + json_data = res.json() + + webtoon_episodes_data += json_data["data"]["episodes"] + offset += limit + is_last = json_data["meta"]["pagination"]["last"] + else: + print("_fetch_episode_information") + print(self.cookie.name) + print(res.status_code) + sys.exit() + + + episode_ids: list[int] = [] + seo_ids: list[str] = [] + numbers: list[int] = [] + episode_titles: list[str] = [] + readablities: list[bool] = [] + + for information in reversed(webtoon_episodes_data): + episode_ids.append(information["id"]) + seo_ids.append(information["seoId"]) + numbers.append(information["no"]) + episode_titles.append(information["title"]) + readablities.append(information["readable"]) + + + self.episode_ids = episode_ids + self.seo_ids = seo_ids + self.episode_titles = episode_titles + self.readablities_index_list = [index for index, value in enumerate(readablities) if value == True] + + def _get_episode_image_urls(self, episode_index) -> list[tuple[str, bytes, bytes]] | None: + episode_id = self.episode_ids[episode_index] + + url = f"https://gateway.tw.kakaowebtoon.com/episode/v1/views/viewer/episodes/{episode_id}/media-resources" + payload = self.kakaoRequest.get_payload(episode_id) + res = requests.post(url, headers=self.post_headers, json=payload) + + data = res.json()["data"] + + aid = data["media"]["aid"] + zid = data["media"]["zid"] + + self.decrypt = Decrypt(aid, episode_id, self._timestamp, self._nonce, self.cookie.userID, zid) + key, iv = self.decrypt.get_decrypt_infomations() + + return [(i["url"], key, iv) for i in data["media"]["files"]] + + async def _download_image( + self, + episode_path: Path, + url: tuple[str, bytes, bytes], + image_no: int + ) -> None: + real_url, key, iv = url + file_extension = 'webp' + file_name = f"{image_no:03d}.{file_extension}" + file_path = episode_path /file_name + + try: + image_raw: bytes = (await self.client.get(real_url, headers=self.episode_headers)).content + except httpx.RequestError as e: + print(f"An error occurred while requesting {url}: {e}") + except httpx.HTTPStatusError as e: + print(f"HTTP error occurred: {e}") + except httpx.TimeoutException as e: + print(f"Timeout error occurred: {e}") + except httpx.UnsupportedProtocol as e: + print(f"Unsupported protocol error occurred: {e}") + except Exception as e: + print(f"An unexpected error occurred: {e}") + except Exception as e: + print(f"Error get image_raw: {file_path}: {e}") + + decrypted_data = self.decrypt._decrypt(image_raw, key, iv) + + file_path.write_bytes(decrypted_data) + + async def close(self): + await self.client.aclose() \ No newline at end of file diff --git a/downloaders/webtoon_com.py b/downloaders/webtoon_com.py new file mode 100644 index 0000000..33346b6 --- /dev/null +++ b/downloaders/webtoon_com.py @@ -0,0 +1,122 @@ + +from pathlib import Path +import time +from typing import TYPE_CHECKING +from bs4 import BeautifulSoup +from httpx import AsyncClient, HTTPStatusError, RequestError +import httpx +import requests +from data.path_constant import DOWNLOAD_DIR +from data.webtoon_request import get_webtoon_headers +from downloaders.downloader import Downloader + + +class Webtoon(Downloader): + def __init__(self, webtoon_id: int): + super().__init__(webtoon_id) + + self.headers = get_webtoon_headers() + self.base_url = "https://www.webtoons.com/en/action/jungle-juice" + + def _fetch_information(self, url): + res = requests.get(url, headers=self.headers) + + if res.status_code == 200: + soup = BeautifulSoup(res.content, 'html.parser') + title = soup.find('meta', attrs={'property': 'og:title'}) + if title: + self.title = title.get('content') + + description = soup.find('meta', attrs={'property': 'og:description'}) + if description: + self.description = description.get('content') + + thumbnail_url = soup.find('meta', attrs={'property': 'og:image'}) + if thumbnail_url: + self.thumbnail_url = thumbnail_url.get('content') + + author_list = soup.find_all('h3') + h3_texts = [h3.get_text().strip() for h3 in author_list] + author = ', '.join(h3_texts) + + tag = soup.find('h2', class_='genre').get_text() + + self.author = author + self.tag = tag + + seo_id = url.split('/')[-2] + thumbnail_type = 'png' if 'png' in self.thumbnail_url else 'jpg' + self.thumbnail_name = seo_id + '.' + thumbnail_type + + self.latest_title_no = soup.find('li', class_='_episodeItem').get('data-episode-no') + else: + print(f"fetch_information: {res.status_code}") + + + def _fetch_episode_information(self): + url = f"{self.base_url}/prologue/viewer?title_no={self.webtoon_id}&episode_no={self.latest_title_no}" + res = requests.get(url, headers=self.headers) + + if res.status_code == 200: + self.episode_titles = [] + soup = BeautifulSoup(res.content, 'html.parser') + li_tags = soup.find('div', class_='episode_cont').find_all('li', attrs={'data-episode-no': True}) + self.episode_titles = [li.find('span', class_='subj').get_text() for li in li_tags if li.find('span', class_='subj')] + self.episode_urls = [li.find('a')['href'] for li in li_tags] + self.episode_ids = [int(li.get('data-episode-no')) for li in li_tags] # start with 1, not index + self.readablities_index_list = [id - 1 for id in self.episode_ids] + else: + print(f"fetch_episode_information: {res.status_code}") + + + def _get_episode_image_urls(self, episode_index) -> list[str]: + #url = self.episode_urls[episode_index] + episode_id = self.episode_ids[episode_index] + url = f"{self.base_url}/prologue/viewer?title_no={self.webtoon_id}&episode_no={episode_id}" + episode_image_urls = [] + res = requests.get(url, headers=self.headers) + + if res.status_code == 200: + soup = BeautifulSoup(res.content, 'html.parser') + img_tags = soup.select("#_imageList > img") + episode_image_urls = [element["data-url"] for element in img_tags] + if TYPE_CHECKING: + episode_image_urls = [ + episode_image_url for episode_image_url in episode_image_urls if isinstance(episode_image_url, str) + ] + else: + print(f"get_episode_image_urls: {res.status_code}") + return episode_image_urls + + async def _download_image( + self, + episode_path: Path, + url: str, + image_no: int + ) -> None: + file_extension = 'jpg' + file_name = f"{image_no:03d}.{file_extension}" + file_path = episode_path /file_name + + try: + + response = await self.client.get(url, headers=self.headers) + response.raise_for_status() # Raises HTTPStatusError for 4xx/5xx responses + image_raw: bytes = response.content + except httpx.RequestError as e: + print(f"An error occurred while requesting {url}: {e}") + except httpx.HTTPStatusError as e: + print(f"HTTP error occurred: {e}") + except httpx.TimeoutException as e: + print(f"Timeout error occurred: {e}") + except httpx.UnsupportedProtocol as e: + print(f"Unsupported protocol error occurred: {e}") + except Exception as e: + print(f"An unexpected error occurred: {e}") + + # try: + # image_raw: bytes = (await self.client.get(url, headers=self.headers)).content + # except Exception as e: + # print(f"Error get image_raw: {file_path}: {e}") + + file_path.write_bytes(image_raw) diff --git a/helper.py b/helper.py new file mode 100644 index 0000000..c221e21 --- /dev/null +++ b/helper.py @@ -0,0 +1,23 @@ +from pathlib import Path +from data.path_constant import DOWNLOAD_DIR, NETWORK_DIR, TEMP_DOWNLOAD_DIR +from helper.missing_episode import get_missing_episodes +from helper.missing_images import get_missing_images, resize_and_overwrite +from prerequisite import delete_all_empty_episodes + +# delete_all_empty_episodes(DOWNLOAD_DIR) +# delete_all_empty_episodes(NETWORK_DIR) + +get_missing_episodes(DOWNLOAD_DIR) + +get_missing_images() + +# episode_path = Path(DOWNLOAD_DIR) / '結局創造者' / '0.第1話' + +# images_path = [] +# for i in range(11, 29+1): +# file_name = '0' + str(i) + '.webp' +# path = Path(episode_path) / file_name +# images_path.append(path) + +# for path in images_path: +# resize_and_overwrite(path, 720) \ No newline at end of file diff --git a/helper/__init__.py b/helper/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/helper/__pycache__/__init__.cpython-312.pyc b/helper/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4c68aceb9650decde3484f12f4101f2294a96f21 GIT binary patch literal 137 zcmX@j%ge<81j22-X(0MBh(HIQS%4zb87dhx8U0o=6fpsLpFwJVX{1`k1Qg|Gr6!jY z$M{x;rzVx;=jX*_q~;W)7RAKJXXa&=#K-FuRQ}?y$<0qG%}KQ@Vg+hs1ma>4<0CU8 KBV!RWkOcsdm>)6# literal 0 HcmV?d00001 diff --git a/helper/__pycache__/missing_episode.cpython-312.pyc b/helper/__pycache__/missing_episode.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8fb9905c6aa0b48fab946341e794fd80cc1002e5 GIT binary patch literal 1250 zcmah|%}*0S6rb6Twpg(xyI_^7pj)M>g2tc;@dJq_Y7o3>NKI4N0k*W;vO5x*EQtpX z96V?|fbk$l4IcdixT=?~wP`b{Cokf`pm6FzeGA=6kmyVHeZSwE_jcxebMqbq^ls+o zxR*!hvtzi#)s^)yC<{nMDo&#e9>Ex?9Kqw9%0CoFxV#V*SDn--X2R_xon%4|_!zF2 zJHiVez@zmVco&d`Qrn|W2GH`BKv&VdF9^+H3#Xi#9pq-2_>nw;<~aCx$ow~6Ki~wl<=8w{5r;7;VdJ;6R730?MPPI8?*khBGf*7JiE4SAPLT{miRTVyK$IJerx< zl*wF<=qeNRWQH)&%%wHTJZ9FQL}k3DQzjZoeS$FFqz2>DL}y~o0DshH2!PLm))QsQC-yx$ko7Ll!B%LEhd`GwLT%O4jgeG?QO~ZL;&XqU60egTd z;f@!B&jz0l{XqM~u7WRA@wb=#M{WPnl3%IpKQMp2a-_4cH(ZJImLnJK$i-sh@(;u{ z-^7JrXGK!VQp}cOCFxW}4lfKp87?TjWu?zn`pQbbt@N)*MdjK`(pIjQ06DfB^# zR%E3t$80(F@=8hWcF=kNEGQMEW5s~7G+hjIRfDaCw&UftnB5lp7(BUUmVzg~AA;%D zVPZ5C<(b#{S7~k3a2Bz-kypq#_geP% z{=fcSfA8a8yxx5X=;P1+zWpnQ(ChsLf)u8a*1MTbVNPL-1u7gC#sdusw}Jw`fqjB%&7 zqkwg(%nzY?e5($jEHY4>t#T-{j-Q4fHGl`nzl$7?A$02x$b$|1hCKB#l%?RN!SlcK zy$AUigNnO$l>||*adZX~FLvFLOKaS_Qznv#m&!d6sUsjelCxs;=q!Wux130W(%laK zExlCkVwSC{&5BHQrYxr)bRtC7;G7uf4G(*987_mG!O%;$LGQII!&UVzP4F)8WlChw zjzy&I3?T$LzSkQnR#Md32aT85HCq+c>O}ZoqM*??{;! z{{X!xDm*F!dfO%Ik}Aro&BEuD~co6}ONE{Qg)3QA0} z>4c=%Y+41spxds!EvuRyO-M6RBAOQTY5SliO{EmETE)p^G&K7_Qr0wCiABqDtv)TQ zJ3PImT8=|aQn(kD6;ZlpV_74gy0uK(Vd?DTxSEPfQ@S=enz=50qNh^ItUIw**H!Sh%wH;WyfYsubY8GJht1C6eCG$7 z2se!575;dE4?Oig@qRtE5;|*z`pr>8(nTQ+k5k^XXZ!N{cXAKoSgTc+(cAE?*c9~`*N4Q>M!_0map6N zbuS-X^Yv`7VEccxEBBgV1hJz7fnL;ES+Wa3WONgpznJS zGMM`Scg~1pS%|Md%IhTH&EBT;iej(fiSMtND~vPvS4US{5D;(DRxDpEo^h zn!lW{Y1u#?wkvn~H@^9)`-%IZcaEK#frE9#w=lUlZ2G&GubIAcdH(#IcY{NXO^@SW z#B-g?-j$<$Ke5)pxH&Lx4cssXZsZT&ob!N7O}*vjO*g;Ll)JX-?tIk<>0gl=FUezD zBVX&w!Ir^3`o})@qRSNtayC6aa;4PIL!zJyVFEQ#z!^;!6uk&(#c3{X;~S&~AVqPv q+wSwplz2BG^{J;oNB%Av0c996jGv)~XXwNxa}bBOLSEce4Eq 0: + print(first_level_path.name) + print(missing_episodes) diff --git a/helper/missing_images.py b/helper/missing_images.py new file mode 100644 index 0000000..71562a1 --- /dev/null +++ b/helper/missing_images.py @@ -0,0 +1,44 @@ +from PIL import Image +from data.path_constant import DOWNLOAD_DIR + +def get_missing_images(): + for first_level_path in DOWNLOAD_DIR.iterdir(): + if first_level_path.is_dir(): + for second_level_path in first_level_path.iterdir(): + if second_level_path.is_dir(): + images = [] + missing_images = [] + for third_level_path in second_level_path.iterdir(): + images.append(int(third_level_path.name.split('.')[0])) + sorted_images = sorted(images, key=int) + max_index = int(sorted_images[-1]) + for i in range(2, max_index): + if i not in images: + missing_images.append(i) + if len(missing_images) > 0: + print(first_level_path.name) + print(second_level_path.name) + print(missing_images) + + + +def resize_and_overwrite(input_path, target_width): + # 打开原始webp图像 + with Image.open(input_path) as img: + # 获取原始尺寸 + width, height = img.size + + # 计算缩放后的高度,保持宽高比 + target_height = int((target_width / width) * height) + + # 调整图像尺寸并保持比例 + resized_img = img.resize((target_width, target_height), Image.Resampling.LANCZOS) + + # 直接覆盖原始文件保存为webp格式 + resized_img.save(input_path, "WEBP") + +# 使用方法示例 +# input_webp = "input_image.webp" +# target_width = 720 # 设置目标宽度为720像素 + +# resize_and_overwrite(input_webp, target_width) \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..88639bf --- /dev/null +++ b/main.py @@ -0,0 +1,99 @@ +import argparse + +from converter.converter import WebtoonConverter +from data.kakao_cookie import COOKIE_NAME, COOKIES, TASK_TYPE, URL_TYPE +from data.special_list import KAKAO_1, KAKAO_3, KAKAO_7, KAKAO_PAY, WEBTOON_NOT_PROCESSED, KAKAO_ONLY_MAIN_ACCOUNT +from data.path_constant import DOWNLOAD_DIR, DOWNLOAD_LIST_TXT +from downloaders.bomtoon import Bomtoon +from downloaders.kakao_webtoon import KakaoWebtoon +from prerequisite import get_download_list +from downloaders.webtoon_com import Webtoon + +DOWNLOAD_WEBTOON = True +CONVERT_ALL = False + +valid_cookies = [] +new_webtoons = [] + +def set_valid_cookie(): + global valid_cookies + for cookie in COOKIES: + if cookie.name == COOKIE_NAME: + print(cookie.name) + valid_cookies.append(cookie) + +def get_kakao_urls(inputs): + result = [] + if '1' in inputs: + result += KAKAO_1 + if '3' in inputs: + result += KAKAO_3 + if '7' in inputs: + result += KAKAO_7 + if 'm' in inputs: + result += KAKAO_ONLY_MAIN_ACCOUNT + if 'p' in inputs: + result += KAKAO_PAY + return result + +def download(): + if len(valid_cookies) > 0: + url_list = get_download_list(DOWNLOAD_LIST_TXT) + + for url in url_list: + webtoon = None + if 'tw.kakaowebtoon.com' in url: + webtoon_id = url.split('/')[-1] + for cookie in valid_cookies: + if webtoon_id in get_kakao_urls(URL_TYPE): + webtoon = KakaoWebtoon(webtoon_id, cookie) + webtoon.download_webtoon(url, DOWNLOAD_DIR) + elif DOWNLOAD_WEBTOON and 'www.webtoons.com' in url: + webtoon_id = url.split('=')[1] + webtoon = Webtoon(webtoon_id) + webtoon.download_webtoon(url, DOWNLOAD_DIR) + elif 'www.bomtoon.tw' in url: + webtoon_id = url.split('/')[-1] + webtoon = Bomtoon(webtoon_id) + webtoon.download_webtoon(url, DOWNLOAD_DIR) + if webtoon is not None and webtoon.new_webtoon != "": + new_webtoons.append(webtoon.new_webtoon) + print(new_webtoons) + +def convert(): + for webtoon_path in DOWNLOAD_DIR.iterdir(): + if len(new_webtoons) > 0: + if webtoon_path.is_dir() and webtoon_path.name in new_webtoons: + print(webtoon_path) + converter = WebtoonConverter(webtoon_path) + converter.do_convert() + elif webtoon_path.is_dir() and CONVERT_ALL and webtoon_path.name not in WEBTOON_NOT_PROCESSED: + print(webtoon_path) + converter = WebtoonConverter(webtoon_path) + converter.do_convert() + +def main(): + parser = argparse.ArgumentParser(description="Run download or convert") + parser.add_argument('function', nargs='?', choices=['download', 'convert'], help="Function to run") + + args = parser.parse_args() + + if args.function == 'download': + download() + elif args.function == 'convert': + convert() + else: + download() + convert() + +if __name__ == "__main__": + set_valid_cookie() + + task = TASK_TYPE + + if 'd' in task: + download() + if 'c' in task: + convert() + print('MyWebtoon') + diff --git a/prerequisite.py b/prerequisite.py new file mode 100644 index 0000000..4fb0f3c --- /dev/null +++ b/prerequisite.py @@ -0,0 +1,62 @@ +import os +from pathlib import Path +import shutil +from data.path_constant import DOWNLOAD_DIR, NETWORK_DIR + +# input: DOWNLOAD_DIR or NETWORK_DIR +def delete_all_empty_episodes(path: Path): + for first_level_path in path.iterdir(): + if first_level_path.is_dir(): + for second_level_path in first_level_path.iterdir(): + if second_level_path.is_dir() and not any(second_level_path.iterdir()): + print(f"Deleting directory: {second_level_path}") + shutil.rmtree(second_level_path) + print(f"Empty directory '{second_level_path}' deleted successfully.") + + +def delete_all_webtoons_without_episodes(): + for first_level_path in NETWORK_DIR.iterdir(): + if first_level_path.is_dir(): + contains_dir = any(item.is_dir() for item in first_level_path.iterdir()) + if not contains_dir: + # No subdirectories, safe to delete + print(f"Deleting directory: {first_level_path}") + shutil.rmtree(first_level_path) + print(f"Directory '{first_level_path}' deleted successfully.") + +def get_download_list(path: Path): + url_list = [] + try: + with open(path, 'r') as file: + for url in file: + if 'https://' in url: + url_list.append(url.strip()) + except FileNotFoundError: + print(f"The file at {path} was not found.") + except Exception as e: + print(f"An error occurred: {e}") + return url_list + +def rename_episodes(path: Path): + for first_level_path in path.iterdir(): + if first_level_path.is_dir(): + for second_level_path in first_level_path.iterdir(): + if second_level_path.is_dir() and '. ' in second_level_path.name: + print(second_level_path) + newName = second_level_path.name.replace(". ", ".") + new_path = first_level_path / newName + os.rename(second_level_path, new_path) + if second_level_path.is_dir() and '(' in second_level_path.name: + print(second_level_path) + newName = second_level_path.name.replace("(", " (") + newName = newName.replace(")", ")") + new_path = first_level_path / newName + os.rename(second_level_path, new_path) + + +def get_episodes_with_wrong_name(path: Path): + for first_level_path in path.iterdir(): + if first_level_path.is_dir(): + for second_level_path in first_level_path.iterdir(): + if second_level_path.is_dir() and len(second_level_path.name.split('.')) > 2: + print(second_level_path) diff --git a/rename.py b/rename.py new file mode 100644 index 0000000..ab5a9d6 --- /dev/null +++ b/rename.py @@ -0,0 +1,17 @@ +import os +from data.path_constant import DOWNLOAD_DIR, NETWORK_DIR +from prerequisite import rename_episodes + +rename_episodes(DOWNLOAD_DIR) +rename_episodes(NETWORK_DIR) + +for first_level_path in NETWORK_DIR.iterdir(): + if first_level_path.name == '怪力亂神': + for second_level_path in first_level_path.iterdir(): + if "話." in second_level_path.name: + episode_name = second_level_path.name.replace("話.", "話 ") + + new_path = first_level_path / episode_name + print(second_level_path) + print(new_path) + os.rename(second_level_path, new_path) \ No newline at end of file diff --git a/rename_drawable.py b/rename_drawable.py new file mode 100644 index 0000000..1979d1c --- /dev/null +++ b/rename_drawable.py @@ -0,0 +1,15 @@ + +import os +from pathlib import Path + + +res = Path('E:/') / 'Projects' / 'AndroidStudioProjects' / 'WebtoonViewer' / 'app' / 'src' / 'main' / 'res' + +for folder_path in res.iterdir(): + if "drawable" in folder_path.name and folder_path.is_dir(): + for file_path in folder_path.iterdir(): + if "menu-dots-vertical" in file_path.name: + new_path = folder_path / ("menu_" + file_path.name.split("_")[1]) + # if new_path.exists: + # os.remove(new_path) + os.rename(file_path, new_path)