3g$O$@gDD+x+oSva7;gm?{G@7)AP#?}2YwkV>pdXq?g!Dz)FED`FW-CNFV7ZyQ6- zER`!JddD%(`RqWWYbzp%GIp%;)h_w%lSYuU<_h8ex&u7`WOY=mf_Nw_%up@nw3w=+ zW6RVz(YfoH=+yY76p-BTgXN$P^Q6NTQ!a+9KLU=dCm_{V0}=Ku44N%TO>O?1!8bXb zRTaFHd;5ji@P{W$vACz$%Gu_i$b{NQYD_5{Rxf4P}fP7}?2~N8Tv_ZRr3INhAVDc9fPLC}ces za=6S1oer^=v1bjspw? LCm zaWi8--c$4QH%Yl~>aDs`*s{r;b@=||__lT-!!q kU7RNC#T@M$nh4gQc|k6UDd1mk^y=8!AHBJ23hj&}4eN%Vbjt z7=EDvT%D{UG0mHxQHz!#<^9?9Ezj-g(Y11nH?C>d5V2GnCBaansfJ6%qe^^wdP1M@ z)`9EC{~=N-?m0@}u}ktN5)dWX)gaFgI$vjgYsmU|_J(oIGF3=~iPr5G``soG%-vF> zKdSVt@b<#nMxz0JDuHtjy5W~k?-SAfC80k?Jgl9VX_Cd@QcVAK2mM5xo#?MXOuFl* z_c!HKSHHNq=}&T6|NOk8dkuG&SlF49-5zxzxJgS(Cu;%Iwzw;^yPyJv`MlWzhii33 zx(4H0W=;XQtIM`R1Wd(+>~0IXXw>UqD#`-k LZpMF-_! G=l9Uacx&TJYxb6%>&|DQFdx#Q@q1gW03pv_ zNpJ5Qnl-Te9Ge!g#0S9qG2!+E`^l-! LT3(KlkQ+fyaVtwC=Ae(?bi1j6 z9-YB5g2CpMvU(tk+lC=I9XJ)NG*Mzt^r1zMe}0ryNaH7_7v eHOCJ# z>AD0vz1hf&oK?9%QCKP{&Y2=UONSL;D}Pxx)SovM9vco+h*?6kM5A8Z&_KtjF6*DtQZ7n`Tm=aJz4_m8Xw!lQIepWOt`vO2+*+D4UU-jd)&_)V zp?zXxLY7bTY3Iuq0XG5MnKmz)@l7gmeSLGwH9lSawnp8d`8zXCUdvre*Hz9bFaNGh z?5`$?gFWFG$%hBA9vblsBg+#dodhoHRLDY{NlTNaE}Lq>)p!W`L2XLx19UK}e>Wym zcqIqB)G-c};{5!&o~JP@S=$XdW3W^{5AuwHm0ulX+o(PV1h=>Ia0=tE(+1-}k=sLc znZf?wWH8bk_p{CK%<#zAICScFE34GK14xfKxwxHHSiL#g(5V DrZglxW-76J@FrMtB5FB3|x8BkHAExw1-@<>Ezc} z*DQcrI&plFqp_Id)SUN2fY3Sl544*j55w7LeALZOiq31+t(z5V^OA!S fU_%3vkncI@ufh((3BE0^V$XDZ916^RVlF9azGFR?K?V z3;v0TKjF#nj*I_4I!QKmkwo4bqH6l-s$!JZt1m)8xT!N-I_$^ xKT09=boMq%xkOF40O`>ArK696scAl=90lBqv4 w*B8@|^>@Y){ZAcZ zMrK-CpYhBPX;;_Yx%U1QlGYkKZBGzRmuS1lU1u!^i*Y?FRT4@s5*qoa=I7t%Ro 9Fu(hnxAP*@w|?GcsFTaP0&i zU(|u@CvMe(q(6D3pH`gn>_-(;hbXTK_9wrDc|8^I4AikNCXO(kx_`laB66=KX&c7K z;^}D}h{bL7X73$d!vKYO6~RKEChjlq%X~3B$}~|-rt3D{Br-H;)mc}}OFiF_QQXa` zRMu#tM_FfQn*J+b$EkH>#`Cdtt&Xb+J}9r5>;ae~_`RFb($o9u8HQYVmC@15sFk*y zCLhl ;^6lb#Q+}kkxoj zv{-&XC>yJOf#P@l!lZc;I{*2PM~PwD>P-IMAJ@bz0JRq4Z=R91$9UJ_gW-Fx2j&`c z$*#`jdMApzl$ZlMYU|~l*&g(n1^IBJ&bXXE4!BOEm!q2WDl+zdJFT+_ *OU0Wh 43cBX5<*h(^18UamF@p(qyC3F7DUM{ZDd($vB((NMaeMI&nbwzwN;ozphLH| zdlaozsRM;;IpHo<9r`?D0(EI}I=o#p81M`(%MTpspi}~oeGH`d7)bg}wl8|zektP3 zAkm$6wVGGeyc(5ztHeK9SWMeY;S9CE_bvvX3gA$%ip<49hmz!-9_UKY<<@v?$n> zD8lzMa|z&at! D5n zH!2AwBwCwZzgl$H#^B)(s$%nKiRVZg#FtgbST5694jPRw;$OdA@I;n^PGl7-) bY6@GMIc*po z04mgr Nyxr@2ZR6oL z*{dxbW&okDub pm z)j0p1cz&Q@)t0#UE6ZttokV|M;pV6321Z^%-^yO=_>eqOlQslLBddw5H2;Gs@fHo) znW-ixupGqk)-%Ib(4{-LiRUBtG;OHTa3-ZJ(8(HNu{#w2b$dMn<7g>=Xm{Cq*Jy1W z@OQo4d_wDIL`WxD%MEK(dgo@fQSSn?&e=vwpaIFeMy?iX!vHL@CnY(La`lJhSVX@j zR*EE2eU?YjJ6rpOR?m~=w =@y0#_`*8N} zU?TQey!MM~6cwCh-BwEc4$^Bap2zbmw$}Fkd61P{fA6#4XyK85+C^9%Ij-v2m7=oB zRLASf6$S?R*3+(q<$5N+_nC%Qbv~lcS+)!)+PLWZ`(y-PYk#Ah+-vXfhz;}XC_0#f zYA4}(2Xpa6^v+Pw*4E>A<(}fM$Vnek4?2x1kcbUA3sNc= sq_m&M&JBtUQt?gV$&0158y?iOHyAi;wM4estva1ZVdcmMl-Uv5{; z%+_pm_ip#}>GR1%epZr3M |2#3yx+ rX-nD#sDLWNhv*S$bV`KGRl<9ZcpJDo7nN=lzmf@*j!@z(G zBO;7@GI#tvC(`pP{Vkm_nlD*J&!=x++aJbv*PJJWvN&xgGTW>s)JrI{>}rST|NkeU zKZnqwS)o1kBj2v}#onxq5 wY0{4eJLMQ+5OnWuz=gz8%hUzdL$ z+JF1@4g27%1G9GM_8Fg?n6x#{#i9Zwq)ek+li79thw4u`=2cI#>jk6u*r=G+(YB{E z|JUH-EFQ { zVYoWE{B=Egyvc2r=BYCBzu#DE>+thD-#|t#lo_I_Ac&Wder?K0r5lCO2~L)Xw#3C+ z8{(`^Q`p@XueX_7d;2!}{m_b p5TYuUV(v)E2f@@uhNA_Ql$31b2zmK* zs+TO1ndJ+&*6NjuhhuOmOH2ISBx&_MN>le^sse!)I63_pz)!xL&kx%vOw*<}h%QK> z_R}>5Q>~r@DH-IYH!{%I8P#J1BX1c`GcU@fAJXQV1jF2}WE}rq%-#J}1W$b8*}_ zxOd#Hhhygc{o}k} {@dZL#oU4SHDKfJNJmE*367+qqM}z@K|!LJ_dwZ -EXvyJ>_keT{2T?SS$Cz1Ot8$Vc+)J51O(o&u<3& z3WOqlK06(L54%GXcW1$p!lX>gSf(WYFHeo@9^8X%-gl?2*G`$0-0wu;_(wl-bIA2> z50D &!Ka1FpUK9}F1!7QQi&dhQZnl@0>L1G zhV;)yaqcp6aP2(MsW;C*>6ki{FL{Uqs7Ap7h0YaT7lpwm(2t}E<|;Wb=bX`7PdibM zIhW1q>c|1L^?1s|iWuJ$yxmXF%V@u?w<7NqH0pRXuKd#5dhs^Tr } z82b)RsNx9)+9$Nv6$t11R?1{NVJ3ELWZKu$ry7e(b6ygR?grtunl@X4 ^*S5I*Yepy|&7xGd(>O<`kT z3zD7acgy;XNnM7y8YEf@(~0h6mz $Ju^vkp z7$@O-2@miO @}g`0mma& z@J_E`Bg>>G3JeHxl}8aLC9=45xPvzrlzpX~I*vmuWq=e(;2E-icg6fjMt6Ha&o{#} z$k(B?uzeF+aqVJm&`kZD%Bpd2RZ*VN9ck69bhcW!{qFG__Yhgjc-so!Of55`95yd4 zFpL6x1S@HXX>5AJKknTeiFsBP{oF)Bu9DR+I?|`6Q=$%Sm#M;?uG-Ld;xHZ3iI0qu z8qMIOO}u_Y+se#=4K6G0R?D%FyW=HZ(JIW*?BL+5y{>d&%-`Cf1?5^ay=&wqB=g%L z+nN#{o}0%*$udn2FibAK7zZQNSo#d2)ju~xg%wd%f`*YxWxYS$r <>WxDH${&Ks#P-a5L^rD-@W#1Y%cis4Pj=B1m zJ~H=pbA6z&oqDM?<=Xay)=cwr+l`A*IOe~un$7UV`9gNLhA**gtADz^tErxgUIIP~ z2uPeRhY~6)>qkV05 eP9f&+0C-`#OsypxYSaU6sQtXo)7KnX!$g6XqczLW`?0yoD~>dQt2F z_XLe^S@034NTM9Er#dce CD%1B>;?Hfe0#H=eQK%NU#V$vC79SipA})8wozmk6(g9%eH3fGg@}y*7$W3 zT018nNU-L;ysG{8zuMU@!5YGKh~6jB$(&zwS>)Tc9x2+r%nFkw0gQ@xj41%tr*%vb zPaN!l9xS =`x=_ k3jHDH2Wgpj$zqufGedSWc|Bwe&JnG;@`|(DQvyHTUO#T z9T7yv2ZUhQkdvK{(wP~X9DIXz@Vvcx|Aps$*o}MTE64AovffTkis9&e>W{)| _4EXr4`V_k4b5Go%Jk) zN=Ei6(ns1q800Tv&eR?N3IKJ^pvdL210I%i-=%yo^94m*&*BcRb4t5^y+h5ruL!9) zwsiTJcXaNnKt&|Zkz|e(Ni`&z^h*08gTQzhdih|TR7%_L5 KHa~pY z80)$ozK ? z*_@TdcPx`|Js-Z`{WJ!-&C9eq0ESTHTwdYh$KDVs@egk2#y$cLlu|bo0h_*$#tsXW z_xT0_*S5=eLPxVwh%Hh)NmVJfmFmBOO^I?~gYutSi#8UkVSFqVq>m$UP07Tefo)@{ zRfl1qc^CbPB!YV}g3l-WnWY^TzeuiZ>rK0gZSVRxkgxDBJ*QQ7e?J1rVLQa_xYI3P zM?l}8COOpb&+*^LuWW1%E^hsMzw}Hx9BC>Jp8JZ5zH_@WhuxmVPw=~no%P2~C3X%O z6H0Q#ca>|0b}*?b87(Y$5v=GogmnChl|k?6^0>RIz>)`K06}-DpOkfc+~Au~u#QCS z4;32L2QRz|MFO)wSuD3t_ZtBdFf}RzXOmQ*`$y`*bqZ-XILD8SGBQ|vt1y0kTUWtr zYv+GED7;tyRy#kr@$&JTCI`dWEmU-M!=s*a>BPJmwEWQP!JJacQ(8}E)v5?WZ` vf1Uf$cIqmN4)eWwk z9|Z6GdMG(U*jef?h5brvr_(89v1hcqYND?%lI&{@J9|V_Os{PIyRLz_uH1ml-OWG) zb3W2iBa*>SjRtMRbwg!n|7HkQBXsMzKZshjgges8qd9dROqPM;JiMr>xZpDm)BE0J zD&b@RywXKSJ50Smubb*AbL`uh19DNC$H)!i#H@(aY`DE?RW--mON*M?(Yw 3#LO!bcCFCypPHL zQ^}{n*)}uuiVhA}mJmg`?w`FN;urg!BF{wV@?}h#^1souH}<{ZF }ZUUanJ zz$f41L2L<}&?q`ACp28Wv=uq^611>nfH8B*NEtKhUpr 1*ibXBFa)%u2`K1GEn20*seK2Za=`#nZF6>4woT$!w?I$<%<8Mo_ zIajgRr1)W|g$@-lq{05mW?Gsm#eNfiSQJ;ohN8wb! M|pNTsvjjGS2S!8+ty8IY+DEt&42-_Dm5#S z8Zl0=jlV?VGAM1}IQo%)J^SJxco^y#wo~MR96${ZR9$ks)cD_#krMKQOja6c;@l0e zcNzPM@H6lwVMV=x4_eexaK*|!UzFeLE68FK5fxs_11Tf`FZU~mik?0K<9vY5$%pSt zDl`a+Hp6t@roF@ZW)a#{{XyU4kp}lb4oG1Dntp$YRkK~#`a6`| Yeefg>iK=yjMh2)xTS zhT;=Uz%{ojP#7cb9%a9+Ez#!`!Z_;Q@tvUSKXCB77N_Er%#B`TBc`5 -DEL0I-q+FaXp$gg`?+^8TN*H{CIk@91xHbsIk)?!wKZedu ztV7e_^Nbx-q|mPeg)o??xB}fA$RJoGNOW>f(g+V0Gz+~f@<8YbYdP!jJ~xKwTf{BC zobviZ0usDrXK!#qnel82`_KN1Z7E~`qj$&>yW5uqd_SZ3?u*?Kta@*Pt>@ NcnNS+;2WS_>0Ao~Z($63G;*N21s#3T13%w2aW1DNmJU# M<&NHgh7YK3i+RKVnKG|b^J3uvf#cR0l}TZ+EG zmd)N_OYpl?L*CpxX9(F|<0o68r1sBA8n&DPfnm3#s*R~P&RR4xtX4ZBC4K=an|0yd z@GxpVI(Q;;>~AG$pJ;x`GG%L~LR$nDC5AoCFKH{dIJy(CX(n%9?xvE~ay#WlWv#f9 zHt|77^Tfv0>a{Y?m%4`1i&G7XN~r@=Ml>FWIFPk=boRA1UjHpx)tPCB)C$;OD!o#) z+;r+x*w4@*OG9DLN ?85=XPGYukAE6%rCX|Oh|p>8jnAaIH-ES^pvaKcs$roOPP z+^4fvE!T87TSX1;APL6ea>*Y2si a9F%2 zh>&WdaO*qLs9vhY)^-QA9!EAR@FpEFERopyAceHtjQi*A6x{rQ?#uXNP}TdS z@!SD$k#s)w^E#(Y6+e4ANR$Xs6zSp(mz ~XTbflt8f*Jv`gd>0RKZlE z8iNZpL$i>t72^bcWWF2U_@ziKiYK%ev@Di|iYfzCwqJT(b^g$sGymaq)5A3!8fphR zPWgM}Mk5s)DdYergn*KsaBMP?Xpa`nox>SkInfQsvyxsTO0=;o{^nC8?o^etl}m+@ z(dUA8IhfX;k5L+ouphMdnLKSuXWv#IhXx$ZRuEy5>7&peFTydXB2T$!SmZy`)HC}a z!vy|^zD40+J6k;?ZtkSToH(WMwo*u8ICTE#=>pVHxNQI()M-w>4`UI~K z=PmUKhBCu^E@K5?D1|>@m8QSn4&%soYOolG85WJ89d?Yt8(Uc+`J=C29g1iF5{9l_ zV+!AoE%eyxjd^W1k49FeE~WLeY};-XQWiHqtX_5_;*!G^*m*s;*@NKlR#DY-$GcXD zOZKi-qI{_=ng|5N>d~-cJ FEjiTuRQtE!=jDC`*nS168sy2X{?K4JTJ}5PVakpiotuxl$dq`*@P* zI?! uBdAaCyJfM z9f #P1p@jt3Ch=eepWYA8)M%nV#= zS`Fz9DrJ8%wMwyt9~kJ1bPfHywRbFdwo-?<0)bR}&d_fPX0hr=BnG2Rg+un0k7lto z0|}O6GRVJtE(rQeZ3{cQW+>#C2BbUf#mF3tWZj WKVGQ{4VbtjnyrUd z+|M|AsqN_03`8}m+a6^ZFCQ|o6fqHeKt3Vi5<|5RGWnUK1Y;KMQeCGe-J7tUi0`Z! zqFGLb`(G=%aGiBU>Re1oNNtoxR9mx5Ab3!OWCJ>BqTKHYt2i&;N2W~m(x6RPP|HIm zU9D!LPrD{M3~oQo;Q{+*I#Rec6fueF0fSW )|E3uG+qDw5JBv9 zJa82*|J&qx7FIW|Bt!N2BCb~J7)c(mCIP~Id{E#6#wfT4YXE5CajHmXv+kjCqH4oD z4iSzGp6x(~0t|R4k#E>GDW#0*d}RVZ%WtJLmPKW{i$WV7N)-*9NA=J^(>$sQTCE|^ zQCk>(4USGyL$qN;pb0QM=njn#agqRj5YjNdA%X<;dXErWBl>L-{ r8KEk);Wh473dfN`Zy=ZHKI(1XICn9OUmzYAb#Y=}G% z=0z)j01i2AbmJl>L4#pi1gl7thXimn1535_g{jMzC6qS4I*@(KkX2sCkD+A898z18 zL&INHG%!ED0uK7ZaGllm6p5>`!NbRfqL@xFllQo!459qez(&-2I%J>>Z0sK(Ud0y@ zL@=zWqy57@9OGX7SbE-@3SG5WjE^b(jAxzl^xULy>MdrbTn0rQ0q88C2?w7um-Xz8 z5l;Tf0sxk mJ$6*Jxp8j7dvp?;^7~_=s}k&w6x6rm_cJ}_432MWZKk=W00w(yLLL0n zBVAP2tg%(rJg*W8Sb1TP_M84R&?z?zY<$fqR)TaIYH3x}Hxeu{@fv?K5r#3o)8!&1 z6x1&Ks@&`%3;W|YYJ7}fZgyr8HC|6FY46<8ya^>tfEw{~YXk|4BZuY=OOxBb2wbIc zUiVGdN(*zOVHIfeet>dhj<74rsrRih8C*8dnKu)Cg$yYD+7epCQpo`UwVBX;PTylH zqM5xa^HR#ykd=drtItX?D=L=Gja2z9sVu%BmBq}cDktKH3RfcDF~S2t_l~QX9^ke9 zmYYR#3 _C!I${*Yc3O6R&H28I~hOTDhsi3Ugat3;e;BhU#!TdNz8RriU8ZR)v )SNsNevqC*z>3ccW&a2SD>@W^%~v-=W&gRiV@bB zPLTHA5Uq#b^+PJft-O?f>e~vG3PMEh=yhaZ=%uHnWDzpZ|5I2 QsUI#3wyC zu_O+B=kw%fFEJ$R_flpU@Jd|N_=VOuo=ct5>6~Nr2d{c0bwjn}S@5a{V>2U}Ve{xH zJT{AaGb`=gMeN2a|HCqEH?X*TE}kzU#p|{KP|J3)C5v>GV$G!l^(AVl;rdkk7=2NL zDpNt$YbN<3X)Ic%lip{@nO3D$S!ZosM@O?^`vI@K?G~s>CAp1;ZFNR|Fy}`TA_PmW zAviY25Yyd#12vUmr7de2B+|n`rITr*ZM18X%=Le9QL4PHZ{$7^xD*ApPzg*@PzP5j zZX_|OF{Q0;xw1F^k;jIX5h{&HN>C>F;7Kz``z#d2E|Rn=yzfY5b?xmZ8jwPFwBAFL zxPL Cvc>y4i1}+$P@1zKsKq_44npS?N2VI_1DB!N;>Kpw zW)4M7mP&zT$6T?RRrHjU&0<*7*nfij{K@TOTos3S3Jc8UVhyA$7RrVGS?V?!otHCZ z0v{+`EDlR%^V0>4Ux>`Kxb=Y$T5`U%KQW6@mUV=Zz00Ht@X2 =TM*KK$oC$7);FeT6g{tvWg~^QBjrdX0GG;c+529Fecp zw&RWbO62)-h+tM}X%A{*uu&l{?ayV=`GC7I@<$t9F{mu&bjJp{x%dRjH}Iv^F?**- zH#YRNhOxZWHYnRa_$tn@&@YE-S47I#UB3~7Gh&N `7vziZgxqoe&&KNGzM&v e$c~Tt zPlF1f@Z--okiY(MZ$f9NG@wIVFBW;`ZMTTD|3##;$k~c@%b2HzMy_fjlp%+m#yn3U zdwHQUl DiB2DPd+1Rl3H{kDA^nAh2sF?bGa4TckId9YQ^ Z^8|`eHmnXG!d;8|TG-V9MVI#`Y_Eea z`#8#{4u+ZhXSL1n((iVfer%%&qH#N+y%NXkE$|I5k6pH-jYzA%u{Y!N9H*CviZMNC z!ldeXy_zlx751&oMU(^h{MnH&Xsr%6rKciBF4(9F9sFF#;vf1QvN+(!X{1dR>xw>X zQ#PW=d7Y=T0$}>NCfy|KF_NliuXaIar%nf~Aj@%yrXG3|B2yXmiGlt|kIWI!PRwWp z>F4D01Fz(ihcsBM2EwxQb=GIT+!6I16ih6F_fe9S9*;&6lWzg;4SZu6a3gDNH31bf zrl;@8P?J7rfjZh=iAinRq78x(sCG6!sbu6JlD|{DN%KW@Fv^JE|I#8sY@iex-t+zd ziQfGrNJVf&wV2z@)jHl+sHNv9o69+bpO>(kK XyXjNhrSF>03^r$^lASFfO=(1R} zuudWo>J;lC+4PeL2WN4-KrUfn3}gsu3TKC-1yMs7gd!y_{ctd;|G{5Ib-F6z3I|El zvG XDl>I}%J0|A+V*NWe#TvA;YZV$h z+h^p48JYI%xbd9;e(sX@<}4SpW1#P`E~k%;Yav3hokubrxC1s`3D2UxP|A=zr6lD0 zw5r((H8!4~j#(Te@f(!|y39oV?_-EzL|}WdS>!?X+VKfDw@8MK_*5i3lqmpCiy30K z+nLnjnh=t!v*h`BHWKy7UZW+tkAh;2|2*q)1|QsbPngNqwiacL)O~Bsa-Z=l+uP(f zt)l}&&wn%7sP+T>C^mh+6{AsoEl7c3h+vkVVIeXaQb2J!ft2q>^Pt6-qW&$d`#;CU zp<)J2?O+yMD^|5ovpFKxR(=*KkI=Oe6cdfS^QeiAFY+B<#HtvQMyKXNC1IS-_kZn{ z&mU}gwP(qEX=LtP`_#EOSH$I3{}oOPb}+{;x9jBUbx{B+u8?sMelv%6pYmoir^?o? z4}l_3V`X1M#kcag+4Z?u_g4xBud5*D*Y#l|?DE66#v&>7V+be7fVf!5nUf;&l?P>= zIKdQ78&2L}m$i!3c;`qD9EM(lu}tGBVTCwKF@N#Dl5!Qqg|hDTsBA5i@`DMAA=glN zogP ~BK?A!g3 z=5=F- 1dHhfsvb#Wgr)1^n{l7%&uY7M z47ZW=o~USptgIgEaZ NfUcprVFAqC|ZZ(AbhMuP8MXG 3>k`&lE zZs7Yk(m%;=RH>r5!74&7903Sq!||N*FLI&&e9BCe_R^y9^y1lMfq}qppA)W{$Q*IB z7(NBD*?*6JAYbxeR3wt_+&wiTz`0#Qx#loaOt$~_*G^Wj$-$u>IEGvVbKBAHo2p0w zN*eK$w@qSg0k_i-lPbd?T~ LvQ12YUWPHcL1pRwS$~A>XB6fv-k4bgj%+#>^);k?ZIXUIewMyqK4M3}G zaH FbohBdQyW%pGIBMS(b1r#pvEOYkmU=e0M zncf6bSt!YQJkVTW#$#LXiN&X*_)sXv{m#LiU{`^MkGJz%J=xwUB$BG|^Na8c-s;gj z*!ZA8-S;i0?)~IV^QFMR#(t1kfq&@9cHLt!bL-E*oW<3CPVG7wUQ*Lr28y0p2dsHk zePE&|ypP%Q<4q*NOy%^CUJa_~$lDYVih$xp59xXGd; nbJ4BI;`u-v(sMwGZlw>Gp+GRI){^_`9F=#fcPBRs4vN!{L>orh@)9FEe<)X0{aw zHXb=!t;D(Y;8aSh{dei!tO-UbDii~6j~%=g`VV%CP1y5pZr}NQTS`t=I%bLwR(~_K zV2y1&LUZjfrkT=;1k{wlQ4Qt~DNprt#|6{rt)_P|uNgDmBPmChHw;XD^$cxBh|XTu zo%z^CU2Dt4hJk^+Qkw6A9=+JOxf^qrqAt?|gwQQ00MhFIW*c7bn)g#bPF5Pa;!>=( zc-DVl#^$#PKO(If`72hSp3Cq{!1PH-dRo;IF=P}o{$}nUeyRXwbh^ND9J-r_W->&@ zAHN3aw0ZYf4;pJ{eD6%Pj(w{!qZR KBfKd>R}`=P{Ei?G2y-Va?w;_TntRGq0LOcV@l zaB9X+b~%a;tyBN7>4nzI0;h5F*w+h`N1jv`6T7yS`#|d@1*iFabP|;4KKjMYy|CcI z`_>JQpcIifU|ciSz27nS)SAgu16C9Ucwc^1(j!pUgvR&;3#sXhgeZA?d$a0R1GpVe z7)I!XK+owad#fIg&)iSWu==mTU5c3-s9uA)9ak6#?N4m&c$XOM6&k3)2fPqrlGRqC z{zM#w( vQ+srG>J0!kqh*^O)+Q6 zWax7x8XEVTJ87I6wof@iEDdB%ILo5`{`Vf9k*bn*WcvF05*^%M*H~G5gL3`4F(vBz zV+aGQWMsot-`c5(mVU8FxgG{3f|ByaGP@EG0`n-ihEq)_{6kEx*CP63FBC`c3D=uB z>2Ll;4uSTVT(Z9eBXoT)Da 4QtC|*O_+0=8$*P?CI4#vrmbEJqas&AuhwDggdv% zB(m2*Dp@8AXZVrWX8s=zn3QWc @ zv2ci(-SLlu?HY_sLU-&?KI)!Buzu*yQ(VP|i}g dube12xX<^@uqL1=O2D^$-IqczBH4TD;W<0F5UX8S80qo~_Xxf9 z_e2U^T1pN$=s_?(q~iWxL{;hido{Fr%sX&YVH)S|Yc<{Gs+AqtI8%VJynB)d4?)|W zM7@4f(9^&d9S<(Z)8G@hDfe&3OW~|Sc?e?_k%SMMRh=b^^Y;L-==Bj}Aas*;{!F#c zzpHC *^Jb_PvT_>|y{`!3cO4PnDG_LW_ #-oAmxBhH698kqCi?#Z$(a~9)`@WDAu&yN m2779F?L}$=Y9z??ScW4aJL0xq{}S$TuP}&x6g{^TD#X%CeHP(*5&?e}n0N zVpUb{w?^k3-uU$Ru5Q`wjkwlveG9H01l)6L6o02MHiJYzakjin+=|9#WLWv;Jgws7 zRPI{5_!zFsO+~Ol4X;t406ZqBu@$P$$|O?%UpJ5gl7Ik7RDz=IAzWmDsSiGY7glt| z6s{9TZHC2%pwWIc2}sCy1q8jW52(?*v+FScIJn?j7k#diaEc`Vr-cr3ktX*uSt9h7 zBvqW<{oJ>$2rZk}k+gT^1%Rj)$#7MV7+@_o(3ecF)Ox=D_FT*=O(w?_=yZ0vDng#L zMke4jTMKn{g0G>))wMqV4HkF~c9pe+`oWnO7Z&c)--LYzFZPsodJ^-|o1Ks5DLy`m zygEK!TT~>cGHaBTyn8GtAhn&2SIR43Sq*_8C*>Oe=2mY?&lE+`1XX!~-w)&GVa|zP z_8d3_#>+>?>Ii`)9($ F6TndV}l^&>% zTgFOzm_^mk&F^gl?p^Gwl42G&*!gXl{3TZxld@Eq@;)){n;N~c(#wg_+v5>5n}SZP zA^gq5)tleTjjXk`wNDoJSFhyp#D9lJ8jO3Ip;(JQp{9P#v$nf6Yzq4`#F$AE9i8|c z@nltX8WE;9qs9osWSf`Q?cZP4k^9P3B|F5Bt?m5Q=xO~F2i~15*GET0#=Y_cZ=9a2 z#Ajg0=86uBsQ676bi6*lxNDY*K$WVomHMwwN|JDJFI$De6ZvAXXlQ8kGmd7_`0tcF zyu8+4Pu^b7kgIU6u1g?fNvfaox~`^TZ5KfMm?D0HM+|1$L^uED%GvNLK};RX%!waq zv3%qQxaP_ E7*lyvE+4H1JT^K5*_Gnrcp^H4w#EfLuNfD9 zvkSFkXOF4)st(sw*Kz`
%6z=GLXcnUti~>%OH^oZr*Y%Xs>(xF)MsaAW=cQH zkvP{6gMb`58!_L_)IaZK!;-39?+xXe(`0FBWZNkZWv3zF-0L01OFEV`^GYM_a$`~7 zGO=v$;2nb`{@JCw!W6yD{WtERqe9dQ0t5@i!>cf*L3+LBc>*#fRYS`E`xO_k?~}hT W_J-M}5+VlxEwYkI5|v`cLH`3Cj~- ().getSharedPreferences("source_$id", 0x0000) + } + + override val client: OkHttpClient = network.cloudflareClient.newBuilder() + .rateLimit(1, 2) + .build() + + companion object { + val dateFormat by lazy { + SimpleDateFormat("dd MMM yyyy", Locale.US) + } + private const val SHOW_SPOILER_CHAPTERS_Title = "Les chapitres en Anglais ou non traduit sont upload en tant que \" Spoilers \" sur Japscan" + private const val SHOW_SPOILER_CHAPTERS = "JAPSCAN_SPOILER_CHAPTERS" + private val prefsEntries = arrayOf("Montrer uniquement les chapitres traduit en Français", "Montrer les chapitres spoiler") + private val prefsEntryValues = arrayOf("hide", "show") + } + + private fun chapterListPref() = preferences.getString(SHOW_SPOILER_CHAPTERS, "hide") + + override fun headersBuilder() = super.headersBuilder() + .add("referer", "$baseUrl/") + + // Popular + override fun popularMangaRequest(page: Int): Request { + return GET("$baseUrl/mangas/", headers) + } + + override fun popularMangaParse(response: Response): MangasPage { + val document = response.asJsoup() + pageNumberDoc = document + + val mangas = document.select(popularMangaSelector()).map { element -> + popularMangaFromElement(element) + } + val hasNextPage = false + return MangasPage(mangas, hasNextPage) + } + + override fun popularMangaNextPageSelector(): String? = null + + override fun popularMangaSelector() = "#top_mangas_week li" + + override fun popularMangaFromElement(element: Element): SManga { + val manga = SManga.create() + element.select("a").first()!!.let { + manga.setUrlWithoutDomain(it.attr("href")) + manga.title = it.text() + manga.thumbnail_url = "$baseUrl/imgs/${it.attr("href").replace(Regex("/$"),".jpg").replace("manga","mangas")}".lowercase(Locale.ROOT) + } + return manga + } + + // Latest + private lateinit var latestDirectory: List + + override fun fetchLatestUpdates(page: Int): Observable { + return if (page == 1) { + client.newCall(latestUpdatesRequest(page)) + .asObservableSuccess() + .map { latestUpdatesParse(it) } + } else { + Observable.just(parseLatestDirectory(page)) + } + } + + override fun latestUpdatesRequest(page: Int): Request { + return GET(baseUrl, headers) + } + + override fun latestUpdatesParse(response: Response): MangasPage { + val document = response.asJsoup() + + latestDirectory = document.select(latestUpdatesSelector()) + .distinctBy { element -> element.select("a").attr("href") } + + return parseLatestDirectory(1) + } + + private fun parseLatestDirectory(page: Int): MangasPage { + val manga = mutableListOf () + val end = ((page * 24) - 1).let { if (it <= latestDirectory.lastIndex) it else latestDirectory.lastIndex } + + for (i in (((page - 1) * 24)..end)) { + manga.add(latestUpdatesFromElement(latestDirectory[i])) + } + + return MangasPage(manga, end < latestDirectory.lastIndex) + } + + override fun latestUpdatesSelector() = "#chapters h3.mb-0" + + override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element) + + override fun latestUpdatesNextPageSelector(): String = throw UnsupportedOperationException() + + // Search + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + if (query.isEmpty()) { + val url = baseUrl.toHttpUrl().newBuilder().apply { + addPathSegment("mangas") + + filters.forEach { filter -> + when (filter) { + is TextField -> addPathSegment(((page - 1) + filter.state.toInt()).toString()) + is PageList -> addPathSegment(((page - 1) + filter.values[filter.state]).toString()) + else -> {} + } + } + }.build() + + return GET(url, headers) + } else { + val formBody = FormBody.Builder() + .add("search", query) + .build() + val searchHeaders = headers.newBuilder() + .add("X-Requested-With", "XMLHttpRequest") + .build() + + return POST("$baseUrl/live-search/", searchHeaders, formBody) + } + } + + override fun searchMangaNextPageSelector(): String = "li.page-item:last-child:not(li.active)" + + override fun searchMangaSelector(): String = "div.card div.p-2" + + override fun searchMangaParse(response: Response): MangasPage { + if (response.request.url.pathSegments.first() == "live-search") { + val jsonResult = json.parseToJsonElement(response.body.string()).jsonArray + + val mangaList = jsonResult.map { jsonEl -> searchMangaFromJson(jsonEl.jsonObject) } + + return MangasPage(mangaList, hasNextPage = false) + } + + val baseUrlHost = baseUrl.toHttpUrl().host + val document = response.asJsoup() + val manga = document + .select(searchMangaSelector()) + .filter { it -> + // Filter out ads masquerading as search results + it.select("p a").attr("abs:href").toHttpUrl().host == baseUrlHost + } + .map(::searchMangaFromElement) + val hasNextPage = document.selectFirst(searchMangaNextPageSelector()) != null + + return MangasPage(manga, hasNextPage) + } + + override fun searchMangaFromElement(element: Element) = SManga.create().apply { + thumbnail_url = element.select("img").attr("abs:src") + element.select("p a").let { + title = it.text() + url = it.attr("href") + } + } + + private fun searchMangaFromJson(jsonObj: JsonObject): SManga = SManga.create().apply { + url = jsonObj["url"]!!.jsonPrimitive.content + title = jsonObj["name"]!!.jsonPrimitive.content + thumbnail_url = baseUrl + jsonObj["image"]!!.jsonPrimitive.content + } + + override fun mangaDetailsParse(document: Document): SManga { + val infoElement = document.selectFirst("#main .card-body")!! + + val manga = SManga.create() + manga.thumbnail_url = infoElement.select("img").attr("abs:src") + + val infoRows = infoElement.select(".row, .d-flex") + infoRows.select("p").forEach { el -> + when (el.select("span").text().trim()) { + "Auteur(s):" -> manga.author = el.text().replace("Auteur(s):", "").trim() + "Artiste(s):" -> manga.artist = el.text().replace("Artiste(s):", "").trim() + "Genre(s):" -> manga.genre = el.text().replace("Genre(s):", "").trim() + "Statut:" -> manga.status = el.text().replace("Statut:", "").trim().let { + parseStatus(it) + } + } + } + manga.description = infoElement.select("div:contains(Synopsis) + p").text().orEmpty() + + return manga + } + + private fun parseStatus(status: String) = when { + status.contains("En Cours") -> SManga.ONGOING + status.contains("Terminé") -> SManga.COMPLETED + else -> SManga.UNKNOWN + } + + override fun chapterListSelector() = "#chapters_list > div.collapse > div.chapters_list" + + if (chapterListPref() == "hide") { ":not(:has(.badge:contains(SPOILER),.badge:contains(RAW),.badge:contains(VUS)))" } else { "" } + // JapScan sometimes uploads some "spoiler preview" chapters, containing 2 or 3 untranslated pictures taken from a raw. Sometimes they also upload full RAWs/US versions and replace them with a translation as soon as available. + // Those have a span.badge "SPOILER" or "RAW". The additional pseudo selector makes sure to exclude these from the chapter list. + + override fun chapterFromElement(element: Element): SChapter { + val urlElement = element.selectFirst("a")!! + + val chapter = SChapter.create() + chapter.setUrlWithoutDomain(urlElement.attr("href")) + chapter.name = urlElement.ownText() + // Using ownText() doesn't include childs' text, like "VUS" or "RAW" badges, in the chapter name. + chapter.date_upload = element.selectFirst("span")!!.text().trim().let { parseChapterDate(it) } + return chapter + } + + private fun parseChapterDate(date: String) = runCatching { + dateFormat.parse(date)!!.time + }.getOrDefault(0L) + + @SuppressLint("SetJavaScriptEnabled") + override fun pageListParse(document: Document): List { + val interfaceName = randomString() + val zjsElement = document.selectFirst("script[src*=/zjs/]") + ?: throw Exception("ZJS not found") + val dataElement = document.selectFirst("#data") + ?: throw Exception("Chapter data not found") + val minDoc = Document.createShell(document.location()) + val minDocBody = minDoc.body() + + minDocBody.appendChild(dataElement) + minDocBody.append( + """ + + """.trimIndent(), + ) + minDocBody.appendChild(zjsElement) + + val handler = Handler(Looper.getMainLooper()) + val latch = CountDownLatch(1) + val jsInterface = JsInterface(latch) + var webView: WebView? = null + + handler.post { + val innerWv = WebView(Injekt.get ()) + + webView = innerWv + innerWv.settings.javaScriptEnabled = true + innerWv.settings.blockNetworkImage = true + innerWv.setLayerType(View.LAYER_TYPE_SOFTWARE, null) + innerWv.addJavascriptInterface(jsInterface, interfaceName) + + innerWv.loadDataWithBaseURL( + document.location(), + minDoc.outerHtml(), + "text/html", + "UTF-8", + null, + ) + } + + latch.await(5, TimeUnit.SECONDS) + handler.post { webView?.destroy() } + + if (latch.count == 1L) { + throw Exception("Timed out decrypting image links") + } + + val baseUrlHost = baseUrl.toHttpUrl().host + + return jsInterface + .images + .filterNot { it.toHttpUrl().host == baseUrlHost } // Pages not served through their CDN are probably ads + .mapIndexed { i, url -> + Page(i, imageUrl = url) + } + } + + override fun imageUrlParse(document: Document): String = "" + + // Filters + private class TextField(name: String) : Filter.Text(name) + + private class PageList(pages: Array ) : Filter.Select ("Page #", arrayOf(0, *pages)) + + override fun getFilterList(): FilterList { + val totalPages = pageNumberDoc?.select("li.page-item:last-child a")?.text() + val pageList = mutableListOf () + return if (!totalPages.isNullOrEmpty()) { + for (i in 0 until totalPages.toInt()) { + pageList.add(i + 1) + } + FilterList( + Filter.Header("Page alphabétique"), + PageList(pageList.toTypedArray()), + ) + } else { + FilterList( + Filter.Header("Page alphabétique"), + TextField("Page #"), + Filter.Header("Appuyez sur reset pour la liste"), + ) + } + } + + private var pageNumberDoc: Document? = null + + // Prefs + override fun setupPreferenceScreen(screen: androidx.preference.PreferenceScreen) { + val chapterListPref = androidx.preference.ListPreference(screen.context).apply { + key = SHOW_SPOILER_CHAPTERS_Title + title = SHOW_SPOILER_CHAPTERS_Title + entries = prefsEntries + entryValues = prefsEntryValues + summary = "%s" + + setOnPreferenceChangeListener { _, newValue -> + val selected = newValue as String + val index = this.findIndexOfValue(selected) + val entry = entryValues[index] as String + preferences.edit().putString(SHOW_SPOILER_CHAPTERS, entry).commit() + } + } + screen.addPreference(chapterListPref) + } + + private fun randomString(length: Int = 10): String { + val charPool = ('a'..'z') + ('A'..'Z') + return List(length) { charPool.random() }.joinToString("") + } + + internal class JsInterface(private val latch: CountDownLatch) { + private val json: Json by injectLazy() + + var images: List = listOf() + private set + + @JavascriptInterface + @Suppress("UNUSED") + fun passPayload(rawData: String) { + val data = json.parseToJsonElement(rawData).jsonObject + + images = data["imagesLink"]!!.jsonArray.map { it.jsonPrimitive.content } + latch.countDown() + } + } +}