From c7229bb76364d95d739c85219b207d290b0cc504 Mon Sep 17 00:00:00 2001 From: MixBadGun <1059129006@qq.com> Date: Sat, 25 Oct 2025 21:54:38 +0800 Subject: [PATCH] new render --- assets/img/other/boom.jpg | Bin 0 -> 29335 bytes konabot/common/web_render/__init__.py | 120 +++++++++++++++----- konabot/plugins/air_conditioner/__init__.py | 91 +++++++++++++++ 3 files changed, 182 insertions(+), 29 deletions(-) create mode 100644 assets/img/other/boom.jpg create mode 100644 konabot/plugins/air_conditioner/__init__.py diff --git a/assets/img/other/boom.jpg b/assets/img/other/boom.jpg new file mode 100644 index 0000000000000000000000000000000000000000..dfd333b8071534b0c388226f5be5ba9ab95ec094 GIT binary patch literal 29335 zcmbTdcU%+E`!yJ(NtY6u2na|Epp+oJh;#v|fzUx|0i<_Sx`f^(H0d=#gwR2H@4X8F zLRWf6m+$X=ci*!6$L`*l%pbWo^Lfr>o@dUz=ghykf2)8;YD%g~02~|u0Ox)K{96Pl z0B|2X_|Jad@a}i~hxqt-c=*JGgai*siAhOGh)GDu$f+KYkyDbBkUXM&L`h8p1OiDZ z=;&!_=&5LcH2*mX4(|PL@bHQ7@rh{2NXTgZKevBv0Lq6rSR4#44jbSBB@Qkn&c7}I z%YB^qIRDiE|GnWnxQ~&5@F5W~$^8KIBftY3T-*nExc`azesth{KLC#spX#ZgJOQ<~ zB_W$DjZjcR?nCxhRqa5X@k0(_E4N@GVp=+S2F7QcT--doB4E*%V&W1CuN9S)RaDh< z_4ExOhDK29ckgX%?d%=gJv_au8yY)0ySjUN`}zkaCa0!nX6NP?)-dZEo7k=Go!z73lhd>Fi_5F)|KP#_;QlvQ z_s9PR?0>^Wd5`M>9v&_p;eT-9Jn+8%;8Nn@KNTdPlGi4*bfsn!3VKNMDj~P3orqmn z=MZS+Hcm{-A+q-D=s(c@3)%lWV8Q>tko{l4{s*oF04Xlc{hx&y{XBtzHR}_0%tN6 ztDur#*u!NCs1_P$CI9OavdIP`hdO4j*WEczvFQQcyd4rku|uvp)ssAliH zH5(7v4WU&U|ekt8)LN!#`F*fo6ef z`*gPGygPmZXuKHaZe&IWozI*V(shy7xRb>Hx_Nt6@KQhMd~#2&MW*IAQ~1TgT{jEt z+JI_>1!|5?T8WByT6exHvuyauL8d0KWA8J4N!ujdDZ5ODWHaU$RRR443)oguEiHlkM2_^K1%m*4CkYYoNe8 z89notQo7JBcBJjq%4zlonybi7esnLvH{mI==Y)vR!9R-qn*xR71O+#cC$N_z57G`E zAS?&WPOeI=bDr$I(D?!tI>yFa+Vt)J`R}+xHM#c67)noEsK5@udAm9mK)pn`! zc|#_qMrLzYpmSHe5|uFEAzBy4s!te{gSD$Ltn~Uu0~wsbtgv+Irx4GF(U0&KWf^iJ z#)~ph2tSc?8NW)(cQw3fDJD+b1NU7j|LMwTgq3lF5awd=vS%U;WPucdXH12K4SPC) z`CvI|sh2H~7Y*tkeZHx+qSd)(5UOXTZj6!sBj!-4&9jPI7AS$-p7xMkp0Rz)uCOHO z$y?v#o-`Vi!?!3lH+L-~EvXrp3FRx(mm=;Y35>0-XGQ75+D1{jok=S>Vh!qMzJ=7a zVqksMQkqO%U0`JMdEv=IE+JdYbly9|j04WN8hy8^jM61O!yDCaCd_B%o{vjS_@Kr~ znskISr5o2e<;-^VZfUQVw6W6`OFA1f&hPz&dNY(W-U962Ak4<36P|yyqoPzRMUZ;< zzlK!f@chv{8AS>Yie!*6kCViw9T3DXF-F)IKJ3yvA@0h)4+jx7C{u$cQ{#>w`Ee|8 zhjeRd;^B!JTj_a8r46&|nMk(fnMfX^OQ`n2BY(l5{$c{ILbdPLx-)!wjiofDq6SY( z!^lkMc%;fAzVK*s`zmEVWvik3<>g>r_0ENdPxWJax+Twwso4$DsDhR;wbtI#eTIyx zQc<)+U}DmK@kCrj#CIE;nTvdwLEOAnA=mt9p=<@=cN_22DSS()tnGT?%f9oXD!j&L zq+k}X`Hd`^#YQGY{gLf{^km~#b%R{=-ti<^x@@Oj)k7e=m|!sh*n6MrrY5iLT33JU zE>fEw1AjkIBmBdohr2SPygO6&RYA$j5OOxK(0;Gq+g7&L4cD+>1I>pw8C{^96SjBX2%V@mbiy zPty)rwJ&7V23rnIN;IcrQz%hQhzd!u?l`2Ba<_;2nqF{e__PdL}AkM9GLkZ#*yxGkTzJX8m{ef|h6{;>Kr%caeB z$LP+h=cjI99BG6~=05<=ZQ1_EC)x5BTAOBzEJ04|BXZ^1KF|r7LG82J;ThnfZbw|0 zGvOg3m4bN0FZZ^5MOUus$*ZZT+_{i8U7aJcNXxu9nviy6-7_+ih)&ppy|;=Ymq- ze0-Iy5#Hy7EM6^dwo}W*wY9StmxBPr(gl$oR=#gqpSX`Z{JdqKB#$zy$u}nt zS*eYEiX^>1Kd;jR3vz=zM$*unSP#-%4HUc^^}@qZ~p?foz?O7c{NaPw$|F znU&!Jl7(umbQy%3cc3wouFo5Al=G_Y#pmL)NWy6gAM;2{QHkjxQgMx+Wg&{@mm%~y z-Hjo|yaCOW{{SX4@QnbY__xj}0fF=*68}q*Uj60z@i zg{X3&JxH8eQ+VaRd6hcaGp$Y-deCf62oE zbXsU!60|G$cEc{;{gK^>@^Ud5&gkK+t}Ta~7Xg|rT$G6$<*QdU&Il3nX_lD3LA$Y# zeE7?_7gm$hHJgf&VIlqXo6Z*kqJIB?U=7Jg0F_;WNv?rS^XYHzo}>_dRQ!nFSpl4=iA{lKS0BR!c8Z>IZ6g-_I2!xO0%b5ex%lxGT+zfxQf}XMp({S6l8VkG%^kAGaTU068FLVFZ!oM z9bK2Syb>PrYP_Ef6<67XU>0r_R+Fu=W#x-Be`b8E-WsD~PyZLp|Fi36Ze#RoTcExF zU0AXEe%P3YM*bb|^C6vo01LaElQX&w6xO0oAG+b2tJ%M>(m1n%oDQikpkC(O9 z-xhbPE9%x%Zke;kIB#d(<;Jb+#8{n03`$z>=^k+17S$Xc-cZXF<*b97Umq5g<;1wW zd{u5P_zz&eOp1Lq)!SJAm8q}u2nqZ(|KcVfvv=Gtr)T>-E0LmYR$8`~2uCdWuP26> z<9F9Y%BWvJBJ4G1Y4;xctoeFqcPxi&mU-3XBs)%;`1Ncg->YmAF15FXM&I3w5>B(z zj#NhE?R~Ci<)oas;{0kqF<#XvHMoUf!Am=76Z|DO)uv_9$kMhwrLB_wbpuVW4c$*` z(xwekkj$3b=vV5@D9PPe2G*y#6-2x~cz;aXF= z`lH6jZaxE;S1O!&{{W#K8=ojETp5vIWalZWer?t|RsWa!yUUSPZ;_B*>h;nJ!Ww4> zFj~CqoFq^9e6;*!!I5Xzx8qL|xuQSaHg0=Q$_5?fiqO?oVe0bn0k+u)F0KGgQQ^zKPb}(@TSjB$;w6)aa#|1^*B`v?V zfIW?GSj8OlPiy(6G#0;aWGwC~P$|z+9L%w9CL&fnlKi8!hHkL`B!_z}z9s+gS`|^E zJqJ+|d@Mw{zR{CdlY8s{y1PMOk~xdC%YF=(e6jzH#c!(|474swk#W*AR~&m5hf{5Q zv?KH!0FfuF5namEs~RNz0W?FmOQab{u8}b9+5adf7egYMNn7a+lD!n$0X7xJ_KBRD z9iT){J?(K~laVd?wZ->%o9<^Nx34ff(%Cb|F}|3(YS0*_>2WznIBkGOrSK7Q z^}UOvK_sR7Fgl)bbB=NAgGnx{LHAS)-;lq7>0Mp=*sO-wS4{&Y6Wtf?!x~s&M%uCP z@&u?WVt-SaMYsXaGH6(XI#sOv#UjBv4Y)omMwyLABN`XX)}4JieHF{QBpQv0vw@~R zjH2=7I4vibPeRJPAcW>vJ%?G*?;C~N(Z&)1^nVH_yKu)^CnYFjp%_!*ag=$ay2&!^8yHGA|G+~A#mnB-XQ@-XWlWe2(UON7FhO41 zXC9a7fix8hEkj<_f;=_oB`SzrwfBKf9c&!#(j#iDQ+zOKdn)AIoH?xiN+!W_RX8~( z2#{D`TRa)?5Pg~dv=dUQR4Ws0OFOAqL9HQ*upihdZuDoO!UV*IX7SGLYA1WxPX*f} z8SR^jvbR4a<3GliKxhOe+h>o8C8vux7YtJs1-(%dR0Iz152wf#uZs*UjMbbqH@s$N zot4VBMrER$FJG#YcIcpgX_WA{)a!T!sTXZ0*wv&H}HphoAN1 zYM@mLWj}$vRkNSh1P=+CePZg7`fdeN8iYu;ZTQb}tlN0#cq^mkOxhAR9w~mf=V5J1 zW8|Qb0BOlL>->n_bc*<2YM4@T>#@8E#i+mlW0OY|gNWJtq6Ejq;SWhLgOsmGSB!YC zQuvLAbO#941T;sY6&U9-6yz&7PiBlj;>Iay?PcTyN2HaqTa~b_7dKvPb!1 zoAOE6vj#R)K4(sU1#rMq_+92oX@6OJmQ}NDE6T+nJQ5-u-iHLL0&5vdUq=Y#L2>$k zmXz@p`t6T;)vTWv+9k?m4LDa!ZO3~9-zo1Y0l;~1;iShKF-7I{OoDA-A0BqN#R?IP z7@V(2zlD3wbzr(1vfNkxR6*cjMOH%DJKFf)Oa`Hkm`*gRLzNMewGsXXysnHIDH^EX z!Esf!5(fLC3P8ahIv^f>6||ZomaO!&=q~+dlPS-ne357dykt5ibVmg{bWhwYbtj!X zD2fv@iBac#;@uyJBhzn0-z(!%-q*XH0S8QQ13~;5k|uh|h`*vpV(CZAI?}zOix{!6 z5qnng&I&WqNb=2p3L6lM9%=Xf12vfRO{sAl@inClTGCLVaVmU`3CA>AC5&?RF?_ez z|G}eX^SKFTY}l(E+u|U_m>E6krO&DHrAmWCuaNBJd;N|Ma=T*uZu(+ZBeDew5p6hV z9p*t&r?{5*9apSRJ0M()zjDvdT}5Xq+VfJT3F$TL*gT8 zOo-{zqYB-&*N#MB%*LpfaY;9S%Gor!qnN`cS7G^OG+Oc(<%lzXK|B>Xs2@#c1@nEP zKjLnd?(X_i>u$A2(YNjkQkx-5$<4ukJk2QXiZMxfp;fF}AQcVRuNp12jZYxWu|0 zRnoDIO=^-}_z*es)B1fMA#md_=!nKId4h&@W0uexX(E|S>T1A86z}Yg*egob9_3JF zBM|4T=z^D#@y>%M@uS_1Yg@@05s$hkq5iLx*=?9H2kphIR(B4|JyHCb6~$b@2d{U# zoWR;8(Mt>tvxHz9w(f>3K80bsq6EP$C-pLT4DCR{0}9|HdMHHW|v{g0A zB2{Y2{g-89Kt8-w`o8*IPs=xF*NFZDK(|BFhq+UY!k36+MUPTLIvg6ViyNV>{)|Tp}=@b z1(`TA-J8tlSyy{r5xa*6Ci)y+9h@J|U)x?fWSuA0OZuQjNN5s>%BpwDMuSXNCVG{t z#mv7;WYTcvA%*~Mf~G0M>2)8{-9Qo-T682S^xzuUqwOmuj|G<`v9cd?zA9RUoOJQ| z=jQ`(A>JHtl89c7OP+K!S}z}40rL;ZMwfng!H)FmQKrdLkJh2o!S5?PK7yX)3%nw0 zoyt&rV~}23s`_YE$>6IJuT#xL1CpqUnWw<200egyD@qq-lh&Xqut0z|K}Y5kmb(Kn zj{o<>8`Z-|rG}~Ml*be@DAr7t;!Q$DCjCS%UI8(&$?Id2%HT87YmpFVMVeJXRroOC zX+4mG@`Hl~gBa3Obf^gjo){a^(MYlQ#1($=4?wsbZ(l3e|9J%8mm=gKol{fB9C{{* z;jXy>!dcRfLJ(Ju1I}9Wo!CH4BQ;)`aipd$2LCiI{B1ri;%dF?yw1s;6X*S~|KIPG zzK@fpX6by_R=tG*?k0MRZ>uJ?%^*^*p#!7Ap^V!#A}{VvAh!xI+RTwI9FwTM0zt~U zO_SeF!lk;64cn>I`GWL&1{-y;oF^}YXBteY?wZlI;8OpJlIW9c9)D$j;Ql_!Xa;=- zpDGn=Lp{(4{2)N;+c#|Eliww*c|hY^wn9G1+1R|anu(xU@B(i%O3QZU>f_>=0(2H)7<6cjnC%ly@9qnX%5vvf<%`D9VbBY4&PG!QU)xV0 zE{3uhs4&R+W6C0t5k0tcpx@%Abq&cctBTM8=ni{o6>;S1>)ABP;$J>JBMH3{65i3< zwG%M@nhiY6=rK^GJ^}thR;U8>-#$b&Y7rNuQ{{6xYC^2^$MGq142?V#w4<{&8__vqIlP zIEIj)p6d{0-6iv2YX6&!VLx+6QVIAmvt_xIAT0m~z796-8nkN6d9*X@Li>w>CCtSS zu0sDCx{#&0p+?6}$BISR@;NlRW=>*Q(Ox}eVuhOBeRNXObG<-Pp5y5-dSJlCi_P!A zZ=}RtOFBCBQ;CEaTZ2ALIaj97a=iQy4h34I9zL6zqa&I^Jr*A1* zob1#7aFk@hC`yojNTCdslOb0Sys}!q?|G@c-*!@MFfYop6+O;B;v3g{J71M)5pPqg z)*S2|m?86hlnf8L!hE`2rcn7>O5T$l?s_rfYtY#U<^j_nlAv7>!S%3Hu1%tJedRaw zAM$)!PAQf`-%n8@VZkd_gKd>E^w=6(q|^gwd$xrUhjn&JO1YCJzIk?F#T-5MJDMjt zrhVAk@+fwTfc?Ql1619W7tz!5{artF7V=&i0}U=PmsACv6L(f8Gn8HmhXPiLkTEQkg^y z4&p73Tp@`{f%=C>e8Q&SXkPXwF&0Lj>1`Te269nvbtFGgD4zS{o1LNGTU=BFaKIz> zFEctwuOX#A-kOF725#cuBl^e_y(F1glE>}Orn;v+WPdECf})9!bLe2dtjOttK`ZDc z(UzL`x>HuoT0Q0zsNe6s6!}2eK>gUVn!p2!oLh6$vO~3q3Q_-o5yC6P)fx9kQnqiU z>-a}t#SdW(mJw-153gS&cwgLU<<3#!{VQAV#f+Hw9Z;;qW!?r^X%Wx=SlS#f5-zkx z9_F;+O%bbEU%Bs?br*ab^(uEL11dA83?Jmvl{V#j<{l~pNG7>U!M#}=77s^56_Zk( z2`5??TJEwc+T1w@PU5HoUerz)O>hu0r_@wY!W4jty}7H`SX84^W!hs)xsBNzhmWjA z$6wYWQU3rK*>Ds)TxCXx=Hfwf>}+SHlW#ix*&nqm_2g}31KB=O(r6+1GRE+dv3iwR z@Pt-zSe;dbr*?>%dU*2na->9bP`0I&|K=zs{TS47Kr$sh;koS9S zv6Fbf&rPR>~nx;yuuc9Fg?_9=v=Aa<# zJ*!pysD#RfW9IB9%$*#Y3Z=W5Pe8v>?8@1+T45Nsx%`AlRmIMnGY4W)-P?_z^GbJX z{2#!(zO>SvBC_h_atH*?r=(F$S}3adK`je>UqLTGV^v{)9`pFb?2PYK!wJw%ZGp|l zDbx_!LQ5BI6H-Irhc51T|Dh?tteY3E9A}WjJ5}W%Ih}giEuDYdhU2Q?-PeYy84*=- zjb>Chj^$Y=J1gdXtOB-=SEytA3PBpSVKd+})OHDqMH~_-1O+}*Ly0E8zn@MQP;1ytYHUv~KP0e! zbv;>v3qDgu(*ktD3UO)5XA;J#>|}JI=l4;CeW8M};V*OFQKL4?rRf;zr3 z4PSjh!SFBVC9Af&?iE%3qEqw_#KAk+wB!4><}3J%=At`@u3;Aw>daa9e4m-~ag{-< z<}0f${x}_aZe6pZQ^l3iVjs7zD@7$jhm*Tc>z(#*UO#WEd^vS%8dzG+W3y-?P~Y?3 zr8fsUm_BGkNBs89nTGN?yrIGMW-{cD-sWXGb5T^j_v-D=8kZ@3r8{qSbLJ21N1 zus`#k`HtjfXR&1HukLofFfyU(QS`{B7>_OWsjyz(G5pIK#TaD~&~RT7*Or?Ue`u|t zO01x<#bj@oOll;fOo>DWg|&i5-KmfU#1t2 zQ|2bt&e`~!?QOFGtHsg%kvZSOrv{!q6dG7<)SX5Ag2{m^6`xYdZvpcRmSSYsrs!GqjV5NWrte30o|)ODu* zi*aN2^c#u3hYry@30_EC+ov{d!*b(|F0lDxhz0{@>RZxW-nGvNpq#RM(?V}(B|Jg% zFV8^v?vS+xfBb%&$y9uoQMiOnam^1SiJ4?t7{d-?z0{P$&x2c>Iqh)5ZakN)oxZa4 zmX&Ys^b30?=UcRr+38z?ZYVEN1bap^RH+2TvRi z=BNFLr^Y?=JwNx+jW^t^QsT^_qLiO|+c{4~Y=L!T$qzOFpNM~j4{vP5rufl! zkiMu$M!a+}POmj%`qD$fI;`csR{t(Ent1MEyNo`j5Khe-?>BS0t13)15j~y0dEJ_R zuKZM4t(S^^6uisW6Z&ka3qo1C-RY0NI$Nbc=fto!+cy7_Dp;k^P0?{jnAOCE%!1x* z`MZ*bccwb?vovLYbl#T98LyZ?E$IWJ<}rxWxRaL7QIPbB=KTUVYHjW&{%$4u2+IEqs{5kCx z5F_3QU!U-Sy$oktYj} zy6@TdE;mY=Q(XFTYBh`^J&C?`6R-C0{sV-cPQjx1UeE{U%?eUGu=MW687rF@I%*{=?%x+Z4Xv(?4_WDDnBuH`H9LL;98%k z=H273#BehIp&V;+kk><~-G!$vl;`;Cjp%&OUD#FwcbOJ=XNBMp`-jKK#f=x0ur|rx z@uQgI8dBi{dv!x2zeKiN&Y+2H9p>z$TA+I{&VpSNmp zp`pcW;SVGsd1J<;oxY^H-&0bY(d{ng{V+-SD)H&n?K1&2A*zolrnfAcx%@1$YW7uw zsmcfrfA<7rZoZGq>o1Sc8EY-inx}Emet#kygXJR_Fg2mhmOAK~ z7amz`h7ToG9yHQsSw>O7r|f8gX{;Jryk15^8^t}u>6(YksdiI4=BGYu_$Wv7eO%@U zqj7^xfuoF2Q5MvOLYD@{pFirF{7?=ATXz*q2(a=WAF1L;iEnsRnl&H7I5Zk4Hcd`f z=mn{d6D|z`inYWC73pI{6AX}i@xfg|#{6r@PAPb)Xat|!LH~p7FR?ZjyV`v40Wz0` zp6qcoL(&k_gU$5*T|vWH(SdyhQnMBHyg>%ha3 zXLVc&W)-|A%u+jI@u_`92Zh`^DZapQ_pim=cYDM<6LBkG;D#3b@)HG@Qmv(uaBPgk zcMWj9hPbK7sr`O{=|{GHJA*9V|Jlr=u|2EDN?({44ZrRQ#8b*XqZBKp@QZ9@?9O_` zZ`c?OKahG^N)jO8YC=}@=Vffl)Q(&Pa1E0+{@YT-_Gf_T(Zo(2H^b!x__uX;YU4<@ z-avCDGT=ST?R)~n!~SHisOmD3Guum~nEtF1-qYz7ScJ2pthoM@wDVJ0bnnM|Wke;f5AaL}PFu zbnQ!YJj}2r8p*@2NsKXwZ^i!6HHPx70mO!#cbRdUoF)q1l>UMb+%Tm+G{&PjL+Us& zEWh3sp?4CQpDtwBYVqF+{&0Fxh3ocNF~Gj!a^UQFK+T#ws=DJR)t{%jESXA#Nb_nZ zYDW0hbR!y-!@*IChuEw<^^g?`TsSdiiA=m=A4vndJ%{s(SvSD&$+5o+V<@N%MGRuc zMITl^oBI9C=!wlJ^wjrOm!P7U33s6Zsi0{3%FUXgALVZ}|HNZCWqx2zX;flO-3>HR z^ZFN+k}krOq@6dz3x&M^ZnrWWg3`4IrV8c@3L>Sr@*-6RtPd;YYQ{~|9&5^^O!-K* zIbel!V?k3+&G;m*%b5c1Kz8G9b0@o2)*8D$4%Xvc zSryc6VqZJ7YsH$1rHi=be(GWbYxps#v%=B@6-CIIxWcDn`}lm=T6hh;t=o~28ryD( z(C=IiepGt!JSV!+@aDIA=}{hUe-zpeZ0r<$&!iiFm9m1fAnEquFk#NYil4WJfd73$gmwSC>t)oN3-cBlhWx^ z?v_};WB%r{QZ9CZ=pj>(8R0sa?2xZlER1B=n{$5S!Y^^LiV!I@YNEisBv#h?bi!8E z@H32nXEW9*y1pAmjN%*dxJuZUkSZ_9V`D})`L510F8Xn5NA9T!#DgeAL+%&7}KZWa=7~DA$V?w4u|I5Y(kXTo^&(M)KaM7v7m`fiyvEZ z=Ion4TgLa)-FkQ}9n7ZZj<{U<&^G#`Ex=W%25Fc78*hCAJqq7i{L&4xlYRK7;2$8> zQSZheweoPe<}1!Z+C*wJ$bk@T1M{oY{Rz$S;6NYjaFA2vk@AZkq;9J+5_GZr3 zUUDhf9n^VsYn+OVMRezEf3H?<>-H&zgGG}Y!z-b60XS%+StgH2`U4!b<#yRro@sna@>SEMY-@rJJG%ej z1SM$pbbWCT_AVJjx`lSHlLb06K4=235&67ZS_VMn;C9Z1kMrPLJhJCP*@Lgf+#GIb}}f6=Ln3$R?kJpXENzA9C22Lbr34tK&He z*MZ-O{uY$SeZqY%=pU)3JvUT8?(`e;G#{A66~h0ONlEw=RY)$63wODa2Pbv3XDBr% z3@ltKaZ@ZksQES-R@G4nEJM~OY0{q#zv_^8l>NY|Z}^${^pL33r@z>-7hGZ2O*!la z4s}PkKs_0{opWgv;=_2F{H978hS_mc+o^~8W@JvIYmU0PO(r%Bc!Zv{l@u`$REQXs zsIAUWg@<*g|8e-iP5>N&i3W?flp_w_MZbJdg*KL}s$y$~PF48n@z#vu9xXYh9%mJ` z`pxoV>l06k+%|!(NtvU4QN~^CXMu!_h}`q$C(T&`fgbZKc|Cg*=^kO90lq6 zL~aJOr+g@@c0Hy%d%TA>qO_Lww-m~JGtKZVk zKJ-u}{8pjizV_8KiA`%OJrRpUulaLD29H20-_7TpOGxxiJYP^`R`@3=oIF zGc=og1aSJgGSB@g*%(h}@l_E!#F*ehG z)Yxh9W`~BZM~uIZ?f;!E;l`DkZS{dE+tsumuWweqxHSFN@9D}Eo8C`Y82HS(;ETtY zsP$$^sIhRlZc0Hw_mOb`f&K`ngX1Qbi2U0XG0^i(T*ejG|!d^IcX%7QcM`&)@u$Z)C@ zH)Yi(gqnK;f%gCDcNX(%05RYwoq&Yid9^QDJ<5Jz zPlr*=9GmF+t)1?1Yc^f!#~eS%q&tA0=sS>dR?`UxzU1PfgL> zhR_fDfAyx%5&V*wQI*fo1K804j;dACz#*|{A&nGP2%0#OebCjL`B887Y1s>MMMy(S z>;&6{7z-2&&S5dOKTM^1&U~hs$FJ+Sr@BCn4}M3_3O^atf*&r|;+R0Ri+vIwpW#_x z6H6jps%io4-Om-fMPC|0TneQO;C9&t4@urxr4bei78zEb5L-4-mb$IeWU1siaca^8 zX-f?>+ahv3QG5>47b5#hr+X=qc}mG!0dw}ypJ z$Xi1z$4sk6{+~R)yIu6d##3~M3n^1z7!>$T3LptNaknhlEf{Zg$3WgOU;76nf2*8i=k3~uUS>u@Q1)V zXzgYo(G@Px-)UqGR&rnk_{fz2iz3@Y$E~5-wU=~`^+AI84ekxH? z6fZXAz9u}LLfDIDpY8pm41i!?t$$*HUTt+j{sHW={yLtRg;VXmIdMJKBqlHsZ!h?D zV)ex&6ikHI66nl$%|aP9eN9J)k54lf{z%)@4b#ff`P1_M&MagFx?P#oUp4Ri52>!$ z2I0JeDv6(@(A5&@oSL1hX7lv0;y{dne|FE4 zx#b@}k5$Amji&eOG#=lAVbOw=PdxrUiW+TA~Dtx}kT_f9)OB=eaJ zs=C!~z6NTD_@vygRc+J-<`O+9>n~Db9~nioXr6by>D}BjI#}}*c+tSw^fb~`Q#o|% zcoE5I7rxp+Ggotyp0@d=Zq2M!iX%GX&Fi>AaDa#t&6ObarLGUEos>;;|AfR}7shZx zZS6nZ#4rBG9Mp%((H<*oPR{Sr+nQ*s#PeZ_z`@wyT|;R0OwM@P6`4GLhB*WEyKMLF z4Q|BUoCtVVyrSRs!SW9; zdYVi%QfNJ`0{2c5KhZ2z(I-T#n-w1V8z7usQFH7QDC-(`G{;Gxh(R770TZhPgi*xcrU{BBy=5bYh+Ji ze#NP*VJPBynj=gu>%dc}B88fp*~C4Gli@UB=D`lZdQ)#5mukK$ckgH&dRSy_-`>zp>n+bnvSZ5ViY*9;s3Tc`WFc6cBY)f&(rj)lD8TF`!N)%n!#TGyCcL!xGr0<9&wxcfFN|MR=GzDk!K7 z(cMk>Ti~&@D+ztcBAp0nP2D?BrVp@naiDrx87jgPhDP@eYPX+wyGn3!!i2JPz?XiUK zSKO6`m{04n3)&G`IXw+l3(Cd&_xeHI!iJWYp=v@7bm-`16ehec%-PjUwcv8weB;lZ z!_P=?5kiWvRCj7;b5qGXN=oe_tj&6+o@lusKG7ZOha9s~@~!yp&3OLehhL5?yiSrc z3dasK%odlL>}swXp8e%Y>NP}|EAGxPFZ3ql_Ilhj?tamVe3|j;{%yto?d29F(3fK< za98_(SeZ13MV2Q`->$!r){wfdndT;Oo-raTQ|lT`2D&0ea!2tM4w8Dj8@u(@u4w5BP;$={H$dbv*c z!EcYt-xgPLJBEn3P}FHg2atkrqSm7X_1Y&%>;h$E=WSlq1Cx1@Vw8>pGIaOx!&-*M z#I2eAvd#^x#vcQsCY3H-oqXv%qd)2?(eX@cEBC)NqiKGn68V#NVoZulWQ19zzt<5m zt9#3ePJmF72bTR!J2aSB-k;BdS&fU&z2tJ`%SDjhU>aj2*LrW+k0N{`Frb~< zG2{U?=+T07`JfE{p6Ehg1|KUZ$VkGZDK?uu$GW@O69cTOO2l&^mw*gq3r=GZ82Khv z#9p)iI+4H`k+$Lbo|$Te1Hbg500#El*2_;YE8)TJM6Upa+8AS^ukY@3sNcyjZ}lTN zg35W&W2Ksy?V^)eFR{uum$okxc>6PPOjyDKWMxXx0I7>g^TUhIrbT|Knt%<6a2$_Y zn#`7GNq{n(OfdIXAN`)$W6USupm!iw>LtU0>UtEvv4)UK8-x(%%Tq7oD=vc;ZKPpE zeP}Ir7kczgVd6i{4C`|iIC!7qkO3XD)dxMtpBR@8FW?;CEm$}a2^mG>XQEA* z=jerMj+PeKfG0_LMtRy$gma)nRs?(jvxLISfwglh@TU!_xKeaE+mj(b$_<*_x`Sb3WFhah zBko`YB|j`1%Hfj`b8;2y z2J4d2FD2S~YM412p)-+idTFn-&2tebZdN%=qO`tCrsyYGLr)GjLasx68d zQEIfRV#bIaqiQEHYLC*|tyr~+6tzc4P!{VG2)?|dW_`Zd!5)XxR z(MMP!pUH%teEoId%LrDVj*g6ryneg_kDp?nW_+X;yOh}jnEhG5|C7B1P)jYH?fV~h zy*|A$XI!a6j{I9}>aSzwGV%JD!KyKBU{z&%l8@Of4fzsjC}lMg?R}l^v9@x&saAMm z$a4H-q}cgI6XHpW(N@C$zJy$t?h_eVZJ-st{re)sFsTuTJoz1Ec~YGBD!ccp0eTdT2gQGxRV}!_%Zo& z;_>>-3R*Si^3Cn!J?qB!2O@LDt(C?;JIG$pLpFnl0=4rD?_uliDkI128oc^}#I(c) zCZ9aCVzTYpc{0Y;k>k`di?wa2eUS9=fzc$vM|N0WgTiiH@=s1%vFDDGkqDaG#;%`rDptg`ST+c ztQDo(e0#-;L1J)JjBd19FKHFF&Bgm}%{_XBs`v*WDwx&NQLMK~*I{lVCu4s|){we-x97FiMshyqpKyLB=>>$gdy5cwWvV}FWV)p)w z?+6w7958-b#{i7E_mBL!nCT>)7;WxkW5pgv|MJ52ea$%N>m>!Nb8J<`4|q=s@Wzdd zPuY*E65Sj=z0>bIZ;lI*nYw-Lai!LUg_>*+U6+QFy{?AfoI31kgx3_{@CGVGq2YR@ zT}a;NszT2;P38s&17CLVR8rvN(v;&PfxyaT3OLoQ&xnT;L$CLBGX&zk#UgY8VSDdx zL#>%wY7d;lxnA7L0S-z^ON>dEvQnJ4aJd4vXpl~Y-Ise)Xp zkixoL)SB+8;5W9HTN3#S)Xjb0Gi#;XeUDy)`qD+bjuC6BcH=18@foDbZT>LIVLdaL zz!gRNbqfESs^hZueP-8BM}5I3ceDu$#EkYb=wl~c2)F?iFVv@hjClsjb%$xr4&XSS zg;cDg4t|lu1$+w9yXI(L0l&eFmDtr$F7zj6uTCPD>n4&3j^{&>PNw~<#TsC9W!9yu zDN6^L-sFKlD^mTZcWo<{+U*rZd`BQbF?Ibe_U#a;i`Bv^-K!wP!hTeW#)B86QQX_l zf!z0y`EP~X!==NNCgW`4i+0PmT)KVg8`C&fd+gpLtc)U_XMU+Px-Y{N-c%}fh5jSN z&#++SSh-FAvIa~bD*k&eLZbwji%?6xqp1`7iBQqN`8Ypl7fe-(S!xfK+X*CKWYoTp z0|d2OuDX_I0QP@!=trO3KIi86x57aJ?=yRaB%wmse_ewpv@uepyKFsi2YH7`Mv?^k znk5%{#a;`Vdbg}&QpNR~1x~THfst{R-p3I0(H@Tbkd+AUglkQq_YY)Vygl9@n|L~N z2lydq&$jaejZ#$;*%NGpww{8Y@Z+g~I9Tgu6 zm>mzxO!a-aZr;DL7(7*7Uz}J`)FA(~CorI!mzG#F7<4WPacbF@>abHjV6ddresO7L zgzPl)JF;cGL*V8HX(q+jseU@ovd!BsSVUAXyGgNr$RN^tCnZHQ5@S$R+yQHMrkk6i zgIBK#^5u7fgZrb+sY6eQ@6Jo#wU_7&Ou~Be_mQX!{kEHyW6j5vb6G|A#xyM}15ZDG zAs1Y0)=kjSjk&@WfxzLpG6kLo>n-LYL1MD5W16NPZm?<>&*3bHH3LVFy7po%6J^)d zlVGRktqVd{7U_>CPIP-y;P{B0kQy86HIP{0k;tnB5NmwXJ;;5YN0^`kF%i@W=SyDZ z^WKG2U|BrqXevTg+{)T2FMwpx4>S*Ml#D;!u?38LR{1JItAI!qygzlZ_`5p3lyw5jewVpL^L+F6=^XtoftKPtul(2=8xPS`&GF(C_@G(rPz7X5tar5Rtq$t2 z8!e{4#;ZQom+-|Z=2FhdrgdWH5^Jl}(`^f3dzAin5^-T3Cji>0aVa0Nazx ziG&c0f(1!@L5Ml=_@_&$MZ+e`LcSx7ujLu%s^ zbeYM>W0Na$h&6Ufbu{j4Wi72Q#)5s+SpPf$Eoyx#QRQfU!K)|WC+C6rbUvv?g!l;G z)uz{Mw_i9}*xGDsUq7te9=cHL@VwO`_|Zp;>*(h$?(s{6V-OB^I}rvxO~Sg&!KLTH zpDMh`9j)hEtCcXI3FmkKt(PfVhWz6v_V%SUxZKG#smn~#k8rjUqhcP-*f>hIem0IH zQE*=dDyPuVQ&6u^B-Qw}P9r}W_4=yzuvNTR)V1K_jozKp!546w7=;nBBK*KorXpz9 zIQD~Ss(poDsi$0cU?E@wuRN2GArXGHYWe#H=3xHult-|UHAtcA(f%i2ac5=+g$;bg z@4}Wp#b#Y*OIK2&EyuC{)V%yYC>r>_@e-gv>VTKG)odE2^&%HqRg0;GRECw_D3rmk znVE8rwP~Mk0>M7Hcaaq-|3I+wOP~*d72QYsAIBAcI1T`sUFRav)B6{Rkn|v#3*0lb zo<1F87)nN?27BDsg!JdkAK4~duQ|#L(X3UC-=FHSys~JqmRi;B8=xv)L%7KiP?WAffJH>$gw*4{fP`N_L8;tRGg$ zFUUS0>gxBg#4uSUp})yu4s3F;^{=inJV+mYkq{r8YWl337nvdHV-~eB(0UK_Y%Q^W zw+Iiby}J1+^x54}>+w`CE;R;Utje1cMYbZRE-nXj$l(?$9W<8W`n7%jY3~3hB)B7d z`G=Mj*g@juuC1a_Rw(MAUvIG<^gZ)SCSX^$lG2oC~CK^^*lDtbwo+)=rdjNyhLOUnA1`lz;Q37SX#%U=j-bzEt-Zc zP&0x9G${yEq`X`8=Y&LrlBM7xnbG1WdY`NkPIqVv4cH;m?AB1JbU-hbqv3Fvh@iIF7 z7#605>$MT1<*uOqrY)XQC}*7>m{l`Bc=5eLImhRls;4EKmI7SmJvR+w*2*=cePr)w zw=KL&J;owC1=U-Xr=*xGA5rzF>Xu(5j|K1y3Xfq0ZNI^A_X%EQ#4>V#1=q3M>*%Bl z%mll2Zr8tb@a-k(g`FGm2QwP$o^KIFoOpaobS2%BdkTYezp^Lsw6-27*DD z5@$Ojk5Y;EG4R)Vn;LG}B8p@hc6aIiMS^%-5YG!+=g<~5$ei>eoWc_qBCyj2>ylJ! zHNMyyygsI*pl1428Qp8JmIHK)W|aiVuOTbHvuOG{{R_bQ)&}yO4tGD-Bq4Qx##^D1 zj%gkpIv?%(@*v`>_*A}{T*zk;=F>_yi$Q1gO*5mg^Zu3X*AMK>jNeYw*p^s{05--; z3P$|FBlg&+{%AQ+ z@Z3YWKQj0iNdn-5*;01oMW_kVVdKAfjt&S-8G`vxlM07_7*uM{ZcTmB7?nidy#R81 zsfmd1i@akb7s??XnrZa;riQ-0D6I-oq^43Q!C7r7oo{vqM?vCaREt`?&wUWQp}s1{ z#%JQ(E5GgzqofmsEtb`(Tz3X-Ym&@V9GVKJdVhcEI61r;mPxEzh-y*4ltLJ#hG6w$ zO;=9z%K3VC5(y*9oi0t@2JE zz||&m>qUHJC0)VKPb!3b`ZrGmdV)T6ftt6Bds?#+b%-^~g61;516ZMGD?Su%(!Fh$ z%ba=R-SOo(2;Ke7WRiKvH@~i2HiBii_SN3pXW>f73lLchhZ+Y8TmmAl3?M+aXL}MqKBxnE| zD%AsV_U=OWO#?VRk~AV;G3wom6wYtkr~OvL@%(lEerLCZ}U+b(1~u9NBUJQ-jYiThWF>>BUyt z@U?4qD^+3mF@7fLQP6X?IXc8flwg$6pyT|on0ujDL_~^}fDG~*L zr!NU(P9_An6yNlt@wKN}M3o#?_g7Z*|Mb=f7@s&4AM*;hu2uma3j1!Hj~1&vjI@41 zLG7cHI574Y*I}Hvv*nn`V(Vq%QJ1-5-8WhD059?-0oFJvs8*MaVi*S;!C#$%H9$vM z{P8vH{H3A(`dz5l`dACQqk=BcD7R&JMZ;ICk>(qs6)ZE+P1gN|etMwCn)>N{5T}yI zDnGW4oXq)aA2#yavZCRW4nVDKwVVV^Cygt?rb?&$Depd1BZZeuQ|vU=O!aog7E?{_ zjH(9$?SV)t<#JL7(?soUgO@aDMP0o~f+SSSL`t|eg~e$dXz1$$>i_4?YG50dT|ZSFcY=ln7R?l;aZL``%k^25o}5F#QLmlJ1t*qb&|{5v@&z z`f#H!F2WeSE-xrz^HpQ{u;MfmO<^vbmL%1lNd|y_szP7nxflX=>C!^ZI=Z}-y%g-N zk1Uy#@l-D#2Y;iC-5x!ZGx-W=b_Y`QT?qIY*&j zH?hpjNG>b-mLogJM)F)@9iDGs4PEL;U;l19xm{meB4%=}UisbUPl)L5WTtx>KKfa8 zKn6#LVlXiamqrdcKPCMtrw=grA(VZZI>m}ffx14+r#y(gM|IgO2~M&yDMaYY?QJfO zf!J^#9f*af`2>%5Xn-@uaW~6!mWzQ3?88AHqjUR;IYxK-b36sv1@VOwocYS8;E)w+ z4MUSjkBA*VO-55cQ1p_FU!JuXw}unBYF%X!eSK^SJ(ZF>6oFWdYS|b_fS0QfQnrxQo_0@Uue zXDspAJk?aN!7CSdiF0v6rob}8uot{lHuW-lEg$J`4=;H$DPg^p7=^9afwBs}__+4; zL#%MUeQgrA3_Qh>{+>9mWA55uEf~loL#J#<*mP&PstqtIT<6apZ|EJvtg9MVW)_?T*as@S^3u;|Rspfx2HuI6Uke3EaHfb=Fq#NX zrjX6?q-SB~vCT8$&)?-RA6$EIU_bZ+OT}lpK9w*Cn2od^m{7CLo=Qj^5gCwAh&vH#5y89JnXO7kmN9o8Yyt%7SpO4Jwzizv{#S=kyU zZQH;@HQ1MB8&bU{;H}+XLf3s<#N>lYlU$Ovof0(qa9>+`tPfp7Mm`Meiy8(>F}s2b zQ7A~*TYWLZHS~meH69>40C3=f+7T=Fq;2eabSfIM;T0(m(4}ii^fc(;Jm!t%rSvDL z1vy+ix3e6d2RU$d<*0m@fu~-%hw1qXEgkpdK9ZThQbBbU;C*{sI^Ru>avL`nvGee7 z)I%LJJC^)Idbo}Zvm*or(1pgQmlQ>7m+GtZ2rGL*FZUw&d<=|jYGMb3^*B+Gd3bmY zL<(l{X3IA+M_#J$7s)L-V_)MO8Qv4tvu)scMUVAweE$yyOHfm)(ElpQVHFidxreG9 z`LhPV;*U#>4T9CR9Ktki8gd;#rIjawKrHg==he})0>oQ;Vgb&z+=IIwobk}U;?o?= zgo78>iY?;-DqRGSkq)X_XpChdX0DUJK^lD*KXMPD$aae7Ka99~4(l3Ppw99hG2~kP>Jg&RUD}`Tt2xLtf9erjuQGsLTebe( z#^kQbi(qrU@BLoeHXp-cxUb3vnqN6IWsK~9h%M)Tq)H4c`0IV)48QIBR^GUA?Q=hehdylsg@JrXW%j1v)V=ghV3 z$y-E`vMr^O1{Uj?Fh?@;^_<7krdI-{#YK@)?^)Lfwb2 z-K{Vgm-Tik)8fzFxQb}A1h`X}qZhP>hmOm*Uvn){5&g&%uAD90fC`@KmQzSEpR)_mCXuM@~w z^rGQE+19SlX{H!f08PFAjcx5&3;bi;YLndjL}`A>)WmaLN69T6F{&QEORgnsJz^=T z{s?w;d1IYrxkGKsux$@m1J;6n^2$RI&|OKbJ;UiDqXv&K+jpF5=6O(C~{csUv`AQFkzxurtJ<=2(0aw^C3E&%cot=)3>a2GmZ3+`F#`9ft zuWw#zYe9YGNf&YPA)~yV!AIJQY?di*$ShM(sH_N8W#ejC@Bx>5vs^hBZ+^ZkH>0tm zPo;dgW3_qMMgE*~%9{6KBZy`=HPVzzv1)QkN?e2~BCvc3xOg~h6q|0bRyi9cW8Mk?z+7wCd*VjVI6I+x;$C?X?Gzv zMaERnIk^O@-{T@Qb^)E^L5f+G;O|x06pJu~hrH77;?~kC$zUt9)o**NJ$DjRTSVjr zBs+UmU>>E--gvtH8?1Lg<|uDk7482;F|oSsC8uAUwRSv2*LSxFbOITl)yG}IKI>b*Ht1co+ zpGQKkv_^}w;7rqf&{)BTmn&Y-*I#4FUcav9Y5HoFlI6!YT^XZnTc@esfF71IIYT+f?Py=QWHnJ z49l?jH)%dJ4q5Otnk(uF$EMs+_^P?e6%mm0xPO8c4)$cr?-uw?OF?BcQr5J6;Sog{ zL|m?jfNa*kilkS^$-J$|8G#4*#2cG)jja{<$-Vig#VVY!%VT@9EGmC=&~8CFh_K+bRDWk@G8Yx&p6GbVtm0ZMJ zLtqtHiOayd0U|fZ`+9VW2-MpkV$$ zV_$D1m8pv{MNX=rKb5=l?V=IgYdD4PN64PDd*eQgIEoSN=O?^6dBWKWzw<*XH zdaN+6!ap*S~y}og7J>e`HR!Cc!e-;1jsOjhh_pPMTARi zpXkh!@6qY;@)x92Q4rfn4dF4QP;&l60;`;!ONu-p$iBx~KPE4GsV)xC&%!^Ez(|5V zKMPSTJfW^x?Jnd)Hn&k5@Egn(AC%hD3#z9c-B+T!{K}rp9WsJz`-cb#$QT_(O5A-WmVj6FlSgxswL#5E!G#vu8Gn3 z5V77jUKPdMN)D54)+BPDqunnS!_W6fj+w+t!gW{Q-R{Wx>5M`tHt^li5nmJcGXMWE zNXQZyBy|3pL1JF+%Wd(6#EI@;FVOLYv3GF9`OLz*avf@AVw6rLr!g4=az|lbDyK05 zSK!1~m5p5@c3#k4H*;b~vX`^W)w^{Sd#C`%;Yyl1q-<2Kp<+v9u0Lr`!hW@S6rC+N%W=vznt39^Jfa8;aArR?RhKUiLmD+Q-_q zG{)PRL^0Y5FSjFU!9&!5tojChkJ=zFG^funj7%fQyEi2}?y=jhPc57U0 zXj5S{xw8F}k1F(H+mf2{E0N+g|utO3)HVg-^x@+h$ z2HV4Oz+>`U zqZL5@@0>BfLl_GNNDWTMB0^?CxD2(ds(rKINxa)ivpJ?$T9;%x!twr}ff+|?maKH8(iN|d9o z%yQ)e@*M{F%lV@K&I6Bw30TVJS~tZW>yeq!@cm;yHHX``2lS#2&^}ckHyAi91NBU; z!(;i%uesE#GYsoj^uR9(Kf2rt?>^8fD0<1?2-XX`rUEoU+-eUj<`%Pf7WM$)ItrkJ zu=g0^g^K1hILl>w)M==x0L&cXsHPCLmFmFm5~?C{#VlAus$$V;6IWE#P24| z;|+X8x1mzO-hI$MKqw&V2y?4jYuGAL9|%YS@W-2=J#<^1p+zxz+SH{KC5blm77bz@ z9zeliW{5ygkF?aiU;@MGFnA??QXO3J-jN4UlwlKuL3B^}NWUtRpz%{(461}%t%E{e z3?i~?ydjiWE!RHgrXC6`4YU%YUE=nd@YFgtEk4`S;#pcBMYc7mfon}JkVSo%l~j48 zo~8q$G$i-_%37-2E#L~FpMqS2Rxmf+4QyyeO8`_^kSl@`orDr#6GEyE5{3e)mgs`^ z<$Xe=*bsWAWrKkFMG`V4d%Xb6IfngD zE!z}wSGgK#Yg?i`Pb(gOw?g&e@ml5lP=tm_*v~^yiwtr5>Pdn<;*2(MRt+v!p+d>+ zKH3x4krb?)^*m0`>kKrGdmCFY-*pgfswH9U5L8-e?OQ&bYR@(WkU1(G>QONUv{|M- zp-)>3ESlBu6}F2OK)EZq%h4LM6=Yv5mZQa|YZXxv>1XfBp>p8gyew;^&GSpE)FDP1 zaypLXL>r;5XF*^ZzJl@h;95&)?BtVn_e(i(XzyGCrQETbS)Lv}oQ`BEj4JO|*%Ioh z=%)hI$=B~jPwsAw{2PPOKWwL?6H9I>(c1rZQ<+G*NG$&vyM6#Iq>c%DSA|vS9#ZPz z+#+|wPbFJuZ6p?cdF#0JOiRPb$1y4>c!I(>w_ZTf$B1o<9mA8sXA)s}3|Zzct-l4Q z?vAc8>_4R_PM1bkzAmZ@^U)f30>5>jqmU15o7X<269CQD6*wA zT6Cc_wWINQzO|ye;kvxRqzrZE&xdbg+9+ zIG)jiz5IKdiTn$?dOP_o_biLMI%LKNH`yy9`Hk2difB-2@lgBLJnwq@8&ZjB;VK9i za5|@#vPW|8ok5q<0Xgxz|v z0t3<_vt|oETON^dkND9KA+upEMa<#^%9SSL!;-XyQ0LeDpZCY^h*_bU9-jGze~$Bi z7}C!^vg1+3^nX6GLwwEMN-TcBb%KJl`hNe|d*dOm;%w5=FB12MH?ZJNy6@m`{+rYD z@eh8g`ZV(P{G9pu5GGnxrW#Ab+LgrEF$aHtpY6x@(9g>!owEj`?)R{bt37+-!Gm6uI2AVoK$ZI>`t>gUu$Aq{QOLT-jkq86-+$JLbagTeGmY_2U9m_dI1M{daXfo0bOw4!j*n9di#^zadY@MY^67f6w9n`=8mneFqO4I-gIX zfR;suqbKG(m!C+^D=F0^z3+Sf_XrKYNa))%J}n!3BMP;=_s=wn&Lwq?G7K_H{vP}9 zh6lYBNihooPjLU;jS&>aawFy^zn?Yyk3zovC2NsE%mV+tR^Of9>I-f9DfM~k@avcc zWdY;^S`3 zmpn7vFA@mr;b%Fcn|ybo7et6Uk|pZs=yCJNnO5Pa-#DxuM*kFXhRTh$H}Y;W`s5I2 z@zwSD^O^z__;K}nx;BU)LI5AY()<2>x9nNtXZ+#UDNuO`|3Uq-RB~YkUHrr1Tf~;F zUnE5P?YI_HC%5T+dl;cApQ7PV@PjCbBt21(J|co1^j4O>@?<9(J%Om0uO`aG86^?1 z@FRiQTxI%gn%3wmUUof-dCoHt%Yuj^@)8aIjpHeP@dirB^nI~igY?hji+?GaI4R4y z3jVfE(hzfji{e^yoNT{Hw4+>zNP2iC3EW-v!pSSL-LcwMVuQ}3Gp+t2`Gc8)**U1T zn`P4Cdi~5SH5P#v-Lw4OpkEGv1st_Umi)8lB=({luU$*(IsT*V= z?p{e!x988g)eZZ5ykm^^{>8o&_ulB7ce6jUE)ZAK^6%B;paFPbfre6GKI4rYCQSM5^jzz)%W4>5b@I(LX5sMWXc&I*ChfQB0m@zp?w1`)tXX6v?cA znda-b$m>yN)jxRNWDNMnu`rv5$SVp0L#`YsKl9%tg7;yO#)$u!54khCt@WHn+32rD z@D~#QtS75?uU`_|42d2o=szF1MeIp^ceZ%__vl0&{N=!y&90EDvIORT-W@+Y)9SxT z9d%;4^k}wG_g_ook8jJf``<(8Jf{hP5=Z_c>|efxh*;Cxw?tldGIz|k*q*`X&&oNI zcU#gllT4^!{)aaCTdUvP@bttyv4BB=#lk%^nY3m;yxtB&Pfs@MZGu1 z`TzE|#MK%GUdY&gZq-~H)e4(c$`11G*QHH+rKHAniV7yWGQg+w-v<+?e><4`$z7i@ Sy#fz}{}0paSs3ou^#21vxL^4I literal 0 HcmV?d00001 diff --git a/konabot/common/web_render/__init__.py b/konabot/common/web_render/__init__.py index 77f000e..06dd688 100644 --- a/konabot/common/web_render/__init__.py +++ b/konabot/common/web_render/__init__.py @@ -1,10 +1,30 @@ import asyncio import queue from loguru import logger -from playwright.async_api import async_playwright, Browser +from playwright.async_api import async_playwright, Browser, Page, BrowserContext class WebRenderer: browser_pool: queue.Queue["WebRendererInstance"] = queue.Queue() + context_pool: dict[int, BrowserContext] = {} # 长期挂载的浏览器上下文池 + page_pool: dict[str, Page] = {} # 长期挂载的页面池 + + @classmethod + async def get_browser_instance(cls) -> "WebRendererInstance": + if cls.browser_pool.empty(): + instance = await WebRendererInstance.create() + cls.browser_pool.put(instance) + instance = cls.browser_pool.get() + cls.browser_pool.put(instance) + return instance + + @classmethod + async def get_browser_context(cls) -> BrowserContext: + instance = await cls.get_browser_instance() + if id(instance) not in cls.context_pool: + context = await instance.browser.new_context() + cls.context_pool[id(instance)] = context + logger.debug(f"Created new persistent browser context for WebRendererInstance {id(instance)}") + return cls.context_pool[id(instance)] @classmethod async def render(cls, url: str, target: str, params: dict = {}, other_function: callable = None, timeout: int = 30) -> bytes: @@ -19,14 +39,46 @@ class WebRenderer: :return: 截图的字节数据 ''' - logger.debug(f"Requesting render for {url} targeting {target} with timeout {timeout}") - if cls.browser_pool.empty(): - instance = await WebRendererInstance.create() - cls.browser_pool.put(instance) - instance = cls.browser_pool.get() - cls.browser_pool.put(instance) + instance = await cls.get_browser_instance() logger.debug(f"Using WebRendererInstance {id(instance)} to render {url} targeting {target}") return await instance.render(url, target, params=params, other_function=other_function, timeout=timeout) + + @classmethod + async def render_persistent_page(cls, page_id: str, url: str, target: str, params: dict = {}, other_function: callable = None, timeout: int = 30) -> bytes: + ''' + 使用长期挂载的页面访问指定URL并返回截图 + + :param page_id: 页面唯一标识符 + :param url: 目标URL + :param target: 渲染目标,如 ".box"、"#main" 等CSS选择器 + :param timeout: 页面加载超时时间,单位秒 + :param params: URL键值对参数 + :param other_function: 其他自定义操作函数,接受page参数 + :return: 截图的字节数据 + + ''' + logger.debug(f"Requesting persistent render for page_id {page_id} at {url} targeting {target} with timeout {timeout}") + instance = await cls.get_browser_instance() + if page_id not in cls.page_pool: + context = await cls.get_browser_context() + page = await context.new_page() + cls.page_pool[page_id] = page + logger.debug(f"Created new persistent page for page_id {page_id} using WebRendererInstance {id(instance)}") + page = cls.page_pool[page_id] + return await instance.render_with_page(page, url, target, params=params, other_function=other_function, timeout=timeout) + + @classmethod + async def close_persistent_page(cls, page_id: str) -> None: + ''' + 关闭并移除长期挂载的页面 + + :param page_id: 页面唯一标识符 + ''' + if page_id in cls.page_pool: + page = cls.page_pool[page_id] + await page.close() + del cls.page_pool[page_id] + logger.debug(f"Closed and removed persistent page for page_id {page_id}") class WebRendererInstance: def __init__(self): @@ -58,28 +110,38 @@ class WebRendererInstance: async with self.lock: context = await self.browser.new_context() page = await context.new_page() - logger.debug(f"Navigating to {url} with timeout {timeout}") - try: - url_with_params = url + ("?" + "&".join(f"{k}={v}" for k, v in params.items()) if params else "") - await page.goto(url_with_params, timeout=timeout * 1000, wait_until="load") - logger.debug(f"Page loaded successfully") - # 等待目标元素出现 - await page.wait_for_selector(target, timeout=timeout * 1000) - logger.debug(f"Target element '{target}' found, taking screenshot") - if other_function: - await other_function(page) - elements = await page.query_selector_all(target) - if not elements: - raise Exception(f"Target element '{target}' not found on the page.") - if index >= len(elements): - raise Exception(f"Index {index} out of range for elements matching '{target}'.") - element = elements[index] - screenshot = await element.screenshot() - logger.debug(f"Screenshot taken successfully") - return screenshot - finally: - await page.close() - await context.close() + screenshot = await self.inner_render(page, url, target, index, params, other_function, timeout) + await page.close() + await context.close() + return screenshot + + async def render_with_page(self, page: Page, url: str, target: str, index: int = 0, params: dict = {}, other_function: callable = None, timeout: int = 30) -> bytes: + async with self.lock: + screenshot = await self.inner_render(page, url, target, index, params, other_function, timeout) + return screenshot + + async def inner_render(self, page: Page, url: str, target: str, index: int = 0, params: dict = {}, other_function: callable = None, timeout: int = 30) -> bytes: + logger.debug(f"Navigating to {url} with timeout {timeout}") + url_with_params = url + ("?" + "&".join(f"{k}={v}" for k, v in params.items()) if params else "") + await page.goto(url_with_params, timeout=timeout * 1000, wait_until="load") + logger.debug(f"Page loaded successfully") + # 等待目标元素出现 + await page.wait_for_selector(target, timeout=timeout * 1000) + logger.debug(f"Target element '{target}' found, taking screenshot") + if other_function: + await other_function(page) + elements = await page.query_selector_all(target) + if not elements: + logger.error(f"Target element '{target}' not found on the page.") + return None + if index >= len(elements): + logger.error(f"Index {index} out of range for elements matching '{target}'") + return None + element = elements[index] + screenshot = await element.screenshot() + logger.debug(f"Screenshot taken successfully") + return screenshot + async def close(self): await self.browser.close() diff --git a/konabot/plugins/air_conditioner/__init__.py b/konabot/plugins/air_conditioner/__init__.py new file mode 100644 index 0000000..12f8e29 --- /dev/null +++ b/konabot/plugins/air_conditioner/__init__.py @@ -0,0 +1,91 @@ +from typing import Optional +from nonebot_plugin_alconna import Alconna, Args, UniMessage, UniMsg, on_alconna +from konabot.common.longtask import DepLongTaskTarget +from konabot.common.path import ASSETS_PATH +from konabot.common.web_render import WebRenderer +from nonebot.adapters import Event as BaseEvent +from nonebot.adapters.discord.event import MessageEvent as DiscordMessageEvent +from playwright.async_api import Page + +async def open_handle(page: Page) -> None: + ''' + 开空调 + ''' + # 找到 id 为 power 的开关按钮元素 + power_button = await page.query_selector("#power") + if power_button: + # 点击按钮打开空调 + await power_button.click(force=True) + +async def up_handle(page: Page) -> None: + ''' + 升温 + ''' + # 找到 id 为 add 的按钮元素 + add_button = await page.query_selector("#add") + if add_button: + # 点击按钮升温,无需检测是否稳定 + await add_button.click(force=True) + +async def down_handle(page: Page) -> None: + ''' + 降温 + ''' + # 找到 id 为 minus 的按钮元素 + minus_button = await page.query_selector("#minus") + if minus_button: + # 点击按钮降温 + await minus_button.click(force=True) + +def get_user_info(event: BaseEvent): + if isinstance(event, DiscordMessageEvent): + user_id = str(event.author.id) + user_name = str(event.author.name) + else: + user_id = str(event.get_user_id()) + user_name = str(event.get_user_id()) + return user_id, user_name + +evt = on_alconna( + Alconna( + f"群空调", + Args["condition", str] + ), + use_cmd_start=True, + use_cmd_sep=False, + skip_for_unmatch=True, +) +@evt.handle() +async def _(msg: UniMsg, event: BaseEvent, target: DepLongTaskTarget, condition: Optional[str] = ""): + identify_code = f"air_conditioner_{target.channel_id}" + function_handle = None + match condition: + case "开空调" | "打开空调" | "启动空调" | "关闭空调" | "关空调" | "开关空调": + function_handle = open_handle + case "升温" | "调高温度" | "加温" | "加" | "调高" | "提高" | "加一度": + function_handle = up_handle + case "降温" | "调低温度" | "减温" | "减" | "调低" | "降低" | "减一度": + function_handle = down_handle + case "炸空调": + await WebRenderer.close_persistent_page(identify_code) + # 读取 boom 图片 + with open(ASSETS_PATH / "img" / "other" / "boom.jpg", "rb") as f: + boom_image = f.read() + await evt.send(await UniMessage().image(raw=boom_image).export()) + user_id, _ = get_user_info(event) + await evt.send(await UniMessage().at(user_id).text("空调被你炸毁了!我们重新装了一台!").export()) + return + case _: + return + + screenshot = await WebRenderer.render_persistent_page( + page_id=identify_code, + url="https://toolwa.com/ac/", + target="#kt", + other_function=lambda page: function_handle(page) if function_handle else None, + timeout=30 + ) + + await evt.send( + await UniMessage().image(raw=screenshot).export() + ) \ No newline at end of file