From 2ba79a009498045389b73e1834a725045cdaa424 Mon Sep 17 00:00:00 2001 From: stevenyomi <95685115+stevenyomi@users.noreply.github.com> Date: Mon, 24 Apr 2023 07:12:26 +0800 Subject: [PATCH] Add MangaFire and create MangaReader multisrc (#16138) * Add MangaFire.to * Fixes Updated 'searchMangaNextPageSelector' Added throwing 'UnsupportedOperationException' for unused selector * Fixed naming of filters and fixed parameter's name of minimal chapters' filter * Fixed language parameter in query * Clean up MangaFire filters * Clean up MangaFire descrambler * Create MangaReader multisrc theme * Move MangaFire to overrides * Refactor MangaFire for multisrc * Update MangaReader changelog * Remove duplicate filter entry Co-authored-by: Druzai <70586473+Druzai@users.noreply.github.com> --------- Co-authored-by: Druzai Co-authored-by: Druzai <70586473+Druzai@users.noreply.github.com> --- .run/MangaReaderGenerator.run.xml | 11 + .../mangafire/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 3037 bytes .../mangafire/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 1620 bytes .../res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 3732 bytes .../res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 7279 bytes .../res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 9337 bytes .../mangafire/res/web_hi_res_512.png | Bin 0 -> 12839 bytes .../mangareader/mangafire/src/Filters.kt | 166 +++++++++++++++ .../mangafire/src/ImageInterceptor.kt | 79 ++++++++ .../mangareader/mangafire/src/MangaFire.kt | 189 ++++++++++++++++++ .../mangafire/src/MangaFireFactory.kt | 15 ++ .../mangareader}/mangareaderto/CHANGELOG.md | 5 + .../res/mipmap-hdpi/ic_launcher.png | Bin .../res/mipmap-mdpi/ic_launcher.png | Bin .../res/mipmap-xhdpi/ic_launcher.png | Bin .../res/mipmap-xxhdpi/ic_launcher.png | Bin .../res/mipmap-xxxhdpi/ic_launcher.png | Bin .../mangareaderto/res/web_hi_res_512.png | Bin .../mangareader/mangareaderto/src/Filters.kt | 0 .../mangareaderto/src/ImageInterceptor.kt | 8 +- .../mangareaderto/src}/MangaReader.kt | 117 +++-------- .../mangareaderto/src}/MangaReaderFactory.kt | 0 .../mangareaderto/src/Preferences.kt | 17 +- .../multisrc/mangareader/MangaReader.kt | 125 ++++++++++++ .../mangareader/MangaReaderGenerator.kt | 33 +++ src/all/mangareaderto/AndroidManifest.xml | 2 - src/all/mangareaderto/build.gradle | 13 -- 27 files changed, 653 insertions(+), 127 deletions(-) create mode 100644 .run/MangaReaderGenerator.run.xml create mode 100644 multisrc/overrides/mangareader/mangafire/res/mipmap-hdpi/ic_launcher.png create mode 100644 multisrc/overrides/mangareader/mangafire/res/mipmap-mdpi/ic_launcher.png create mode 100644 multisrc/overrides/mangareader/mangafire/res/mipmap-xhdpi/ic_launcher.png create mode 100644 multisrc/overrides/mangareader/mangafire/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 multisrc/overrides/mangareader/mangafire/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 multisrc/overrides/mangareader/mangafire/res/web_hi_res_512.png create mode 100644 multisrc/overrides/mangareader/mangafire/src/Filters.kt create mode 100644 multisrc/overrides/mangareader/mangafire/src/ImageInterceptor.kt create mode 100644 multisrc/overrides/mangareader/mangafire/src/MangaFire.kt create mode 100644 multisrc/overrides/mangareader/mangafire/src/MangaFireFactory.kt rename {src/all => multisrc/overrides/mangareader}/mangareaderto/CHANGELOG.md (91%) rename {src/all => multisrc/overrides/mangareader}/mangareaderto/res/mipmap-hdpi/ic_launcher.png (100%) rename {src/all => multisrc/overrides/mangareader}/mangareaderto/res/mipmap-mdpi/ic_launcher.png (100%) rename {src/all => multisrc/overrides/mangareader}/mangareaderto/res/mipmap-xhdpi/ic_launcher.png (100%) rename {src/all => multisrc/overrides/mangareader}/mangareaderto/res/mipmap-xxhdpi/ic_launcher.png (100%) rename {src/all => multisrc/overrides/mangareader}/mangareaderto/res/mipmap-xxxhdpi/ic_launcher.png (100%) rename {src/all => multisrc/overrides/mangareader}/mangareaderto/res/web_hi_res_512.png (100%) rename src/all/mangareaderto/src/eu/kanade/tachiyomi/extension/all/mangareaderto/MangaReaderFilters.kt => multisrc/overrides/mangareader/mangareaderto/src/Filters.kt (100%) rename src/all/mangareaderto/src/eu/kanade/tachiyomi/extension/all/mangareaderto/MangaReaderImageInterceptor.kt => multisrc/overrides/mangareader/mangareaderto/src/ImageInterceptor.kt (92%) rename {src/all/mangareaderto/src/eu/kanade/tachiyomi/extension/all/mangareaderto => multisrc/overrides/mangareader/mangareaderto/src}/MangaReader.kt (59%) rename {src/all/mangareaderto/src/eu/kanade/tachiyomi/extension/all/mangareaderto => multisrc/overrides/mangareader/mangareaderto/src}/MangaReaderFactory.kt (100%) rename src/all/mangareaderto/src/eu/kanade/tachiyomi/extension/all/mangareaderto/MangaReaderPreferences.kt => multisrc/overrides/mangareader/mangareaderto/src/Preferences.kt (62%) create mode 100644 multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangareader/MangaReader.kt create mode 100644 multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangareader/MangaReaderGenerator.kt delete mode 100644 src/all/mangareaderto/AndroidManifest.xml delete mode 100644 src/all/mangareaderto/build.gradle diff --git a/.run/MangaReaderGenerator.run.xml b/.run/MangaReaderGenerator.run.xml new file mode 100644 index 000000000..f92f92a8c --- /dev/null +++ b/.run/MangaReaderGenerator.run.xml @@ -0,0 +1,11 @@ + + + + + diff --git a/multisrc/overrides/mangareader/mangafire/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/mangareader/mangafire/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..bb7c377bcc34d1beec7e161746bdc2929c1f8015 GIT binary patch literal 3037 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 literal 0 HcmV?d00001 diff --git a/multisrc/overrides/mangareader/mangafire/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/mangareader/mangafire/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..e4ae40abf9c473f74687274aba6256364c5538e5 GIT binary patch literal 1620 zcmV-a2CMmrP)AM$#>5xF1VuuEBKYE=l5P=OTO%!b7#(+ zGjrz5WHEvfj36fz04e}&h^eN1$pIBI*zEQDipI}cx=V-m2_m`)00`h1~v%CoSXxPqt5FAk~#_9KsHDY zHsFcx`?9&L9)O;mCfNb`Qig9iGXX}uG_y#~vLI!+W|jsIa*t7Dn^3aklRU|&JTX_Mvl;yM*`YczPMPtP)aI-&#)pT;nGjK|W;WG-|r0}uz$ zmL?7>U)@2-sb`nnhse{8v5!bm(8JTVakhV@rA0`eL{3=j!vjUEW zMLv{|R;+#m#R_K6kpxctrQ=fA;PSQa4hL3BQ{kw~9gtEdx2!@S7j*b_05!lY-9Yq0kf}2=amAPzzeRL;|?nA*_!rR`AN25T=)VGJPc?hdt9t&g&2p zm|;=MQLeYkEkE?&dK3p4cB~3w?sPSCFEGl=b0I@FY)M}a4F7h)puM1(?B#*TE1zu#iA6v`-?yA+W zJkmGb04gV{@M;X3SA?Kur>#+70QOW76=BMF1)Ej|p)k=$dLyOFD!~br(`p!9bWd(~IZVhfp;|6=%fDq=PHy z{$B|&0}{rSDX6MY!BJhM!b5<`CxabCKy9^(fv{kX-zN9;5H6h8Gw&Z6?gQv|yGloi zRUK2J;K6(S`0Pkr`ln&?B#&?|4;*PpAl_v{AvnQ(3Qet05ztts5?ws6L)V4%hZYC0 zc4Y_^kS*isCymmwdVKApnhL*0B{2f7bvY(qmk9Y3C+{rh9o&=AS+ z=ywU~52i^4#*Gn)EiG|sY7*bLZvYfmZ=u>+FBUHJLxos@Z@!A7KQM-Z zz%RYMyQ4^UnbO-!1=92H+);uFw|KbD{SGv4ijZMY8oOV+O3ywO&M?+#&|u-vCtXy( zF5EAldpbgW_~Z8!*SX&T=!A1;Qs9_4!TwJ|XOu8lIDN7wXTFav7Ove|siL$*$?rA# z@qrk+I!*lcbISIt&@>DhN=rRC1E`v6FOWe$bDu(6>%-KvwvD#cNBD1s+Zw`T=rm}E zwCS|7DMF2_+HBmqDNJpxx~)iTZxn}kzyPQhZI^ZW@mQP=z8R$#pNUZO>Na|BZDzkZe8c==!Y@SRR-59?Bu<8&WD7EFWa*HcMqy_BM%Lm>gk7zyD22%t2X zOvY%Kf&0XLa}&U*vuDp9pkW5?6ZZ`Og98`IenaWLIV)61{MtwL)-_;7ls1Bh6P73jPW0DP0J9U SgU@CF0000>7sq%lzhK&-w1T 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 AH~;_u literal 0 HcmV?d00001 diff --git a/multisrc/overrides/mangareader/mangafire/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/mangareader/mangafire/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..c993005becc893da075eca28030fbd0a4a6fde07 GIT binary patch literal 9337 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 literal 0 HcmV?d00001 diff --git a/multisrc/overrides/mangareader/mangafire/res/web_hi_res_512.png b/multisrc/overrides/mangareader/mangafire/res/web_hi_res_512.png new file mode 100644 index 0000000000000000000000000000000000000000..6ede68720061fa7d371e3a4266aa2088e110553f GIT binary patch literal 12839 zcmb8Wby!tT)HXclaA-stL`u3rq#G&e?v!rnjzfcVBOoA+NH-`g-3UmEv~&s5@A*C7 zcU|wl&->Txy=Ki?_q}G#nwfoFJ6cs)_6Y_l1_0oR+#4x%0HBCRC_wwK@gtS21mNY7 zoRoy7_u|2Fw3nu3ZvW{`p_P54?Q;ljlGZcl(5a@SBci`Q3mx#%VaYf+5jA1bu!U4Q zv<8^e?ps`_Efp(9)PeoaiX&~0<|{s=rT~~8v)sy$%G}phk*Yy;K>;`8AC)sTnHhv5e7VnM=`|X z|3A13p^V4;xB5Tve;WUZS5Qoo(ep*x?h>TY8KtqnB>ReerLBS61;X3p_?N?@#ZU?n zg!zlJ3et0R!DTP@e2mnBADv2EkcP3K>hbr3YzAt`@=uXK!vG&cKx?+OwSEHxF;+TH zkVW&%eAVc;DjEq8C(h$-JJZ-}Y9|Q6BZJ%Fi`$F59Qx|Se9h#ijOreuj^N{Xwkj_h zNHn1J(XI>`3*-ln$p*CYbs8P0V#+J*>CDo@wf_i?4g0#9SUI}cq7;Wgz+%3gxpH9A zN+~o8f&^%nyD@|BsI`?nK7S(P1mdoLtLxK*`6b2Nc2TiFqOMzW`&r_b<(;XgB!H^6 z;~o0ei&m>LhBCBbZlFaE7yg^)$l{}EY+y5kCl5mqk~y5q5$3}5YHbKpPx37oh9#Ev z5+nIoj2k|+MK2D9fU|k~2tsBb2ji@FGlU46Y9;<@DZ+IvQ^hO@_XXU!7_$xPaiQRW$gS+MMof_d)2&I zgrm1QSZdm*yeCGV1t_cz1644R8Y*)?m#FliBf(|;>eR1LEzm_%qzkEX<#=o%YvwC#KI1i=CYNc%ccT}gBtZD* zTB|nBaI#U=IGua8g}lW15O&546barUb zc-7yxRJ%Q`9&0X|yxySBMnXb@XYQ#x8g`HmuaLEq?HYa;QgC2$pF22jdD$nmH;<3C zwj;Wlo|5M}qAsYT*0h-Pnqi}iT`T9A8646TaHIIjQD;a+jau;0R;yK%1=pevBT_+9 z7$v_4JiPTtVY#vzBvV-8rLT&y08Re;F1AsKZN!+@6yy-Pv!dSk(!bG5hj-16ab!(SkMhX;h zzie!uWZ4q`53}`o=X|fDSySe$Tg5p6V|;L9I;FhmLO{=aeL<)-QZ(!Sl^@xQ_l$qD zU6B^`Z~0WtVkqUSNi}-klV*uTvoaXGj>nPi1-oCaEMDy>Je2Ea%+?eyWV_+%y{22C zcYSXPCVy}He@vMxIt{CStI(S+fMKW|N!lufsOIKb?P+WNN>GgE zDirlH;H$!OpX7pFqOihJCGXi5{wyPn{dp@x7#HQ>tjH7a5%iyi`ecR1V4MURU zALR^tsU%LIPs+W5k=DR=%;Di)VLxnAho{F&cEbOLK;iSbH&UD4LmZ2v(u|x*bDMFF zlbpqZ&*%=78W|QmqD`BmnU&5l4$tS?_;h6hX41Xr$tooYqF(PZ_NEI?cQ`m)y0bW1 z7P3qgT`P7eg+Rnn7u+XPF26tBD%Mx1m|oR4Rp1uzkB0yxIQCRP!P_lO2~i`v*(HsQ zxO8_$T#H{!fiM^fATMwTr}64Ff$G~YDC5lMwSZ;IXgZUp!VAGWDHkc)V{Y@3=qt@ zwA9APnzzz3q$dGV_Jddo1w%ThA^~>(Z3T%!S?9$D0t1LDf?MG@h{&L(zNyk7ru};L ze6mgg=MRWHZ#5bSh-PK`Q)69RE|;=0jCpB-G<*Cz4+KNRFM_P}1nAVPzO(D(iAJ)y z6yZE;%;D%_EYZxUMZtTw>u*4;Dr`d>x_b-wQrJW_HZQdjnPUQ#9Bh z*3tZLJjk;p>ev?2Q_L(DE_3Lgh#4dac)m>hs&O9Pt4EEkZZu$#=#ZT)!leFrpIk}) zVS;o+pFUOC^;G6(02#u7Dd2YARiu)BPdRvt>8RuzXkV4)ktWQJuOqNbi%5;ck>=M- z#?wu{9$DpbvxVtDA86(;H`DT$be-_9fS~-GmimvJPKwJ)E^ga)*TzDM>1Oq_9TisW z|9amfoW=H0jg{V0Y_+)?-&$S^3;J5G3>ftp4+z|-l2vzs$?a$=VI-WV8KxydnABvl;p68Ms6TE6+S(?yQwx5}$71Zgl1K;#Yh6z}Xa%kHL%MlTo<89OEx&56 z(`iNcp8OqWO=(6U0n5ir^&PY#dOB$wjXw4WkRqa)*3l>k9!_JY%Uwvtx#S-ra}2qF*fZ_6Y0 znWk9xJzGU&Pj4pmKkp23*W|xoxv{)zHtu}+%$PFE44f41$P_2!TuOJ62@zv`Hbu%m z1hH?B9*^UOB2qL8OvamFlAp1#!OdH6@@jU(af%DU_|iQE3308cbhLAo9?{j~G0Xi9 zfkWUOHyM@){Q^rv6K0~NWyBt_yZ={CreD3lvPZR~+Oq{>L;ZI-irYDF3iAsmfk(9h3lth0-c(+p`zS!hwExG>BF#G*L9q#c6ndbR7<6wva{@Zpf_$FSSlK*#1|3cv8 zzR-IGarKwLy9i>=RmzTB;F1-8J&`c(jBX0@3t!;J?f+5b9#5KifWLG=ps@L)qM1XY|Ne5-exD1%41Q6ypGOSx*w1~>9t@k65 z@SEF%9ltm9IpU_Hum2dw;=8lnc+yEvY6!)933{i+AAFyx9E+|;uiCag>riua#$t5A z0{X|{PhT;x!wS5rma&2izW?Vl5JktrsYr54NatNG1Oc@8sw7)LC3>~hU4)D)_=$MA z{9lT6ltrsO>n5ALnE9R;gwNt3#gPzkp~{7H{6n`(`IBL9UPQs+p0W0B`%}iTxO1_! zR_yXW6s;yRN5d;V+2r99he(+JL>vMpM=9;Fges(N$%LL`v6>^PzD7EcAhqda8u>@% z&mgOO!x6hlp^Aqn9^?HoI#F+Cyo@`4FC)o|f1W_K&8IRZ`5@}^0*Csn^Y&XuFF$(U z(h8*I3BmC5v}ZHh=3O8Cua)Bc#7AEO#+vsktlweFW2Mi@ju0j6qRvyOliZU*ekfLp z_X-iU$AMKJZ&}A;$K{~)tHb7O3qEwy8kb{~m%}mDe<)MSU z9*F;-My@m!TXfa_)(byNWIC4HZNN8h$Llg@OpY~f)$h2_)nkk1ag|YoqPG{AWzu`R zo1dhmf`XgD&!Rl>#9=0d3}4bcUT~uiT7n-6IKIEr-_gK~@#FQizA1ZHRLd+>~AoQQ_i9xL4+!W`3WP79~NrRj9I>kaFAh=+X*tk7%9=h7Y zS!St(_5_I^#9tw>~G1sU~rz0UYFD6-V zK_cq>LBDiYQ4I@i1J14<`0)nEYCd7F7#Cr|v$ocn=78`?=81PqqjruCR^jkkI~nwY~pp$KZE!%i+y7~7%r zej?t%u_gZ_@;!lx5~PHMGC0$qwGo0@ys$gncC3Vu6w^%2UzOXcN-RVyud*!~93K$b z^58ERkpQFp-L0`vxR}GVoBU+)+@-M0NMcC_h-dcwe&<5WD3Esp*|@?|D!H_cR= zRR-n6a#idz9a?K$gJkWAx-%E`%Ze7Gq3DpnT3)H@BrW?@S3kE^Hs9D~!f^av){GIKV-0M;;5{YC>-s#i_q}`0y5jiwZ4dMcvz@H&R z=w87GRKwe&DADJ3O_iE|9I--CyliZC&;Xj)L2qdbrbP^mwOLL^ormiEn!#9wCpA(X z1<;}nBWpV**@i9zu;qJ}_qZ_`+-Tk4l*olHadZDYhz9Vxcv5egQTF+xejj$ra}1C7 z7kewQSmHqZUlZkg)F;kdq@R{UZ8*)v4kqQ(ndjVV5vKip9|T2l%`=EQ zpFe)tYKT7&0a?jm5=PHo+njtLf&d@SLr(fo!L^b5x1#%lC|+i?Bk`?wCdU&OP5?-Xo!2$vG^FH?&5jOF9S%h=S0i=JzaHRO$I2YYM9wDB($j^FdF&5l@ zFc22>Z$?WP#Tjm^Zj{kufZI%lCghu$^o#M+77d^xXr_q8aqihNa9MrdvQt)1z3P+r zPlt^($O@XT9+qgZbixaF%ktH#djIdRoKax&2Qqn_7}9otA=B%@%2#`INGcFA*A1tf z3hLZ%UsBvU1j!_Bue4!suRv`UB5rwp>_4{`bfP%hnc?@$VuWAs?L%_R^?o$#N#4qW z%}t;EF~8F}QNl>-0NL>YH2%%iUYHmmS&*f=yp*~2p&7CffFvr^;Xk6P;7R@X5el5U zKdpF^vl0?jt`}c_caKruTF87^NTD;01}Cd6QVJAxnzSc!-`M<)ETprJxW_C9<*aS` zM@?^BAd`PX*h12FAMETrU7!BS4ufN+H;%y6&Hl8h&W+5A=BqDaIdt~|82H7mTf+ER z`kiVs46+uIry1?y|D2vK(9g6w<~QdJ6bk3aPG?C8XfFEt1hN)CFm6h3v)6D@w!pkxs%6G)m81|x zqb-MdLjZ+8C$3VZQH!C6#Lcob9}Or@{tyUKnpm$?b4_#8@o-0l0pg~s3XQxK0-hIO zxz^e|O8j$XNoaf09RuIK*Dfv^oONlrP$t=EiHJ4PIp46>98^P#klQ2E(|U|f)(UeI za-VsV#)83;^9*Ls4js`73Irm|HUg+E&P?TKUcA3CjZ6WNPgm9^9u=p>3 zQt7_aKKIv->)nbc#5`r!Z95Pp8*-jauY8ik2ZNovxw8{nKvdaZ-=;SYe>q(+9Knek zVcQu^K`V+Z(0^rLq^Nzksq*qH_ODRSF0pM4a5l3&-%q1Bz*V6FhUX@P)E(G)2BF)f zX$3`@o6qne=k@aLPhjFDqtz+7!hD0gNoC=o+XcVT@TiO^MCB~L7>n#nZoXuR;J(0w ziAN|~SH<@aq1&NB)=D6_lqhb&{p@tF{JkcW`tA)gA;AufRLPXd2!J4=aoOnbnz^K)vUZR@%~9Pqh;8! zjM*;fpUkUjS6t=aYgTNHZXasVkz^;N^Ewa%{xa&GH>gWLQRlX@bg-M*N!ukae-APj zn<#j;(soT^X~6QukQ)pxBa_+O+kaeL8cQotCldc zhF%O=fS*o5f zveHGorZiAopVD|q{TiHXR8W-!x7{$d$hdx!ArG+AUY)Pl*PN8V0adNOF*3HVB7n1& zoD66g`pm)Og~?s7Z^j_9;M@C*9I-^=&xx4YoL&Ha{CCjy(X?zAFU*U-wTzbNO}jXP zZ-yaMU1A^An1nt5yxsU27QFE%Jx>cw7jw{P9i6$1c!gQjzS)c3JHI%lipWx+BM$&Cfh2_Qk%f^yvbdc{d!k-{2gkr+EA$TQpq*uR0j>r zf7$Q40?UzX^C^M0qSZJ~D{&f4~FFVUd%sxQey*$7f_3>+| zUe>z=*=Qu9r)9hJOvvKp;DdEwd{RYJU3B4I`u+10HE$J?#qwf}j3-Mx%DH1$*x{2( zIWfT7(Yo49??|PwKiFf$(XR(4ymrZP>aM=P8I9j@Cojn-0gBdRU4OTtvM6QI2WZo; zowTigIxd6>cX5}y2dBQ4_MQJ6A#qo8n~m0n4y7XWI(VAGx2Y4u|F!DhjPyr1pTh#V zr+$2KsGYDAuF8Q(7x4YAn86gIG-A0)>V@|4LT0YI3t^CVEdd-yWkf8IuhAbq z*B(2=R{Cnu;BFc2XR#&Uxt3sxCOl#DU0m-65oE|(*Btxz{g{Q{Nc=1=61)pJ@C;bU_74$DYSU!Q zHM{Iyz=(VIr_sRWEOW!!%t4Y&2vqx0ma%-dlUWPeNwMQO$6 zGnPq2I4>=lF_}W83g^DKHW_8U_o&2!aMJNP$ERrn0?dcn8u`&X=7^6g7)#uW-M<6= zVSzVLKd0c_$J8r3Q^L6Jw;{GYYy0o%B5s5X((a>-Tw> zHqDMut{k|hOpLvKinq&z2HNH8aS#cXmF;TN}~CwT~;(;;559-B3);;olzG6_dL`q#{(KYN4o{3RNz3+l~-n= zFvv0L5UJtMt`RFhO`fb(lt9F1vp+RCGU23QxJv0`UHIQ$U8#2=1D#HpeD!ZmLhIin zi#Vxi|Jl=6$n7e6`1dM32*~Nyl73?f2*3wCPw*_Wm%KI{RMvj*dJWQR2Z5my?FnWX zYed*R-LRjep=M^RGCu`vKhzqdqx8H>Qqbky~Y@}YI`xo!`bbZz& zG313=T13DN zSgh&6W_PkxhVRS{!7G>G^@*kKLYkQey=h-h&qyv^O8w zlS-3A5-lj0e7D4xZ}WEa3bl!Zl24umdCtL<9l0ySDtf1W{>V7oHF7`&@0_&HcCrTp zad48Q@t!CCNlV(8wM2_G)=CU^#s@lth<;y|5~X3ZHm(*GWL37E&4Q#(!K}y?<*o@IlPE>ml?l*6;`J zcb;!22`5aV?r-fG+_vi;N6Oo;!kAE_rM@@{HPE7_Gx+H+ZfxjoK=?P#2=6^;B0o%5 zBJ8ItraUNVRP-HITi(hEjKmQ456?31h!nMIwYd_`+y$HGim0TfVv;1eaRGM2*r}j} zHlvMe)cUunt{-F*@I+V$u%9{@w7Z`s$47pnLbmvxGf!p4KRyI9e^F&^8{m8UJaqeA zhBIBgeaYD%psAU4|BBvIdV03f@LR+u%A$t{^BrQY{&iMlaS23a&WoCUzUxxaqG|e= zqhi0OYdCd5MNk({5yQ0^LWCk04CLfH<{|)(=@SDKl zT!GISt;?Eei^p5~x7LV-ys&lN^J1R#{EPIl0H-2y#xEnwGp|y0D>12;FzflR0?6H|Y|Pm2ljV+Ij9C3P zyyS4UlX&awFq;1-p7w)I^&aZe2tEfaHa+I~xoF-56|Kg}DW+<};-i)49qw{Le=$mL zx&nSW)P}sCTQ$8@dh4;+_x&M4cQYNv8mINE~5LB9?GhLjCJbuj z5f|0X*Nl|kDqZ)bT-3urYs{nP-LFr$fNDcyJ}ZkZ_NxFTIDlX)4V~za{vw8iP_0n| z_-N9b$vP+GUUn4-Cqrh4w6sL}CO6$g-t&WBn+u0aF1SEEqK1`}6r(n9hw>>I8l@55>QLyg0S9yg!&@16fsG?jd@@7{6N6aF*r>9>VHR?^{|O3<~-L;i)e zMV^o67>8Xc0DO&9;$>k(yI}>Z0Xy6j&4CXBbBoL_1OVABM7gpGPjTi*+C-tg?*ZS5 zjU7)4mly)P(X!cf)mdwfw)u`9@gCR8217nsxAt^Xln?-erm#U?1{E0rj_DI+Ll{{5 zT{Mwpm*l}sd2?ZFwwLiD-HQdW@CuvPnZ>-=`>Rr1sQ|AdMv^4=7&FJl z4R!t(%_ylF7!)|wIac%wX5_`~rx&`o?rT@pQ1wDMg?0y7GCW+zJHEZW{B?FPgs%n# zEq&!ljQiT6^V~yBPbp~^^@|h_KOxR4Ip;U`96OBf_4HX!3_q91p#N%sc2(MneRsPT zy{Vr{R;Q+D?jW7R*s-7m7}ltVOAG88pASgqewwW5U2;bN@aF}Zm1ESsz_+yWG09t2NOp%0E+x~<@4C(40;4U48g<841nCCHn zrH!zMw-XfqcKLB;(Qc|z`XZhze)MC?A+T{wXLM|Gz&uMa1lwOV_`84vNDih$mtw#pF*6TdPmfNPvT>Uc+aT@4Al?LI!kavArg; zT%-WgPTDoCV+*4>&AyBn5tUdsv3|};mFJbI`1RMo6DUwr zX0({qMkCo}aFZ)lG){h_>!3TX3a~DiHhIY_@tVI2dEmu_LOBT~_a#ZTN^ABpKL}fy z^^~y^PGiC0i@| z>4ZSMGit16Q;}bGFnHw4mMETXd?;&%)C|;5lyI1^p6hishI(UxuJ#%?39KVe!-Z0d zv+&qe_Jv;Xb8Dep&040wf3CV#nF@40j|$N6N@VI03=1rg2RU)u{< zS=JilY+roAfOVN1{KV@S96w|W<3W0_1F5n7D4$#FS=?idewt^dZ{F{3 zWVTG-nIJQPZ4ypk{7hOYXP9Vl7wzZm{v_s4`~q~@$5Y1DC_@~FtI`=a9CRxMf6CoL zCUsd7t;^o)B~O^6^HqMgUWZUcSgP&&@oD zPCjP5>zAR{9%7}a;J?J%e~r}HOZ094e)PjW!#tI)!?3!I64;{>oF$j&2lq0^ul_&> zLG^x-5s;PgE2jY_g_;8wF<>WROy#Ck-Sd`kVyzc+T{Vp3zswi%5ccDHYNl3EKxM-M z?v2dC^F{MO4!+%Wu52Ej|EBi;fy5=2z5txxf1YMQ#lK*~aPebmuxcwsx zJpGzjcV}aSQM&ew{RzRptOxdINNpOC&0&m7tjDR#PvMIN`A1{zYQz(#B;Z?&CA4dR z)i#AQU+6C*>M#7|dPz4ikXf1_JA^bIFD1abInB$83iDSo_!fl#HDA4(>iARqqcD)S zhUF}(vyh;)j$cL693-4ud7(lP?tJJZ=5X_Aknm9~v!|Z>Cf_Tvfy^Cc*&)XWzDRyn ziwWvwD72&GGk~m(?)Btbs?9y~d+SXIl(a&s3^tjOjJP5j4QpW#;6E0E0Woi*S(wpT z2S&}qR=)UP^tc1N{kApw+ETcwKA0T_PIx{(p+@A@(NZ}sE z=a0=%ZnMeS>;y`JLmuB9E9VrnoPqdrYkHJ$938e$!veH7)EMe*>ugCpx)F>RxKGL? zo`mR~#Fm|kyX)ASQ?0pATr69Abp}{svq@Nyl|=H5UTJj77ppUo+ujEF`TDkaGz=%vZ~#uH@*T^gsvA zoyN@cR048^V=*looIFHSP z=(y)e{IzNOgy_mp*w~@W-I@uw4!>_4p!|pSw{%1P@q4mXjFbaplWznsTV4P&)nYSY zkSkpM^TQ_wOz}_Y-Q|o^4C;YI9hTojZ%xXWgglC!AmUNENb7+75OPv&rM&A_xPJ0V1G*UhFf5p_KV9|%~oG$gSk`8eHs=6|nvkRcbI z$N+FY*;GsEFMr)e+qM=jikJvq2@<$)gQ3z+A)6($rt?xsfm$QVSeGY^C;jDxU(qwo zv01J+Pdbj)s8Pk0Z=FoI%{txWK44iXJLpso;DHA`^ra_TPTF_PrC;9M222y|3HWy9 z$pb@W6}LER-(iC4p`e8t`IJYs`*N09WVqJ^>%bgah7j8MPL2RBj{M@nn%vELJuVPP zqg!nJ^oG-vr^;(**z4t=6)~kC))Yg55xN$ruZH2a-E5()Iwewif1aK%Sk`&JUDq#0DQb%OE>!;5h8WPaNhe59vXxCSIivGEb zO>?B-jP~5Pf$$#eMsIZSn;EZU)L_WaH*cb9y)>^U|32|C{%EpoR0h*|3{S5Cc=Oj# z^?p(KMmWEI?A_99%eIl!>J(5UkS49q+K&pvKQ}z9_bC*jB#OM$uC~SazCsa=1Haf2 zuYUpjqr68&uN#AGtFdmgW4)J&h7|%N2~yeIULKFG33Na}(Z=@d8;{y1+KljkI2l@p z7{f`!L*>(_S|`q|yuvsSN4je#%I{`;y|yr*YpzP!P;vc|Accd6X5aHvzs3mIR ze*xOp3~rB1b~^j0Sy-)$3D`2ZDt)~#+D$KsqgJx6FVqfxW%R6&2j4Hnw(wHk<^V9={?ggh-YG~UpKM2e!pLu>NN4cCpDs2F!{&+Z%z4!vw>MUG$|zi z*Uf~tzq=}Z_pik@J(DkNzrmez7N&SGR^q;lPW$8EDVr+gG5N!zxUxYeuPSMd3mD}c z8IQXHtw!Q1enBaN()E&m5Vf~Wd$ZM6@?Q7mBOX*dLh&JM_vik|cPuiyQ+ho~HsA8hq|Ne@__0*d{MNpg8TE|hqjRr#UIhVfeBow4|}Lo*V&==I@AN5XME|c zBN?p(c^{an9w9k|2}@T_GFuDgvz|fM$(!gbNh%mpM6H2*vO!psEBh5j#Y`bzysLZhTip@eOs=gVu;qH%Ne^5~a+W zuRMzzp3S!m)p%LP>VCLnW+?jH_c~<%-U4V+b9ARZw^9jxX}=^SN-hi`QI%sf6(f!L zc#KW4vaw`*SjDUubX;XpNojYPd(Z3qXvIyoE}Bpe&Ch~QwEfkP3e;ECD6nJz8&|E5 zukzVv2wrKF+{x?@Zk>EzRXwil;zRXE&5kwl$Zg(P<1IYTKOW_S_%QmI^Pg>IWKOJo z3`hoLBXxZ-rKb;7`N9S}%X^)bJ&ta=po9uz&xcvm)6z3y;{22Y?BKvh zUD`+UP!PQQl4KSKB;0e@GaWj>5y=^hFYVI+I#I8A$)Jn~ifE?f1K zyb1+rjT4$!SuZKM^Sa$mgRQ%}QurPhK7%IiIi{FJtP+{UvV4xZb}CZ!09)gR*1RbR zdNtvJ{CsY=Ugz;p%kUnZ3KOlXASln2deyD3^N&X58O=PUn>{}@=yHLHKZ$O+_h$8U zsIESB6T#j2M=4H(1HS7nUsH;w;{X5)@|X5lqL6$XI#|GKFQ xN2sD9{&)CaHx`)xFC_k>J4p0@N$Ao(qP`85W%hX?QGsv?, +) : Filter.Group(name, values) + +sealed class Select( + name: String, + val param: String, + private val valuesMap: Map, +) : Filter.Select(name, valuesMap.keys.toTypedArray()) { + open val selection: String + get() = valuesMap[values[state]]!! +} + +class TypeFilter : Group("Type", "type[]", types) + +private val types: List + get() = listOf( + Entry("Manga", "manga"), + Entry("One-Shot", "one_shot"), + Entry("Doujinshi", "doujinshi"), + Entry("Light-Novel", "light_novel"), + Entry("Novel", "novel"), + Entry("Manhwa", "manhwa"), + Entry("Manhua", "manhua"), + ) + +class Genre(name: String, val id: String) : Filter.TriState(name) { + val selection: String + get() = (if (isExcluded()) "-" else "") + id +} + +class GenresFilter : Filter.Group("Genre", genres) { + val param = "genre[]" + + val combineMode: Boolean + get() = state.filter { !it.isIgnored() }.size > 1 +} + +private val genres: List + get() = listOf( + Genre("Action", "1"), + Genre("Adventure", "78"), + Genre("Avant Garde", "3"), + Genre("Boys Love", "4"), + Genre("Comedy", "5"), + Genre("Demons", "77"), + Genre("Drama", "6"), + Genre("Ecchi", "7"), + Genre("Fantasy", "79"), + Genre("Girls Love", "9"), + Genre("Gourmet", "10"), + Genre("Harem", "11"), + Genre("Horror", "530"), + Genre("Isekai", "13"), + Genre("Iyashikei", "531"), + Genre("Josei", "15"), + Genre("Kids", "532"), + Genre("Magic", "539"), + Genre("Mahou Shoujo", "533"), + Genre("Martial Arts", "534"), + Genre("Mecha", "19"), + Genre("Military", "535"), + Genre("Music", "21"), + Genre("Mystery", "22"), + Genre("Parody", "23"), + Genre("Psychological", "536"), + Genre("Reverse Harem", "25"), + Genre("Romance", "26"), + Genre("School", "73"), + Genre("Sci-Fi", "28"), + Genre("Seinen", "537"), + Genre("Shoujo", "30"), + Genre("Shounen", "31"), + Genre("Slice of Life", "538"), + Genre("Space", "33"), + Genre("Sports", "34"), + Genre("Super Power", "75"), + Genre("Supernatural", "76"), + Genre("Suspense", "37"), + Genre("Thriller", "38"), + Genre("Vampire", "39"), + ) + +class StatusFilter : Group("Status", "status[]", statuses) + +private val statuses: List + get() = listOf( + Entry("Completed", "completed"), + Entry("Releasing", "releasing"), + Entry("On Hiatus", "on_hiatus"), + Entry("Discontinued", "discontinued"), + Entry("Not Yet Published", "info"), + ) + +class YearFilter : Group("Year", "year[]", years) + +private val years: List + get() = listOf( + Entry("2023"), + Entry("2022"), + Entry("2021"), + Entry("2020"), + Entry("2019"), + Entry("2018"), + Entry("2017"), + Entry("2016"), + Entry("2015"), + Entry("2014"), + Entry("2013"), + Entry("2012"), + Entry("2011"), + Entry("2010"), + Entry("2009"), + Entry("2008"), + Entry("2007"), + Entry("2006"), + Entry("2005"), + Entry("2004"), + Entry("2003"), + Entry("2000s"), + Entry("1990s"), + Entry("1980s"), + Entry("1970s"), + Entry("1960s"), + Entry("1950s"), + Entry("1940s"), + ) + +class ChapterCountFilter : Select("Chapter Count", "minchap", chapterCounts) + +private val chapterCounts + get() = mapOf( + "Any" to "", + "At least 1 chapter" to "1", + "At least 3 chapters" to "3", + "At least 5 chapters" to "5", + "At least 10 chapters" to "10", + "At least 20 chapters" to "20", + "At least 30 chapters" to "30", + "At least 50 chapters" to "50", + ) + +class SortFilter : Select("Sort", "sort", orders) + +private val orders + get() = mapOf( + "Trending" to "trending", + "Recently updated" to "recently_updated", + "Recently added" to "recently_added", + "Release date" to "release_date", + "Name A-Z" to "title_az", + "Score" to "scores", + "MAL score" to "mal_scores", + "Most viewed" to "most_viewed", + "Most favourited" to "most_favourited", + ) diff --git a/multisrc/overrides/mangareader/mangafire/src/ImageInterceptor.kt b/multisrc/overrides/mangareader/mangafire/src/ImageInterceptor.kt new file mode 100644 index 000000000..95c18c79f --- /dev/null +++ b/multisrc/overrides/mangareader/mangafire/src/ImageInterceptor.kt @@ -0,0 +1,79 @@ +package eu.kanade.tachiyomi.extension.all.mangafire + +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.graphics.Canvas +import android.graphics.Rect +import okhttp3.Interceptor +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.Response +import okhttp3.ResponseBody.Companion.toResponseBody +import java.io.ByteArrayOutputStream +import java.io.InputStream +import kotlin.math.min + +object ImageInterceptor : Interceptor { + + const val SCRAMBLED = "scrambled" + private const val PIECE_SIZE = 200 + private const val MIN_SPLIT_COUNT = 5 + + override fun intercept(chain: Interceptor.Chain): Response { + val request = chain.request() + val response = chain.proceed(request) + val fragment = request.url.fragment ?: return response + if (SCRAMBLED !in fragment) return response + val offset = fragment.substringAfterLast('_').toInt() + + val image = response.body.byteStream().use { descramble(it, offset) } + val body = image.toResponseBody("image/jpeg".toMediaType()) + return response.newBuilder().body(body).build() + } + + private fun descramble(image: InputStream, offset: Int): ByteArray { + // obfuscated code: https://mangafire.to/assets/t1/min/all.js + // it shuffles arrays of the image slices + + val bitmap = BitmapFactory.decodeStream(image) + val width = bitmap.width + val height = bitmap.height + + val result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) + val canvas = Canvas(result) + + val pieceWidth = min(PIECE_SIZE, width.ceilDiv(MIN_SPLIT_COUNT)) + val pieceHeight = min(PIECE_SIZE, height.ceilDiv(MIN_SPLIT_COUNT)) + val xMax = width.ceilDiv(pieceWidth) - 1 + val yMax = height.ceilDiv(pieceHeight) - 1 + + for (y in 0..yMax) { + for (x in 0..xMax) { + val xDst = pieceWidth * x + val yDst = pieceHeight * y + val w = min(pieceWidth, width - xDst) + val h = min(pieceHeight, height - yDst) + + val xSrc = pieceWidth * when (x) { + xMax -> x // margin + else -> (xMax - x + offset) % xMax + } + val ySrc = pieceHeight * when (y) { + yMax -> y // margin + else -> (yMax - y + offset) % yMax + } + + val srcRect = Rect(xSrc, ySrc, xSrc + w, ySrc + h) + val dstRect = Rect(xDst, yDst, xDst + w, yDst + h) + + canvas.drawBitmap(bitmap, srcRect, dstRect, null) + } + } + + val output = ByteArrayOutputStream() + result.compress(Bitmap.CompressFormat.JPEG, 90, output) + return output.toByteArray() + } + + @Suppress("NOTHING_TO_INLINE") + private inline fun Int.ceilDiv(other: Int) = (this + (other - 1)) / other +} diff --git a/multisrc/overrides/mangareader/mangafire/src/MangaFire.kt b/multisrc/overrides/mangareader/mangafire/src/MangaFire.kt new file mode 100644 index 000000000..5459e630f --- /dev/null +++ b/multisrc/overrides/mangareader/mangafire/src/MangaFire.kt @@ -0,0 +1,189 @@ +package eu.kanade.tachiyomi.extension.all.mangafire + +import eu.kanade.tachiyomi.multisrc.mangareader.MangaReader +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.source.model.Filter +import eu.kanade.tachiyomi.source.model.FilterList +import eu.kanade.tachiyomi.source.model.Page +import eu.kanade.tachiyomi.source.model.SChapter +import eu.kanade.tachiyomi.source.model.SManga +import kotlinx.serialization.Serializable +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.int +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.Request +import okhttp3.Response +import org.jsoup.Jsoup +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element +import org.jsoup.select.Evaluator +import uy.kohesive.injekt.injectLazy + +open class MangaFire( + override val lang: String, + private val langCode: String = lang, +) : MangaReader() { + override val name = "MangaFire" + + override val baseUrl = "https://mangafire.to" + + private val json: Json by injectLazy() + + override val client = network.client.newBuilder() + .addInterceptor(ImageInterceptor) + .build() + + override fun latestUpdatesRequest(page: Int) = + GET("$baseUrl/filter?sort=recently_updated&language[]=$langCode&page=$page", headers) + + override fun popularMangaRequest(page: Int) = + GET("$baseUrl/filter?sort=most_viewed&language[]=$langCode&page=$page", headers) + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + val urlBuilder = baseUrl.toHttpUrl().newBuilder() + if (query.isNotBlank()) { + urlBuilder.addPathSegment("filter").apply { + addQueryParameter("keyword", query) + addQueryParameter("page", page.toString()) + } + } else { + urlBuilder.addPathSegment("filter").apply { + addQueryParameter("language[]", langCode) + addQueryParameter("page", page.toString()) + filters.ifEmpty(::getFilterList).forEach { filter -> + when (filter) { + is Group -> { + filter.state.forEach { + if (it.state) { + addQueryParameter(filter.param, it.id) + } + } + } + is Select -> { + addQueryParameter(filter.param, filter.selection) + } + is GenresFilter -> { + filter.state.forEach { + if (it.state != 0) { + addQueryParameter(filter.param, it.selection) + } + } + if (filter.combineMode) { + addQueryParameter("genre_mode", "and") + } + } + else -> {} + } + } + } + } + return GET(urlBuilder.build(), headers) + } + + override fun searchMangaSelector() = ".mangas.items .inner" + + override fun searchMangaNextPageSelector() = ".page-item.active + .page-item .page-link" + + override fun searchMangaFromElement(element: Element) = + SManga.create().apply { + element.selectFirst("a.color-light")!!.let { + url = it.attr("href") + title = it.attr("title") + } + element.selectFirst(Evaluator.Tag("img"))!!.let { + thumbnail_url = it.attr("src") + } + } + + override fun mangaDetailsParse(document: Document) = SManga.create().apply { + val root = document.selectFirst(".detail .top .wrapper")!! + val mangaTitle = root.selectFirst(Evaluator.Class("name"))!!.ownText() + title = mangaTitle + description = document.run { + val description = selectFirst(Evaluator.Class("summary"))!!.ownText() + when (val altTitle = root.selectFirst(Evaluator.Class("al-name"))!!.ownText()) { + "", mangaTitle -> description + else -> "$description\n\nAlternative Title: $altTitle" + } + } + thumbnail_url = root.selectFirst(Evaluator.Tag("img"))!!.attr("src") + status = when (root.selectFirst(Evaluator.Class("status"))!!.ownText()) { + "Completed" -> SManga.COMPLETED + "Releasing" -> SManga.ONGOING + "On_hiatus" -> SManga.ON_HIATUS + "Discontinued" -> SManga.CANCELLED + else -> SManga.UNKNOWN + } + with(root.selectFirst(Evaluator.Class("more-info"))!!) { + author = selectFirst("span:contains(Author:) + span")?.text() + val type = selectFirst("span:contains(Type:) + span")?.text() + val genres = selectFirst("span:contains(Genres:) + span")?.text() + genre = listOfNotNull(type, genres).joinToString() + } + } + + override val chapterType get() = "chapter" + override val volumeType get() = "volume" + + override fun chapterListRequest(mangaUrl: String, type: String): Request { + val id = mangaUrl.substringAfterLast('.') + return GET("$baseUrl/ajax/read/$id/list?viewby=$type", headers) + } + + override fun parseChapterElements(response: Response, isVolume: Boolean): List { + val result = json.decodeFromString>(response.body.string()).result + val container = result.parseHtml(if (isVolume) volumeType else chapterType) + ?.selectFirst(".numberlist[data-lang=$langCode]") + ?: return emptyList() + return container.children().map { it.child(0) } + } + + override fun pageListRequest(chapter: SChapter): Request { + val typeAndId = chapter.url.substringAfterLast('#') + return GET("$baseUrl/ajax/read/$typeAndId", headers) + } + + override fun pageListParse(response: Response): List { + val result = json.decodeFromString>(response.body.string()).result + + return result.pages.mapIndexed { index, image -> + val url = image.url + val offset = image.offset + val imageUrl = if (offset > 0) "$url#${ImageInterceptor.SCRAMBLED}_$offset" else url + + Page(index, imageUrl = imageUrl) + } + } + + override fun getFilterList() = + FilterList( + Filter.Header("NOTE: Ignored if using text search!"), + Filter.Separator(), + TypeFilter(), + GenresFilter(), + StatusFilter(), + YearFilter(), + ChapterCountFilter(), + SortFilter(), + ) + + @Serializable + class ChapterListDto(private val html: String, private val link_format: String) { + fun parseHtml(type: String): Document? { + if ("LANG/$type-NUMBER" !in link_format) return null + return Jsoup.parseBodyFragment(html) + } + } + + @Serializable + class PageListDto(private val images: List>) { + val pages get() = images.map { Image(it[0].content, it[2].int) } + } + + class Image(val url: String, val offset: Int) + + @Serializable + class ResponseDto(val result: T) +} diff --git a/multisrc/overrides/mangareader/mangafire/src/MangaFireFactory.kt b/multisrc/overrides/mangareader/mangafire/src/MangaFireFactory.kt new file mode 100644 index 000000000..3fbc88e6d --- /dev/null +++ b/multisrc/overrides/mangareader/mangafire/src/MangaFireFactory.kt @@ -0,0 +1,15 @@ +package eu.kanade.tachiyomi.extension.all.mangafire + +import eu.kanade.tachiyomi.source.SourceFactory + +class MangaFireFactory : SourceFactory { + override fun createSources() = listOf( + MangaFire("en"), + MangaFire("es"), + MangaFire("es-419", "es-la"), + MangaFire("fr"), + MangaFire("ja"), + MangaFire("pt"), + MangaFire("pt-BR", "pt-br"), + ) +} diff --git a/src/all/mangareaderto/CHANGELOG.md b/multisrc/overrides/mangareader/mangareaderto/CHANGELOG.md similarity index 91% rename from src/all/mangareaderto/CHANGELOG.md rename to multisrc/overrides/mangareader/mangareaderto/CHANGELOG.md index 1b0c78aef..f4fce847c 100644 --- a/src/all/mangareaderto/CHANGELOG.md +++ b/multisrc/overrides/mangareader/mangareaderto/CHANGELOG.md @@ -1,3 +1,8 @@ +## 1.3.4 + +- Refactor and make multisrc +- Chapter page list now requires only 1 network request (those fetched in old versions still need 2) + ## 1.3.3 - Appended `.to` to extension name diff --git a/src/all/mangareaderto/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/mangareader/mangareaderto/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from src/all/mangareaderto/res/mipmap-hdpi/ic_launcher.png rename to multisrc/overrides/mangareader/mangareaderto/res/mipmap-hdpi/ic_launcher.png diff --git a/src/all/mangareaderto/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/mangareader/mangareaderto/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from src/all/mangareaderto/res/mipmap-mdpi/ic_launcher.png rename to multisrc/overrides/mangareader/mangareaderto/res/mipmap-mdpi/ic_launcher.png diff --git a/src/all/mangareaderto/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/mangareader/mangareaderto/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from src/all/mangareaderto/res/mipmap-xhdpi/ic_launcher.png rename to multisrc/overrides/mangareader/mangareaderto/res/mipmap-xhdpi/ic_launcher.png diff --git a/src/all/mangareaderto/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/mangareader/mangareaderto/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from src/all/mangareaderto/res/mipmap-xxhdpi/ic_launcher.png rename to multisrc/overrides/mangareader/mangareaderto/res/mipmap-xxhdpi/ic_launcher.png diff --git a/src/all/mangareaderto/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/mangareader/mangareaderto/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from src/all/mangareaderto/res/mipmap-xxxhdpi/ic_launcher.png rename to multisrc/overrides/mangareader/mangareaderto/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/src/all/mangareaderto/res/web_hi_res_512.png b/multisrc/overrides/mangareader/mangareaderto/res/web_hi_res_512.png similarity index 100% rename from src/all/mangareaderto/res/web_hi_res_512.png rename to multisrc/overrides/mangareader/mangareaderto/res/web_hi_res_512.png diff --git a/src/all/mangareaderto/src/eu/kanade/tachiyomi/extension/all/mangareaderto/MangaReaderFilters.kt b/multisrc/overrides/mangareader/mangareaderto/src/Filters.kt similarity index 100% rename from src/all/mangareaderto/src/eu/kanade/tachiyomi/extension/all/mangareaderto/MangaReaderFilters.kt rename to multisrc/overrides/mangareader/mangareaderto/src/Filters.kt diff --git a/src/all/mangareaderto/src/eu/kanade/tachiyomi/extension/all/mangareaderto/MangaReaderImageInterceptor.kt b/multisrc/overrides/mangareader/mangareaderto/src/ImageInterceptor.kt similarity index 92% rename from src/all/mangareaderto/src/eu/kanade/tachiyomi/extension/all/mangareaderto/MangaReaderImageInterceptor.kt rename to multisrc/overrides/mangareader/mangareaderto/src/ImageInterceptor.kt index d0f86d0ca..cdd7af521 100644 --- a/src/all/mangareaderto/src/eu/kanade/tachiyomi/extension/all/mangareaderto/MangaReaderImageInterceptor.kt +++ b/multisrc/overrides/mangareader/mangareaderto/src/ImageInterceptor.kt @@ -14,7 +14,7 @@ import javax.crypto.Cipher import javax.crypto.spec.SecretKeySpec import kotlin.math.min -object MangaReaderImageInterceptor : Interceptor { +object ImageInterceptor : Interceptor { private val memo = hashMapOf() @@ -22,11 +22,9 @@ object MangaReaderImageInterceptor : Interceptor { val request = chain.request() val response = chain.proceed(request) - val url = request.url - // TODO: remove the query parameter check (legacy) in later versions - if (url.fragment != SCRAMBLED && url.queryParameter("shuffled") == null) return response + if (request.url.fragment != SCRAMBLED) return response - val image = descramble(response.body.byteStream()) + val image = response.body.byteStream().use(::descramble) val body = image.toResponseBody("image/jpeg".toMediaType()) return response.newBuilder() .body(body) diff --git a/src/all/mangareaderto/src/eu/kanade/tachiyomi/extension/all/mangareaderto/MangaReader.kt b/multisrc/overrides/mangareader/mangareaderto/src/MangaReader.kt similarity index 59% rename from src/all/mangareaderto/src/eu/kanade/tachiyomi/extension/all/mangareaderto/MangaReader.kt rename to multisrc/overrides/mangareader/mangareaderto/src/MangaReader.kt index 6c0dff429..16842a8d5 100644 --- a/src/all/mangareaderto/src/eu/kanade/tachiyomi/extension/all/mangareaderto/MangaReader.kt +++ b/multisrc/overrides/mangareader/mangareaderto/src/MangaReader.kt @@ -1,15 +1,13 @@ package eu.kanade.tachiyomi.extension.all.mangareaderto -import android.app.Application import androidx.preference.PreferenceScreen +import eu.kanade.tachiyomi.multisrc.mangareader.MangaReader import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.ConfigurableSource import eu.kanade.tachiyomi.source.model.FilterList -import eu.kanade.tachiyomi.source.model.MangasPage import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SManga -import eu.kanade.tachiyomi.source.online.ParsedHttpSource +import eu.kanade.tachiyomi.util.asJsoup import kotlinx.serialization.json.Json import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonPrimitive @@ -21,62 +19,25 @@ import org.jsoup.nodes.Document import org.jsoup.nodes.Element import org.jsoup.nodes.TextNode import org.jsoup.select.Evaluator -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get +import rx.Observable open class MangaReader( override val lang: String, -) : ConfigurableSource, ParsedHttpSource() { +) : MangaReader() { override val name = "MangaReader" override val baseUrl = "https://mangareader.to" - override val supportsLatest = true - override val client = network.client.newBuilder() - .addInterceptor(MangaReaderImageInterceptor) + .addInterceptor(ImageInterceptor) .build() - private fun MangasPage.insertVolumeEntries(): MangasPage { - if (preferences.showVolume.not()) return this - val list = mangas.ifEmpty { return this } - val newList = ArrayList(list.size * 2) - for (manga in list) { - val volume = SManga.create().apply { - url = manga.url + VOLUME_URL_SUFFIX - title = VOLUME_TITLE_PREFIX + manga.title - thumbnail_url = manga.thumbnail_url - } - newList.add(manga) - newList.add(volume) - } - return MangasPage(newList, hasNextPage) - } - - override fun latestUpdatesParse(response: Response) = super.latestUpdatesParse(response).insertVolumeEntries() - override fun popularMangaParse(response: Response) = super.popularMangaParse(response).insertVolumeEntries() - override fun searchMangaParse(response: Response) = super.searchMangaParse(response).insertVolumeEntries() - override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/filter?sort=latest-updated&language=$lang&page=$page", headers) - override fun latestUpdatesSelector() = searchMangaSelector() - - override fun latestUpdatesNextPageSelector() = searchMangaNextPageSelector() - - override fun latestUpdatesFromElement(element: Element) = - searchMangaFromElement(element) - override fun popularMangaRequest(page: Int) = GET("$baseUrl/filter?sort=most-viewed&language=$lang&page=$page", headers) - override fun popularMangaSelector() = searchMangaSelector() - - override fun popularMangaNextPageSelector() = searchMangaNextPageSelector() - - override fun popularMangaFromElement(element: Element) = - searchMangaFromElement(element) - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { val urlBuilder = baseUrl.toHttpUrl().newBuilder() if (query.isNotBlank()) { @@ -106,7 +67,7 @@ open class MangaReader( } } } - return Request.Builder().url(urlBuilder.build()).headers(headers).build() + return GET(urlBuilder.build(), headers) } override fun searchMangaSelector() = ".manga_list-sbs .manga-poster" @@ -145,10 +106,9 @@ open class MangaReader( } override fun mangaDetailsParse(document: Document) = SManga.create().apply { - url = document.location().removePrefix(baseUrl) val root = document.selectFirst(Evaluator.Id("ani_detail"))!! val mangaTitle = root.selectFirst(Evaluator.Tag("h2"))!!.ownText() - title = if (url.endsWith(VOLUME_URL_SUFFIX)) VOLUME_TITLE_PREFIX + mangaTitle else mangaTitle + title = mangaTitle description = root.run { val description = selectFirst(Evaluator.Class("description"))!!.ownText() when (val altTitle = selectFirst(Evaluator.Class("manga-name-or"))!!.ownText()) { @@ -171,70 +131,45 @@ open class MangaReader( } } - override fun chapterListRequest(manga: SManga): Request { - val url = manga.url - val id = url.removeSuffix(VOLUME_URL_SUFFIX).substringAfterLast('-') - val type = if (url.endsWith(VOLUME_URL_SUFFIX)) "vol" else "chap" + override val chapterType get() = "chap" + override val volumeType get() = "vol" + + override fun chapterListRequest(mangaUrl: String, type: String): Request { + val id = mangaUrl.substringAfterLast('-') return GET("$baseUrl/ajax/manga/reading-list/$id?readingBy=$type", headers) } - override fun chapterListSelector() = "#$lang-chapters .item" - - override fun chapterListParse(response: Response): List { - val isVolume = response.request.url.queryParameter("readingBy") == "vol" + override fun parseChapterElements(response: Response, isVolume: Boolean): List { val container = response.parseHtmlProperty().run { val type = if (isVolume) "volumes" else "chapters" selectFirst(Evaluator.Id("$lang-$type")) ?: return emptyList() } - val abbrPrefix = if (isVolume) "Vol" else "Chap" - val fullPrefix = if (isVolume) "Volume" else "Chapter" - return container.children().map { chapterFromElement(it, abbrPrefix, fullPrefix) } + return container.children() } - override fun chapterFromElement(element: Element) = - throw UnsupportedOperationException("Not used.") - - private fun chapterFromElement(element: Element, abbrPrefix: String, fullPrefix: String) = - SChapter.create().apply { - val number = element.attr("data-number") - chapter_number = number.toFloatOrNull() ?: -1f - element.selectFirst(Evaluator.Tag("a"))!!.let { - url = it.attr("href") - name = run { - val name = it.attr("title") - val prefix = "$abbrPrefix $number: " - if (name.startsWith(prefix).not()) return@run name - val realName = name.removePrefix(prefix) - if (realName.contains(number)) realName else "$fullPrefix $number: $realName" - } - } + override fun fetchPageList(chapter: SChapter): Observable> = Observable.fromCallable { + val typeAndId = chapter.url.substringAfterLast('#', "").ifEmpty { + val document = client.newCall(pageListRequest(chapter)).execute().asJsoup() + val wrapper = document.selectFirst(Evaluator.Id("wrapper"))!! + wrapper.attr("data-reading-by") + '/' + wrapper.attr("data-reading-id") } + val ajaxUrl = "$baseUrl/ajax/image/list/$typeAndId?quality=${preferences.quality}" + client.newCall(GET(ajaxUrl, headers)).execute().let(::pageListParse) + } - override fun pageListParse(document: Document): List { - val ajaxUrl = document.selectFirst(Evaluator.Id("wrapper"))!!.run { - val readingBy = attr("data-reading-by") - val readingId = attr("data-reading-id") - "$baseUrl/ajax/image/list/$readingBy/$readingId?quality=${preferences.quality}" - } - - val pageDocument = client.newCall(GET(ajaxUrl, headers)).execute().parseHtmlProperty() + override fun pageListParse(response: Response): List { + val pageDocument = response.parseHtmlProperty() return pageDocument.getElementsByClass("iv-card").mapIndexed { index, img -> val url = img.attr("data-url") - val imageUrl = if (img.hasClass("shuffled")) "$url#${MangaReaderImageInterceptor.SCRAMBLED}" else url + val imageUrl = if (img.hasClass("shuffled")) "$url#${ImageInterceptor.SCRAMBLED}" else url Page(index, imageUrl = imageUrl) } } - override fun imageUrlParse(document: Document) = - throw UnsupportedOperationException("Not used") - - private val preferences by lazy { - Injekt.get().getSharedPreferences("source_$id", 0x0000)!! - } - override fun setupPreferenceScreen(screen: PreferenceScreen) { getPreferences(screen.context).forEach(screen::addPreference) + super.setupPreferenceScreen(screen) } override fun getFilterList() = diff --git a/src/all/mangareaderto/src/eu/kanade/tachiyomi/extension/all/mangareaderto/MangaReaderFactory.kt b/multisrc/overrides/mangareader/mangareaderto/src/MangaReaderFactory.kt similarity index 100% rename from src/all/mangareaderto/src/eu/kanade/tachiyomi/extension/all/mangareaderto/MangaReaderFactory.kt rename to multisrc/overrides/mangareader/mangareaderto/src/MangaReaderFactory.kt diff --git a/src/all/mangareaderto/src/eu/kanade/tachiyomi/extension/all/mangareaderto/MangaReaderPreferences.kt b/multisrc/overrides/mangareader/mangareaderto/src/Preferences.kt similarity index 62% rename from src/all/mangareaderto/src/eu/kanade/tachiyomi/extension/all/mangareaderto/MangaReaderPreferences.kt rename to multisrc/overrides/mangareader/mangareaderto/src/Preferences.kt index ea17a6fc9..3d58d1cb5 100644 --- a/src/all/mangareaderto/src/eu/kanade/tachiyomi/extension/all/mangareaderto/MangaReaderPreferences.kt +++ b/multisrc/overrides/mangareader/mangareaderto/src/Preferences.kt @@ -3,39 +3,24 @@ package eu.kanade.tachiyomi.extension.all.mangareaderto import android.content.Context import android.content.SharedPreferences import androidx.preference.ListPreference -import androidx.preference.SwitchPreferenceCompat fun getPreferences(context: Context) = arrayOf( ListPreference(context).apply { key = QUALITY_PREF title = "Image quality" - summary = "Selected: %s\n" + + summary = "%s\n" + "Changes will not be applied to chapters that are already loaded or read " + "until you clear the chapter cache." entries = arrayOf("Low", "Medium", "High") entryValues = arrayOf("low", QUALITY_MEDIUM, "high") setDefaultValue(QUALITY_MEDIUM) }, - - SwitchPreferenceCompat(context).apply { - key = SHOW_VOLUME_PREF - title = "Show manga in volumes in search result" - setDefaultValue(false) - }, ) val SharedPreferences.quality get() = getString(QUALITY_PREF, QUALITY_MEDIUM)!! -val SharedPreferences.showVolume - get() = - getBoolean(SHOW_VOLUME_PREF, false) - private const val QUALITY_PREF = "quality" private const val QUALITY_MEDIUM = "medium" -private const val SHOW_VOLUME_PREF = "show_volume" - -const val VOLUME_URL_SUFFIX = "#vol" -const val VOLUME_TITLE_PREFIX = "[VOL] " diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangareader/MangaReader.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangareader/MangaReader.kt new file mode 100644 index 000000000..1008ac308 --- /dev/null +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangareader/MangaReader.kt @@ -0,0 +1,125 @@ +package eu.kanade.tachiyomi.multisrc.mangareader + +import android.app.Application +import androidx.preference.PreferenceScreen +import androidx.preference.SwitchPreferenceCompat +import eu.kanade.tachiyomi.source.ConfigurableSource +import eu.kanade.tachiyomi.source.model.MangasPage +import eu.kanade.tachiyomi.source.model.SChapter +import eu.kanade.tachiyomi.source.model.SManga +import eu.kanade.tachiyomi.source.online.HttpSource +import eu.kanade.tachiyomi.util.asJsoup +import okhttp3.Request +import okhttp3.Response +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element +import org.jsoup.select.Evaluator +import rx.Observable +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get + +abstract class MangaReader : HttpSource(), ConfigurableSource { + + override val supportsLatest = true + + final override fun latestUpdatesParse(response: Response) = searchMangaParse(response) + + final override fun popularMangaParse(response: Response) = searchMangaParse(response) + + final override fun searchMangaParse(response: Response): MangasPage { + val document = response.asJsoup() + var entries = document.select(searchMangaSelector()).map(::searchMangaFromElement) + if (preferences.getBoolean(SHOW_VOLUME_PREF, false)) { + entries = entries.flatMapTo(ArrayList(entries.size * 2)) { manga -> + val volume = SManga.create().apply { + url = manga.url + VOLUME_URL_SUFFIX + title = VOLUME_TITLE_PREFIX + manga.title + thumbnail_url = manga.thumbnail_url + } + listOf(manga, volume) + } + } + val hasNextPage = document.selectFirst(searchMangaNextPageSelector()) != null + return MangasPage(entries, hasNextPage) + } + + final override fun getMangaUrl(manga: SManga) = baseUrl + manga.url.removeSuffix(VOLUME_URL_SUFFIX) + + abstract fun searchMangaSelector(): String + + abstract fun searchMangaNextPageSelector(): String + + abstract fun searchMangaFromElement(element: Element): SManga + + abstract fun mangaDetailsParse(document: Document): SManga + + final override fun mangaDetailsParse(response: Response): SManga { + val document = response.asJsoup() + val manga = mangaDetailsParse(document) + if (response.request.url.fragment == VOLUME_URL_FRAGMENT) { + manga.title = VOLUME_TITLE_PREFIX + manga.title + } + return manga + } + + abstract val chapterType: String + abstract val volumeType: String + + abstract fun chapterListRequest(mangaUrl: String, type: String): Request + + abstract fun parseChapterElements(response: Response, isVolume: Boolean): List + + override fun chapterListParse(response: Response) = throw UnsupportedOperationException() + + final override fun fetchChapterList(manga: SManga): Observable> = Observable.fromCallable { + val path = manga.url + val isVolume = path.endsWith(VOLUME_URL_SUFFIX) + val type = if (isVolume) volumeType else chapterType + val request = chapterListRequest(path.removeSuffix(VOLUME_URL_SUFFIX), type) + val response = client.newCall(request).execute() + + val abbrPrefix = if (isVolume) "Vol" else "Chap" + val fullPrefix = if (isVolume) "Volume" else "Chapter" + val linkSelector = Evaluator.Tag("a") + parseChapterElements(response, isVolume).map { element -> + SChapter.create().apply { + val number = element.attr("data-number") + chapter_number = number.toFloatOrNull() ?: -1f + + val link = element.selectFirst(linkSelector)!! + name = run { + val name = link.text() + val prefix = "$abbrPrefix $number: " + if (!name.startsWith(prefix)) return@run name + val realName = name.removePrefix(prefix) + if (realName.contains(number)) realName else "$fullPrefix $number: $realName" + } + setUrlWithoutDomain(link.attr("href") + '#' + type + '/' + element.attr("data-id")) + } + } + } + + final override fun getChapterUrl(chapter: SChapter) = baseUrl + chapter.url.substringBeforeLast('#') + + override fun imageUrlParse(response: Response) = throw UnsupportedOperationException() + + val preferences by lazy { + Injekt.get().getSharedPreferences("source_$id", 0x0000)!! + } + + override fun setupPreferenceScreen(screen: PreferenceScreen) { + SwitchPreferenceCompat(screen.context).apply { + key = SHOW_VOLUME_PREF + title = "Show volume entries in search result" + setDefaultValue(false) + }.let(screen::addPreference) + } + + companion object { + private const val SHOW_VOLUME_PREF = "show_volume" + + private const val VOLUME_URL_FRAGMENT = "vol" + private const val VOLUME_URL_SUFFIX = "#" + VOLUME_URL_FRAGMENT + private const val VOLUME_TITLE_PREFIX = "[VOL] " + } +} diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangareader/MangaReaderGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangareader/MangaReaderGenerator.kt new file mode 100644 index 000000000..2f200b9fd --- /dev/null +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangareader/MangaReaderGenerator.kt @@ -0,0 +1,33 @@ +package eu.kanade.tachiyomi.multisrc.mangareader + +import generator.ThemeSourceData.MultiLang +import generator.ThemeSourceGenerator + +class MangaReaderGenerator : ThemeSourceGenerator { + override val themeClass = "MangaReader" + override val themePkg = "mangareader" + override val baseVersionCode = 1 + override val sources = listOf( + MultiLang( + name = "MangaReader", + baseUrl = "https://mangareader.to", + langs = listOf("en", "fr", "ja", "ko", "zh"), + isNsfw = true, + pkgName = "mangareaderto", + overrideVersionCode = 3, + ), + MultiLang( + name = "MangaFire", + baseUrl = "https://mangafire.to", + langs = listOf("en", "es", "es-419", "fr", "ja", "pt", "pt-BR"), + isNsfw = true, + ), + ) + + companion object { + @JvmStatic + fun main(args: Array) { + MangaReaderGenerator().createAll() + } + } +} diff --git a/src/all/mangareaderto/AndroidManifest.xml b/src/all/mangareaderto/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/all/mangareaderto/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/src/all/mangareaderto/build.gradle b/src/all/mangareaderto/build.gradle deleted file mode 100644 index d4b4a5393..000000000 --- a/src/all/mangareaderto/build.gradle +++ /dev/null @@ -1,13 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply plugin: 'kotlinx-serialization' - -ext { - extName = 'MangaReader.to' - pkgNameSuffix = 'all.mangareaderto' - extClass = '.MangaReaderFactory' - extVersionCode = 3 - isNsfw = true -} - -apply from: "$rootDir/common.gradle"