Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #! /usr/bin/env python3
- import base64
- import contextlib
- import email.mime.text
- import enum
- import functools
- import pathlib
- import pickle
- import pickletools
- import re
- import smtplib
- import socket
- import sys
- import threading
- import tkinter.messagebox
- import traceback
- import zlib
- # Dump all of tkinter's constants into the local namespace.
- from tkinter.constants import *
- class Resource:
- """Manager for resources that would normally be held externally."""
- WIDTH = 76
- __CACHE = None
- DATA = b'''\
- c-rk<31Ab|);?*GmMVrt1Ql(pOIw5jD$f;K5d~ZT6<5%bwt*JfrZg#wC@n6yp}3+xMR@XD0m0u5w\
- ?`EPaR+%mcZ;~-O2Mr{ZT@>^?lhBhCYecQl1y?wy=0~{bC<K+_1tqCI^8vGX&NA|%v-RJx4hQnEG\
- u!(DyW$~e0f)ZoSd9-weA@eWiE%)G1EEQ;dMJ|>#7|U)gG_2y42;Ua(T<$WgdsSn!VtusGd^ka)_\
- }MWMz#nukbjk++}s_kIu?U_e_tYyuwxMtSv3CD0NmkN-LcnkIRFRmbloRyV_Oe@VHq7PA~h%RZ-i\
- XkS7b1b>si-2{_Ic5_hnuDm@NoElZ@%>#lNoG4jgUj#_t}mqp0lcGpcQ$Nbf}N-La|S*}^G(mHPi\
- i_KL%rJ~wZ;1F`k{x5e`)=aLebj&PwRXa-RDk{rZk$8FoJkH53$H?<XJ4)SUt^!7ytgPZ<mSnlRw\
- z$|a)RB9h*Hwcb=3L^cb>}!vEqB(`xGF0hCy#NLdMi8y=S`dKb<cFw7EP&gR#X<0x~qoevM9=&UY\
- 8ib368O)UU!MB)^YTp!AIk>8LnCn%c$^paDT_3{*J*}S%(>ntgMQv8h5SNF}bdqrSGovNPlono?K\
- B~;hk+pngaJ7#qN@+u2S!bj^biZrQ7Qf2~o^g>m2T$Ra}g`%I(koQe0M1>MbtLH7_eX%{=ey8dv^\
- _Sy>K-9O;kM&MH@bM~RckYkyI2F+cqs?#i;EW0+9pI|dAsVrZAz95IsO;&V?MIr_Zu=U&XpHmAM7\
- ;>DHxmGe#;Gk)}#)6QbAh!67e#jlHm)I>!S{!r$cEGkQ0X{ATVL4QYG4NB3_ynL2%2S?7SOl-YOz\
- no%*9W}MC$*$U3zeu<9mtA<7)QPba<g`c8UQm`R{K;d96-cbe@Az7Q7(<@;<A0&n<*lo&X771;3`\
- ox6Vj=VFP9Q7Z1`;+D0mK4`6hN_+m-}pI?KCX4AkL`fyn=C8)l|B?E><;&U$e?VP;^$cR5PN;%8g\
- &Ld~qrb#f%sDSrl9>d^@W>XRL6pnZ?Co*20R7EZe8BDza2yn5V|c>ek8bT2=@a8E2o!BWET17g<>\
- 0^*9)now96Wb;`6hmuDf@QSJ6JVU#B<J#$pmdA!1NMj4iERt=UNnyU~K*GiSRB%Re7o7fs)bRD9S\
- 9*J=&#xuiNS%K`BaH*eDj7Ugcj87o5pP-0}$?Z=?9!i=R_*#491z2TQ3vwlUc>>09X@0;bu1eLo3\
- s_WjE>B*5m_+dF;7C0#4LK9FU-&#ffD+SOv->-Q)C1^SP~mk|b;u9PKQ@%kl1f*e@J+tMSzYGHLp\
- GK>Jx;H;R{VQ^d4(+ML~g#rUE3j}N)Tf3>k?~ZG5$b>QXo`&-Y2j!UD~dKGi#kSJhSSx&zyDzIFW\
- zG?&RXi{+^c~AauDWhA2$#oGT0FT$x+ID9m~ndH&kVpGkRCm*!t7&s=~YNmV-%pVa|K4y*<Q#^WE\
- bq^R*j9TNjG<f*H1)#l|FC{!-qx*Cyz6=nIdEOn@S*@yB<3hR9qk;hdzSzk3ZntjN6Q5Bc7ib|}Q\
- +IxdD%PUw{uVPBIn>R8-nZ&nb)fCT=zf3$Y%DIqQ0hR{q*#$KNTq1&7fD73lu>}y{#y+LL&&O(ek\
- 7GuKlL@=O{o@@$e;$k4`$7enf=5iE{V}G^`pKMp{hYN^SeE<sn>G`#g2Y)WE_<~=?9d32^>-+L98\
- 4ybOo(<+5QseXM<<fZ6Dlu?4NN=lQ1uxxSYKYuqXYH7qNFaMKY%%I@1V$q(VuH3*tu0s4{M}^I$q\
- 30AVFl5V3CP$Oprdhlt0N*uM?$aK$LteyHe#FShHeT+bhP7S>idb@JRHKYCch~lxv5;a!3_jK<4E\
- rVHMys4b^>Mw33JxoWd{iI!Fk9khNf69G`fnRa7%7;`r^eEMfe1u4_sK64t>VwU4@HR(Q)D?IS)>\
- 7^<pVWfe?99CaS&6j#s?PH1(Q?6}yVL^gVxT<fk9R9#A-vZ92I@n#D#*0E|06Uq`3@)S5mOC!4W5\
- 7{g2og9q6++D{i4L;tR$;N`1AkS$QCRFQYEyzqKtA2zIZW(*8fQ_~)q^ib+RZ=<YB?+TQM-3YqR(\
- o?<%t)Zwri|Q<b(~Shs&VyU+aME%ZQhD1SNk;=fXS>TW~{7qPwDW)UF{A2Tb0X$49BV_=!vJa)?L\
- Y>^15bq_{!^@*8V3+j?5^gl-?Nk2-jq1U8VP2CIC1*D)LWQ@cu~w7Za=+8LO%*-R>HplIAKb8xwh\
- ES2O8gO^WcM1K*TQJ_ypv`bSK=ycJ9mm^ku!P;BJJ*Vf5Y*IPSVp^D?(5x>5=W|g{X{PY`NT6tP+\
- t-Chx)4*z?V-<niF7Q;kTs3)8M8(p@6qmQ8&NF+alQq%!2UaJszQ#bu;6Z~1F?w|*m)Mh(K0mv{!\
- <wZzxf11AKS{b|T^bWOJGQcHc%9eF^n9ozT;{|V^RrHMR?lF%AN~XJ#e7WU^jg-n(C~@yUVhfuuI\
- f4sZ{utES>wvxUiauKral@z@xPp(bzV(HHP*c0zv6GQPrTT%y1-eh<s<3c{H*azc{O~2ud$b>X?b\
- -Td(q9B-x;n-EwA%eO%Wo^62$5&zZ8|bN~e|R`BL^pe%5K#OyxCxD7>4WrBw#;6E25mIZHH=7e5i\
- +<)2K}k(B=fCd#$$DYdLADA6Qf__ged{H$}GWfg8+G?jh9gqw{<wdPuQ7eB$)NqctN-)En)0nC)z\
- iW;q7^4FQz*ScIYDugkbmJg-ZO%eSO6}7A*?$#uV{vJU=`)5olS#Lng$LzHNd>1Q6p|7CfBY&@=f\
- PW-$%vGkbN|JX!3gqABXD#UL^cGZ<x`)s0gzFbJ_FuPd`0Idj_Lu$H-||>se`0?T|1N+%>#?^OB=\
- ~&3fbiU%{RTXJd%smrF3jJya>0?l<u~N`mS5L*+vD?luUdX>&jG>j%fk!z4q3J4?qhw=-*JfVh1+\
- `i9=p1i5FTaW!TLVFN3QO}u6wQu{_c(E=4Y;5I(*wp_YCm8cw2YjoAs-!eILJixe&g5d!cXL0|mn\
- 07hloG_wZFcw_RV+HB<Wj)ny}At-b$f;amJ}%PaGRYk9zUzS*)&xL$f?@AiCbSU&#P*B?1s`1_|D\
- 7Wn@2&?Un4q8WXBw|fp?`QG36&)MSj687D@FU<2jdvg!p;yElI^Lie;;jyz`XuQ9_kUxC(<c&RjE\
- x&E|{q^Tg-+xxr`L=!hGP~a8+wk1=LOGtjV}S79itG0GEuY`}g?IkX`SnY8>@Pf9dHX3s+KAtFZW\
- q39`+JXX=g(V(^xs@n-2UDRxAyQoHvizS-+!)L_~w_dU-R96d2b<afA9HINE5%=v3-jW-_mRUA><\
- $5duZPNzLoR$6L|Z<^Of!2KXJ<d;Th5ivHI?Dz9$xq6|P@hJ=b@K_W&XNS09@o@B-yy%Zu)>U%Io\
- m@OKRN%<Ln?_eb*=0v#|<h~Ia8FI*!Z)~&2$bn4|>y|CZco1VVxh1dTzr2QLAAM+{Y3IB%q!E5{*\
- UO#&CFhRzizCG`S|Gro?WXtmv!ZS?&htHlC;=p_$FXS?S_h0_^d7<nmr_bIscnHeC2hWwQdTaR^?\
- Qvk5>mL~<<O6w+`Ch)T(D&oln|<#-HOYr^in1=r-|ss<%lz+4RokREP!=#ByMEX#<QsqS9{-MVin\
- 9Kn<(|x7J-~cmS&$w`Qz_plA1Grerzq>8bnv|=FD%&nT<NM0)|78U-lBZGy?Vm7Hy$6i>a~BLS`h\
- qxvmKx*1E9eshH#MH!*B501^cj$cQGy=7I<fQhhNVgjbb;B^V!gWjo7<pvAasQFvT*{Rmnz=E?kn\
- Rnk}q1I_ku?3c6->7025(wQihW5r()k*(lb_My9gGi;~%n5zZMEWsXx_wbl69xJnn!L)W^*HM{EB\
- jtY;bPFz@JiA-VR@46D<v8O_4ES%o1S>;}DjpxLH1I4E}q#am_@s$nqRC#NPODikbpjpV%nXcI-7\
- 1g*B>Nyeb&&J_W73=#7cVpaMmpEwhV7b`nz1CY==f(L2Hf(ZNvkcF4*G|LP6&#F<jXhtkWn=3ZE+\
- LK6gqZ{0cNpL}&sAL}yw01Du34vgYbzZ`Im+3?u{Dey-g3TNE0M4Ll}h%?S?(!3j|;j~;<eCFTr)\
- 3CXb8Fh@CKoPT`od~Hi-9mnKpQh{cX}D@&3^}0e<RkgHfZz`|HO8-1)E#ii-IA!vXs4vB980{QV;\
- UMhr~@hlAg<*5{{^H0aZZf6wB+(~}m^Q1!hs4Q?1WW@Oi_1G89Gx{e+-;#_=+A+t04nOOViWcJgg\
- X4J(VfPL3tux)TZS_Y8WG<w7-<7eIf^XFC7+t&9uee3^@yZDWtMm%)aZBO0t(}-^tAAZ#Ix#NZ}*\
- n7hM8=q)<{8zYUUfKK)7JWYQokitq^B?)9ZPPCq<KDd7xBd1nKJB_~h<jzmxJ@}np8UgvA*1%0{L\
- HA`O-<XIF1lvw{?GWX^3+^W*Z+stvR7twYs&fF{p6OBYk!*CI^g>yi_-6X=j@+<eD{?nzq{y~4+=\
- _lc^1t1Y|9(_tV)~s^w0m7c9>`M|32|;-)@_<tMtex-t6Az1UPH%+i#pdIBi+yhK&pFeZJpmleXM\
- #^WFa2@|}Ge^49*6z3$7Q$A6X8bmV2*M(+D?Q|5yGVNu%~M|y9$Wk#3gyD!>(*pk)fH;y{0;WXdp\
- Q@fP)_{Z3hm)K_gXoIfTu1IU^J$*Bri8m|H$vxx<`0#QlDm`@URh^*E)z2JKbkt2>?O1=wYp%|>O\
- c?)N^_DYNj(s%uHv3O!kIo){&6>Qn8~QDNW21e<^_Oox<IkZ7oxjCvKm75VZY=6|(><+y&f9Xwh3\
- 71LDQn#3PcDa1TQ42#zU;+2Pri6PH1B=jxVH|h8MF8CQ=U6x;F3iztjpNlcF4PH(qQVY57TGO2YB\
- +O-{FBnp{S%2j9LJ2Lw9ftPJ_cwhlbZO;nqGj=y8~N59^;jq_J#~yKT>o$CtrnBjC+7qju%`zC8T\
- W<qJA}H1EuYU+4a_b@ToW6Iw<!EY574-S*XzW2(BZx#s3hAKd`=|N8k5-^{Zn47wg(D1vt{wzm!K\
- -MuIyv*@IPoQCz8MH34Ck+G?p?fB{)zq8jyz|GwnhTU^L9O!_*2aV1yDTZF%8`{=f5B&~>D{H&k#\
- uvfvZ~8sADf5aO;J2z?_g$Si0(MWyNz2^4=jTQydhd7nVE=jNZ=UnPZIk+~fc4H2Pq%BkH|%5)WE\
- ~2nEQ0-tVEs4jueseDp1k*Z7~_C#$8@*#9Rc4?`s(zYzD4luS2<}P&4af$oYtv1Cv8w}@8f!G-K*\
- 22%a(uC_ceQF<7X_7W4kr{b#KefBU(D!-gxD;Zyo<Bg4*fbZC4D3UF_$x?hW@(tlW2aC&w{8yN_D\
- ++QLpRmhYLo`R`>{{+Y4qV3xgU+omtQ?HJpd@(E{@R6hLk={Y?M-Gde{yBm%h{MVFiC7wZx53{Yg\
- !PT~6;t0oGFEg2c@4eS=9#QdAm&Whj8TNKnuT|aty*d}BZE?JRM9=Q`eDMHW#4ZQ)Jg&#tKmQk+y\
- WV>D4ez{C@$!LiQ{PVWn{v`V*t)XuqjjfsdU!ajzWXNjs2lWk=A@nHfaBjB(P{ojsF{Dmm!pn7Hm\
- #w@#1WH5b%KZ1T;Eue31`$!KWx)D_B-MAZ<_{XLe4$cH+IW}2k%*P+oN+j!EI+%?mOdVSlYRF_w7\
- AwMFR?Q-ak04;h(JJGp>cD|LEC$duLnG-bd!VzgJqr1!sLWYD-^R(KPnbKDHwJ#1Tuo2x9hrde61\
- @uIZB2@Zni!wN1{1OQw%@m+U(3G<g52GhcZ2kWQKKgJ<wxFYcW3qs`H)=b3F!tWGN$aO9EoGc#f8\
- AogGcbldp)*E<fJ1J{35^3<w1E1=seuYaALW-F>#eoyPVrXFy_y=%V6&N>S2EGgM#zYdmm>Ul>!E\
- 8~<kYyLHbRh%QA|LCWK4zV?MFF4Y-->hCR=iZBUjlH@T{J~;>WY<(Uxwc!Mt8Um>xcI{K#@@pQ_P\
- (S3k~syolc#puzG&K~Kb7tpJ9R94_~<E1&n%d+`;q5zVMj@?TVL;YWPR40;=R_~nECd*hyPtYG;_\
- =JuU$nsg9i5e-=4?%UYXr^{-?+7{jIZP*CU>97dPzqq`K4d+t>EFCv#o-#ECo7UmS4zu+D|muV?p\
- aUAy_q+%q@6vh#wbmnNS4>mwW9DLR1_=emn*Wo0G1w%r0>vqEj_nRe^dH*H)vaCOF@!ru2?+0yx_\
- hUcHZr*+m<@b<J`cVrhf^_Vo~$S3~XnRWsEI(YD3JDE`T?s-S{N!=PwSkuyV=<yA(;Jw!;XYZQ>r\
- Ht0o(^jP|c<Z&>vvZ$J>vP8q8yBW!HqJWhthxIQhxJ=ab}d`|Mf$kiBPaFxd-@r@o@bwDzCQ;V#}\
- uY@Z}`v3l3j;&8<_qFe*MZtw%<0s_VviUIxQ_c^2kD_y?@tCKh@svFxW7AtLMp&ulpghar#3KJa*\
- |>bB}#>IaD#SFFW)C_-;jR+Y>C&^_h()%x&Ir?>(*C!FK$2m+r}FVrd`Nsp+NVm)+EG(?OTNe?Z!\
- }_2<9$XZpmSep_}bt5V<Hv1j_GXWy=`*|}ojnC=a`zIgqb4URRPOsy9~J?uYiXv36D==Rnn_NIHk\
- NMAi;?y#nNS*z0e?>}M0_O_d@s#szB?zaZdjA^(1c23Kh^Z(xG*8iUN&DxoTkI(IV@b#B>y8#~jd\
- <5Kh{VaBsbK2Q$cYgWQIreY2*<RkVXZ)b&AMN$ywu^qavftkq?YC#?utPq&(_U~RyBzWR@i!J;{P\
- JDHoVPCgFdYtL&F1FZgTHYVxrf5i!3&T7KD&Ev*XA=9uU(dvc|q&P#RpE=tNF#>p<(`qkM>;ne`&\
- K;<42jZ|G0bo#kNtO_G_GAPkXfT=9B-*Ta|Y5#1WltJ#%~CgDduOjNXw0Z>)RJH*Zs#qw0UBesOu\
- XqSfbr+ViqUHx<|pS+if`+Y{#<RKMir1L20uNn@Tk<BsA3Pl6uZ8z)ZemAm%Xxo~(6le~Uo=T^LQ\
- T-uf2WLMe0ShdHtVEuntMfd3X0r&Rpd&I)i@_u;mkK*zB-m>JD`&yjG{)=@Ua)i#q@F6#SQvb>c#\
- }C`G^|d8i&u_SJz$af^Qc-o?ys`_b?z>{^jvr3SIS+=-x$~nD-Nt>$`XoPiH`o6$uyf1R*IwK5Kr\
- !~%MxQoj#1q3OU4_H?2C)n0AJ%7T=(ooPV_8_j!h1NgKtWSj%fdny27+~gGWsR)GV1Upr=?XekaX\
- R&Iw9KvI=_Q>Wm?#gz->Y?@h4?vppf_ya~VjKw!ltxeq6$P3Bvz{#EZCvc#jLRi3f2X8^#lE;z8W\
- Q*f{cE&p3YvmzP8z!^XQlt=Gx_RdMY1CBwKiQRmQh=%8()kNFCTrMjV~9<V3ye9xD}O4IAt!UUGj\
- wZux(`&Y#O<iuLyKk=XVPy8qT6aR_-#DC&H@t^ol{3res|B3&^f8sy!pZHJwC;k)viT}iZ;y>}9_\
- )q*N{uBQ#%>RMBOm^abO7S20F5fer>UN{4#DBGYWcasY;(yZfKb$>jG4VgC`LAUk;S%D1((+$xSy\
- !b{zmWK^zW3Qq{7+f_OZ!R&6941I|3WSU;X<)V{FlZCg~b1)pg|=3Pxi3^@n3tWC;q22|C4ciNc@\
- k?FOr@3pR)XyWFVXPpR)W<mbD||fAlg?O#Dw-{#$)~Nc=Z0EFu1f1p1L%1<CEZ-D+z`vEu&{NF9H\
- PRvI71g8%g?#Cxl)9mRtGsp`Ai3TsDZ^M7E9^4=<A1GD*0=Qbqv+7a<Tc3}zepU%`su(1L0KRz;$\
- P5h@bwut{JF9XHIe;OYW|7mPM{7?B1kF_J>e`=2piT}iZ;y=~@#DD7l5&voYNBpPxAL2jF{}TUc{\
- fGEZ>%YW*vi~6dll>Rjf0F%Yq#*n6)aF0!|442A1JM4jROdf}_J5`}|E*G1+W$@bC;tz`fAarA{3\
- riU#DDVtM*JuLkHmlS|C-wTC;!i>%YX9!9(&OF52?+6I{$_EPv<`o|LOcU;=d96*Rlpp=RYPd|HH\
- *c=f5U5|Fa=E*Kmmc+EEP2w}wOf*N1Fgo<c~DH5}qUf%s1#{u7A*1mZt|_)j4I6Nvu=;y;1-Pays\
- ii2nrQKY{p9ApR4G|HOadf9m4B1m=G+Xy2zn&<{Ik-wzS5{a=ZkKdVEJUp{++%V(<Faq7iD(C(LW\
- hXvXvDW7g2o6b<k2L1jxSH&^EZb%)!k%=0{##vs82l4e|8*eNmKBTnoayz815Ak^WF-{cFITEq=U\
- 7u+E=wjkaN}WxnCIi$Kq$cm>a~JOsuM7`sEjD*j%*Q9hyXePwR`!2@^Ak&`PSCCE3yrSpeLi1H`%\
- Q)q(bRr#6Yd*i%MW-j@8YxDHu=;KyKj{b0|TP(c#jD-hqzA{?hAb!!hI7+x4uT<-qwCETsEbv-Z#\
- ^|aK9nwK0WZh%@%YI!T0q+_t`=B_MrQWp!;;$JxY)*=pKUa>x1sIgYNA?_ZdO==|T6lpnH(++hmf\
- B_pLI?#`_kTB+KtJ_<gfXa^?57pnC|uFATcR4!XAo-Dd>drw85Jg6@%o0r!Pr?(0LxmmVfR_Auos\
- 3{4N43O$4A9n6nleg*S0nBOY$kRC>!>|x}wKD0dBLg<5%zSzU)TYYGKwT0Gq-*r?!YPuKOli>Cys\
- 67g9uR6A8f$d#Oh<m$2dm7x{2Dir@+Uwx<JfOYLV?UEvXvD|h5dPe7XUzJ6C$O-Hg^!ZF@Ae%FPq\
- T0d3tbbS|J{#;XL#P)cnEi`=iNxIW03}4f6u~{IMRM13%^B>|74-RbiI;=y<$xJavDblgeO_(93$\
- GF%)(z$$V{@yzjVDQ2DC>#`yk4ECM*3h3^AGhlc=r<8J;(p{?BQUfekEN3);@Fuu@pc!tY_qzMF-\
- ^O`?4!3$0=3KZ}JlNM7JD>(elG(Nq)H-a_f#GDr=K3quF9M!gq@;rscJI%p0-2eWLJ!`YyWO(FV+\
- T5mI({%U+rHTuU({)zrX|5T<w)&ErgQ~g8qr}~HLACiBnf2jVU`iJTts(-Z5e_s&iQ8S7DF|U8>z\
- `t&cbLiPr|HMrGkA#lvzqsG%IFkRE(7zm%*WYm-{bHiO>Ge-2KL)s_Ig9EaQ{{glgttGr6=H8kW+\
- nMQ3B<itYGH7c^vCPfypKfmHx_#QsBC7T7u7$e&|j+^+1D)eC;5+y{7c~{7S1O5kBj_E*SN=|l;l\
- 5d^p~!$Vj-RAAJh6r$-XW<iR+(zSQrHU^Vy9I%fT4?^Nk78KQ&VR(Z||jd~Ph1t-euq^3lfe8JK5\
- d9BcZct@0~iJv;laSQwTB_0I`9WHnLbU%LK-g$pb$|85J@Uz)qC0^IU!UHT`(zMn-Ft$(a8|MKgX\
- S;(ULC$9S+-(sOJ)jx3!xVAim>Yupo|HXC9Q>p%m>;7xp3+kl$C+=ZB)jx6dKQN!_AF6*+tp15b`\
- %CqY@^!5GpH%<Q_$!e@tmhx7{_$VOWB!fmAM4LQ)A|doKc@2fBZAi7<0t=eko`$~>2D^CvcF0c-*\
- Ty9|4H_T@uff6-^Q>0A^Y?A$v^GCh#&o}vj516`|rX9EA79d{imrV|7OvD4_I^mwUze2PY3mNLaX\
- {8uu}bV3XuPec+wy7knG)L@IPZk|6}<e`d{h})&}_>qxy&FPyP?7{vr9N`iJTts(+~dq56mDPxTM\
- gKP3M|f2x0|{xKSK{-7W6IDc?p82YE$`I~BVFzftH>HOJHbx<muKdTm|nwUPFzZ}2wml2b|di$?q\
- d2ROj<7gl90nj<`YGFtW>cEr1e~t*rzyCUxdjQLc{sGrdvd}rkeSwKA{6=*Stc3X4?e$~fnG}`(?\
- ^&3VApPPKc>VWLlGFb?7M^C|5*E5fLMWfFg<oa+e7+`rZD-ew{5qXoH}Gp4y9WPtn=BgT>v~@+3y\
- 3znw)sTJuNxBax~<TE-J0#cZn4X*Gi2B4vTKlE*Yj&6bfN4zTXt=iU1!LyZL(_+uUk1;@w!>KE)=\
- et+Ic8l+hy13D%T*tu9saG%C56z*LK-;hRU^F<=aA)v^3<?-l2^0vdd{Ar&R|UW~k6STY*P%-Uab\
- AT}1}$$}%IDr9_r_nQqE-R<6rx`Yx{%0_qBR9U`w={B;gaV2lFM*xm*XaZo$n@b(~^C-I3z`Y=0L\
- c!7ly)GZU+$xbZH1^-_6;C4$X81;@(abX*Oj7hY4)TFY<l0K6E&EpNG&s-Dp8*BRDJ3oMNzM>8T(\
- H}`N(FcF;HHbb}hDZl}b}dQZ@Cb-|ftBckJm1d%`n;t=pV<(5-$IdKe-<{W$k=&N#*Kaleo>&!hZ\
- bAAxj=<JS4J6chDx8}MT^m=uSz+tHh?}#x@9%`9Hb&YL?5CL(TC_m^db5XeTY6r^ba#3@%Bd_lpy\
- `Xq2RYayx-*cIxFhenxOZdWuXVrrybuZ%q6_b=a7g#06(hCwQObK*d(M6&hH$j5b|`=N1FE?n`HF\
- Si%|5zfNLL9i9TWHgKvS@&OKU<J{VR&?Cic0i9UMPHgRsdn+5uZ=x_2e5YHrtCj1Ke*ISxcI5L6d\
- 2iN{uA%1IH_zwEZIw=9^lcPYJc+f{`+a@F!ea?$f*|om&H<+`omMrv<!eWElwq&CZhUW}y+mew!7\
- &aL+mz1pZ!GPzf934mcSiwdP`&DPgp?_!v`DrtVKBQk%KNEeZeW(6m@{BKNd`j~hM$8}5{20xznz\
- 8;)^YgU6lSuSQw)H_;Uk(kjPcWZ8WZ&Y)Iw1R~Q1lru>x-v~eVtzW%-&p|5@~N(qS$99D#-qs=tJ\
- }&`Vf7HK13g)kD2|$M1)!UyU9O5cqD^=iOpb+e+dBcPZAE~-zGfBKT>Fre=Th^N(TRIN1NOp$o>0\
- Sfj+wZ`*DAZHldTm-6smyq9aV<KmzHy5U-m$US|u}f@jJm;kr?9^Vl5w>$&y*>npNl*LK-;hU_|B\
- c5Q3F_O*iix=?nVExWe!Ya~wwzebXz^Xo=|u>NbPmt7aiuCry=cG-1?>^fcLIz#2#Y?ZX?JLXNEX\
- L(uVWehHRp%Sg+Gz_F|h6*oaJepe=ADNL|%Vjt~wq-Kk-0`|TSa)PPg^Qm>EHr@M4=D^ctbW4z`I\
- {j8SuODjAMkNkZxiZfJSXf|u!c=J+FTjAo;?Mu=RbdsK{Uwaaar*UfnN>Kg=Jvva};ihat=mG=hs\
- 2}*Zq~`bb~@(zl$=?;Q0Q;mOnw~Qw~#+fu0e@SEvxzmkB<nv`rz;*(p^1FRA=gekwnepUO|=k8$}\
- A;(f+cD?gTbt*md{JH^U>QqVj9<9+8;DZkPg>OX?H`@UjP{()Ss<>3*H<(I}v7h1UddgjJLjlpN8\
- c=<8h7o)ZzQOl13_nq&Z!sVCdsCy?^`Frv*M0$q!DxGt3-|@ON5z3#<^AnG`*^voQ{_7yVbM#Z<T\
- >d2x|2gXGQoQ^amV+^SqO4keqsC5FF250DC##n~+OZRrpX8tFKWhJ||3m#>8voGvm*zic{)^^6Y5\
- tqmKlCHj*T1xue<Y;#`ez0&bE>X?OEk!a`1>^?`xh!dm7mH_<)`vf`BSg_M(%$|_5ClYzW)i4%KP\
- 6Wx~B5}M?@;`f0fU1r0V`>2}!g6o%;{)ai0S9+~+`{uTl8TZvSl%U0nDY&{aW;-8T<^Tch~>i~lx\
- BzxDjLkpE^2zrH3r|4rw=HvWq~AnZ*-rd#ceLZ-#vZ2oKKzv=wf#(zQjtq=MQdM`aFo@`k<Qo2$;\
- Wcf-D$hTAuf4S-d%e^y7I`UtMFA|@E`6uyJ;xp$v`iU6C!X)tfiD)<?V!g;>VGh>|xvv<X0oS3P{\
- l)tt(abR)$n_>T{#fK+eti$<_gRPUzY5xX{KO*E>dgOX3baChcxz)6z7FCA`kU*bBmWD6@;^VueO\
- }bNH-luUP{;QxgZP&phkm;|C=Yscc~ubo<FV#2C5Zm1n13n6EB_=v$(etgf7sWe^52E`htR+1ITn\
- =vVL@X_PXf{(&-!{#796r?BNh55w}J6CDACYA6h^A_6aNp4wIPaz{*mM#uis{&j|K7{s;`Ul;QeA\
- KKLsq@4BGuCzo22vf&M#BjRF1bd`?EsSV`+R3+LlYOwRvtF&I0|Gf{qyi(sr8$(Z-wpx^#O5B<%~\
- KmOv(NjD41Pw*K5hgwj6q-(wTPbB$SYhn3`RDa_wKhf5o$=-fN+J6WqFGlqrV%dL|u8r)!QUBSR;\
- }@%sA484bi!3mH50!sxL$}9h{=}^L&nz(i411dYr1^7_pVX8etFPZ#ef`wx>(^GdA3#{yenT#Ya{\
- HNB1+)eVX+Ps@5H-TbN5O6K-wgI^_xalRuTRhKzV+hoK85@@oBw7AzrNOV{%hmEApAC>4O512zD*\
- (j&E~%u{5PHd+Jb)5gWk)Q$0NlnrQ=UGJs@BHd=>_lLvQ&dx=D28bd~5V(LIk3N{T>cr=_n)c?k<\
- 2gK;*7zp?NjXtOCV0rPAfalE!ynDoEl@1phF^$@v#2YGl=mWPQU(mp`;-fRQgtW2)AKHh$+AzPNd\
- +u(LnnhRS<>FZ9va~#u`<}p|3>?cH${y}_>4TF>aZJ;bqxL2c>iRmk0U{D?Md4zp_qteIlGYdlvO\
- 8;~aeT=B}7vEB;OaJ2SM6~H|jG{ho<9(oL(tpo@c4e`KcE!x}eSGeH-xN(h8v4gu{^G5F@veUoss\
- 4?3`=iwUVxRf<7`4A@{f{nDj8m!qXPxxpJ^qOI_$v{{pD`PMw*q7^rlezN_W9Pau&q6O$-;FkT<z\
- QDo5w<fZ(9S@vrx!_orMe@?EG)_!aMjbej|MQo-95wtdr8iv~ivS7o-Qt<|!s=tKrbj_`ea-;%^q\
- #vv8zJ+}*jZVB5Y;)*fgL+xfTX+bK;U{xSUd@(6Va<9wHwMQQilh5XqaQRwSaJdUGO;%|#_{OJbA\
- FQ*UIG4Hd`*P!?h<Lfh$AGc*PSsG+O{J~fzn^WG!!l(%1NBTYvfp!g2_!;#3pAC-xAldw}FU+;{d\
- qapmhf@5J)CG_6w#khAx0xKj9{PmiuRPvJ_!~|>eNoD1EcGk6{<+2A`XRXf!TP+D#p`Dy;~xWl-{\
- xktk7l>;!Tpm^^@Ea*cj%uqGxcp^s=m#)m4%&RsNV@+6|#`M6WZ)MS#OF38=6+~U=#m_&+r}L-TI\
- yW?^#?JC&rEaFkBvT$ogSLp(PT#ZEtI=W3fMfV$k>XynlNNh->Ge!V@e!AN2h~K4+o@Gx@k(?R#4\
- U-_PgriF4!MvT%aV?<Mr+G(vl|@fyBAmCt`jviMa{zqT&S_gZ|?+#S^4GV=Rq^B*n!g87GH|6K(9\
- lgm$9l=2se{6?!k5$SKEO$_h-1@IONudvYk7oRCOfQQ-Q-(KPG;JeaycRlowIQCtTS?;Q=DX1uQ4\
- __qw$;KDBu=dGwbb`sqnr`BKIjf0oVSgJmNW6cP)#-m6-U%EIe$U=};EYbt(7?aXw87~UIzdqpza\
- I`zUAmW0%L%%k=C9?j;qAFM2!TK#5C{YUfj}S-2m}IwKp+qZ1OkCTAP@)y0)apv5C{YUfnXV+RcK\
- cby@#X6<6}YGU4quNxLXBxB%I5_Ss?D#N2^-27(N=r9SrDAEQ82MAS4`6FOT4Qi@O)DW8qN{J!_!\
- XMcloy8;m<S7<V<G$Bs2D+|R-lEKC5=f*$LMG-3&XU^s9aNq-hD<~sfih`Wp0AhAwcMlTrXc@$60\
- z@0s#LEQaoBNh?}nsEr%^*ce_?YkSSamu_}_yt4{YPhp+1RswO2m}#1EL5=Y2#CA(eMug0H}pF!+\
- |I%{st*V$2J{1lrzzbE#(F}tYS_c)EM~AUfW`}iBm}l`==tzT5KlPtQBVzE^S%MDQ_!3u!Ky&dE9\
- g;dCFy)rbpd**JC6DV1Pj2%+pVP_dNwB=HZI`$Bd$3fL}ErrFzm;|bQV4&J&noyA)cCzwo9Ez^ay\
- dpF)Ta;;)yk+o3Rh*!E-LJe+h(G0OzF6WZ^#vsuO63vlT?!0bF;-bw%`OJ`Y5Hws@-WG!|SeTn3^\
- IAD#$>HiXqIctJe*3vG@538I(XS3vao{e6O#8NDRm#lqnvo&=MCj+{#qq>qoM9--&##e7UWk<UBC\
- !VVWt{Xh@?xQ1B{;wdxear`6RH;*S>+W36y@g%MUgMed9JUOi?PBQ%i3#)lQ_Z$#UiAmITu`YZ}g\
- #8IT3E=fOj*XrLalaFR5DC(GyZUL2<rdq~`?(HbJ8SiIuT0)InaStvevUCcvF;p5;!My8)Hk$C{a\
- =ja6Hks?#R47;b4ZHRtH<;Ch>Z}teN1>_VGfBmAp~%b{L2uVd26))J%!IJq%v^segbc&TTG@qjzw\
- @@llB}C<md(B>3(L;U*YMRxTkL@kX@@9_GRHx7M=!k_dRas<169=jq$4h527_k&QOMNZv1r6f6{x\
- Z15Y!a3gSuLX4M0@4{->QIwgTVHQoU8=J33{UqOg|J_gqm?%{JKX4n(n4B~zbLh>Vn&pnyZzeL|W\
- !-4jyn1#K0pWrhvXMe%zMB=0d&N~}zzk_p1xL((f$Q#pf8~QC>Z$drrARl`Wk_TwRgZ4A62FeNU?\
- ?c<SK1ANQ0{xFZXK)>Wewd1gv`GQ<ef>HZYhRCh()TCQCKS-d$jChzXqSL%g`J7Cmc;}E=ZtW^0Q\
- Z>C{`17fDF)Z~Z-Z!uNw5yk4*dZGWdQ9~k07#I1~~tEJ{Y%W1NU29NMyAHvO!$eHG=QB=k+uqsio\
- V2Ib45@kcS08K1UK6sH;C2z~`MTR1&X~2DmT!-v-qI=&QIlku>4@d$Yh8``uME#!p(H&w?)vkP-A\
- tdK{56A%OF<OAO%YcPyL-WV@5>KpUc43}`F<<nls@AG(1t`}LLr*?goL(C$B4TQFQB^iZ1-7dSwi\
- pNsZvy7P&YDT^!+_kBj&ek=l-D~cuXeERJM%;OwPq$GgoOZ-QOe(y&Xk<xUay=OH0b<qYG=L%^*E\
- <p~o<9;E^I^b0heHakT0q(7fW-S+eX`Vu4B&Yz}g!xg{0dKL;lSpYe(C2<M{-My<Twfw1K?BhLV>\
- EW-9{}wOGZMJYvL_0D+zaBqe*!@(av{3;<xi-+F$8Btq5HT#Q%xi!L<k(qKOaRMfIg&YpJ?PbHHt\
- nz?#~}XBqT(My;!&}iaOwZAls5ia3Vy0hBmGZ(%CNrqk#53v!kd3(9b0K9M=w9-`^P_tv?2F4<Lb\
- HG%k)}EQ<CfU5JEX;eg1`ncWPuH;Z5{&W>VCioW3K%mH<vJ%6M&*>AGYgGfk-0Zxr#Y<eS+PZ79}\
- FH-+)WOqe~HKMUYEhqAIm|yu<6uM7mHxS|hoMVo}P6%z#M-%zlad{Mderop#@dTdd_(ue?`zs4a5\
- %~n4ERmc6y_v@QgaiSe8}t`MwpL|NVY;Dj{$C=b8_n|*5((oVvh!u|-1Agi6Tr35NX~XeKV7uGPe\
- ??RL^zI}lVWs=biNPQT#g|zBP1}chRF7*j7%XK6+>j_G@ni4MX*5I_D}@!jsAyHVbAbkAkKa2rB5\
- Y^7QrIGz2>h*Amh(La{CfRABh_vGXIJMf|Y<ifSMwZb$1feWL^aH*Z}0agJ4bIJ~q931@Qd=Nl1$\
- mLI0TxxDSZV;vrZWlOQtxVVOxp11!tOpx;Kaki>_O1c>B}foqbe{MSTK{uv}Tgd_ouU*Fa#|9g^T\
- 98duIeJ0?UUbIh|kW4^-qI&lXqt9w9pHqQ(e-red$whus3CV~uNUimNnh45EXQ~pC6}Yzlgid|H^\
- KPtY?~eO(^zMybOd>)^a-dznw>tIWaSN97ew}4Rnsg)aAfy1!iJ)IR%tGZG4Ep!<pF?6nNFl7ysU\
- Nsk)(UfhIL6Uy`@Dk0fskT20QCD0EVDql&evI{9h?>fKXCnHe2Rorw(r!t9)R|+iR>>3_xF4P<O?\
- -gTm;dbL2aclK85*=edX75(yJkX%j48pmQ7%7`v%f~(*J02tWJ92np)2UEC>4U|59gJP9aq%{U>(\
- A13GDTT>_S2iq0}TL2M`eC;e}q3)JHi2W^59>0EK#-?vRi{?S)*4zZo|pY*>Svvkt!<^;-nxz4=b\
- O6(^6C;blq`oq>^7x*U&y%HebxKB^doIjqG(udeh`cL}b0aJC-u08?sJXU9(>HJgDf6{+tpr3%Pp\
- wDM;mpJEnosRtC9LIjdX3~Gse^uzU37QheydDRs>(hwzpY-1h?8U-oI_UQa#LrjGV-}pRumTR@bw\
- H%ywixQ~!YK1fzFs;)?^^oUIOGxc_v`WNbvLm!Df<u9ejAT2d~C0k2hYYKk27@S5BCBeNo*zkCul\
- ~!PW?J0hIzArzBRwq#7@$Gf-dypW9CHM7gVHEMurkQN&gADagPo@d<C)FOQ+|2(=CwPXBv|J6U@R\
- OoxB(wgFNjE`pz_S6B|kY2@&Hh9lUrf2Kkw&BR^PA^dJ_J{u3g`WF5S~^F{VHIX_S7$j?K>KGJ_e\
- )ctKe=LTJ9VqVZc_ii0|A^#qv|AgqVSO-5=nwXDCI`Z)~&>4QD{{$m29Q4`Qb~Py<&+5p>EyOm`e\
- }W;vb-nF6crw9+JnRFa-yW@bIG)%>`cE(ncS3yE0nXNuKJH<rHGk57f^it8gC}@~b|-_=U!)`bn}\
- }Vc{{%yUb7|k`;LC{yrj2d69^c+45xYqL35Mft9elakz;ttUq`Sj}z6XKyp8zmM2VdSYFkLq!mJK\
- 3-^gk7WbMSiXZ;U!4;0YaRI*CoB{{++UvJT#yYtY#LdmU*WNGu}#CzuAW4&E4XM#$kh())neL;6o\
- J6$LtY^M(QG6$APBBmF1+PZXdnV6#Tv;9Nnp=L7sxhb&Aa_K^M)OviIN_%kfZw6^F-%R#Im{U?}?\
- D|GN@b`<I0xxRmEOb2a~$mW;ypI|;tf!M6aAFU&u<-``!e?m-vK7Lv?^5?@S($O>bH;dRp`cH@%w\
- 2sUF=Kc6caI+3MIFndH`cH@%9@fF5qa#e?MIC7zOe`V&C&Uc1bnvJ+!Zft{9Kk(5H1|jPPlz4P1a\
- s{G_t6pmE5r`ce?m-gunrzYa{gX_AiF=(f71U%1@^ahY2?wS2;$e{?`sCJgY=&eb8OPcqn#0q&#Q\
- FE!1=@q(tkqC@uUtu={<)_&)VM*Vg>0xA?COh%v%FosUz<Fh!v#&gqTB*U#|0X#{Ig+cz*+0`y>4\
- )!~<h>@M)^fxIfky@2A8D(tko+@DCk)nyWMJpCEQVMhK+;1pUa-!Kd4F#EU*g+BC-d46%XqpAZ+=\
- b@1sS9r5<m5%0f<4W$2sxB%zf_GskO^E%=^QV0L(oL|y^Lj0h0kIvgV;yhLd|F0kxkp2_mh7UFJ=\
- @T7so~VQW9%2FMKOt^-M<bt_bi`Stga4Jp0@8m%+^}9FpT5%(=SUs=pG+(u{U^i?jT-s%vyM3R?E\
- RZWEFk?S#0}4bb~|0II8O)vFD4d{{uAPc|I^5)-8$mDPzV1nAQq7R6XJ$d8u`?wBhGVl@P7iafb^\
- dbH$1J8Pc1s))Z<&QgjhiOPly||`sm)JBhJ%w@V}f`K>AOJ8(!7Oryq61IZOxttBD1q|Ae^V4UK&\
- IMn{~->)?MKv4HfS5I4N1kxyUfh;y(G{?8*8kp2_mhEFu|=_4I+I&|>=PGSM+KOt`TMI)cq>xi?f\
- 4*owvEFk?S#08x|yPuw?bi|8(1$JwU_XT1D=|3SZ=%s^COLWHlt;TrYB{q=$6XJqA9ei4#Gwu&G#\
- `_hqf%Kmc7o4nvPqTE!y;ftq=zlDoSV8(vh&e9S!6%o_xc{vq?n8(br2mANW3~=Hjn*0WH9F!RMy\
- w$HC&V28)WN4bopG1yh`X3rLHbXKIbP7gr!G3<K2=BD^~4I&e?rXhDH!kjPlBGag_aR3NdE~j1={\
- {-wb9uSLHwOr_*-NA?-4sl{|PZgKOH=J*yR4jU%Wmdkp2@AkH-xWrm<2-8vTeRr2mANVZII?of}~\
- q^*YkHh*(1UPly?w*1@C0BTPe&AJFTFC8Ym^m|?3%{`}5DT7+pFu0sx9B9@T;6JmniI{5QO6zQb#\
- `a|paA~Xj``cE()=j!0keNm>hPDfft6KhES38v!~9sG&bKlk-I(yAiXkp2@)$3`9e=^tfUdd>oUl\
- vraq{U!ak7P3IxbFYOrJ0SA8KM6f`$O8HdvJs0&{|TmHxDMVtYfzg1(~)KYv554aU>a`J!J9b-rF\
- p-OG-ncvNdF0@VY3e26d9D}B|6evODrP&Czyi1I{5Mj=->0NhW$XhpK+X<*q2yE`cE(%WjgrsjKO\
- K^S#vm-SVj6zFdTaJ;~Bjc-=HJyhlo|A{{%y^5AV~Pz2`>_$LUD>R|Cf$r2nM<1bs5`sR`+$kN=-\
- ^<l!7*8R<X4Fg&4yCwG{Xhs8j)fU&?yEUd6dIG)&LI&eO{MThR4Vp2X%*O3R@H@G*kjzEYS6Lsj_\
- cVOOmT%B}|0WKrf5eQM^#RTa4OSo4@Ue*!o2!yDh#}7Y-p(f{NxX%3K6AKB1h;gkBUHclM-|rU=>\
- 3oc#HBYw?3kigX$J^f~psrn~BTqPAkU^{@5OiaV&c60hG0N9rI`cJ&SV<u0hMsx9^)b%dOFHuQ0g\
- zu10znT3>TF}3G0xwGI`cP@SV|yh#)CR^YZt`c)+Ph=*#f*kEF};$0r&au(V<&+$0?tB<^jn!SmF\
- VF!=is^oHza#4|qKVVmFsW=!hG2>eYZa<?}$$eg-*)$BDIZ2ks-S18rwJf6Rgh$c{Uf=mp~bS*<$\
- tT%7Z}l==Xc!TF%ic0&r^0Qq!@6>ieWn-Ou&Z-LJIt|k`89B8w37ev3;_f|09zATl|2lV;)cpnmD\
- U2Tm{IT}K&ju}v29|q(0`#%V@mTMMn)5(j836k$qb>@2wu{vg$561iaXdtp0j)PhFO9v0WVxe<_<\
- Q?0Cbvk9}bYi*bz`bB?Cg}g)Kz=`r#=mv)z?ES6FVdO+|FW<bvE9@<`F$|Y@7J3|V#9E}PTGH-pt\
- |#t&T`Os!RFx%FyHsz2qLo~^D`TD(!DrA%W{Iwa{L0~`Jx2l@pugMe;JWEYFw(5=4g}ADS^xLtj_\
- W*A`vhgxZc+s1O5LIXe}TbpkL2#bkh9d1TRy6K0l<jJUBmfB8h^b$b;DV|2~w+9WnIUWNZX$*k9-\
- ^*M~qpLX5<y80!D2MDB=iQUtWN%9;zF7rH}d+2)Z*7>4mN)c<irZr#AP?tePz{BMhu(XF$5yZP9G\
- 5Ix4kQ2$2}xpiZnP8#D`8htHR#=Rn_<KBV9v1zh0hQv_+2NJn;^Jb4u8qc+8Sx<>To~|bmi5j@aw\
- >1X3kM^o`&Z|DYe4>-a_<p8^%Y2{C^0t9_=a;1tHpD>x8;Q)O>H>yBi<h|@=-+d`jfEa0E>WR62K\
- rx4WY&yxBB1ZRNmBN6BPctbcTBb=kppc@c9^L9==+iU0%^k`Ec~pKzTfh;Gg+`Q0vVe@q7xOoCh9\
- -!-OvUcyT26yeUr`KQg0T%2mSj@$-X3Vpq==76LkL-Fz<}%WCHeI?+4mfAt91IX+QG*6CpAj#6pv\
- Sy8i`;eyRxJF*ySIe#wG8iD`021T?|^6Rk5v4m?BpGXr!V*P<LmPE9~t-M>J8e>d6p!k`}iHv-vP\
- 0pxo!8sK`$>L_%7MUu@&nO)!h0OVIq4QMmg5&?Y{kZ47Xu`Ij?`fM`KBC=^lchGP5DxKfiDTU}%8\
- euy$l|;-C90%h0O|P=B9mM??yZJT#4gH3apJsi)eLam4$Tap94o*RO-3yWVo;;gGjgVl#vFX1fl;\
- >2ncg6a0V+1tAvBPi@IYNQ}*ZJ;@us`cbaoQam;atgYKz@S=iJ|+~N2vRc1KGGD3PImFqEbK~cSj\
- TX2yw&}5$gUX5cdNTL|g`u+o98$qJ((B6QRta&(FR@z7CihAw6+##j!*_LJa%-b0U;g9JdZ85~|X\
- 5X@qp$#lp!%LP7$M_i?WO3?gAjp#QIzBcw0-Nux7q2xbA-_#&O>Cw~drfqU0CLgYTFE+7&TOlX&u\
- MWOq1h=jU;_UfCWpm!b6+6uuKpiS@EC}i<^lEsLyKSX9rErq)wwQQ;h#skkc`Y;L^yn}Q<D&YS0F\
- QTCTvwS>3An3(EK)=r#c~}CpXVnN~Lv;Jsu@8|)WF+VS?oVrpQuoP6l5scyBJ&#{g<m20e5w(Q2m\
- 0&#Cq(Bb<qm51O#z-O_->TExsK{(f(mdi%9{qr7mfGL1Nxwe)~6-<uOy#>1Ub+z%1=?s)g03Om;m\
- =pJRK#k&^P{tM9P#0o?&oLl(K|&eHBE?m;%@J7D4nrEtdkV52PxNVd0}F<p=Hg$)-0xz`hpl!D@?\
- =hv;*e?1obZXv>Ft&2~pAH@J_F_VUCZ=dtjo0et~nv!QbblN%>Mbn8e`*bJ%ZvoDeRa$61HE82Tp\
- K=U|BhpsH#Za`lV!*U)UA+dn#gD-$F=iIDeA)iQ^6l27TK|E7BTA9FkKH683*ub^@`3BVitt{LM;\
- {FeU^??4%UpGh=&_^ZtoV5<df#@I5sI#u|+=0D_td_umd@jeRGY>b=Sl^OB8=qGUlmpy@FcHXpFj\
- 3J9#51D)G>G5W-@l6LeanvrX0x!zz<L1p6X6^o*?1=gdhjvdZwB(X3CN#ZvW*2lF;G6FHJ}O9KEw\
- ?NaJ}DYFkkT;A3KpZ>43JLxA1<Qk@E62i1Wg9K5lH#pM?iN|M}YC!gnCrk`R)=FR<BQ*^%}bFJvK\
- ?NNmbD4()88Gl6dy?xXrUrGR~Q+^^MQk~}r?u|XFibyUDP+y)lDGKo((CM+T{r!0;H(MOoir0@%f\
- c4z2IitI+TkJB#Td}%X@FMsfQh3aPlVl+hOYgFq#qZ>f9Ri?Ti5U7XfpXpH$&v7u54-W&a=M$921\
- a1(01Ncl1pR;f?i2DvRQk34kK|J^IQ9gHKHqGAxIy0IO9z9vOk@pdOF$mb+Jq4nT;SovPH^6!h*V\
- M0J;hh-KRvP2uTnyQ35cHrA3%7xJbA+MxAU*-2Z-e7O+^=rcdh~1%&%am~2Rh<-%L#M_1R*-$S<Q\
- Hs$nF@+uM}E%-*qt`pPT~XUdKcMwyTHp@p}WWyFQB(tuWwTzbO{3HwlS}gLqrEBaX5iyeDlVA3tB\
- uWquS319@IE4Jb2?MbH*52gEgub9ui6eO#jt#w{@%Cxr`}_<RECJ;6+%|IupRzw{+|z_|tVi}F5*\
- ``8*m^pSw`4J%oAoP|{&uC=|y`{`JxppTsGVC=pLvjX~RI1Ok`g%DfdxN<z#8y|(3hFu`~?Hxd3N\
- Js!+{}}h8d`G%zYQR2B35YgA1VW+#Z3xi!$ZClFe0Q>jZ$aEw(VxVNU?JeV*`<7*Y&Yq#?lsW6SQ\
- rMhZcng2(1)Ot&$aC${Z_S!yc0w}d*ow@kgUM<sUi^f0&GfQeMdiCxStno8gL&L&EFGJ5$LZH&mz\
- Qqf@l}pnxy)UetK}N?n>Uq)80D*p(D_~;8d<BxJLgen70qstbjH_c$PNWEaKUi1=Ob}5F!SyccJ}\
- V8Hjrlo&nKD=XXd98^p&z^p!iF_vJ~~2?X<jW9I#N-GDwb&@OQvh;!#?7yKd%?}2EKk3MwJrf&zY\
- HzeCW^rMISZ+`}Ht*wcLk6Czw*E9bD(Qo4AAlew8$-+@UYw(tWk6<L4AwAUNrqCpB5m=%gLxae(!\
- jPY<N6;WKT{(i*cA>~n&wL1}kx5ly$mo?5XqH!Bp>mI?@9?-yN?3R=TbVf7Qa&>jOPAjMrcE*V`V\
- N-?rB9b7m90?vY}vaR3Z<9lCB0+zG6KrdF?-nonW^uPJ=tAja0XigQ)CI)gQ;f^BzAgm_6q~E6G*\
- |vpbQ7!H-w2Vm|(4e1!(O+@WA}GbP&mgfC!sA(6cF^08JhE(Hun2jE?-Sm#1d$D6WO_+!l6}r)>G\
- l9ptT17G<k~Mm7b=OM1wfs#!rFT4V%h>X7}wG#eH4%_qxqgHnYc`=gRRvq%g5#aFD3vfHy_Q>;(y\
- ?a$K{Ys-udSAhjg?+{ZU2W=g45=a1Bhmr&lp#6Eh^7Hzj7gY$PSbWS@=;zHm+ifZhMhh=?t4e#&+\
- Wy?G@;oCTo{j`e56DMH0!q(S8a!UECS~$5x`lb(2=X5#0@ll-(eS)6u>Iv3_mfxW^A>?HZGQ3xJ#\
- XdD?W)i1f$Rx--o~G2t3J<Y&xO)+AAeq``aC`K^M-VR3iYbbZK0nx+5|2HwktAW4)<Jm)M$(Nxsb\
- JR&l}QJpSOg4j{TX8(9c2rd286`jI{yU+3|T>i05r#pWB6ZLOpK{@!Y5Bd0~j>8KIvu+O~v!ULVE\
- t^w7^6!#{5h``j1#^Cp|=&zo!}K4*!?{dszH&l}Br-a^ll`nd-Ah*mxmTmPc3U*cUqhOfV)Yd`cn\
- Zw#J2656lA5YGeKSHqtR?RR#F=YjnTz0bw|i9N*g?6A*$A)br<s|?loyztM1`+vbBw8EhNqrT@I`\
- >%SR2luao2T0L9Zw%`H<DEV9^MJuxL(up`m}&_5yftuICOvR`Bupe_2Z(2==WYDCJz(0%9w2@J<2\
- zxR&mKI$4tg%1*2xYWKPo?$3qXMW^YN#gWJ=HF<KuwmeEh5WoIhv^tYH+M7b-vJ8EaO1&d2|<0cv\
- 3R+vjubiq9p&%jY-b&!xZ186@YQ6mMGq$^6gT=QU*WFLL>4e=eKfk;`8@T^jrZl+OgVPaDbSk7Ue\
- j|4dHcri_lNpgaNNpWjlR|7w-ZQ)DPsKIt0g-`c{=-}w~h{~G1<>GdJ!7Zv9p11OoTw4R_im#Q@X\
- skG|T62Ns^$obcB^S4U#zX4Oq3JV?qe+oDM+;RT8B_K;p9SYDK@RyGB=Zyg*X$>>~-WI&x5d4SW`\
- Tv%n5(lq0GzU?vDd;|U{h>K1zTg$0mcR_O2Ir>@g6U!Ju>MjXkbGe<y#nX^gBc#MB-GMzfvN=p<h\
- g@DVw84;Yk3C!`mV4Z(db{mQe1#*ZjYc@as5nK{<BLL8A|#n{9Rnc%vRF(kn4W|>xb!z^_d@b<@x\
- qd(aRICsmy1Gj9ylPdbRFesO49G1odjq+d`!-Nl9Src_1Ee<`9fV9AW~U0DA5%1`WXTR?*KA?r%R\
- )hy6C5+x8?2_i|fM+!H&4g%u#4yOyo;T_<kGQqMw`$~)+Hauv^0Fwke`SPl8ZbnraM5wgFX&FydH\
- =*#Wfq;QaYp_|*b@5ArWzgDpSy+gR|9%{fs9-ka%u}}lz*%XUZ@_G^fe!Rjnw7FZ&?Q_Z^_#W{Dw\
- <EeJ!tar%=%3Ub;rGbPeOY)!;n_(%ehkMeJh#ifdk~b*_&9*eVQ+<JwJeO`*J#6tzR`mB9v{zbrH\
- &2yyMxnSzK7{zE`#Cx597Db2g#FMCZ6W@;K6z6%I&>YseCH|)xM7N_Hk~ji8OqO>qo%8t5&9s%}k\
- otZ3B33C&2xESYVPm>|_>(`NPTV{s9(lVHDZG-rKrP{0{4xK`cy?*E5Ifs%Hq+LN~5AH*r}-fBQd\
- iyTZTt^(VYeyph+f@jhQ0>*fi(&W_|fX#58EE?xq$J-?+3ucyA1<pq69yvpr9&*iq6y?DMd`1Miz\
- 8QN&CgFwHI-|%)P)8xG48C##r(tVeOau#|-5EuHE$MZ7Ym&J$sWKK6pR_=yCKeK0mG5b|*ypI1s7\
- XKXv$`qbqAf2^=ox<s0jz4_#d*lWGncQ!3yX0t)Ba@d;d9z|Rjt)xm&?xBs1&;^!N5s-!JJS7aSs\
- C_^ppJP*miK+)P(Nee<}L7_+oVSy?va(fR~*}z{bjOrhmNt5q^koHplv@|rqicukQ+(nrzcSV#0&\
- m&fHF0_w<*FpQcwRuZ`~@j4>cj$D9QKa=t#E+n3R8M93%BLlyv!0S)ag&cGZYH2e;EwU*i&m=P&Y\
- Sla1Q0VQ~I|&%O9~zf}(Gf3<Nv@1DT<@5%eEe+P{p&f)Q|HL%VwBmcnr-abACl5jKsTfGJHe-)4G\
- M%mw_^RvfUAphs`xSo>z?Nk1@d<*3NC|O%3;TQh5o)*Y|FCN!VvcE~b5j$HT{~6%lGnp*;x4QgTU\
- 4NzebF2mOFV&w<E3{v8Es+0tJg%D*%Jd%-@@#beU*&O)3F;R~{kLujoc|uYuKZgepVAo`WstZi2m\
- g69!MKb+dm9qdkDZN=tuT)hRL1Vx`SXzmq<0t#uYrGV`p_ujJ3}^pR2#$B$;Ob;`X6~cNcLZiBCb\
- y0_wyKPUIf>;oA`UxQRYAR+@{@8#5EoK{#nDV`JWAb-;U9o&9WgV|F1<D&mi#o;nzAZavy*H<0$$\
- >hx0P*;j(mCgnb>UZ?j0pcUiKz3iRO-kNI0n^G%+IcR>Hz68c1x&R5%DaNkr5-8af)zE2c&;FmI4\
- ip5z)gJd#;dC!S5z5`{l^e3;E%<%2hiPtkNGMVXPP~2HEotD~%kzm~V-Dn8(<9iR7<J<CVS^FUMH\
- OeCF^Wi+-WLcj8!xy}rH515-8M3~HG#332A3u!c<G7xDJl&aJ=kxg`oFiKc{xt~<IPdL+*!mOg!u\
- xzLL1exe@f(~oz7)(~cMpf2d<=9mAB%p<=LB~1@C*Ojv;3WNAhI?4z`;g<!8iWmE&=x2hDM?6SFp\
- rozs=cg>G<1N?`y!{c|KnglLB9c&(~7O2x<5E&}OMM+vjVuvtKl5Wxp+GJb4{JGe(cLNw|lM{cgq\
- It@z8nWWU?+cPGF){B3594gR*6@!Kr?uEXDEOqT)ertnvmpJx1B=l^Y~XTR5Bx!AAWUrwpq5*^y<\
- BGE~rn?y&6uAI(jr{w_A5qZ0vR=1?E)7p>A=oKK^`k|fM{UDx^Jc;+sY$mi{6L{U!#sm6Xdl^K#z\
- %{%b{S*9a(^nYOuQ`*ah2!d#T$k0_1YAoznT1<H^!N4~h<@ENBCG@d$;<Ot1by7DoCbgJcIa>&-*\
- )3;vS!ZfXmA9GKJsu*@gEw?{VE@e9%YdHpvK|49Qx)9ch1%Q{Jo<Lmf<dZoQ3^HdzkUv7?X4t?Q`\
- +$pT#P&`vuqMaKLr)M^!?&vGv8gjKMa(#e6)Mts#9pBOPsQzm~Q6+xXvVH1sLo;q`_bukrlr!3tU\
- WdcK{&>yrcceQ)TvZfR!vS-f7D!tc>0y_MG)u}B|y|4gzc$9d!bMIg`7q>tx=e88_CP?>i$GyMu)\
- zXJGe#$%cOM9y;r+VlTkjMK-l)$jcJ7CxU5%k<~)@g8tJ!1EK*VwnEJAnr>*6!18Xjv(Dg(zo&UR\
- a#R@<Mro!jo;&5k&A-D2|Ch0f#-RM>~Hh=xK;0X?KqA73OOF+^^Gm4ZEVrde#qgH(ch`@n|!W!O7\
- CCG<?vt~<&e^UK*Reux6}ygHh9=Be8|FjjePhAZ-X%$qv8DqKIR<~;+=lHUVH+KUGty9=`>ZtyV!\
- @{#oMT8#}H{;&-AY09?f-fTNM4lBls9;H*aqunY(zH=Y1U?`*hY&w;apmaSxZZAyLxsBtAdV#`Oc\
- +Dzud<<#o?xeBSF{vN=4|?-DT99v}6xl7;15Pj>Ns9QLKQvhV_z@qcI?1B*Li(?#b6=}m>L*{#_v\
- ^*(#E&v)HUpKskZpKsfSmW-Vk-fPQlSty3qB?<xmzOaQoXJLb{uw_GA_Rei>*;}`^Wp88gZNPY23\
- )`~WGMWnouLeyvVc%*4=UV$b1-qjCWCQ|%Kp+qZgcQNPeEf0&AK&ACa9kH(0RHtZoIl4ko~!w|1I\
- Jc4pH>K>T?L)BoA^lQd`4d?xc?k&B)$RT&SJ&+%w-_1!J!SyK1A}k0(BDC{&0N?*M6EIwrBa_T*z\
- yD-l7QNZ<mrPJjZ!7^ar&a67PI=oX5wxz0SlcL(!MF%l`#yoac+S5$MBU2=yJJ0{RcbxnZNubH+9\
- T=ihOz7j2eb0>8Zx+MRC((FXY^=(r~!(tt9Hc8i^eoto=-^ojR!g!IO>xOaHJ9@l3s1kuhrOJ_ci\
- Uua`>CLc54SfLTbHG)X;g|;|oL!3b@R>dd~*DCeW7HtL5=HM(o-ZK*@7q~_}8^ra*KXv9+@*hTW;\
- K!l7p4UT1v>nAY_nvXiJMs~I{ovZYp7X^&0dxGXSqVtHIXZOcBmRDNf|dv81TN$Cg4RCE<IvH!Z7\
- KkLw0^9SMlbO`ftC8w4iNVtwrD6H+Le1#q>qSp$v0@^`%|FoyVy!VJG7fMwhga9Xy3$E#~~W~cQ_\
- s!m}F(geKt307+3xX#`qLSG+^KS=P+f*vH5vPK4;R8_g%y3!2Bd>50IBvL-@J$n;>=8RFEdPXC&M\
- lHns;nEL={M-^Ci{#|lz({A1_yRN?4j#qj~|k9#sq*_ME~moJrpb%86)82BqN)?b_1Kz|9Zg(wg9\
- g^Ph8M**MTP@~z;78wg6?>B`g$LG8cL{OeTS`mVFxG!N?0_Q&~jNaqE@jk@nkU%@8yCC!$W+L{Nu\
- &)<t4j=8Y_9gae?h{-Yf|humjU&PG`#^~N;{GaH@6r!kFIGEm3D=K$B}hK64a56%Vr9fA4nf<uK)\
- +ps(HI*de=n2X8-ltJG+rFXeBnCmFDm(Zm)d(nu^@yDOpHT*a2;=hN`BD4R&QdNp+LU_%RqhoI4{\
- QexhcflPCl{A6rf%CMisiF?P{!idY%@dF1nQ1WGZmnw?l>Ycg7%n+~@MCO8O5Fdt!ofK;2L75R=o\
- M3+n4s<Y6RsxHZI>(5&$``bSq<r^5C7{=|})B7?X2N-|JkLV7sI_l!z<*AqM9f|FJFvBQM%*IA&x\
- Hk~N-ekeb-W`*iFmwAZ_U+y<p_uo`W=bQx6hfr&$W`#%NTxL8s`xliq*<tc{?^#GJTg_DDKcZYEZ\
- ;K3y{}q+^2S8#WRr!zo{ZAC~hI>yAFsYBRDgpaLsmgzZQzd^3qKx-hm3W6FApfOv1k}RU5%70gm^\
- f<^r0lpJvlW!*<0IW8n+@vwbF7FJ7S?Yqn5t5SNXIC1RN@;-{7*>W-28V6Wq3QnF`v@@+$~_;{aM\
- M*f5d#1a_F_W{)b9DGb}t`mJ0mOS1HGJI^($$l-HLIBK{{l-c;$wYF!t?_kL7};{~ubQicCjDrG6\
- u`2Eq4ICf^lfBitaa3wzjSL+x@2Y-up=-r6_mcf4%%JQ1VZ<W@0-vsL;mH4l;e+cV@a6T4r9_d#|\
- &Ncp2;{Qo1<rx*`yAxEtJDvD%DWI+JZwlqPG0b;Ldrk1XxxI=1mcw%j<yjx*8zp}&Z$NS&)%dS8e\
- ~-2zq2_Ou_U|tt{#z2~s+1|zzTuw~-k(MMw<L}Ned~8UA@Obei2vG=0m}XZ%2eJz5sX<oBK{{1K3\
- 6EyO)BqB1m$_Z4C23Kp=7_cQsv!g3h({^$%9nnztVW_C6#w)fwI3g;=gsFv`=uO%DdNq^4TiHf9p\
- cY_wt7-@7@NHuNe{l6BA1FxnHZitK`G&IpV*ip|m$>r^>sJDZIOa_-|>bjp>x|pu)TVNBp-m%0PK\
- fh!Pfo@;Ph7f9qnpLYcO!ysLCB)7!*<OGC+D&!;NyUZL>r$Hae2L&;CyyDIOxLHWE=;=grq4=A52\
- q=ZsXK6AiE{I@I~S18ldD({X|p+R5bzh&{BLYeMSdDo#rgHwtBmP8sTpF27|#QRGA{iYKCEs29w%\
- 5+AEZ<Krr-bVblBu1!|>97#rEQjRr_mS%SSF-2$3qtP$R$2r8iMI{Je`{eWDEny+g>fo$7)boL6m\
- ZP`l|p$Q4D%hH-`J+`T}_hmH(B|Am`ZuHoV)rCSaTha`0s~Hr96jd_*QAo7;OeR5&tcNM?m>pCM_\
- 5Z*7W;E{1<_H_?7(k{8QuiT|wD@$UVe=OW;hEvRteq4kaHTc&<QZ0+$<o9$aK_IKhJaSF+<nU(kB\
- |_gttVL*s#v$gp!>M_Z3|;eEbjwg*f`l&R?LnGwWwKPdYUI*`bmuvo9av+sGIP%nmoIX>kH=0J^K\
- 3nPp#jgJ|X&YRsYNoaNih-X`2{dl;=%ksPm{|7}8=M<GVueLa?&^O8!P(DX@j0MXx1k5?B0r&oYq\
- Y!U1?`v8S3pI@UZ?ssMR)cwScGFejZm=NlYjn*0R9K`;$Ef806@%hO-+xMeq0m+=$3ndC0m?oR)x\
- u5-)N$BHQ#%vhNS~c*XThK?u~qED%R&GAuQS2RG#S*-fi{vaSO<KilGZqj@c;e@_<wf-mk0g${i2\
- fZVJ4)fcJBOEu!euw$0FeWzZ1ATi&gUcFO$*>bq4so7U2J_5%51z#{p{RapSnGw~6VZT~P}}zrGj\
- `jER8%BNMP3Xv6b!9LFqkRno?D1CB^w{;Qpns}{CE;@LgpJpSt{`F$I-pYaq8(6_wWdGP2jERk&l\
- P7G0h1&?hL@r>IWRPu}abdQQ*J`Uw=jZ$8gCis}}G?n=bxzEet37GHkA@V#a4*5AJM1B(eyck@+x\
- {8GlK-?39bpp=)&x6R%OEwdy@O-N2TfA^v2pOmaf)N<Z!Y&mWe;DVnH?9wD3?Ty(iG5Lzx3;U$_O\
- }?f`D)l7M4v)Rc|?1m3y6&o^ZpAJy5gBkiF76x_6NUH$t&(Bn@a4|UFUBPQLiTATuVWSF@dyRY$M\
- ic>g(+aLC5L@&ik-1<7?bQ(ur6ca*X#^2>M-Xfij@KpkG6j0ey;+4RIj$;$!Aeb5ipyRDT}B=TSn\
- <v7An97l3|xUJt{2w*|{}Bnw}LC=>PzuSxOt0PT){4nZRvKV4$s@?kx{IgD)M{K3&F#&?{5ye|x0\
- eoK-%689579Hy-3ll~6Sw@1;6!1ctkFglOne=H10qH<%u1#Q*-3{!60t8^(OhrcPMG5rPzzxIgsJ\
- 9{Uo{No(K#~Ns`3B*0f=}Ay#><h2dP-gViIVlC&zI{NuEhgG)pbc(k3zif85j?B0oM^kz4^j&!Yv\
- >zF0d0@31aY1@(fc~6!|ODb3+GFxCP_UP>AXj6nE4{5eJ$v#K2i2&;P@5o1kqOb4;|&ecD^?d5C?\
- Jk{jGz3Exdm9^6_IVbts$bIr4Bj=(8!sy&1!Z<%&T2(Z!(be=1x+|3s@;@PO#!`Y;1z!p3zJ`Gr3\
- 9(1+G%5ScHjS9v`_YhdAV2%iu56Qc9qg=;D3^Y#f4&o@K+Nj&Gs&D#+4@q|7x(N{8_e~jxZxAVGa\
- BZOYh)EaOP_E2JJMA)C}^CmFfx0{&(*Eev#$sl5(VL%_$H%HLk#wuWc1oxPo36c5RA!zS!;~MjIy\
- pH}mjy(Sy#CiOS!Mgr1tPZ66*&zD$z<nvZOyV`#+~K_ZGCn7jO{7mqVE_667K%XhUxadj{!!NQc{\
- ZGX{)WrR-+aveJ=f*;`P|L3yswXakF)r=Bx%;EedeGM?DAXeI@inYG35;ZZA(yAn#6H+`+Wlhe9(\
- &5jJN*JZNeYx+dt11vQXImx%ifpB}kY5urI}w8f{W^^*qzQfZtXgVI%*pjepl5#mg_+Gv1gXz9!|\
- XIbDpVJ>NF}zy2r9>C(OUWs{#!4VZx@KV=)UrGI$Av!$1Kg7~V8@XZ*NjLt1AsWzMczBOG8?HRBI\
- -s86|8G-lBaz;1Qi}x*@eU0^Ep4#sV1Miy(@qgKUHh$v2Zw|h<`x-cpgi^P3ya)MxYlhDj>b@<#E\
- nUyOFTE`z;NI4%?|xpZJ>b3}+<i;v`^FCU*%rIE>$>kikFfL%roSS8TBO>>))su99&n!?c;D9HUX\
- -uk`*sD(=y2Z<>K^4=RNr;dePg|(d0amVWt9xO7xYQ0tD3mJN#$iPi~6c^&k}3Nkl*_P??I{$#Cx\
- nC+HC&&R=fv)`og<H&cyr)aft<j_WG&OUtbATo2)()E@kzdP_;GtD<Cmao4>vctY2GgLG`crW_!E\
- 9Yu5JqyU{MIw*3`(lP$3RZ{`t7?L=$Pdu_a>YLIHq2AlX=MtkE}*xpKs5ejr_&u1%2Pou0sguwO>\
- p9Qp!V*BZ@=h2!D<n=Z-sD-low)V$)ymQnbbe0@qO6(6k_Z9<z5Jz<5b3M2|jAxPHK4aXoeLM@fU\
- `<;9HSELJ1a9E#1K(>HhvJw8_uD-Q;@T<BF<Qx93iphj%g1E94H}!{oZu}W+E^v(`U;+{hBgdpz6\
- XBe<I{&g+=q^SH_=uQ_t2v6x)~sz&4fPq(JrP9)NM(|C6G*@Kaja9^D?*=ioOL;h+xjN2d^9MQkf\
- G*|1G29Tz0I#g4YY1xxVKcJSU22tx})^+MXR82cF*r9oJ_skHI`?f9QDrRWq-1Vga;KdKWtSXxKl\
- -Jis-_51`{&&SuVI4GPl5?|IuDFJPT=Z4eEfVj;^UddtsD!TH8M2~r229@OrvH&_n`1(p4g1nj3_\
- -?2%S$3`&5S5;1s%-@~B{W08+^HEUS5RHvYCZ98x;(OQvebH<P^l5)Z6mi`xi|0QU8k=An`KxR^Q\
- 5ZoS@^j|U_k-1J884CL|7#F!ceMh~$CCTz7!KBi+zwyQxjt9PzPsOmeDnoz?|UTSpeW}Wq%n6ZX!\
- EDj6a0SUaUYivXW?8F#n?C6Gx;XU;(A=e`}1V)*BK;-@hrbh)`sDjIxmdgOFp-M(OGAN!XXfN?qn\
- #xY72wj?Q1~#=WEnw`MHqSm{>1eWFo(nkS-gi?+P(Kko&(rJc-JWsE5%0h3veJ#OJS@+%CnV{D;Z\
- XSf%j%Y}xY}NmzcwCfQs~@VG#JZql(yT7G#Mj|qAv?cLo8=I=F$SNY{W(yM~n$Q`n|14z>HA1X`d\
- PTAjk$^QO85|<z4TH15*qU`U-%F>^c#N|hP2>!jt5>An&KP}12FZm+cu2L^tmgMDM1OEN^O8w)Dl\
- Dzz@W%EK3&XA>FlH}!oN!HFw7z(lV#TC!;Z<Ni6Nf;2LabF^oU-DVDMfSJ7b<UAkwfvdjw<`!918\
- oEU8M(<?ez}|npJ5=KuZ(+>_es+7J43Yd@-rYu#jw1wEdMfDI*y=cC&-@Nl!WESbvtQY>~n?Zd&$\
- OtTfx|MKJ=BdLSs1Ir1B4gKtIJwxJUL*5hNGn{s`6ftDgvgYrV+=TnCcu)4mKL+cxl@8;||t!%fZ\
- ?*0Z<|4bN9J(~d@7w_hIS-3hXH7n_);28Fusaud?QHJqPheg1GhC_C}`QCcrL#)LdMKzUy?_B{?V\
- IIRVs-@o{I5a_?v==sp|LbQKl3`_^@Afz>o??Jo&QJZX@0K;s9%YR~s@*6exj(z=iAo8<%aJ@uY2\
- S*y7Y+(6qpzITSt$}IWDU0_dh|F$WzIKi_Fh=^PL;L0jLA2AsuoR5*k6QwPHkt=S5trPC1otJx+R\
- jxk#>jLY>+ca!#+M<RGm^%~2PA0OaSc=MQ>om*HuNo7yx50Iw6kEHGQMM&YjFHMg6gba3~sBUk<V\
- azlDS~cJ{h!|cseMpTOoe_jQa)2CHASNn4D%O=xB3}{!7n+*zHZ)o6leE399Eu#E|y5uPu0A4z3-\
- aX(Ao=Wnm_C+((CNnenj2@5cKX!F_r3Nmpl3U*5*&M9{xou)n(PypIy^J#n}%?@3TT1LO@5&$c<8\
- x0~Veit|vpyj+X<TvTwpJGl-f+Ia#vzl=UZe-F{W$9ahNcwhJb_?*{sAo`{G7()BT{F0B66a5?o$\
- xre#pwIWOTA6WvWEfcU+yInkoLjB|aX$g>BS2e3^da~MuRBmCzTtCEs7E()`kCqvTfxUqOS<HpfV\
- -FweF`-3w;SxD=LHb`IG`^Gbl!mhon|znKLQ(j9p7mb{ugfM2_6v&eWII*LZ9IEpf$Tq@XgR>Zxy\
- {B*joz)Hwqao;_r1W^}_Gf&EoI6W--NA`0v)Ofxpeczw4BLTO#~z-qtalM%nMyCjQ$j(op(+h2`J\
- Jin@-K2VId|&B}-0U&qRgzZv3ho1M|4HG};!`mp!dU$5r$66sgZ>B;HaAh-tcAyLr>2Rc1Lza5f)\
- pa#J=PrB&PqM3VEYLz@rG~l1_x|Ey2F4)^Sd2&T{g?DyA&FtZ$*<W*Va!#qNbkB4+9iED+no5^rr\
- gOH#>vp)RJ$1D%hijIrw9Z@Mu69&-9Hs7R_O+`FpS!DF4sUs_%UM>Cl{LPcJ*{$=)v@o(T=<TMJ#\
- ;xv;i);woL;BHJG%zIae5uKb=4SKnTtguq+C6v(jVO^EU{{j*I8Z4o=+AcDy?*SJdT+am6be4F30\
- 4$>Qc;@hs9Aj8zaIO#W=)Ns+?u6Ec~OpWU8yw%OVwCnOsrp@j7ZMou#e<$N2WhSqbVquE}+k_@2`\
- -^8C@ctV~XJQ|Wfs6fi<%Wfd1Y>%8Ue+TvozP)F{0URO=ItJ;xsiL2I~<2be4SySU;IXroco8`<?\
- aNe}pUiVB_ZP64KO=Ur;yJ}c2i-OU@B}Q<p<BU2+#-j%fJ{lj)aMgNPfrMv+`#T1)|Fg0VGZ<M}j\
- Lq&^ufsFjBV87YRFiI}*SYFkW~9tS!{MoPdp&v8&MKE@Xl`!)iCGSYoby~>QDTaVg%=nNW;kmroF\
- z;wINM5{m6c3RPzb%QSzaa#-f~AxZN&^mPKOX*L5>g^D_BAC6qk2$E&F5M;QWHg#g%UMwP!+Zd+f\
- QFG8#`5UPpNAnB?cN$GMK9gt#1&-L>KcOI?h-)+LBcUT*)~{*GKnZhnEMrn17Dm!EG2sYIES^ykD\
- ZkxbHkUcO_%Fu!(5+L9x_CXzT;WNLqhQ&5xKeDNXjptu;nD=yCSxGE=e76|c=W`C}5R#sfj^tW9F\
- 1wwPZswi_+dof98b(v$TyF`=~PIw`07Gps%7br$!epSHuQ7n9%mmmDDm_PQe7}q)YZ(cxdk@Y;c{\
- RO@6@95{Oo#J6X{ie;tD|sPK6DtT$dkukgLn+IJ7sZF?4D(;>^48U|`lHt6sjKwL3Q;jxDc_EvLj\
- #NFPZKe-yn+d9wc8t5t^!w84Kgh7<qpLc5~_C3bfQGbXoL{+DHJC!xZLe8mbofjQ&1Hc%}4O-Jds\
- x7C&3>CCSOz6p1c&3|5cs==|RB^XQeq6gedjx`Ip2#WPwl<+Fwvng^r)~cT_PMnbqGRl%>DJRa@(\
- >Rn-wm>H-oDr#F*p-BtKST@@1_k05b1tP*k7V&mw>Hn5}KU^0w@_|t(=EKT+Ze}W&08DbB`i{QsX\
- NoBgow29S+tPx^A(>m&-@Opu>bb4Jyt-2yAYMjud3-v`{xiQ6=9+B24O|=y+b}cKuw{~`*g7CbEU\
- ksI#C$FE3AgBoeM95c)MPM$A<#g5vxY}QE%_?=(cta=4niBRJ6NSm{y!;7+FAWv@_}V&`GA(PFbU\
- Holv@EKrb=SCRy|ddXE2P(ftQRtE^aw4Hm>=g1){StMR8&e8aILe#!>G<)kg785XLXdSV2;Y_Ie8\
- fZNg+fYK#<`2AmCYD!T$sC`iq1'''
- @classmethod
- def package(cls, *paths):
- """Creates a resource string to be copied into the class."""
- cls.__generate_data(paths, {})
- @classmethod
- def add(cls, *paths):
- """Include paths in the pre-generated DATA block up above."""
- cls.__preload()
- cls.__generate_data(paths, cls.__CACHE.copy())
- @classmethod
- def __generate_data(cls, paths, buffer):
- """Load paths into buffer and output DATA code for the class."""
- for path in map(pathlib.Path, paths):
- if not path.is_file():
- raise ValueError('{!r} is not a file'.format(path))
- key = path.name
- if key in buffer:
- raise KeyError('{!r} has already been included'.format(key))
- with path.open('rb') as file:
- buffer[key] = file.read()
- pickled = pickle.dumps(buffer, pickle.HIGHEST_PROTOCOL)
- optimized = pickletools.optimize(pickled)
- compressed = zlib.compress(optimized, zlib.Z_BEST_COMPRESSION)
- encoded = base64.b85encode(compressed)
- cls.__print(" DATA = b'''")
- for offset in range(0, len(encoded), cls.WIDTH):
- cls.__print("\\\n" + encoded[
- slice(offset, offset + cls.WIDTH)].decode('ascii'))
- cls.__print("'''")
- @staticmethod
- def __print(line):
- """Provides alternative printing interface for simplicity."""
- sys.stdout.write(line)
- sys.stdout.flush()
- @classmethod
- @contextlib.contextmanager
- def load(cls, name, delete=True):
- """Dynamically loads resources and makes them usable while needed."""
- cls.__preload()
- if name not in cls.__CACHE:
- raise KeyError('{!r} cannot be found'.format(name))
- path = pathlib.Path(name)
- with path.open('wb') as file:
- file.write(cls.__CACHE[name])
- yield path
- if delete:
- path.unlink()
- @classmethod
- def __preload(cls):
- """Warm up the cache if it does not exist in a ready state yet."""
- if cls.__CACHE is None:
- decoded = base64.b85decode(cls.DATA)
- decompressed = zlib.decompress(decoded)
- cls.__CACHE = pickle.loads(decompressed)
- def __init__(self):
- """Creates an error explaining class was used improperly."""
- raise NotImplementedError('class was not designed for instantiation')
- # Import custom modules in a way that an IDE will recognize them.
- with Resource.load('affinity.py'), \
- Resource.load('threadbox.py'), \
- Resource.load('safetkinter.py'):
- try:
- from .safetkinter import *
- except SystemError:
- from safetkinter import *
- try:
- from . import threadbox
- except SystemError:
- import threadbox
- # Patch the messagebox module to use a thread-safe version of Message.
- tkinter.messagebox.Message = Message
- # Create an enumeration to represents various program states.
- Status = enum.Enum('Status', 'mail_label login_button connection_error')
- def event_handler(method):
- """Allow command/event handlers to be marked and written more easily."""
- @functools.wraps(method)
- def wrapper(self, event=None):
- nonlocal none_counter
- none_counter += event is None
- method(self)
- # print('Commands handled:', none_counter)
- none_counter = 0
- return wrapper
- class SMTPClient(Frame):
- """Widget for sending emails through a GUI program in Python."""
- TITLE = 'SMTP Mail Sender'
- AT_ICON = 'at.ico'
- HELP_ICON = 'help.ico'
- HELP_TEXT = 'get_help.txt'
- IS_TARGET = r'\A(\w|\.)+@\w+\.\w+\Z'
- IS_SUBJECT = r'\S+'
- IS_MESSAGE = r'\S+'
- @classmethod
- def main(cls):
- """Create an application containing a single SMTPClient widget."""
- root = cls.create_application_root()
- cls.create_windows_bindings(root)
- cls.attach_window_icon(root)
- cls.setup_class_instance(root)
- root.mainloop()
- @classmethod
- def create_application_root(cls):
- """Create and configure the main application window."""
- root = Tk()
- root.title(cls.TITLE)
- # root.resizable(False, False)
- root.minsize(275, 200)
- root.option_add('*tearOff', FALSE)
- return root
- @classmethod
- def create_windows_bindings(cls, root):
- """Change some global bindings to work like Microsoft products."""
- root.bind_all('<Control-Key-a>', cls.handle_control_a)
- root.bind_all('<Control-Key-A>', cls.handle_control_a)
- root.bind_class('Text', '<Control-Key-/>', lambda event: 'break')
- @staticmethod
- def handle_control_a(event):
- """Treat Control-A as would be expected on a Windows system."""
- widget = event.widget
- if isinstance(widget, Entry):
- widget.selection_range(0, END)
- return 'break'
- if isinstance(widget, Text):
- widget.tag_add(SEL, 1.0, END + '-1c')
- return 'break'
- @classmethod
- def attach_window_icon(cls, root):
- """Generate and use the icon in the window's corner."""
- with Resource.load(cls.AT_ICON) as handle:
- root.iconbitmap(handle)
- @classmethod
- def setup_class_instance(cls, root):
- """Build a SMTPClient instance that expects to be have size changes."""
- widget = cls(root)
- widget.grid(row=0, column=0, sticky=NSEW)
- root.grid_rowconfigure(0, weight=1)
- root.grid_columnconfigure(0, weight=1)
- def __init__(self, master=None, **kw):
- """Initialize the SMTPClient instance and configure for operation."""
- super().__init__(master, **kw)
- self.__tk = self.capture_root()
- self.create_bindings()
- self.__to_entry = self.__subject_entry = self.__message_text = \
- self.__quit_button = self.__send_button = self.__from_label = \
- self.__to_label = self.__subject_label = self.__login_button = \
- self.__grip = None
- self.create_widgets()
- self.configure_grid()
- self.__data_handler = DataHandler()
- self.__connector = self.after_idle(self.try_connect)
- def destroy(self):
- """Cancel the connection system before closing."""
- self.after_cancel(self.__connector)
- super().destroy()
- def capture_root(self):
- """Capture the rook (Tk instance) of this application."""
- widget = self.master
- while not isinstance(widget, Tk):
- widget = widget.master
- return widget
- def create_bindings(self):
- """Bind the frame to any events that it will need to handle."""
- self.__tk.bind('<Control-Key-h>', self.handle_help)
- self.__tk.bind('<Control-Key-H>', self.handle_help)
- self.__tk.bind('<Control-Key-l>', self.handle_login)
- self.__tk.bind('<Control-Key-L>', self.handle_login)
- self.__tk.bind('<Key>', self.handle_update)
- self.__tk.bind('<Return>', self.handle_send)
- def create_widgets(self):
- """Create all the widgets that will be placed in this frame."""
- self.__to_entry = Entry(self)
- self.__subject_entry = Entry(self)
- self.__message_text = Text(self)
- self.__quit_button = Button(
- self,
- text='Quit',
- command=self.__tk.destroy
- )
- self.__send_button = Button(
- self,
- text='Send',
- command=self.do_send,
- state=DISABLED
- )
- self.__from_label = Label(self, text='From:')
- self.__to_label = Label(self, text='To:')
- self.__subject_label = Label(self, text='Subject:')
- self.__login_button = Button(
- self,
- text='Login Before Sending',
- command=self.do_login
- )
- self.__grip = Sizegrip(self)
- def configure_grid(self):
- """Place all widgets on the grid in their respective locations."""
- pad = dict(padx=5, pady=5)
- self.__from_label.grid(row=0, column=0, **pad)
- self.__login_button.grid(
- row=0,
- column=1,
- columnspan=4,
- sticky=EW,
- **pad
- )
- self.__to_label.grid(row=1, column=0, **pad)
- self.__to_entry.grid(row=1, column=1, columnspan=4, sticky=EW, **pad)
- self.__subject_label.grid(row=2, column=0, **pad)
- self.__subject_entry.grid(
- row=2,
- column=1,
- columnspan=4,
- sticky=EW,
- **pad
- )
- self.__message_text.grid(
- row=3,
- column=0,
- columnspan=5,
- sticky=NSEW,
- **pad
- )
- self.__quit_button.grid(row=4, column=2, **pad)
- self.__send_button.grid(row=4, column=3, **pad)
- self.__grip.grid(row=4, column=4, sticky=SE, **pad)
- self.grid_rowconfigure(3, weight=1)
- self.grid_columnconfigure(1, weight=1)
- @event_handler
- def handle_help(self):
- """Open the help window and show a message for the user."""
- with Resource.load(self.HELP_TEXT) as text, \
- Resource.load(self.HELP_ICON) as icon:
- TextViewer.load(self.__tk, text, icon)
- @event_handler
- def handle_login(self):
- """Decide if the user should be able to login to the server."""
- if self.current_status == Status.login_button:
- self.do_login()
- @event_handler
- def handle_update(self):
- """Decide if it should be possible to send an e-mail or not."""
- to = self.__to_entry.get()
- subject = self.__subject_entry.get()
- message = self.__message_text.get(0.0, END)
- self.__send_button['state'] = ACTIVE if self.is_target(to) \
- and self.is_subject(subject) \
- and self.is_message(message) \
- and self.__data_handler.is_connected else DISABLED
- @classmethod
- def is_target(cls, text):
- """Determine if this is an acceptable e-mail address."""
- return bool(re.search(cls.IS_TARGET, text))
- @classmethod
- def is_subject(cls, text):
- """Determine if this is an acceptable subject line."""
- return bool(re.search(cls.IS_SUBJECT, text))
- @classmethod
- def is_message(cls, text):
- """Determine if this is an acceptable message to send."""
- return bool(re.search(cls.IS_MESSAGE, text))
- @event_handler
- def handle_send(self):
- """Send only if the application is ready to do so."""
- if self.__send_button['state'] == ACTIVE:
- self.do_send()
- def do_send(self):
- """Start a thread to send an e-mail in an asynchronous method."""
- threading.Thread(target=self.send_thread).start()
- @threadbox.MetaBox.thread
- def send_thread(self):
- """Try to send an e-mail and display the results of the attempt."""
- destination = self.__to_entry.get()
- try:
- self.__data_handler.send(
- destination,
- self.__subject_entry.get(),
- self.__message_text.get(0.0, END)
- )
- except:
- tkinter.messagebox.showerror(
- 'Error',
- 'An exception has occurred.\n'
- 'Continue for more details.',
- master=self
- )
- TextViewer(self.__tk, 'Traceback', traceback.format_exc())
- else:
- tkinter.messagebox.showinfo(
- 'Success',
- 'The message was sent successfully to {}.'.format(destination),
- master=self
- )
- @property
- def current_status(self):
- """Find out what status the program currently is in."""
- return (Status.connection_error
- if not self.__data_handler.is_connected else
- Status.login_button
- if not self.__data_handler.is_logged_in else
- Status.mail_label)
- def do_login(self):
- """Open the login window and also the user to supply credentials."""
- with Resource.load('login.ico') as icon:
- LoginWindow(self.__tk, icon, self.__data_handler)
- self.__login_button['text'] = self.__data_handler.username or ''
- self.refresh()
- def try_connect(self):
- """Repeatedly try to connect to the server every 0.6 seconds."""
- if not self.__data_handler.is_connected:
- self.__data_handler.try_connect(self.refresh)
- self.__connector = self.after(600, self.try_connect)
- def refresh(self, is_connected=False):
- """Let the user know if there is a connection to the server."""
- if is_connected:
- tkinter.messagebox.showinfo(
- 'Server',
- 'Your connection is live!',
- master=self
- )
- class TextViewer(Toplevel):
- """Widget designed to show text in a window."""
- BACKGROUND = '#FFFFFF'
- FOREGROUND = '#000000'
- WIDTH = 800
- HEIGHT = 600
- X_OFFSET = 10
- Y_OFFSET = 10
- @classmethod
- def load(cls, parent, text_handle, icon_handle):
- """Open a TextViewer with information loaded from a file."""
- with text_handle.open('rt') as file:
- title, *text = map(str.strip, file)
- cls(parent, title, '\n'.join(text), icon_handle)
- def __init__(self, parent, title, text, icon_handle=None):
- """Initializes the window for the reader to see its contents."""
- super().__init__(parent, borderwidth=5)
- if icon_handle is not None:
- self.iconbitmap(icon_handle)
- self.geometry('={}x{}+{}+{}'.format(
- self.WIDTH,
- self.HEIGHT,
- parent.winfo_rootx() + self.X_OFFSET,
- parent.winfo_rooty() + self.Y_OFFSET
- ))
- self.__frame_text = self.__frame_buttons = self.__okay_button = \
- self.__scrollbar_view = self.__text_view = None
- self.create_widgets()
- self.configure_widgets()
- self.title(title)
- self.transient(parent)
- self.grab_set()
- self.protocol('WM_DELETE_WINDOW', self.okay)
- self.__text_view.focus_set()
- self.create_bindings()
- self.__text_view.insert(0.0, text)
- self.__text_view['state'] = DISABLED
- self.wait_window()
- def create_widgets(self):
- """Populates the window with the widgets that will be needed."""
- self.__frame_text = Frame(self, relief=SUNKEN, height=700)
- self.__frame_buttons = Frame(self)
- self.__okay_button = Button(
- self.__frame_buttons,
- text='Close',
- command=self.okay,
- takefocus=FALSE
- )
- self.__scrollbar_view = Scrollbar(
- self.__frame_text,
- orient=VERTICAL,
- takefocus=FALSE
- )
- self.__text_view = Text(
- self.__frame_text,
- wrap=WORD,
- fg=self.FOREGROUND,
- bg=self.BACKGROUND,
- highlightthickness=0
- )
- def configure_widgets(self):
- """Put them in their proper places throughout the layout."""
- self.__scrollbar_view['command'] = self.__text_view.yview
- self.__text_view['yscrollcommand'] = self.__scrollbar_view.set
- self.__okay_button.pack()
- self.__scrollbar_view.pack(side=RIGHT, fill=Y)
- self.__text_view.pack(side=LEFT, expand=TRUE, fill=BOTH)
- self.__frame_buttons.pack(side=BOTTOM, fill=X)
- self.__frame_text.pack(side=TOP, expand=TRUE, fill=BOTH)
- @event_handler
- def okay(self):
- """Close the window."""
- self.destroy()
- def create_bindings(self):
- """Allow the window to respond to certain events."""
- self.bind('<Return>', self.okay)
- self.bind('<Escape>', self.okay)
- class DataHandler:
- """Handler for communications with a SMTP server."""
- HOST = 'smtp.gmail.com'
- PORT = 587
- __slots__ = (
- '__is_logged_in',
- '__is_connected',
- '__server',
- '__username',
- '__password'
- )
- def __init__(self):
- """Initializes the DataHandler instance's various flags."""
- self.__is_logged_in = False
- self.__is_connected = self.__server = self.__username = \
- self.__password = None
- self.__cancel_connection()
- @property
- def is_logged_in(self):
- """Checks whether or not the instance believes it is logged in."""
- return self.__is_logged_in
- @property
- def is_connected(self):
- """Checks whether or not the instance believes it is connected."""
- return self.__is_connected
- @staticmethod
- def __check_string(value, name):
- """Verify that the string has a value type and value."""
- if not isinstance(value, str):
- raise TypeError('{} must be of type str'.format(name))
- if not value:
- raise ValueError('{} must not be an empty string'.format(name))
- def __get_username(self):
- """Retrieves the value of the username."""
- return self.__username
- def __set_username(self, text):
- """Validates the username and sets its value."""
- self.__check_string(text, 'username')
- self.__username = text
- username = property(
- __get_username,
- __set_username,
- None,
- 'Sets value of username.'
- )
- def __set_password(self, text):
- """Validates the password and sets its value."""
- self.__check_string(text, 'password')
- self.__password = text
- password = property(fset=__set_password, doc='Sets value of password.')
- def try_connect(self, callback=lambda status: None):
- """Attempt to connect to the pre-configured server address."""
- threading.Thread(target=self.__connect, args=(callback,)).start()
- def __connect(self, callback):
- """Connect to the server in an asynchronous fashion."""
- try:
- self.__server = smtplib.SMTP(self.HOST, self.PORT, None, 1)
- except (smtplib.SMTPException, socket.gaierror, socket.timeout):
- self.__cancel_connection()
- else:
- self.__is_connected = True
- callback(self.__is_connected)
- def send(self, destination, subject, message):
- """Try to send an e-mail with the provided information."""
- if self.__server is None:
- raise RuntimeError('cannot send without a valid connection')
- packet = email.mime.text.MIMEText(message)
- packet['From'] = self.__username
- packet['To'] = destination
- packet['Subject'] = subject
- self.__server.starttls()
- self.__server.login(self.__username, self.__password)
- self.__server.send_message(packet)
- self.__finish()
- def __cancel_connection(self):
- """Reset the is_connected and server attributes to default values."""
- self.__is_connected = False
- self.__server = None
- def validate_credentials(self):
- """Verify if the saved credentials are correct or not."""
- if self.__server is None:
- raise RuntimeError('cannot validate without a working connection')
- try:
- self.__server.starttls()
- self.__server.login(self.__username, self.__password)
- except smtplib.SMTPException:
- self.__is_logged_in = False
- else:
- self.__is_logged_in = True
- finally:
- self.__finish()
- return self.__is_logged_in
- def __finish(self):
- """Finish the conversation taking place with the server."""
- self.__server.close()
- self.__cancel_connection()
- class Dialog(Toplevel):
- """Generic widget that should be used as a base class."""
- X_OFFSET = 50
- Y_OFFSET = 50
- def __init__(self, parent, title=None, icon_handle=None):
- """Initialize a Dialog window that takes focus away from the parent."""
- super().__init__(parent)
- self.withdraw()
- if icon_handle is not None:
- self.iconbitmap(icon_handle)
- if parent.winfo_viewable():
- self.transient(parent)
- if title:
- self.title(title)
- self.parent = parent
- self.result = None
- body = Frame(self)
- self.initial_focus = self.body(body)
- body.grid(sticky=NSEW, padx=5, pady=5)
- self.okay_button = self.cancel_button = None
- self.button_box()
- if not self.initial_focus:
- self.initial_focus = self
- self.protocol('WM_DELETE_WINDOW', self.cancel)
- parent = self.parent
- if parent is not None:
- self.geometry('+{}+{}'.format(
- parent.winfo_rootx() + self.X_OFFSET,
- parent.winfo_rooty() + self.Y_OFFSET
- ))
- self.deiconify()
- self.initial_focus.focus_set()
- try:
- self.wait_visibility()
- except tkinter.TclError:
- pass
- else:
- self.grab_set()
- self.wait_window(self)
- def destroy(self):
- """Destruct the Dialog window."""
- self.initial_focus = None
- super().destroy()
- def body(self, master):
- """Create the body of this Dialog window."""
- pass
- def button_box(self):
- """Create the standard buttons and Dialog bindings."""
- box = Frame(self)
- self.okay_button = Button(
- box,
- text='Okay',
- width=10,
- command=self.okay,
- default=ACTIVE
- )
- self.okay_button.grid(row=0, column=0, padx=5, pady=5)
- self.cancel_button = Button(
- box,
- text='Cancel',
- width=10,
- command=self.cancel
- )
- self.cancel_button.grid(row=0, column=1, padx=5, pady=5)
- self.bind('<Return>', self.okay)
- self.bind('<Escape>', self.cancel)
- box.grid()
- @event_handler
- def okay(self):
- """Validate and apply the changes made by this Dialog."""
- if self.validate():
- self.withdraw()
- self.update_idletasks()
- try:
- self.apply()
- finally:
- self.cancel()
- else:
- self.initial_focus.focus_set()
- @event_handler
- def cancel(self):
- """Close the Dialog window and return to its parent."""
- if self.parent is not None:
- self.parent.focus_set()
- self.destroy()
- def validate(self):
- """Verify that the Dialog is in a valid state."""
- return True
- @staticmethod
- def apply():
- """Make any changes the Dialog wishes to accomplish."""
- pass
- class LoginWindow(Dialog):
- """Widget to allow easy process for supplying credentials."""
- TITLE = 'E-mail Login'
- WIDTH = 45
- MASK = '*'
- def __init__(self, parent, icon_handle, data_handler):
- """Initialize the dialog with the necessary information."""
- self.__data_handler = data_handler
- super().__init__(parent, self.TITLE, icon_handle)
- self.__username_label = self.__password_label = self.__username = \
- self.__password = None
- def body(self, master):
- """Create all the different widgets needed for the body."""
- self.__username_label = Label(master, text='Gmail Address:')
- self.__password_label = Label(master, text='Password:')
- self.__username = Entry(master, width=self.WIDTH)
- self.__password = Entry(master, width=self.WIDTH, show=self.MASK)
- self.__username_label.grid(row=0, column=0, padx=5, pady=5, sticky=E)
- self.__username.grid(row=0, column=1, padx=5, pady=5, sticky=EW)
- self.__password_label.grid(row=1, column=0, padx=5, pady=5, sticky=E)
- self.__password.grid(row=1, column=1, padx=5, pady=5, sticky=EW)
- self.bind('<Key>', self.refresh)
- return self.__username
- def button_box(self):
- """Create the button box and change okay button's options."""
- super().button_box()
- self.okay_button.configure(state=DISABLED, text='Login')
- @event_handler
- def refresh(self):
- """Perform a soft validation for the username and password."""
- username = self.__username.get()
- password = self.__password.get()
- valid = re.search(r'\A(\w|\.)+@gmail\.com\Z', username) is not None \
- and len(password) > 3
- self.okay_button['state'] = ACTIVE if valid else DISABLED
- def validate(self):
- """Attempt to validate username and password with the server."""
- self.__data_handler.username = self.__username.get()
- self.__data_handler.password = self.__password.get()
- valid = self.__data_handler.validate_credentials()
- if valid:
- tkinter.messagebox.showinfo(
- 'Login Success',
- 'The credentials were accepted.\n'
- 'You are now logged in!',
- master=self
- )
- else:
- self.__password.delete(0, END)
- tkinter.messagebox.showerror(
- 'Login Error',
- 'This username/password combination was not accepted.\n'
- 'Please try again.',
- master=self
- )
- return valid
- if __name__ == '__main__':
- SMTPClient.main()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement