From f7bff02269923a0b0a18f181e862ae381efe0e34 Mon Sep 17 00:00:00 2001 From: bdzxb <1443103348@qq.com> Date: Thu, 14 May 2026 23:53:26 +0800 Subject: [PATCH] modified: __pycache__/main.cpython-312.pyc modified: _conf_schema.json modified: main.py --- __pycache__/main.cpython-312.pyc | Bin 9997 -> 20549 bytes _conf_schema.json | 6 + main.py | 317 ++++++++++++++++++++++++------- 3 files changed, 251 insertions(+), 72 deletions(-) diff --git a/__pycache__/main.cpython-312.pyc b/__pycache__/main.cpython-312.pyc index 382d3f538ce6305eaecffd1e7b2356322eb9e536..bec6fdf20f8a41390a897e02ee2d6dcabe56fed3 100644 GIT binary patch literal 20549 zcmd6P3s4)^x$tUNs|N@WAV9pAmqE69`4KzVwT*fB33mLpf~^Wx7|9agm1G+txoMiY z!40W#o5aw>0k=s_8Yd<5B@<4!yi@MGrAl-ll}+~l9>|9!jK zl^}|J^Y`AFuFd(*<2z^1Yrpfoj((Svlt93<`Y*I&&qjjy6cf^;ibQTZ6a;aGU

? zBwPx!LVq;;uxDw5YE}dB?;3%C*u4Hqv zE5)4R(wp_JRCB73Pdn3G>E?8o!E6xHDrbf()12wbGH1DrW}_?HobAdn=eTmsxk9}AP`1>(lq76=Hi=DU zQ&{~=N^w3(VgtdZK1#4@wv?ABG4G|wRGXLek*4&Um@+AN<#wy5tJOsDs!qGpW8-*z zz1zdJ*xYXGAzRZ?TepYTMN$o2R(rQe$*UWBx;?hz9$vZAW94`aXFFtfV^uAtclGo* z-6oP(IeQKrg3KgrZ+(vkDs|cqnN+;8wTHFw^mZq-#?yQ3tgVNqH@V<-JdH(eUfFDS z+MtPKH*~|^(``9swPX9KJH<4fv;D{0E$w!vlUKL1Ld2_siMjFm&t{%@>iS!wH(oi{ zPuEt}RMm7urcVIsNa8l^N8&NqxHiJ9U+LWEj0isjEQjhA)G^>1+FsoP^ zLNzRw#*r4=lo)4KFF{NAuxeXEC&_9aRmuyN__ejPv9;I)n~qJi={i-cE~a+U*Vayo ztqt!?j;RIP3Hl3L3PL?@C37llCLNn@OY2m!hDVj=bT$KW4Fkk+g*jsW;`3Q9%Tjy(6**g%M&L|8D;tf~OQ`2#=7B!AZ(TJV6tLPa$;;_q&g*6Lni8r;=i-Djp`o_2eCVK-|m7JIkdW3ljbht1t(=e0JM)$X)--5j52 zb9F$p*XlmT@(E(X?y~ms$u9BBw7{2k$mZeI-8~lgaBWhJV_op2@LJ4*+MX`15W2^! zI=ZYLJIiaab3#6^aUXFy?H;QYK4_)e=IrDO08>QQS}Yw-tJ`g{xN&Y7hI#mlwGDM` zyWKX<-DYK7_U^W-$k5ub$+pON+rB#9HW*WPRc{|x3@!DiN;{-V8hjaKyAMI|`NPB& zP0~qqP-7gR12NR~U^8qAJZ0SMi@`UiK3#?WayqeY6OVDUK1xsr|$; z#mm$&g-PYL!w6Fj~~5u>W9;B{oBmI*_rc? z&kR0&?ddnKz4p{!-X5HJ=SP4!{Y%Weaq$Ha?fR>4O~3j0%+L>}-~RE9$9_KZ{qY+U z-@E?gI3frYKYjQS4);obVt<#l`w(mGu43E2&{fBztQ9~U5`gN?c{(^7FgT3I+U~S5 z_D-g|$HUl;1MlNzq`DPfsU)>vXBm69hw0_u5aIfm!?r$V@z)~vTKfPWIXvMd-Cn2j zuE?IgUg+0<86EkwZ-Cxyb$t_bPtP}K+imavCT+Vt-fz%&d(ZK2fPG}%j3>lh9@AHH zTTL0lq0QlE%He6sEx^Ep0e+wyQg>bl%z{NY{aHK5EygUe$IYvquz?_lR|7i$oWoLq zbL+Nsc(_W$QFVAZcMlZXj@vrCz*zu4=W^LSz_K`dfRWZV9q+L9dVojgkVoQ`_U_Ie zlai~#gvf>98@T&1t*}Y#1jE%}MgsCbcB>Osp+8;b45FD!0m*#`&et2r9n7kToPyEZ zk=$^0RWQ3sIOuZ=M^}xk3g<2f<}Q)=gV9AJi^4fI!JHaENdeYiK1x>ZnTJk46wX>0 z%vuO2x+~em;q2;Qc6FepF_hhe4PP;2p4ohQ^H}Nms&iF=qS}yQ>45fXqH#ryA~1`qZ@9Vj?x;Ow#G3u)Z{?FP+p2D7w?SCzGf22C<+ds4w|QUv?*% z$S?hx7e3MQ`JE)fkbjF%CM~!EFG)O|IDBNdV^UutH*?qa!}_wIzU<3cE!6+N?j<0P@mDPA%0tEg2ek8?S|#l`>FLg8)~Qz7SWLPK@AOAA1tRJK8KsaZGc?L z?O#F+SbLc}ipM>%z<4`k?gVN@o@YSxKTEw7sVkf#aj^VYI}V@Z2L|3Db0Sik1OaCk z_D*;lG7~T5fY2eo(XaF=9VyWgDdto7$d@9V8}v%)NR>*Wkfm7V1v&vFzM4JHhw$s}G15#A(rV%HB}#9Y+iy0@pA_Y`eDdtW-k}ccg&rrv$dJy@dwj{P zao)&Mj#TQIU+-BcAv%z{&wCuT^D-dm)3Z6TZHO)U zQ|~b|xxUo7^WaO3zbimK$i7EgdGp&Ee9otL;JGsIk-iX^Tk^oqU~bcVX^!RdkRU1H zvH6GLaOD((M!XheZLw+2t0RP0zS#iG!k;mdHWji#lT&mgyu z>C1G~OZidA7Rne|uo?`m23LHKm28OCmts;~skLs4go20bQ zDAkqJk$$5utC2VW`^KN`%XVy$Fr$zymg@L(d^v8#U>3+?Nw61MqBv5_mxC+qGvdy< zP$KRAevth9NrOw31gydX5^faAtCZ(UcdMlq*s`;fKV3`2%+ro&9VzD6EybeHC)0|^ zHzZICf4&F4R`K!Wd-h3jsh6DIm+yE`%8f#~clrMSbuX9au)xzMwe2`4#iGz*jmD*z zFJG!HP{=|*<0}A)Ug0Z*xn^LlmHr}MI!F4_{YCd^J5$&zV5L|TvL@->z9MO+ z{KdW^SpUu_mJ~axaFaud!B6hd3zYNbJwADtxgYc#mI{5v>_X|S%L#vpuf*Y!%A)Yz z-?lUcL2VDpM5)bto??r9=`hoaeMLCS(N_^J>rxp>pscCBasICkgw%6F3-!z{MORK+C{LvkF><5`S(CU2m02qL3~1<<31L zK*A^dxhHbreGA1{RK}mz`ms5!%X+wI>)1BC;J@qZhknX@CGd+hV?{rqzoKBQx{%!=U@PcJlm< zbA!zFk+){vdFtjPuUvoSRmO{KHZybjo$0rpXL#D)4MOiOG{%6kg6-*c_Vp{7Z9ABL zRr8K*Ele@5DDKykv6W@*m1X;RMMb}c*}18;p@}K(r@fxe%9Z`8#mvU~os8Avv37L1 z!1`qJqF#FX(hJwd-o7?|ZszS%bDrT^q?rJP z4fG8ZHeS>_&YU_8(dl3O;`+!tGeb{+O{b3Wq7w7k(6iTH|H-wp=b*Tb;mK`VyvTQo zRmH-Y=U=_?`U@Ox5c_}p{qKMGW)|Y zO%z+ehQ;-`yeJ=tm~u~`1E_Dj@y2U6e)ZVRv(L#ri^1TvF0a#L2V)glwp6UG1MFh` zRCTqLS2lwBq(A$?gKgerwwjvCHt+qN*5%g9{`yLDP38Ru!1l%2I>C6R1Z$UvPe4lm zs!%*0FP{FpcQ~1APd|o}a_R9K@4O=p@-J`y_zPuKzrELsswpPn#`&LKdu~X?gK4{d zenQ0SSC#EAbCt2YqEb*|@#L{~VLHXeZjL-EOeZXQ&(l;GtZ8L z=IqAL{^j~JPs2pO@f?}aYXd_w-yf|rtq}AG+!`pDr_tcmXaI5A+j;G#miq0>EIW5@ z9XrCJr0R)15X?M;A{ECK*0WvbuD_qFUPfJYb}R4QqG0d97ekw6ZM6E00;7 zhj{~PS_Jb~x5t9U4^)qd`WI;}_(a43HJh`?%5u1^;6)zJX5*C`ceTK%j-%;@&k&kM zb(zJ}gUTDhyvdWCji=n+b_hHiPoWhkiS0Sojg5)6Aeyzcdk^vY*q#WzP<6p)i8fhr zM_D4t1iRbb?et1o#K%H<&{ETG{P)_j!#f0am%n67+}y4i8@GL+0zRX zNFU=si)1Pl6+mFP5)E4L%49JP7A^O&9**S`aWySqP=X2Pm52vQCYKYpDy~mAb~90! zZZJi8LDfWidRS{8pCYcEr2}8Y*{y)=v3k62UK3d(jNx1DEG&*RP6lbnGzT^=ixO{eX&i{pK)jz;+4W`sjOgzI|Q#0+QtRqX(5Z-hV!!yF?G^b z5VwIi@vKD{r&bt}1(#9VZgtzlTnbI=x^CMsVKD_np&!CJC_3G!=#%uicY~siO(P5v zK9d;Ro0QcKExEr9b{;gnC7((>nHb2dohW^y>eZ@1ZT)3qz_58zvjtUJ2TvayNVuAv zAI>%fvrPlZS2J_Y^qlS)NW7Ag8BSq>Da=@ID5Y{h2}(L`(y4@#2}58(F%4I35(4U5J%hYTwQnx>edu|;F~<6RTpH+--9E^i2}*c!O6 zCD3v(P+%F@c14$VYRkzjft;0>GA^kDYqtfA+b4Ao$k|ono^bWrVD;KS)w+POep0tS zQZ!sOUKpsjKVYnz)UAS{Fr}jpjXX5IY@#q!SRd5o4KxpJoJz?UHhllYmCV8bvn-Uk zJfL3=m_}wyfjY&CU{*yaYvK6P@nwOmnt_e-J8NGrndo}0a-vXt;gm6FVAGY{lCh29 z+{z*K)sph_b?55F4^8xkO4bMU%#dnWGnJk_yz7T+rVM%0Oj(#&8Dv&oGF~ovGxzcX zZx)1@jYCOyR77Fv=;4vW;ew^Xf~6BJmlp(=Z3-1^{;)P!&@z-5(Fg_#swWc1{eir1 zP3qT7r5B8`;RW{v;eYylR}494ww&G)C|-Lx@sdBlv`iXW;f1prB7b2xuPT^V70#;} zY6LrLIIAX@RTIiuGI971S@ly!25^|taA8fbuqITvWN72n!jf=db+E8{!V@U04i&B* z+BluZjBXy;Jk&Im%Z%2I)Qxq8a;t|LujUtxnn%pzbSS@SsClZ*9&q&jq3y`?^!dbd ziD9Nb$kbo13~b*MT)%gc*%vtW(9otUO!0ZuIaQ#%;qs2(r+>KrcQ66_hBkd%R6a~! zg*oUO=^M`s71j(Z`P{-;lFDyYOfjbMUE#{L!OFFlcLnx!1opB4TX%r%3GD6-RIUwG z9tko>&S_^8iQAd=! zdR!THH2u(0uR+vV0k#m+zG?Ygy_B=T!-l@NJVvt@*_Vz!+k^o7HH z!IX-Crs6+uSt+9Y0mbKcdKE-z-uPkahg6_)YnW;I1Jm;5EQO_C z-dRHAmE9sJTrXq6XyQoX*pac0fT{7l-5<35x-Hyf2{u^*-|h-E!R|X8Y;p!%-NB}w zKx1#9^vI-p6BjI6{>5hr5D7rUy&Wj+Z;Fz3r6l~`l(WmA`Co z@!oXpq(QrzNSw?!?pl>Nxr&DPN0fFirTs`(zj)6Y?H||BkO-^jz51kZI=we9iC6Wr zT{dSg@DAvv)e$Snpa!0TjN2$!i#`o85>+fFl;ab%;)A40f(BjTfOBE~<0GTi-ItY5 zE6yksL?0p8P^ngL9-z;a6~qq8@_~XeQBnSm*Y%sf!T!;R4a005b$aom*=?0 zjpKkbPCUv~t`5xaPmxVUV#a>}l6w&Zf|+PKa*`RAOlp=+rDYGOr;^jd$xJYr8QU;+ ze<-;Mj-~$;EK!eC)hKw<VQp@f#C2dxXjJpOO!iR>GK4F0@F($#=m zk9`)Nc$SUWdG^TeNb#%{py!C=mmz5a{K~t($ES?06j~}k(+2FCpN3YE>7MuaXdihY z*0S++SX3<5;6f$w|1$=VU1R(+U?j4oC7xGE{CR*jlu?@i_;_fgApw3I3qm{_i?n8b z73dXlh8kdo)KDKe@_CO3g(u;O-ydmRSQ>R3J~f6HWcv)X4gN5GjZYQN0^`$2wH3r* zhLRX;0)1utQQXB-1y)%V&(0x@63XMZ8OJsfQ%B(_62~1IvTCWvKm|yV3V@cSSi%7v z&StO<-StaAD~;63|4iQnZHV@Swhwhf0HYcqfnsS|1(9D=Zh*u)^Ml7liPuDOLogDM zNWLMyvwaA-H^Pmt5qCWV0LMLB8uS7%YaePs|NS&L=*onL{|M>V+J3BDQ3wb2)D zT>QzkXCDU7klGqhJKT6>0<NYY$i>} z-(holJX|kk)nTv-1GFD-Kg9s0TT?=ms&eNscN_yWCh(Ni+QCy`;K!Jir|caa+&U<5 zBiR9>8+ioUE7a{zk_j);%Ug)+0$M0p=YjE0I+-+>BFPelib;)WTC}7Odq#o#2pOse zw2|VWzM;dDnv##x^9CBGbox`9P6E?2t{m7jsjG~p>48lj>8fUFuyNnf5E)r>tnw4t zfx=ZG!)j>LSU8$8k^(%>nitmu7S>%_9H`h3GHwK0e$rHO+9}sbS2(#im|Pr6E*+p| zNu9R%<1A)~LhpdpBdcFepV;-r!B-E4YJn#S);WZe)vchSl z!L-sqSz9RWz@+ZL=T}mUz?^7{CBdV1xMWgS7{7(Et|+K0LheFa9I??iEZR_}c(-aj z)0{%QpR9ny`zhKD#nk)8dSXL9^?{LwtPk>O$oim|f;g{{_Tn5q0nd2heET8r1mBcT z@W>oOJS#j<8&nR?dw_0}bLh6*Mtr&&@3fMrQ^Yqlmd+gIFCltt%CY@E>|h-WQng7>2mGPrmam4yO@QHplgp8avbj9%vPsSIyV1I<;8c`}V*hdQw*{(lBL|hAkE(EaSpS-6FaAUE7rin-&_05VrcPCbi<%IrVi78N|B=1ti|h z&^9TlcMIzC8rM?q)zOgk-dY;6eygM)K8Kv4(ifSI+ieh@Ar28pBEZQ=ag^ZRzWXQQ z1DE57>rO-f0CypoI7P2#04w(+VC9t1HtnP05LSLNIWqnuERLsCLu)jHG-2IgOd^y z*T=+DBk{DevH=`e*{5(6I|(*jnEy*L^RI{I=RGk0h6@>zdGw!h?lbQ>_rjdaoBP;# zy4T!i;XKC7y|5N>)8Jej+}V^<>96?S&9;C+lCD$Eh0UL7H7!iD@E4AFI0fF3Ub zusb3+!kQRnPyN>2O*<>fZ{mm7Pn9n&=Qcr1u;krD-N#M*!f)coeG>)zo5-i!L}A>E zyaeDg*PgsM{T6WZXUk16&f`w^O|;|zzmFs6Z~vd`yxeyrW8OruJ2St0W%^g=%1tI6 zPxi8+an*v8Pjlcx4%Q%NPY18E_JRe5732Y)?C#~&onR62+_#KRKx3>$kQW4@AbQA| zG~z)u1n=RMeO`wbWC0Ht8QbkvxRmGaf*kG@C=`z_oTrFXAgW}zmm#NLFN*~cnWO{{ zug_rw4--=QuAEu^^`J=iK-Sa!g(|@|}BV!dX-%RCf0s&SD;L2wSqNJI;Ll7It zP2^_?9w7fJ0w~%+?j-^2BllzG4$@5C#hn%1a8uUC@#{ADJOXp!BMggK|lE&1na1J3Q+pV^%SJ~$OZ%rR5OAN)K&y7 zR4bxvptfOZJGC3Z9%?UUZm0H%W&1F*nc9zFKh=id0M(A5gJKco0A<5e8`X)Rld>al zP(28GsUwKupg2s~DK`Q)briud%7*~2L;%V$>N}V^O081@Sf^}2(5T#mV6$?I5>OhI zTQSw3Y(dbXY(r3VK>0U<$T+B+O(pUd%q}Jj#(^!f*+iOgcx5oP1Q*(M+BLRuV*O<1 ziXrOCKeEP6xW>)oMqJ}&ayza70_ep9QbTIJxYS#fdxbULC#>=Q$Qm1jHU4}WrB_m$K395$)R(g~WPjmCozkQ0 z*EW;HyUO+70`NYmZC*sZpP1RafO>xc4e>cwLIpi5)Vcj*pwC}-|IVj)2CmJ(wne{h zK%W1=L8&-P`YG_-L+U9!@pQ0JKW_yAxUxPJSP)8LK)~mV_Q;G|JPDCi0&5pX_LQW= zdGZz=PYU%>z~#zEo^`Vy)&a?0UGf)gt5cNgwSHU~c4@Y=BGQ$z?E?+14l|JQ2HKP!`;O9xQQlA7SMd;IAMj5X~ z&AnTcZi|_;tsJheAe2VjW1fFgc5R7OCGx;4AnSGQ*$dY$oSUa!?1KWH>OIDKiy#&$ z6g*(SJ0{|uBj^)vJoXy+=6F%q7qKLNoxzzOjLtkWhJ~eaAvhFWAAfxM(#tcC{Q_>V zU3>QD*GFHt`Q*rrix&aWlq~2JMXwIpbr>%Kp^h-h4siG$UW1Gl-r9kS$KdZ_?eL(O zi@u}0ZilxUJ!zUa4lf0PZW0|N_=NRV)+}DL2V)eLnN(56g8MmEP3-Y{dcElN0+*84 zVGh~f>BjviaulL8d;!uiYDUz4ZboJz4+3B&(zDL2IlX4=_?RP@UOS)xhlJ6Bk%EA+ zVq6=@Tr!XdhF@)y;)*W$)W(w=haLpg+iZ>ob5e7MH;#2orY;etKoqJ=!KWm4bznVLvj6i8k8`JG%s zpAGg%;jOaM0hmey#)h!2F{o=qdnA;8@mY>oj)MD-Xk&uGi z9mrieQ9rRXki8)-t+6qWdM{50 z@%Ji>O*Hjenud6MQavG%Y9OGnHsTXc^#nv!5j7h5f$@lESd@q+n9uHN6pJeMfOx@C z5P7doibbJM3GY=({+|F~=j$(JRc+Un7WDSCW@}BuZ{@p@;U-a5Vu!RwX#; zLJSTpnG8nz2v|S5yE7<|J9;h&zQ;J?T=4P1K_$(DUv-c7fr)C~`(%@oPX$+nP<(<1 zjz=}DWE|5Gn{YwC&H_3RAo*bS(tz{6%BOY|iM3xZYPx32?k|EKB1$_+kM!A5~?W;rL`ko8(h=njsOm? z=d5<{^y=*4!0!tk*J2#%mLmdquN}4lZFY0QDWO5fFr>&F>ITFcow)Hu7fJl=IUiQj zp*x)9cgXoMKh^-W4KubQBJ{`I+tUpmbvoH=5O08rw>5<~Ekrz}K-y_xECRufYZu}G zwLeo90Oc03vKt= zy5tCgp=Z-nO4_g~m;(16vxoP<&B-C{RElv-i9RC1l*KX?ubIf6NDSnyp45NqN?yU} zhLH`UTSvBnD@$J}uYO1?<1L-oG_fj>w{}v$?n*}XuxV`9WX8gAcB;q}E&}I{`!1IT zithVk(S~7~&t;}WKc_M8M|qWCA}Zdf0Oy&^MPMQ_76tUoRek1XNipd!!6ipLHVjvi z(uEVk@P+$6n94^QRy6_7{4HbSa_YUzbPN}2A?Lm2bmLm}dv!{TucaXTEvaqFpnj{T zAr#G(D$eHda@}(jqDp8JdEwhgn(loYQzB*H{~_>xXsZ!8ZY0~-*1X3d6YFRJC{d|c z!t0~pw_#}S^yyTDso4gQ|sMC0bJLOMdT2ulS6tri;)n3{Qao0HJ_RxXVBMCRmw4?mG6XzgL*j)BN12pbj# z4GRN{)`kr02DDR#{IQi|3&)!R%#xsC31nze!x|&}LP}Uu5Y!Zmc_!F^reIRD3buMc zW8`X~r8&28kz9Wd{%+$5Dcbk?#NRw1y}Kdn?!S3(^T~&=UHoqB4<3j=a&Y4(FU>r0 z3B1RvXTJL^*txHr`8WKz1L21b;P(w$UqnqAPLvylM?}1Lv_PNWhhspQ*PkZS3pwwH zI1$JVg1zIrM8rLZR4VXSg)D+!Ec{MRg2m$MVZHFvHWprIu^jPQ1%F|Sg~NTw;TVOV znc?a&CRo;S)8d_Wm&*#eF;w|+FJRD%!FdeO03+zJxCA4s zgS*pWfw5IXt}wQBBHM~*B!@u>76c*q{83_7nMCK0sb>iQ6ZwBba4WBpZc*Gis8~nR zrrBLeLS=Y(&tnI_dvIuDh|UM@m97pHF9E|aU45G(R0To~=(=RI#t@x7OXkqU0j6e_ z0C)+cdH|P~-a>$ta)k2Px@e`%AsSpF^-!sF*)0MfqA^0wtx|?gztyV9p_>%9)@0I# z*=0#|_E-mYI(wYGjo|+jC}>t8>@8u8rsepxc)Gw9UzjMqF!HNERUJK?tt#S94G9+< zGx$yMN-O{mZ1Jay;CzfJYJj-ttgpncg-?|lv-aAn1jjZG*HBzK+#tL@76Z;uPF#Id zPI4&Eia&Ue7=tBBIJiCdf|zWnt8i*cUFA?2z$IgVqCKa_AQgi&2>2qYwlGrR0zlQ= z-zeiyn&*($<5i%*1{FA$fjRiGaqSo^$1?bRC-70P60Z>G#DwH|?mX^t3YP-7Zp8l+ z;%j>QKoo*sjS@vPph?}V)y=(+2%jQ?4sLtEf>a4t1_V}tr@Or__}!)7A&y|o6m}`7 z(Fk!ZYp|YXwfIG?;ZooW#Lv||2muH!3dIaz{Di0q5mg@(DIXJwpAZc}qTv%_ZHQR= z2~qh8(Gn(FJ|igw`8N%rFn&xVe@f^+CA2d{{-;FRr$qXniHwg4E#&{1DEyd6hgSYf v6n#vjLC&8P+CW0Vn09>4r4=DX!yU4R203BCbDIz%utCX2#b3o-p|}4JJ#?T* delta 4238 zcmZWseN4Ap!dSK;lDSumL+@<1b=t9P?pF+!nLVsz)jeec@m1&~(|$q(VR}m>5EW>o zDU29^Mp}4>+)3!5iRy0BvB_vG5gr?g39O_Uj3wCcNJJRoJZOcaiVj7D7!x8Tl4lcx zoEw3h2fj?5cKcB`ZSKC%n;1PC8?FzBW5Y2a9QGlYe;fu?dp%C?B)LS}R_yyG$tmMX zsS7o_l~Ttf`9<9W027^d#TtM&RClx&i0>%Jh>&7524_0`@aNjj+MW<4o4^II${;2c zm{cM1ErP2-4NoBA<95JOV^F;V13dt?8M|uUqHQ1WDg<0?t8d#yL@{6C!^eUT{lRmn zcMbLy`=HA-qtXD8j2A&dZGr!0s(?N1w2A->8Mi=!E@`tCR)5sCgDX^2aTY`sPa;7X zilId6dBCV%P+nC`X-R}Rq6_Nr-7jaFzNQCv%DMG&{bhGL&W zB;kv+buaRh>V{%d-=|TVp)(qhW_0J8(IMyrZ^05qUyyo{5H*a!4vkuGJ-Tne0Ww;q zD5Deb3Cf?SE%3I3X~mP@MN;)>+ZA}*UOi*_m_Ea{K4QuXf35L08<2b?i7{hZkd-kC zm7DSbh-1He?2Ff7`EDaq{P3zx8JQxnNZxse)eqmaSZs_%EP5WipeP6@z|oazIDGl!Ju|w#Eua-8{ZQCwiJ7>lv zI*^4P(fK?OUC1|N#x0sTg=o&W$yLV2lU= z0mnf33j(e@;}f<6N&GW-kID+s2m20N%6y;oUC=WigW54-3~a6sRl--Dd*J^#o$!#W z4}Q&6Q6_;jUH$m6BQ1@Mp?g@EL>}W(NoaQ09X%ByBo)I(Qw*C{?%WxX$RUm$L?AiJ zB?L(q5rjx|D9H{B;WSyFj*X5$P2GLv_T3*pv(Gn*t|Yko>?`Y6o_l9@hCeY#8Sf&#iQi{ z{I=(iielI^qi5k=PhXAlEGtL`CN(ykNJW@1&QFpFVc}?MSipxNTo_@wy+{ST?5*jL z6e(UJaRNiiusn8L;BbnT^mq{y5h7tG#z`u6JjM%%H8sK_)ngpaeiX}bDK6yaYT$o) zn_#2Q1;f4(=O^Sn3ml8gj)aBCSze;#mTQF{`aVHP1k3wjpFgNTxhU=Tsdu9V{BM=; zMSu4Md7jWzujoxzPFy-Mot`_mq;I@~MxB>BudA2zzB>-zZ0$_#8|3ow{x^^JFIJw; zAMc+Z%$D9%Wyf!xf3s?D?s$K`^7Nu(V6t<~>bll-wF~wvTWj;y+U$u%YxCsMHGMG_ zmvgktm&~hkhfd~fy-WJORT+j?%1cZeH1GvypPG3pyZfed(R%>CQN9P31O}^CES@=L zx$==b`nNo?YIR`oxxnEC=Yk>U?^&{*T(P<~Jn#pBPfx74{j&oz16gX(T|a&7xBjZ@ z)E#ed_QK4CZ0VwR*L6~IdRJT(S?hABH6LnSu;xOoi=ktA*Rh$$HZ-8TdP56>)ysjF ze4u49uy4BS-D2Ph-l^Div+NbmEze>@TW;5*3&C8V<2wBwVhw$B|BL&V0|)Yf1M@?R zftrpthtumaF#VtM<(|FIFA7u3z(2Lw&{2#CtSw zd1jkunr9Er9L!cPIh)qp{<-RG`;xoiX5Wg-J=-zUG1tBv+?@~Zo^Q_ucP|FpmRyg% zuLPdPHJfKvH=~;yzS)2C#5}XGf1y4X?0ehx_|`kF*^{>e` zjOFwcre>=Q&!NWuLR;^HnNf{Hj?5pd67@+>A-5Rp*L8Hptlsk6ekz*7kE;g7@mWv;YgTlv^{ zGWkP<^SH*i3;yysu%QBF&TGl5Ntl|{tr#uKMql3Oo9moAuxMH<5;WGbWr)pQ<)eo0fvSg4S|m5f?cqvAm& zs^VVx5Y^Ck%)g&};{RnO5n)?-McX6i3KUD(NRq=v70ME{9MHn4bMW@D#)^Xoi~*lK zqm2%Y4xbD2u{0YpNi@fvA7xSKO#Ony>-$CSaVAbNhw*Tt>IWl`{$gI|G9QWp2*$e-du{Qfywl!yMUZPu84d&_4^;1iR@;PS3?q9ao z=k4|L!JNJRZTq2VO0w8ithQ?>uAaC)`j)lgT?#l36DX7z11Rd5>^a)@zV4yHhkP;G z>rcvU?RMo)S}pBbyYi=Ysy(3Q#%?o@H4+opscEV{&ZmY?g%px{B*LLw$8%poc!-n} z8ize6sWxXMDtQjM4o%6=Z46Io8=MkV}iw@p`9P!-y7`_1kqLU$MgU6_QS>155} z=TKMDqhbveLG{Qj{MO5nZTuGIlSRlumfoLCF{26gAct>0t_1lsymhj(`46DzKY;%? mgeIr;%xSU*QC1_4yr(#(Bp6~+cmU)MGEmV@yt|3YQvVJU+jE%! diff --git a/_conf_schema.json b/_conf_schema.json index b406af6..f3805f8 100644 --- a/_conf_schema.json +++ b/_conf_schema.json @@ -25,5 +25,11 @@ "type": "list", "hint": "只接受指定发件人的邮件", "obvious_hint": true + }, + "no_file": { + "description": "屏蔽附件名称", + "type": "list", + "hint": "屏蔽干扰附件", + "obvious_hint": true } } \ No newline at end of file diff --git a/main.py b/main.py index 43e4347..9acbee2 100644 --- a/main.py +++ b/main.py @@ -24,9 +24,14 @@ import email.utils # 导入email.header用于解码邮件头 import email.header from lxml import html - +import re +import zipfile +from pyzipper import AESZipFile +import pandas as pd import requests from urllib.parse import urlparse +import numpy as np +import sqlite3 @register("astrbot_bdzxb_bill", "bd_bill", "账单统计", "1.0.0") class MyPlugin(Star): def __init__(self, context: Context, config: AstrBotConfig): @@ -36,11 +41,13 @@ class MyPlugin(Star): self.emcil_paswd = None self.emcil_imap = None self.message_target = None + self.no_file = None self.emcil_white = None self.file_path = "data/plugins/astrbot_bdzxb_bill" self.config = config self.chatid = None - + self.data_path = None + self.sqllitaa = None async def initialize(self): """可选择实现异步的插件初始化方法,当实例化该插件类之后会自动调用该方法。""" # 读取配置信息 @@ -48,7 +55,46 @@ class MyPlugin(Star): self.emcil_paswd = self.config.emcil_paswd self.emcil_imap = self.config.emcil_imap self.emcil_white = self.config.emcil_white - # 登录邮箱 + self.no_file = self.config.no_file + self.data_path = StarTools.get_data_dir("astrbot_bdzxb_bill") + file_list = os.listdir(f"{self.data_path}") + + if "zhangdan.db" not in file_list: + self.sqllitaa = sqlite3.connect(f"{self.data_path}/zhangdan.db") + # 创建游标对象 + cursor = self.sqllitaa.cursor() + # 创建表 + # id:流水号,payid:账单号,paytype:交易类型,paydate:交易时间,payname:交易名称,payto:收入/支出,paysize:交易金额 + # paystu:交易状态,paybox:收支方式,payqd:记录渠道 + try: + cursor.execute( + ''' + create table if not exists zhangdan( + id int primary key , + payid text not null, + paytype text not null, + paydate text not null, + payname text not null, + payto text not null, + paysize text not null, + paystu text not null, + paybox text not null, + payqd text not null + + ) + ''' + ) + self.sqllitaa.commit() + cursor.close() + self.sqllitaa.close() + except Exception as e: + logger.info(f"{e}") + self.sqllitaa.commit() + cursor.close() + self.sqllitaa.close() + else: + self.sqllitaa = sqlite3.connect(f"{self.data_path}/zhangdan.db") + # 登录邮箱 下载文件 def emaillog(self): logger.info(f" 开始执行 登录邮箱 {self.email_usr} 命令 ") # 邮箱登录用户名 @@ -59,53 +105,45 @@ class MyPlugin(Star): imap_server = self.emcil_imap mail = imaplib.IMAP4_SSL(imap_server) # 使用用户名和授权码登录邮箱 - try: - mail.login(email_user, email_password) - # 查询收件箱 - mail.select("inbox") - # 设定查询条件 - from_clause = " OR ".join([f'FROM "{sender}"' for sender in self.emcil_white]) - # 获取今天的日期,格式化为IMAP要求的格式(如"21-Jul-2025") + # try: + mail.login(email_user, email_password) + # 查询收件箱 只读模式 + mail.select("inbox",readonly=True) + # 设定查询条件 + for emlname in self.emcil_white: + from_clause = " OR ".join([f'FROM "{sender}"' for sender in emlname]) + # 获取今天的日期,格式化为IMAP要求的格式(如"21-Jul-2025") today = datetime.now().strftime("%d-%b-%Y") - # search_criteria = f'({from_clause} SINCE "{today}" HAS attachment)' + search_criteria = f'({from_clause} SINCE "{today.encode('utf-8')}" HAS attachment)' logger.info(f"严格模式: 只处理今天({today})收到的邮件") logger.info(f"发件人白名单: {self.emcil_white}") - # logger.info(f"搜索条件: {search_criteria}") + logger.info(f"搜索条件: {search_criteria}") # 执行IMAP搜索命令 - status, messages = mail.search(None, from_clause) + status, messages = mail.search(None, search_criteria) if status != "OK": # 检查搜索是否成功 raise Exception("邮件搜索失败") - # 获取邮件ID列表并只取最后20个(最新的20封) + # 获取邮件ID列表并只取最后20个(最新的20封) mail_ids = messages[0].split()[-20:] + logger.info(f"获取到邮件数量: {len(messages)}") filenames = "" for mail_id in reversed(mail_ids): # 获取邮件完整内容(RFC822格式) status, msg_data = mail.fetch(mail_id, "(RFC822)") if status != "OK": # 如果获取失败则跳过 continue - # 解析邮件内容为Message对象 + # 解析邮件内容为Message对象 email_message = email.message_from_bytes(msg_data[0][1]) # 验证发件人是否在白名单中 # ======================== from_header = email.utils.parseaddr(email_message['From'])[1] # 解析发件人邮箱 + logger.info(f"邮件发件人: {from_header}") if from_header not in self.emcil_white: # 严格检查白名单 - logger.debug(f"跳过非白名单发件人: {from_header}") + logger.info(f"跳过非白名单发件人: {from_header}") continue - # # 邮件日期验证 - # # =========== - # try: - # # 解析邮件日期头 - # mail_date = email.utils.parsedate_to_datetime(email_message['Date']) if email_message['Date'] else None - # if mail_date and mail_date.date() != datetime.now().date(): # 如果不是今天则跳过 - # logger.debug(f"跳过非今天({mail_date.date()})收到的邮件") - # continue - # elif not mail_date: # 如果邮件没有日期信息则默认处理 - # logger.debug("邮件无日期信息,默认处理") - # except Exception as e: # 日期解析错误处理 - # logger.warning(f"日期解析错误: {str(e)},默认处理") - # 遍历邮件各部分 + + # 遍历邮件各部分 # ============= for part in email_message.walk(): # 递归遍历邮件所有部分 if part.get_content_maintype() == "multipart": # 跳过multipart容器部分 @@ -119,12 +157,18 @@ class MyPlugin(Star): # 微信导出文件需要点击链接下载 检测正文中是否包含链接 etree = html.etree tree = etree.HTML(maildata) + # logger.info(f"邮件正文{maildata}") a_tags = tree.xpath('//a') + mail_date = email.utils.parsedate_to_datetime(email_message['Date']) if email_message['Date'] else None + mail_date = re.sub(r"[^\u4e00-\u9fa5a-zA-Z0-9]", "", str(mail_date)) for a_tag in a_tags: # 获取到url后下载url urlt = a_tag.get("href") if urlt: - filenames += self.download_file(urlt=urlt) + try: + filenames += self.download_file(urlt=urlt,filenamett=f"{from_header}{mail_date}") + except Exception as e: + logger.debug(f"URl 文件下载失败:{e}") content_type = part.get_content_type() # 获取内容类型 # 判断条件: # 1. 文件名以.zip结尾,或 @@ -132,18 +176,27 @@ class MyPlugin(Star): # if (filename and filename.lower().endswith(".zip")) or \ # (content_type == "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"): + + # 处理无文件名的情况 # ================ if not filename: # 如果附件没有文件名 - # 使用邮件ID生成唯一文件名 - filename = f"attachment_{mail_id.decode()}.zip" + # 按日期发件人生成附件名 + + filename = f"{from_header}{mail_date}.zip" # 文件名处理 # ========= # 解码邮件头中的文件名 - decoded_name = email.header.decode_header(filename)[0][0] - if isinstance(decoded_name, bytes): - decoded_name = decoded_name.decode('utf-8') + try: + decoded_name = email.header.decode_header(filename)[0][0] + if isinstance(decoded_name, bytes): + decoded_name = decoded_name.decode() + except Exception as e: + logger.info(f"{e} \n解析文件名称失败{email.header.decode_header(filename)[0][0]}") + decoded_name = f"{from_header}{datetime.now().strftime("%d-%b-%Y")}.zip" + filename = f"{from_header}{mail_date}.zip" + today_str = datetime.now().strftime("%Y%m%d") # 获取当前日期字符串 base_name, ext = os.path.splitext(decoded_name) # 拆分文件名和扩展名 @@ -153,62 +206,182 @@ class MyPlugin(Star): new_filename = filename # 拼接完整的文件保存路径 filepath = os.path.join(StarTools.get_data_dir("astrbot_bdzxb_bill"), new_filename) - - # 附件保存处理 - # =========== - if not os.path.exists(filepath): # 检查文件是否已存在 - # 以二进制模式保存附件 - with open(filepath, "wb") as f: - f.write(part.get_payload(decode=True)) # 解码并写入附件内容 - logger.info(f"下载附件: {filename} -> {new_filename}") - else: # 文件已存在则跳过 - logger.info(f"附件已存在,跳过: {new_filename}") + + if new_filename not in self.no_file: + # 附件保存处理 + # =========== + if not os.path.exists(filepath): # 检查文件是否已存在 + # 以二进制模式保存附件 + with open(filepath, "wb") as f: + f.write(part.get_payload(decode=True)) # 解码并写入附件内容 + logger.info(f"下载附件: {filename} -> {new_filename}") + filenames += new_filename+"\n" + else: # 文件已存在则跳过 + with open(filepath, "wb") as f: + f.write(part.get_payload(decode=True)) # 解码并写入附件内容 + logger.info(f"附件已存在,覆盖: {new_filename}") - - return "获取成功:"+filenames + + return "获取成功:"+filenames # 查看白名单邮件 # if from_header not in self.emcil_white: # 严格检查白名单 # logger.debug(f"跳过非白名单发件人: {from_header}") - except Exception as e: - logger.error(f" 邮箱登陆失败 抛出异常:{e}") - return "邮件搜索失败" + # except Exception as e: + # logger.error(f" 邮箱登陆失败 抛出异常:{e}") + # return "邮件搜索失败" - #帮助 + # 帮助 @filter.command("zdhelp") async def mchelp(self, event: AstrMessageEvent): logger.info(" 开始执行 zdhelp 命令 ") helptxt = """ """ yield event.plain_result(f"{helptxt}") - #获取解压密码冰进行下载解压 + # 获取解压密码冰进行下载解压 @filter.command("mm") async def wxmm(self, event: AstrMessageEvent, wxmm: str,zfbmm: str): - # 登录邮箱 - emaillogtxt = self.emaillog() - yield event.plain_result(f"{emaillogtxt}") + # # 登录邮箱 下载文件 + self.emaillog() + logger.info("开始解压文件") + # # 解压文件 self.data_path + file_list = os.listdir(self.data_path ) + logger.info(f"{file_list}") + for aac in file_list: + self.jzip(filename=aac,mima=wxmm) + self.jzip(filename=aac,mima=zfbmm) + # 解析文件 + file_list = os.listdir(f"{self.data_path}/zhangdan") + self.sqllitaa = sqlite3.connect(f"{self.data_path}/zhangdan.db") + cur = self.sqllitaa.cursor() + try: + for aaa in file_list: + for icc in self.exceltt(filename=str(self.data_path)+"/zhangdan/"+aaa): + # logger.info(f"执行语句:{icc}") + cur.execute(icc) + self.sqllitaa.commit() + cur.close() + self.sqllitaa.close() + yield event.plain_result("保存完成__使用/chazhang 查询最后10条记录") + except Exception as e: + logger.info(f"{e}") + cur.close() + self.sqllitaa.close() + + # 查账 + @filter.command("chazhang") + async def chazhang(self, event: AstrMessageEvent): + logger.info(" 开始执行 chazhang 命令 ") + self.sqllitaa = sqlite3.connect(f"{self.data_path}/zhangdan.db") + # 创建游标对象 + cursor = self.sqllitaa.cursor() + try: + cursor.execute('SELECT * FROM zhangdan ORDER BY id DESC LIMIT 10;') + jieg =cursor.fetchall() + self.sqllitaa.commit() + cursor.close() + self.sqllitaa.close() + yield event.plain_result(f"{jieg}") + except Exception as e: + logger.info(f"查询失败{e}") + self.sqllitaa.commit() + cursor.close() + self.sqllitaa.close() + + def exceltt(self,filename): + yuju = [] + try: + if ".csv" in filename: + ata = pd.read_csv(filename, encoding='GB2312', on_bad_lines='skip',header=22) + # 读取表格数据 + for biao in range(10): + # 暂存当行数据 + shuju = [] + for aac in ata.loc[biao]: + shuju.append(str(aac)) + # id:流水号,payid:账单号,paytype:交易类型,paydate:交易时间,payname:交易名称,payto:收入/支出,paysize:交易金额 + # paystu:交易状态,paybox:收支方式,payqd:记录渠道 + # 插入数据 + yuju.append(f"replace into zhangdan(id,payid,paytype,paydate,payname,payto,paysize,paystu,paybox,payqd) VALUES('{np.float64(shuju[9])}','{shuju[9]}','{shuju[1]}','{shuju[0]}','{shuju[2]+shuju[4]}','{shuju[5]}','{shuju[6]}','{shuju[8]}','{shuju[7]}',' 支付宝')") + + elif ".xls" in filename: + ata = pd.read_excel(filename,header=17) + for biao in range(10): + # 暂存当行数据 + shuju = [] + for aac in ata.loc[biao]: + shuju.append(str(aac)) + # id:流水号,payid:账单号,paytype:交易类型,paydate:交易时间,payname:交易名称,payto:收入/支出,paysize:交易金额 + # paystu:交易状态,paybox:收支方式,payqd:记录渠道 + # 插入数据 + yuju.append(f"replace into zhangdan(id,payid,paytype,paydate,payname,payto,paysize,paystu,paybox,payqd) VALUES('{np.float64(shuju[8])}','{shuju[8]}','{shuju[1]}','{shuju[0]}','{shuju[2]+shuju[3]}','{shuju[4]}','{shuju[5]}','{shuju[7]}','{shuju[6]}','微信')") + # 读取xlsx文件 + return yuju + + except Exception as e: + logger.info(f"{e}") + return + + + # if "支付宝" in filename: + + # elif "微信" in filename: + + def jzip(self,filename,mima): + filename = self.data_path / f'{filename}' + # 输出目录 + output_path = f'{self.data_path}/zhangdan' + try: + os.mkdir(output_path) + except Exception as e: + logger.info(f"文件夹已存在开始解压{filename}") + # try: + # # 使用AESZipFile打开加密的ZIP文件 + # with AESZipFile(filename, 'r') as zf: + # # 列出ZIP文件中的所有文件和文件夹 + # logger.info(f"本次解压文件{zf.namelist()}") + # # 解压所有文件到指定目录 + # zf.extractall(path=output_path, pwd=mima.encode("bytes")) + # except Exception as e: + # logger.debug(f"解压失败:{e}") + + try: + with AESZipFile(filename, 'r') as zf: + logger.info(f"本次解压文件{zf.namelist()}") + zf.extractall(path=output_path, pwd=mima.encode()) + logger.info(f"解压成功,文件保存至:{output_path}") + except (RuntimeError, zipfile.BadZipFile) as e: + logger.error(f"解压失败,原因:{e}") # RuntimeError 常因密码错误触发 + except Exception as e: + logger.error(f"解压过程中出现未知错误:{e}") - def download_file(self,urlt): - response = requests.get(urlt, stream=True) - response.raise_for_status() + def download_file(self,urlt,filenamett): + try: + response = requests.get(urlt, stream=True) + response.raise_for_status() - download_dir = StarTools.get_data_dir("astrbot_bdzxb_bill") - os.makedirs(download_dir, exist_ok=True) - file_name = os.path.basename(urlparse(urlt).path) - if "." not in file_name: - file_name += ".zip" - logger.info(f"下载文件名是{file_name}") - file_path = os.path.join(download_dir, file_name) - logger.info(f"下载文件路径是{file_path}") - with open(file_path, 'wb') as file: - for chunk in response.iter_content(chunk_size=8192): - if chunk: - file.write(chunk) - - return file_name + download_dir = StarTools.get_data_dir("astrbot_bdzxb_bill") + os.makedirs(download_dir, exist_ok=True) + file_name = os.path.basename(urlparse(urlt).path)+filenamett + if "." not in file_name: + file_name += ".zip" + if file_name not in self.no_file: + logger.info(f"下载文件名是{file_name}") + file_path = os.path.join(download_dir, file_name) + logger.info(f"下载文件路径是{file_path}") + with open(file_path, 'wb') as file: + for chunk in response.iter_content(chunk_size=8192): + if chunk: + file.write(chunk) + return file_name + else: + return + except Exception as e: + return + #保存文件 def get_json_path(self, filename: str) -> Path: