q>Ii2QP#y3IK>iw^kSkZ$;1mRIhdaNeSt7+0A$20nDn!vJ*0+k%
z7CkJY%{uA
z1Jb}_wRhfpuB<3GyZFdY<8rb-$D6y*{z3JH_74XTso^RN!~ulU5bYpjA$&(c<;s~I
zyJggDIx#V=`Y8zsv@1}ZU>e>-yTGwxJCLO7#Y|dPvCxIc*7LdZl2qDskC
zPpzF+xk1yE8jY_{NXfV;At|Y00wmC{ZZ|;^?Rc=b0ROWjm9C0q%}`LCh02xKdBEp8
z;VUc6E-NkgzBKpXy22y7ww9dOi|;l^;Go(<@F4pjc=#cDak
zAxN;r2p9wo|KNA{@dF4gCI6d@;wd%~&@%`J5P}CkxJ*Y|2S4~XelQPz+Y6a}uq$>3
zRnJ5mFbE|4n*)pIv0GPc#C~$s!hyh9rIfwari59{dLSWa^lD29JWdQM0z}V7{1!8=
zy7cPHB3SW8GhAhQCE@FwBv%f5qp2JWzD&k2S>>xMzR$(~4d%1bY>R^$QWXc0o{{w5
zIZ=m|z9~F|?`$=GYmP!ew5;`5w*9oM4&?GL!^%HgrSb2u3T5Wu?Q(K(IcPYu>C1Mq
z9tULlmnlLI3=v=~11A~)5|tSTjyJF>0*Y<$IAH^MLpMy1S%1q&r8tq`O->MClkxN+cwuQ9>G|C58|Lq>+w6euU&8HT1*(
z6Fe`@IWP8!9c%5guf47WU2Ro-92y(|0Pxk-l=Pog`2PkL#?!37>VXLWjDWh*3&TL`
zBP;B7ZKErjfY(-vF$z0OW$I{pl`Bfr8u6tmoI-|)WYlAZLqf*-VQOR}Ch>_%(eW7>
z^0`8qJExsj4_Cn(-%mo;{U#;di@GO+hW!e6W!kzzOhalnypLM0mK4E(DHV|t^H7Cn
zZENnG6(A%QB9gIA6hX{bH+~|z&H{^zY@dgz!rH}p7XniNdW=8jX!M{z%XijWvfx0f
zcz66jNWA-5Nb(gH!tVb&n5G0%OJ}QlL0mfYDz1`#%&i|Jbtd&KrzRn-m_p|r))Z6?
zi-a5OaxlF>%}vb=FE7NB!q17cygjKS>GMRM{!4zK`F+$XLk}LP2jQHfq;dTM$;UM$X+{vXOXfTMLz=s)DKdNrlaVxc0SguxLdrqqP)
z|9McG1j?|(+KBV<_-GwunQ5GxF
zClwBRb-R2ihA)2ssw}H|n-4SN`;#c2L$b+u@RoLUTFg$Ti_x|I)50fi-qaf;f}UK5
zA-tD;|C`+<)xWvxzH%Wx{);b*PaV2f&}aC7b$Q3XtW$zfrHONs8d>NqVts;Ta-k|#FFL&JMyp($B;wCRA
zx@;Z)!9=uSMgJZYdP(>FqJSM+#-CfZcSQ@cqnE(MFCgKvTtc@_zjg00)9>c3U69c0
zVDLTMG!ddr^mH9-cD?^q8M)hf_XA?nfYMBz6%r1*(uy|lMDi#xj(t@MYS5Kp4k2_~
zC5=S06;AW@=R933rxm{!@OCG_K1bV1C54{c3tsAUd^Xy+#MJ&TL@{WJ2M8DRsUn84
zQ_(%NZ{FW~ntZjGzny)jmkVkdwdgVJhl}E>>Nt|*PyR(0Ak(KIsy+wu0jf(|{#3$^
z6$W}KzsEGct4}lqID0`VX(f?qts;G5*w&z;7g;3`g=-{74#26@z~$Pp#stIR;?mFN
z{S+}$pA;uMI#Vzkt1z>HVTcWv_b<6p1%FvThZh&=6pnMdJDOK%PL8tx4=z^^8I4?4
zU(ud-V!>`W^#v|aS@kN04xQ3@ioi2MmtioN@3}+*FX(-Lk)iRMhiY2b0r&{IyUz3RhWFa6wfYRh#;N<7-0&;
zHWm5`>a}r$P;;+vf+JYrqRE&i6YVgeYX&<6rcf%cv0$Bxs3Bbjriox{{Q6INK+rDA
z==*GHA4VDnP-c)0c!;@d1biObCW6Ji2o>NZ$B=vhY#=E6QclS6F%NM#P+W-XK4p3C
zA(He+8fm5f&N*sj+c4m(Y4mq?9_Z{pTrcej;g|z68<2cfC=DrRGO=x(XT8pW=+tL5
z?ITwv9bydWw0K~gzg4on^WHXWCdj-1GT%TDV=Vn7U|%o-kLVrL
zwH1pIE0p*%P6gAVdgR90!w$}uXVHg?imI?tttf-TZCSvEqGU~+@i5vb7`P|ne0m$^
zsnDXzh*DNlqR!=x9@U9WaeNSnmCtY}eugUw?Bv-0Ps3qAg1m|jEhZU|0EQOCF^HI<
z8Bs+-Lg?WocQ{EKgZtU|5jALPZ|%*`d1(q<{%2N?-3jQBm=O=rCT0aa<+e`DW?mV;
zb;>BR$Kyc7#=pGOYSad3;go{qScuoNSc8sas5P=Q
z)BX4Hn!JyHhG20|=qEdf6TM^mS{ceha-+Mgiy)-A`!u*=NS#42
zfF7
z`|SpqPjpxB*SZ=9Nsa9D6T=!#h6mlwR^i)$viJK$@j~y`$^2(lr~{HRcp%m@XC#P=
zr`=J%%40sK1I(}_GUV2t98dhqp-9Q!>`Y)TJU*_IPb)m;SPEl%g;y2*6*-ImI*IVj
z%E+#8+TY(Nf+k
znhbCpDg0`9@=r>o7MYI`e%?W@XJEw9g1u+bSFs;6hdOibL43UxMT@Up==`}HcCgb1
z8+C#YpzTmfjgh~D>6nwZdEZ;(8PFEl0CY|kwRdA@aRawwmswuH5wEb^~Z7li}M`Sa!UO;mTZ>
zyliKw1)sxagWH
zG!7sFG*0~o=y9~n>y;7?ARwh>)v^+}W|Yzv_9Nzt{&UD0*lt(EKR)j;0Ldg4v5j
z%|)k+D1)D5_kG&u7K(>4xTmOtW1h^yzzck9&WwXs8g0#&y|iFWj7<7cvYq+<-p^hG
z=Pu)6D;*H|^-K0ceE-1TYh`RGnt|h58f%~*xspXcf5mkv&nP{2V{ev>;56FQzVU(E
zMMr&&B~D#W7an;PE`KgOYTjd!K>G5*-JE@Unlr_vUi@`7Fp)afpSxsAN&XvxC5r6W
zEXQ}p44CUkXN46&G>z{GbO>@>bCe->r4Xv6@8~kC?OAlw%zQ_kFQrSSNl87VlHeCh
z=3o^zkYOZ0$<}^Lwb61Fp-s#f-}6750!Wh7Wx`}wM9Lq@+Q?ybq=Tv8er%TDOF+Zs
zW}U+g3jx`4g4JKuEEw{j;j#xr$3T|+V97xrfAG$h-jbO<4sWz#7mHWvIgUHHr2u_5
zDR^MVZ}&hu3*1&7N{-os_7l9pS(78z#4`8&Yt`rVB1*CFAt9>UE#f%t)fhh#3Nq
zBMg|7>p$QZhpfC|@)>3SbTh3%CFR7FYX!MeDY2;`f$a;9pOXvA!t&s%jBMW;UTxQ`
z>o%d3StX1}JXV`1X#(iI&0=^_4jq{B7T`*7(Z8E#2@59txJ6or1)aAEpMQ>QocEwx{v|?Z|aLWJeaI)-l0?^$Y%p
zsZz0|g7{crsS)*gT9b3NjetF>)pdWe{4w}J6*wh+_o{6&6oZQbYbs*30y!~U9hz7I
z*IAPOGYn*&>{oX&z!cTUputcI?i?Ii3OxI7@|U5v$M>p?(UI-?9hx3aG9i3u$A1L~
z{Eu*J@JTWYUXx90!?xw`u{{gJI0Djhn1Qmkc1EBK!t;i?_~uOyWnn1F--2;ykZ1mm
z;cz_wvb=7y;=leXZZh)PrtT_4vQY>8qHx2bz$uw$>_zNYqS+{mVF66HE5G0M+751z
zT;lVh>)QCpb;|eI-OVn&YL`3($t;AZAx)IX6m+ehZujHYy6!&7?Kk<82`luC$0q!B
z>g9>n-+~iAyuqcO#WE4al!jJ+{}Rens`3P+Tn6+d6l}rM5l=npo>ohm3eZ)s&
z{kPy}y&Muk!+;P|DyPdYkvepPve#_3r*rhFpZJ$rK_T6jf*vRb@zB!=;T~7RMvjKe
zZ80%|#l~HM+~?`=dU-qvENjAi55D0%
zzBe#Ek@OCwtPJu*CnkPAD{>%*9d6mXCV0Xi3?UMxsf`eZg^oH$xLKObmjYX)ue6I~
zCwI=`;IX(%NOBfvtk4fvrZ-$!_ec(z`KvH4FvPfbRvLb1OGM0{`tvZK&Qmh^q@7?r
zd|Pk3jD;bzl_oeDWlD9&e6)G_p_54MmoD?rc}3&D_nAwF;|q^RT-G`4+mt-w3fYki
zs0SylqO7UX&6llXlE_Kl$~89#;!V{w%b(w0#S&p`ss>-~%vUT8CGdN0k?5S(%|yAA
z+3w|(;)>+_K=Txr+DHqyh@P$K8n`41@;m!O(
zHHu<-L`LVl2&B2>C3sJ2j=U97Nvv;~MHY@D8h74~$QVZX^J_j8*&F
zYVZokJbtq$4NIQIXm8!HUkiv1HZc$zJ-h8Vqn>KmqHPG9l<(-hzgdEE>i>L@Kn&YF
zM9oh&26^Lm=7W2&OdY!(jf^w~n8lF8)FPRfZE|u`PC^JT$p>8sk#Cl-I-ob@qs-A*
z-{!u15nW2Tgn4AwS}S-j|9KNNBHZ!r)^?6mx4!&7S!7r{-`Ob{#B0#JOY)KfOhG(Z
z&!!*Q9O^NDNZsCLuTFl5R+rG(xRko;e|yUq(w&+H1V0-LCA6hPS-SoeH;KNoK4Nql
zyoflA!d$vk@mvd&-oUh?Z+sNQA9I-x6RO!bZ$omCGJ8^>_1;fa@VGlXdL?IK_SEY<
z(pZQ2`KCTRMZHa38lQ-hoezr<4DdK|u(S5(FGy?ZY$Q7;EwS%Wd|NXb(0j>^O>M$j-e+Ok@hQMmVdfIw;(wOEMKIWtE1?Ru|ET2w>K_)P|nXZOz!V_uiU_}P&g
zeHeF7$etkKDg_5@*!4NZVXUOXxE7Y0B~Lv+&H%WkyPFh7c=Cnb{aBWf_#wq#@57ap
zwq1_-xZ1kWrnJ0=&4JP@B<`k<+H-l
zn$9aYH^SWC!gZA|`~FTI9R`vf4IGRYeZq5JoO4T4aBWQ*7CJsSm%8h5Dys94vLps2
z^y+r<+Hk*YGC%zC{`zC*3;WXXaS|!~n)L?!Px2Se$M)jABF#_b8|N<38Kju(i>m!&
zT{0rU_`(y7OxR<|m+1IdKx`_EcF}uPv$j|ntpn-yVA9*LR7@zky@DBUhGIesCv#-V
zJL@e8mgEXM)oSBOY*V;SYk113?^K|1FESuqM9<9MN(OuQgkRZ?{udV2h&MrA9ie}`!Loiom#
z8WE~{;|i)rq&O!j7eB~AjxQ$3Q$oV!L(aIkqW?%;+6TfpCROXZoKF(
z8)~;$h^9Ky^{2CeFubOV%HqG)f6?nczQngSqb>d3>pcuJ40NPjb1X{h^6XfQUM?iU
z9%~VrE?kXnq?&SXZ=TILR8w51qgOAl)6LT#*I5)${ABzwHG;c2zVu){bV$6A@C?*x7ZHFR?P0zmGM(?-boK_uxdR@FFHb*->m!T7B2F;8zbin
zFqiHk7$4e5fNb3JuzzwZLiQNXY&RbEIFs0B{1eI160CQVXX|6XlB~#Fq=z-Vp?gCs
z!?RZ;3kJRjBqNdx*j&kH3W_j(yXEtAowF$;@~9^j9}H`jA7)>j%j8^K)0Di=9HqEA
z63z;vc}qjs>qQXCwY|OQM*u_K^c&{P5D7q!L0bBBk-
zn`UHAl03`jwNFTf%m3GbSsXq7_TlEMGnqY{7T0z^A4;omblq|MClPSg&{o%oa{6_f
z%^xNhYcsGB;efU=RPd|*5Ib-WKo-8jJo@8`Ei}1r)25hIQ4W!*peoFSr{q>Q(7kS-
zu(=5QXmrpD9jjQ!VfVqp>{(8Hf_cn^Wx?AsNc6l<8DVnf%@l@HjJ;oSp%(|srpt9})`jRr{Exo{dY3#Pg~I0p`8};EFaOJYAM&V4CHveb
z5AS)G4650#+|raB`YM5^4G}+avv)!ER9Mo%4>FN`F0B@t|CU)+>rAS*
zkzLs<#4P%jQpo3ds`Wm(_p{8p;W84{=zMfP{}p|7xUY=t)fy@7dHWEk_+j+yq|re4
zF%9w6p7?tL-tiOMctX^8Sw*$8j~8cs`j|F$OY$|tvfmA&tB6HaXvL7>N3j$c@$Y)K
zJk#5n!^yRqP6)lC7?cj9
z8n0^w_Ws_MLF03+X~Y?v`Am<-_;Et5skJ*E8uy%ND~8YqmFiJylWlg}%e53u)j{FB
zs8NkF+mVn-?v12Y+a-*^^a7tg24aiEz;NnrcXEnT2xrkl>DE)G1PkExA$R9#tJH`A
z{PXoLqago?1d4aa!|7w~*8JjPm#pG9R2ZTMVoR*`QmCY@GT5O%XZ-if-a>yu3{uPF)`putS
zf|O!s0-6A_8T`KPVvLld|Uoe-__(Dlj3<@`NY)>!ETNv3x
z0OB=E^R9*yM72xACB00)bF}r@cvHL#b7!BG@2sjn8P|l+xQM^J!ee96O(hpNJgsm<
z9jU5Lhy>F@W>=1s@TyykqdQHwJi`oC;-b-IM*52wj_v0PI}V>#N{Nn<|C^*yzj(S=rrBX7a^e4Kh)@H>SHrR(c6>LqKs6&?Srxlf
zIwaxWQy2@=rhtDMo%O%ub#uo;6(tpM8Tqy_%L|}qoYTYXr0MZ8YBZfgUWwZk>CQvX
zF)>L<4x;cLGcw|jRyHUyrbpFnG$+w{3$5!-o~W!cn8?D`Ln>=}6AY1l9iQIhVjVRD9+&;14TFdYKD#+H)~@npr>Sa1w7;cdUQ4+6S`&o7DHEjzRxEw|!k6@4Tf7L$
z{eF3m8_=ZasgvI-*JJete&D}I`uhpmEGFy53>Wg-E)T&|(tBwkM*KFuLYg;croalUuV+lx6KZOlV&`W&!F;2Hr`KJ%xaj4;
z98P3;DRxJ4_$%6k?BZZXt+PQ>k3G1beXCVF|!iBCaCCfo%LQS
zP;=A$klNo~>EafnKgfTKIpVxHxWqs)@VGJLi&39&kLW9Oty>9{OvGn}A3a#{p0LgB
zBH26;CIN$qm*OfBFp22<7HC*FeA9(#6L!+uY=)2OA;DArYTQW1TQseytVJb4PdITS
zraFNLp1OS;{2w}NomkBUk$B|JjMX}2i+a^Aqlv!zK+
z{nrUjs<&0;>tKk$bh92_ZhAiCK%S2A?Ey=@^O1_byQK7kzx
zz;4|0;8Y7}?wO_zCYHxCW`lNrGTIyv48*FH$-2Dv!*dg5(DoS2!j#o0FEaWhx`W*c
zJ;DhpG1Hcbx}zHx+b}zy5`??bo1IOWvi0G1F`2j5
zbNzGmG#%nh!>6~u?a3o=9{H@xP2%YC-$st(*by4s2B!eQi-VSg#rSOIYayiX4;IW}
z+&k(Yo=G1`$7FenlaX0<
z3DyT_))uym86fElCz&OOV+{?aIEi${Gu-(W&$u;NKTL
zu1sj&^G1-k!sCUJX>?AFoy^)-4}LJua6_IkBGG2lzduyegC&$w#xi_k}kl
z<0hm>KE9y>O}bm7Ig;Umcni|-%469EQqh1$P!Y+mC_{J`R7>Ol@u5Tt6(^}6Ry<#c
z1c!TNbF#y3Z3AB0j*aPqlC)B`$``Ik*_!)Jhd2QWVRRIKeig7Ct8^u
zf;Z|GQqYmABUErJFr+T#%%Bg8Q{yiHU>yCA3&1zU85K)|5<;`e^{;MCnz5)1zX}E@
zp(il`8BF$^T|J>Izl{Hj!`UCY7<>+L1laAlLkg!+W99EXN`T(()&v)xJJ&z&0HIh#&aJx`jaJ|^fRC!!!{dU~FXhX*@6x>B*6uL8@JEx9-i9;r*3Q@xRcbQghAw@5nQLkjo)tI$inTnR1{|Xixi8876fq!<$?W7pF->DFn+=-oNqNJJvCINS9~O!0O4wd%dy&z-6@#i?tl^r$cLVQ
zzxJiGx7_8o=%73W)%
zVj<6fhCTtLOpna-CgI#~^51g54K@FIQXxkO=
z7zx1B-(?Q|()2hi{`{y^hz-}+6{V;jnUq|A^X+-LFNQe40MrO8s8lSZ)Q~bYP0&k&
zR3lE1ClkL9L?O_V6n+kv=gH&?UU%2exs3S)7h%MFmL=Nvb0iBdUykk`zOT$HR#leR
zuKY}jYSQ)Lw=o`aZTAViMQmAQPfWuYDxg6q7FobQ5$K|XM+!XB!&rYt=>1lqJ0s`&
zEzBiQ
zy$0|r1+iN~L;~2*<6I9xVn^33#am1-xDu{$qWo<=9FuinRu=Vm#^buI=y!WjU%t<{
z`9`0cj6*|t<99ltDjhvLKq0h9|$~`@xdtP3L+=;
zC9Be$DdN<BSHOZYC+CVV=V+qvN`2r@s;b^U$A2J2js%eEW>vSF&2{t;D1_8>8hhV%s59CQdAR{*Vi6RH4LkHZr%Y8kSo}f8%-ecXwe}ckj1CdL=`A2H1_M
zhERKduQBkM65cWjLV0e5Tg?uB!;2rl-XlVf-!+p=Y
zYuFcG;J5RE;nQ{Uj;BuesO&hY<=sA8CAZ-tv(C)y_&+-dBth7+f>
mrqPoOY7}k#P&p5OM3eH!kl3`8tbO9b0qV-yN_7ghQU3=s^c)`m
literal 0
HcmV?d00001
diff --git a/src/all/anizone/src/eu/kanade/tachiyomi/animeextension/all/anizone/AniZone.kt b/src/all/anizone/src/eu/kanade/tachiyomi/animeextension/all/anizone/AniZone.kt
new file mode 100644
index 00000000..863b450b
--- /dev/null
+++ b/src/all/anizone/src/eu/kanade/tachiyomi/animeextension/all/anizone/AniZone.kt
@@ -0,0 +1,513 @@
+package eu.kanade.tachiyomi.animeextension.all.anizone
+
+import android.app.Application
+import android.content.SharedPreferences
+import androidx.preference.ListPreference
+import androidx.preference.PreferenceScreen
+import androidx.preference.SwitchPreferenceCompat
+import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
+import eu.kanade.tachiyomi.animesource.model.AnimeFilter
+import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
+import eu.kanade.tachiyomi.animesource.model.AnimesPage
+import eu.kanade.tachiyomi.animesource.model.SAnime
+import eu.kanade.tachiyomi.animesource.model.SEpisode
+import eu.kanade.tachiyomi.animesource.model.Track
+import eu.kanade.tachiyomi.animesource.model.Video
+import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource
+import eu.kanade.tachiyomi.lib.playlistutils.PlaylistUtils
+import eu.kanade.tachiyomi.network.GET
+import eu.kanade.tachiyomi.network.POST
+import eu.kanade.tachiyomi.util.asJsoup
+import eu.kanade.tachiyomi.util.parseAs
+import kotlinx.serialization.encodeToString
+import kotlinx.serialization.json.Json
+import kotlinx.serialization.json.JsonArray
+import kotlinx.serialization.json.JsonObject
+import kotlinx.serialization.json.add
+import kotlinx.serialization.json.addJsonObject
+import kotlinx.serialization.json.buildJsonArray
+import kotlinx.serialization.json.buildJsonObject
+import kotlinx.serialization.json.put
+import kotlinx.serialization.json.putJsonArray
+import okhttp3.MediaType.Companion.toMediaType
+import okhttp3.Request
+import okhttp3.RequestBody
+import okhttp3.RequestBody.Companion.toRequestBody
+import okhttp3.Response
+import org.jsoup.Jsoup.parseBodyFragment
+import org.jsoup.nodes.Document
+import org.jsoup.nodes.Element
+import uy.kohesive.injekt.Injekt
+import uy.kohesive.injekt.api.get
+import uy.kohesive.injekt.injectLazy
+import java.text.ParseException
+import java.text.SimpleDateFormat
+import java.util.Locale
+
+class AniZone : AnimeHttpSource(), ConfigurableAnimeSource {
+
+ override val name = "AniZone"
+
+ override val baseUrl = "https://anizone.to"
+
+ override val lang = "all"
+
+ override val supportsLatest = true
+
+ private val json: Json by injectLazy()
+
+ private val preferences: SharedPreferences by lazy {
+ Injekt.get().getSharedPreferences("source_$id", 0x0000)
+ }
+
+ private var token: String = ""
+
+ private val snapShots: MutableMap = mutableMapOf(
+ ANIME_SNAPSHOT_KEY to "",
+ EPISODE_SNAPSHOT_KEY to "",
+ VIDEO_SNAPSHOT_KEY to "",
+ )
+
+ private var loadCount: Int = 0
+
+ // ============================== Popular ===============================
+
+ override fun popularAnimeRequest(page: Int): Request {
+ return if (page == 1) {
+ loadCount = 0
+ snapShots[ANIME_SNAPSHOT_KEY] = ""
+
+ val updates = buildJsonObject {
+ put("sort", "title-asc")
+ }
+ val calls = buildJsonArray { }
+
+ createLivewireReq(ANIME_SNAPSHOT_KEY, updates, calls)
+ } else {
+ val updates = buildJsonObject { }
+ val calls = buildJsonArray {
+ addJsonObject {
+ put("path", "")
+ put("method", "loadMore")
+ putJsonArray("params") { }
+ }
+ }
+
+ createLivewireReq(ANIME_SNAPSHOT_KEY, updates, calls)
+ }
+ }
+
+ override fun popularAnimeParse(response: Response): AnimesPage {
+ val html = response.parseAs().getHtml(ANIME_SNAPSHOT_KEY)
+
+ val animeList = html.select("div.grid > div").drop(loadCount)
+ .map(::animeFromElement)
+ val hasNextPage = html.selectFirst("div[x-intersect~=loadMore]") != null
+
+ loadCount += animeList.size
+
+ return AnimesPage(animeList, hasNextPage)
+ }
+
+ private fun animeFromElement(element: Element): SAnime {
+ return SAnime.create().apply {
+ thumbnail_url = element.selectFirst("img")!!.attr("src")
+ with(element.selectFirst("a.inline")!!) {
+ setUrlWithoutDomain(attr("href"))
+ title = text()
+ }
+ }
+ }
+
+ // =============================== Latest ===============================
+
+ override fun latestUpdatesRequest(page: Int): Request {
+ return if (page == 1) {
+ loadCount = 0
+ snapShots[ANIME_SNAPSHOT_KEY] = ""
+
+ val updates = buildJsonObject {
+ put("sort", "release-desc")
+ }
+ val calls = buildJsonArray { }
+
+ createLivewireReq(ANIME_SNAPSHOT_KEY, updates, calls)
+ } else {
+ popularAnimeRequest(page)
+ }
+ }
+
+ override fun latestUpdatesParse(response: Response): AnimesPage {
+ return popularAnimeParse(response)
+ }
+
+ // =============================== Search ===============================
+
+ override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
+ val sortFilter = filters.filterIsInstance().first()
+
+ return if (page == 1) {
+ loadCount = 0
+ snapShots[ANIME_SNAPSHOT_KEY] = ""
+
+ val updates = buildJsonObject {
+ if (query.isNotEmpty()) {
+ put("search", query)
+ }
+ put("sort", sortFilter.toUriPart())
+ }
+ val calls = buildJsonArray { }
+
+ createLivewireReq(ANIME_SNAPSHOT_KEY, updates, calls)
+ } else {
+ popularAnimeRequest(page)
+ }
+ }
+
+ override fun searchAnimeParse(response: Response): AnimesPage {
+ return popularAnimeParse(response)
+ }
+
+ // ============================== Filters ===============================
+
+ override fun getFilterList(): AnimeFilterList {
+ return AnimeFilterList(SortFilter())
+ }
+
+ private class SortFilter : UriPartFilter(
+ "Sort",
+ arrayOf(
+ Pair("A-Z", "title-asc"),
+ Pair("Z-A", "title-desc"),
+ Pair("Earliest Release", "release-asc"),
+ Pair("Latest Release", "release-desc"),
+ Pair("First Added", "added-asc"),
+ Pair("Last Added", "added-desc"),
+ ),
+ )
+
+ private open class UriPartFilter(displayName: String, val vals: Array>) :
+ AnimeFilter.Select(displayName, vals.map { it.first }.toTypedArray()) {
+ fun toUriPart() = vals[state].second
+ }
+
+ // =========================== Anime Details ============================
+
+ override fun animeDetailsParse(response: Response): SAnime {
+ val document = response.asJsoup()
+
+ val infoDiv = document.select("div.flex.items-start > div")[1]
+
+ return SAnime.create().apply {
+ thumbnail_url = document.selectFirst("div.flex.items-start img")!!.attr("abs:img")
+
+ with(infoDiv) {
+ title = selectFirst("h1")!!.text()
+ status = select("span.flex")[1].parseStatus()
+ description = selectFirst("div:has(>h3:contains(Synopsis)) > div")?.html()
+ ?.replace("
", "\n")
+ ?.replace(MULTILINE_REGEX, "\n\n")
+ genre = select("div > a").joinToString { it.text() }
+ }
+ }
+ }
+
+ private fun Element.parseStatus(): Int = when (this.text().lowercase()) {
+ "completed" -> SAnime.COMPLETED
+ "ongoing" -> SAnime.ONGOING
+ else -> SAnime.UNKNOWN
+ }
+
+ // ============================== Episodes ==============================
+
+ private fun getPredefinedSnapshots(slug: String): String {
+ return when (slug) {
+ "/anime/uyyyn4kf" -> """{"data":{"anime":[null,{"class":"anime","key":68,"s":"mdl"}],"title":null,"search":"","listSize":1104,"sort":"release-asc","sortOptions":[{"release-asc":"First Aired","release-desc":"Last Aired"},{"s":"arr"}],"view":"list","paginators":[{"page":1},{"s":"arr"}]},"memo":{"id":"GD1OiEMOJq6UQDQt1OBt","name":"pages.anime-detail","path":"anime\/uyyyn4kf","method":"GET","children":[],"scripts":[],"assets":[],"errors":[],"locale":"en"},"checksum":"5800932dd82e4862f34f6fd72d8098243b32643e8accb8da6a6a39cd0ee86acd"}"""
+ else -> ""
+ }
+ }
+
+ override fun episodeListRequest(anime: SAnime): Request {
+ snapShots[EPISODE_SNAPSHOT_KEY] = getPredefinedSnapshots(anime.url)
+
+ val updates = buildJsonObject {
+ put("sort", "release-desc")
+ }
+ val calls = buildJsonArray { }
+
+ return createLivewireReq(EPISODE_SNAPSHOT_KEY, updates, calls, anime.url)
+ }
+
+ override fun episodeListParse(response: Response): List {
+ val document = response.parseAs().getHtml(EPISODE_SNAPSHOT_KEY)
+ val episodeList = document.select(episodeSelector)
+ .map(::episodeFromElement)
+ .toMutableList()
+ loadCount = episodeList.size
+
+ var hasMore = document.selectFirst("div[x-intersect~=loadMore]") != null
+
+ while (hasMore) {
+ val updates = buildJsonObject { }
+ val calls = buildJsonArray {
+ addJsonObject {
+ put("path", "")
+ put("method", "loadMore")
+ putJsonArray("params") { }
+ }
+ }
+
+ val resp = client.newCall(
+ createLivewireReq(EPISODE_SNAPSHOT_KEY, updates, calls),
+ ).execute().parseAs().getHtml(EPISODE_SNAPSHOT_KEY)
+
+ val episodes = resp.select(episodeSelector)
+ .drop(loadCount)
+ .map(::episodeFromElement)
+
+ episodeList.addAll(episodes)
+ loadCount += episodes.size
+
+ hasMore = resp.selectFirst("div[x-intersect~=loadMore]") != null
+ }
+
+ return episodeList
+ }
+
+ private val episodeSelector = "ul > li"
+
+ private fun episodeFromElement(element: Element): SEpisode {
+ val url = element.selectFirst("a[href]")!!.attr("abs:href")
+
+ return SEpisode.create().apply {
+ setUrlWithoutDomain(url)
+ name = element.selectFirst("h3")!!.text()
+ date_upload = element.select("div.flex-row > span").getOrNull(1)
+ ?.text()
+ ?.let { parseDate(it) }
+ ?: 0L
+ }
+ }
+
+ // ============================ Video Links =============================
+
+ override fun videoListRequest(episode: SEpisode): Request {
+ return GET(baseUrl + episode.url, headers)
+ }
+
+ private val playlistUtils: PlaylistUtils by lazy { PlaylistUtils(client, headers) }
+
+ override fun videoListParse(response: Response): List