From 670d57374e73f8176c2b0a309050eb7a5d4f9ac0 Mon Sep 17 00:00:00 2001 From: Interitio Date: Thu, 11 Mar 2021 21:16:11 +0200 Subject: [PATCH 01/31] (routine): Update `cmdClient` pointer. Update to the latest version of `cmdClient`. Update `help` command to point to new command name cache. Add `cachetools` library requirement. --- bot/cmdClient | 2 +- bot/commands/help.py | 4 ++-- requirements.txt | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/bot/cmdClient b/bot/cmdClient index b47fcb1..7f9a9e8 160000 --- a/bot/cmdClient +++ b/bot/cmdClient @@ -1 +1 @@ -Subproject commit b47fcb1c2b2a4c4bd785cec525633c5a5184bcab +Subproject commit 7f9a9e816d159eba609c74dbf31efe43d38d13f5 diff --git a/bot/commands/help.py b/bot/commands/help.py index ef80796..1e47b91 100644 --- a/bot/commands/help.py +++ b/bot/commands/help.py @@ -40,7 +40,7 @@ async def cmd_help(ctx): """ if ctx.arg_str: # Attempt to fetch the command - command = ctx.client.cmd_cache.get(ctx.arg_str.strip(), None) + command = ctx.client.cmd_names.get(ctx.arg_str.strip(), None) if command is None: return await ctx.error_reply( ("Command `{}` not found!\n" @@ -79,7 +79,7 @@ async def cmd_help(ctx): # Handle the related field names = [cmd_name.strip() for cmd_name in help_fields[pos][1].split(',')] names.sort(key=len) - values = [getattr(ctx.client.cmd_cache.get(cmd_name, None), 'desc', "") for cmd_name in names] + values = [getattr(ctx.client.cmd_names.get(cmd_name, None), 'desc', "") for cmd_name in names] help_fields[pos] = ( name, prop_tabulate(names, values) diff --git a/requirements.txt b/requirements.txt index 844f49a..9da8333 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,2 @@ discord.py +cachetools From bb300e3c444952a9b20345c74962e4c8e5883c96 Mon Sep 17 00:00:00 2001 From: Interitio Date: Fri, 12 Mar 2021 09:22:07 +0200 Subject: [PATCH 02/31] (routine): Add gitignore. --- .gitignore | 120 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9c0d41f --- /dev/null +++ b/.gitignore @@ -0,0 +1,120 @@ +# Data file +*.db +data/* + +# Conf files +*.conf + +# Development cruft +*.swo +*.swp +RCS +tags + +# ContextBot contents +contextBot/* + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# dotenv +.env + +# virtualenv +.venv +venv/ +ENV/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ + +# vscode config files +**/.vscode From 9634af12457280895f69b24bbfc03945ce61e9b5 Mon Sep 17 00:00:00 2001 From: Interitio Date: Wed, 9 Jun 2021 22:11:17 +0300 Subject: [PATCH 03/31] rewrite: Full rewrite. Full rewrite of PomoBot. --- .gitignore | 5 +- assets/guide-gifs/pattern-guide.gif | Bin 0 -> 424662 bytes assets/sounds/slow-spring-board.wav | Bin 0 -> 299646 bytes bot/BotData.py | 156 --- bot/Timer/Timer.py | 686 ---------- bot/Timer/__init__.py | 219 ++- bot/Timer/activity_events.py | 14 + bot/Timer/core.py | 1181 +++++++++++++++++ bot/Timer/guild_events.py | 52 + bot/Timer/interface.py | 436 ------ bot/Timer/lib.py | 75 ++ bot/Timer/registry.py | 57 - bot/Timer/timer_reactions.py | 28 + bot/Timer/trackers.py | 8 - bot/Timer/voice.py | 57 - bot/Timer/voice_events.py | 45 + bot/Timer/voice_notify.py | 37 + bot/commands/config.py | 254 ---- bot/commands/existential_cmds.py | 255 ++++ bot/commands/guides.py | 76 ++ bot/commands/guild_config.py | 168 +++ bot/commands/help.py | 130 +- bot/commands/meta.py | 87 ++ bot/commands/presets.py | 400 ++++-- bot/commands/registry.py | 693 +++++++--- bot/commands/timer.py | 982 ++++++++------ bot/commands/timer_admin.py | 50 + bot/commands/timer_config.py | 154 +++ bot/commands/user_config.py | 44 + bot/data/__init__.py | 0 bot/data/data.py | 481 +++++++ bot/data/queries.py | 18 + bot/data/tables.py | 46 + bot/dev-main.py | 7 + bot/main.py | 35 +- bot/meta/__init__.py | 3 + bot/meta/client.py | 10 + bot/{ => meta}/config.py | 0 bot/{ => meta}/logger.py | 9 +- .../exec.py => plugins/exec-cmds.py} | 0 bot/settings/__init__.py | 5 + bot/settings/base.py | 302 +++++ bot/settings/guild_settings.py | 163 +++ bot/settings/setting_types.py | 632 +++++++++ bot/settings/timer_settings.py | 315 +++++ bot/settings/user_settings.py | 97 ++ bot/utils/ctx_addons.py | 95 +- bot/utils/interactive.py | 152 ++- bot/utils/lib.py | 12 +- bot/utils/live_messages.py | 33 + bot/utils/seekers.py | 62 +- bot/utils/timer_utils.py | 38 +- bot/wards.py | 41 +- data/migration/v0-v1/README.md | 41 + data/migration/v0-v1/lib.py | 104 ++ data/migration/v0-v1/migration.py | 311 +++++ data/migration/v0-v1/v1_schema.sql | 157 +++ data/schema.sql | 157 +++ data/timerstatus/.gitignore | 0 requirements.txt | 2 + 60 files changed, 7125 insertions(+), 2552 deletions(-) create mode 100644 assets/guide-gifs/pattern-guide.gif create mode 100644 assets/sounds/slow-spring-board.wav delete mode 100644 bot/BotData.py delete mode 100644 bot/Timer/Timer.py create mode 100644 bot/Timer/activity_events.py create mode 100644 bot/Timer/core.py create mode 100644 bot/Timer/guild_events.py delete mode 100644 bot/Timer/interface.py create mode 100644 bot/Timer/lib.py delete mode 100644 bot/Timer/registry.py create mode 100644 bot/Timer/timer_reactions.py delete mode 100644 bot/Timer/trackers.py delete mode 100644 bot/Timer/voice.py create mode 100644 bot/Timer/voice_events.py create mode 100644 bot/Timer/voice_notify.py delete mode 100644 bot/commands/config.py create mode 100644 bot/commands/existential_cmds.py create mode 100644 bot/commands/guides.py create mode 100644 bot/commands/guild_config.py create mode 100644 bot/commands/meta.py create mode 100644 bot/commands/timer_admin.py create mode 100644 bot/commands/timer_config.py create mode 100644 bot/commands/user_config.py create mode 100644 bot/data/__init__.py create mode 100644 bot/data/data.py create mode 100644 bot/data/queries.py create mode 100644 bot/data/tables.py create mode 100644 bot/dev-main.py create mode 100644 bot/meta/__init__.py create mode 100644 bot/meta/client.py rename bot/{ => meta}/config.py (100%) rename bot/{ => meta}/logger.py (65%) rename bot/{commands/exec.py => plugins/exec-cmds.py} (100%) create mode 100644 bot/settings/__init__.py create mode 100644 bot/settings/base.py create mode 100644 bot/settings/guild_settings.py create mode 100644 bot/settings/setting_types.py create mode 100644 bot/settings/timer_settings.py create mode 100644 bot/settings/user_settings.py create mode 100644 bot/utils/live_messages.py create mode 100644 data/migration/v0-v1/README.md create mode 100644 data/migration/v0-v1/lib.py create mode 100644 data/migration/v0-v1/migration.py create mode 100644 data/migration/v0-v1/v1_schema.sql create mode 100644 data/schema.sql create mode 100644 data/timerstatus/.gitignore diff --git a/.gitignore b/.gitignore index 9c0d41f..bcf6b49 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ -# Data file +# Data files *.db -data/* +*.pickle +*.json # Conf files *.conf diff --git a/assets/guide-gifs/pattern-guide.gif b/assets/guide-gifs/pattern-guide.gif new file mode 100644 index 0000000000000000000000000000000000000000..d2add88a4d31bec5465b352ee473d3e0fb31ad46 GIT binary patch literal 424662 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 z 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%0D+xu(#ZCKPr}sqmWelWQu^uc?k*yR>la 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

N!-|N6QM9zidjm9t7080t z!9jl_>aQp!i7(<%;X^+QQ?Kd%ql;=6KuRR2qe(}oeLOn_cfruIo55}~MLHLNFyJUqt|1MMGRa5- z5{OAW4(sFaGwZ0%b_s(w)6uj6VZIcBJ@9?Xk184hN12&( zBC|rSNHoy0plToyX_9#Pk;G>N2_%BZD;O{VK9{6hF8;noY-SPyE8{g<-s1r;CJ#ld zREK3_Q|Wrr_o?Vb3!oo^u9J~4wz8oUyM?EZ3U`#18_t+|GZ@?UNsxoftdKuEB!B}1 z(4X2&VRwBbek^be(4*ZI{qTh9&a7? z$9ZlhDE;M*jJ;WO$w%hX;~GKS)Ne2bio|4qP09v9^Y?6o%q>ZvXM>zj1M@&_vCNOS zju>#Tn9zBN9lodc@sCynIk+U~YlXy5bu+u61L)S%+}J5b;EM7~ zk!rVs3p|d`KmStjQ}JS$cRbKW4&p;ZrMt)e(Ba?a+|COvCAbTt-PxDb!8*#`o2tk+ zr1j`K4CA2-9kEX7e5nOg6c@!?>By8RJ0kV<2}QN2A+mI1>nqLYD!J7Ge5F;3Wse9; ztbz0L*9;VV0?`~N6dv@{vEczZXY?crJ>0su`^J%@dn>iBiIWWpj+D>xvA}$6dA?iC zYeS~5^T|7wL4)lWJ*$c>ZuLXQO4@PG;lq7V@OAfVOm^j7W(xrwm? ze7~tjVXt`OWah$++feh!%_0WBXb5NFBHV4Xf|I7rp7_ zesA8Gvv9F_D(61UsClJ3M*O4EK%6$VVD$U=(j)qU#^zV=(hTKSnoRk~(uz+nH% z{43NqQ{ROLy4_~?c=J|M=eNmT?G{|k>149KN{6d zU2Bs0#v_5(_u4;ot;fF5o7sLG*Hk~fc8U7zr57@;eWPnL%}9TaqdvY7^89BmT_GI) z;^2+l^R0pxN#j)E`os5M<+dVT>o1uo{TiMx-M(-1`$(+>ulM5W`#{pSab>F+ku>yN*w-CcQg`T6p7>fxf@i(l>OhI_(F zziZCDIGQ=1yw^+JU*q_5v}i=m?pt&;}! zL(_K%)0N@;hf6#I1TMLt_iv$@o(nS!Qf#@3-*BB9?mOq5jot(SKl{*shDpeddWN7~0=!*i2M(1I;O5 z_;r`U=D;<)a!n&0f+mNRgs*zbXzMe*)Evnc5v zwSaH2*Re{Q#xen5o~%+WE6I*b?@?dc)9o>QX99~h(Pg6c8u~yK2j@9gOwi7yY^y|r z)7_dSVi5Uzh7DN7$C8%M|F;&s28ULT8sOUeWDD8F#G8E4KFH)3nz7qtd5Y*zkiT{$KNnq|^^2w|kjd1LxGNSY* zSx#S6@VJ_bLeP>YgYd+RQ@!f%x;Zhu!l%hX+P$m-WDU+9wrr}PuVP?>i{z-MAAdi@ zu({h1X(Ed$hfgi)b9x!|NJu;si~I<&p<%Q)jg=SX|D%d_iYd}m(E~}Xky-G;oXVKE zz8m(UnF;8?Pv!KiL7uV7y0O0QoUKPS^T+~6A~BApijIq6bv;!`cywfU;>v>Vt9idj z!N01g&HRObRZ%zZ>aDr|sG204uOPa4vwG}!{r_S`#aO`j64k-T_IAwk2at{YOwP>sSaFLPz`OlKy~kXn9+4rg~t@4}6)s$6z*sD+(_xE8B(G2w-y#+GZdLXkY3PD|XcdA@7u}(nu z!e3R?&69UX3bL8`U#e)&N7?1JvbCIBKS2E^Wi8(g$-VrX_X_s{b&Bqf)~=EbMGREE z3}?DILdy*v^q?9D+?##SRFD*=ytEN0^O9ADR=#WuByrzU^8w9D%8Wh=G0Y>fBdO#N z)dzaIV2F8Wz3ksT{&qt za+Yy`?c&mC%!9hc&{55hwb)0wl651CgycE@q0o>fcChAVkIZp!Tg9XO2Ttu{mH2}> zKUcgygSnES=k2m84nLx@sK3$2AE8ztE(Y@_MgH=D|39QCIKYoczC_g7s^tJfrmY@! z#2N?vONyfBZ0A6{Eb>b;5a$`Cgre`l0iim1vnwtBdbF7djTC(;HTK>=dB(pwOOZy3 zDysrnjWklU*PX$5#6xh$)%S7+9S@BZMff6ph#NOV83e?@HPmjjXxJ;3nq6Hb#)YMt zKaV9yUXVf>DGC}@066R=#*^!L?m4wT_P-w6tD{IZ>LCg!&Krg+jA{V9bQhFpo+!m% z(a+>=nxqDIrO3aYsE?0fI8nfufT(`Wik8y-B?N(IC^%u-g;NVu#oK>xqIL|M5iS z$PvJ$CpuA%BO+HJiHv@yQX1ufCNedusefBfYydo@_LQOqEJd|$HW_S0fjApFrf8;3 zQ!}}*@g|uK6>V4R*oFi|#=-j4K`L%nw@cN`__-8P8Nt#$<~JaWFPS63y(jaG>eA=QSL?cL8+`r?{uj^kW>;pE_UDaG@hRUes`{-fg?7ajrGyT%Cs7 z>)n<%4R@zq_&XwW=xJlaH|?85Gz4?0z(gmBsC^7FQL+TT155X7co#>xn>_tT0tM{< zN}y!tuQ49h)R?~%sLh^()Bsf4SOi}w>;nPB@$%z4hUW&L@*qpCmf(A9-gFo+#zg4E zuKVQ{=L?z+ahhT2u}uO1v-L)>u$I&3SkMVMhQeM^=o7%6_Qh*1g?S1LVO2>z+MOmm z0kK{iDKT^)185vT0yGw4?ezxSAsCo^FPHXJHe|2F>O*PtqSgW!r?m!VGL~sJ*0AXn zMz`WwJwsUW!sHabB#877Rs{)2oghLRi4R8P>5KfE*g7Jp( zDfU{Hmrm`#%!!&wM8)|gHaBe`7(-ITK%61zD{*2qCakwWL9E(&cX1(f}G z2jJ(Q&?t{b4K*~GC*HZ+{?*@C2Y|+oTXod)L~Aa#i8oN&iy=TYzh0OB*F0;_ zm`|Vr%Y7o=*#22Z=(YU}sYC#1y?Of}Q_PZk;(`vzL_542iRjhNCK41Gs~|v8T_XeC z#m?S@uFFY3ncMr-bX`ma42b$~TPynOkIz+i4yeftygqh57d=*c_46I3)vnG#DQ4$^ z&JT9GE_MUT_XplTvmDGHFx}|<$iocTIsQOR8e;4kdvp~d)Ya+y`=kHwy~5JQc#$!g zxi2vWV_)^>H7p0u&y5h1N2Mxs&%00FOa5qWFs1kPb5!YsRM*&dgO}>RKkfe>xuy0^ z$8w4eH{JAm`u6s0ebph+SIzEI4j7FkJj+QtHoVDNE7VKWp=Ubff<8xISP0eu)O+ zuF&41;gaJ~%Q?ongd`Ik?cUX}QZ~-2wYVp%=XWG>pHHM4u6+2tdCg#tj=Dj;bf9Fo z#BcO#p7ES8S=ZiESKl~9?uC82w&S^JI4=0RlM(D)_u@T441m-|0uVTaSyJCQyxf?` zB*LD(w2AAg4;KDb|Wu9BI$@ zx4vscfOEO3VQlZ6yv}U^)DPg%8y(`ebT!Cn3G6rEccd^>fX3PpIVa6G?->&~H404S zY<(v3(6f&vCO7B5wD3;%yuZLxaq=l+I-Fa-Fx;~@olVNIvoON@wNlq91GM4;!i5Ad zszK9&Ul#{r2+vC2xOvv|XhQHukb`lG%d;#7gz>dHTh0+>ehFv4+i#$al0VtuET@$` zzmEYJEDM?7n^cyp*Mj$PBO6V2yu7flJGrj$-tV%0SyICG_u*=%7iC@Z{T5PM%oUFt zRsGrF$7sAvF%pp*jyd#H*e9z0x z5DpVySyqPQnL9xwLYVz93GBC}{pR5wQ{B~9{1Mc5wyy;fR)=2;el=O4sjFYU3)y5f zKJ0n-c-a^aIzD(OEp0T3F7>RSWt!-#MKkE+0Sv$z>5P&locrU90~=%5YO({kF;CjV zF9DQyMn0SyC(8sTK7B9(G20Je>8n)w?-gV{$hZI=k(aIN2&5~G)MuoC?T2_zmdS1> zz#rVRSvV`3?~&B|?wpq!nbmnJ)ou{71SJ7>;xZ#+=OW%ae0EP*bEq-N$zoMM9sK59 z(a9WBO#rMdlU65PSMeHb)TomM@Xv^i@+n%Ki2@lr!zk~y?Sk=xu=UEp&oN&@K2OKZ z=IlSIaLuyr^V0>9hY1MMOVn`zr2_A=MSXr{y$Rr; z#)kSDX3XS}TT?LHqbQvIw=*u^{_45Daggx9e$h!4zq#c1&EjS8_cLwj&Ntjyv~t3+s-(5OBH^?H_+_v7x><0oxt3qy7 zFEx~ww{9#vaN8NSP@3L(W^`@Y=B1c-msggY5b5Xhmf||ar&b@__ooy559_}YXnHPh zm)yW!XXxFo{cm@|W)g(bsnAlF?UpicC-x18oV2XK!cZ?KBdiwRR z3E%ODb{-IwLM-#291z63)RP3~#pUFsN~q7$+ZGz*9)%DUqD3Hd^~- zCF}FNbc5qECjUYc5ti#2raKJCsb!(rhkl!nJR={zxt?Y zj;C9?T`~81wy*j;qPNgYV~hFf51o1bS?xZJ!Rxs`7rc=3g(nT*wYkYV_ZatSdamfV zYgg1O*V$j+z$&WdrAJd2#GDE^E1KD2Z;B(tRCrIChu+90u4Ucc!8etYm&OGJoz*N|kG$D8WO`nTm{vB-w zuvRdJox!bwugeDJ$Pk`s9bk+a=syGE9KITKnuq~uV>E^`twM2qI>X}sw@vCTnSa+N zc+m(HBm{QrIl+F1OYss3Y(EYLplGOe8`|1G`U*IilmgEhy6S3sy6_|xq*|eaH+X5s zJXizB=0<>AUiQcQj;=*Q`0Vb;N~+I_W<*th@k)60;=Uj&+3Tu6JYUs4Gpw;Hb?4B^ zrvDT7DZwSPCwmy~@fE!2f&A#EQrP1}kxq|&tHK^bY=x*;$*h-SVek7+Z*C0%FU#1%m&F1<6Hb)OvDa%C z$i%7`xd?chKQDas_~y?XW*2WkCHB__>+`vU&XdBd2{~9zRe{VBALXo-{xb!0S8fUT z^1RyYtx|Z)QdRPeJC=3^j|=~&Szr-nocGRGbNkM4$>MV6o%g)<+r_g2{yHBHl|ov! z3ztj$4L0t40C#TR-T&;5Wi1*?hVPV5I|Z036%G5E?cD479AIHx^s$?`Q+3xV@JyKN zr^3CREp5RdTb^H^s>Y^kY)gZ5i;G69>~`xslNTL7bVNRB*?qKp@`5|nL=p5pK6O)_ zg4B%{6A{7F@ro2q*JG;=$SV@UsWUQ#*Q52Cb0?}GoxgM(g|^HB{`2GsxL zMR~mtAgJzlOm3FuDVWoaO4cv_(}cTL7-F(pi<1V>15xkh{9`dsGpeE zPY$1}dE1k@%)l@(oRAx@2K-#w)EnlpNsDiQe5bcaRA)B^*`NNy$rx}vFV#R7-4D@` zwt3w+fXPjT8Vb?IqSs5->AX=VR>aj#7V)836obh^1Af59Wpd0`a_NCL<1?%e{t9de zi*2f@V4|F5%Uv|`r#*UI7d{4vE z{S;wUH|lHf zjbNcMy2eb4AUe9o|Hz}1W7SaVoFaXG?h*?BmmNjg(zjk)NSa+m;yEHcXB)zoq;aUc zZpPd^pBYbNBUVKG2{Mb!&T1u6mOC3XvArHYM9%tm!6jds+>jL02nGoc%(OxGGwl-^3ND7Y_@zY%M`y`$2G8sTST9He?0E|N zGngaC$KPb@QQb_u*t1Sd_szEmoFf$e|Cpnm{^+2;=4gG|vbnxpq?|C*y?D)Cz%&>h#dxzV!KjVP<1{FwEnw-h9o z7v0-g>HJ6;9vg6SgpfK36nf1kEmAJV@27sFwXv#Fa^IJiCs(MNq8H2*l|>q78{v5P z_7nq$%1NuEy`NL5-<3aYDiTgA$wWB~J_Lun$DvQD?mkpC*mfKYhUVhcEI8g9ZmZK( zsXN$>*feQSeKb6qKDk9{UKFQQygmk~X~isPCfE%}Z)*isYF)7#!Z&G;dTU>AQp<_b zc`v3@y8XUTO}8pax3)?5(YEdrHNB@%dhJbmUE6xyYWjUq`fr-_-*4+PTR<8qAJqYa ztNG9|4xmH+ojM*m#i5NwkFVrI51NdAZySNsF|cS1eKUr62g9b0<%-7gHe&^Mup&1x z8{1l9&Bh8l#wzM28qp>?BgRtpT6*dyOruX&G@m%5ZcH^&*RqZ_b!j$r-!bJ^Hx1-4 z3v4zE*)cP*H}mH>nb3SPdFLd|{-isHd1kYDPP6HiW{qpn7I&I0l+-P3KUq{opQ^Pt zF5gkBQ@4C-f2y9-vUSI@d*|e{5w+fCGiwS5`X)U?x$#K=*+9Wg?wk?fgbvZv4vOjP zkr_oC#E@itjs!ggNU+}oXQSyPfs=6n@47m66wL!f3)F@PFL}nyb#rUqf<|Ldp^+x1Mvbf~TsRQ)e$K=?fpeV`I)Psu$8vJ-8s*^EtSFO> z<@VO-pIah=C{KsqO-}VV2PF)zCSHf+uRsG`8hBV{*wPG~ODg{F!oVcCHlum^~@fdWNfg?3|9$I#FQ5$-gCy#)#J z9K_>=r}rQTtUmSltBQ(X{-cUg_(41W->RsmkL8%rUsZJ0g{F#X#$vP<|4SA1oI4mj ze=^!FFb7OiMFB3rRWrz16g&HrP91ape6t4!=lL9v3zlLE#-69Cq6N30R(OsIkW17U z>Q@x>L4&WD6~qezQ3TFo@GfBlPO2mt{ub>_-43P6qS{=?V-%YswOvWYu=E@wF+XPK zF}v@CV1+TGPaBM7T+ly`7mhgSSro%8@cV5J!8%$dcVfqk(H@g_=q*Py%@+OD44L5g z#}*~K>ES%~o2@^`fcfxoG+VT!!2N%0QIgwVTa*%Ogo$P5{A-J*h4W6~eXrwJC$P3O zTXbd}WsWH~yC!H(u%p?cp$=RttTR^KMoM_z8GMNCaAZP(Q599?fhG7j`8Q%T&{w?n2(R zLq~Kj)Q{m|oX3wbaiD`xlQyG$R5TVne~M;{mS5j?+}qiO{vun^Y*ERle{E4mhkU7o zYfd_$dJUu_JEnNefZzG_TLTQ%J5!=H~L<8jLBV>SCOb|Fk^VI^O*49ynB zv79o7xB)@06VMs)rBT`>9a84-Sn0MS?4a42TJ2nBNxRc7+1KL0#^j9c*vCKe>lvFX zziWWpVxS{v;rp{3gR$kW8`S>VqNIG2I|Qm7-hvXU=jqAbar<+Q9rFTP?5)f5j`>lp zQQym>B3v;Bqh7WNPT#KC`SN1CznnkLzh|P|$~95wO}^b7+rs0}bdT>2q?I?Ax30UglT75RnHD&xpp@;bQ2$9!+uqvSjG}_{WFa&?%ROzpyAd{+TvO0~f!o z)1ZZK2y{w>=sx+1wX!I1{1+BwD!A|;SQHSm#58raL?5bw#I-}>L|oK(2)$1)ds8k$ zP&dkvtv}*0KSl}PqR&$$P?vZ2jZ_Mue62>HE#^gy%V+pPbVP(>*^}n6NeR0y4iwk# zqed>;ZXyB?&g{2)pN3QLzU_%v{vinS%%Z2h-0{t`XC;$rSmGfJyn0^rCB=XJ(%^As3>BG&(_Lhe~df08b|XEhKZzp`c7q-kW&b^0aTn zah?o1)B>P)_VLmY$sOMFo7LhrETEgi3knd7^!B{FY_DL^zgHdlK%ad*9sL3w7rI5h z-TURDgrD~&&trwIf2e<*d>r6SVm9n%lpLe@3Yi9Wy$m+Ekdx#U68|Q-FrBC^5xY;j zB)Zmi$8%LdMMF~U<%#c);-)bE}Dj#-+odyyPn+-RP8s=|(zsR2|rX%!LL}-G5 zqDrnqU9oH7H(=7dxmeul^I2YfPic*P|Byt~mM4#+1Kp)^ehM?sU~CfYKMF048_cmj z%lcs#R2^>bZYsI4HG}M`%l!_Upc5GWJ%XS(_iS=`dkla0aDj8qXfut>ym;thjoGny z;peW&LosY`-?i_)Ex0MPFTAsK{Zh$f$6-~=Bd3a(rdYJ&=V!3Fyj_hj6N9j(g77C) zo8eBQeYHqB*IAXzTW5ZMIBIg795*_zgZc8pZoA;JE%pk;{s*;+nU^eN0bJOy_9u}AiqqhG%nwHqkisf_%F+1gL$Ze8Rz zlII7P+~~}e(F?a)#&;0;m(iA7uSql{P}%+bmVl(6c1)G#}VK{8MqV%#eN)-#SlUDOl7w+ z_q@XGk>Q!f7uxTcE_4aul_8pI?v*K7mf@8p`?%lh|JHML*ZaM%8J>V=`xUy{Soq!XE6emNa!7mQR~-7B z-oNCWYs71j*qKcKvc!pmbgsz9HGZTNmQyvP%R*TJ6sd%r^;X6%1Ln! zxL*|dwwCwyC5E7yidkOVX*30>1HH2xn~8g~uJVGy`XoYhj;w zBff&f*{Ho$cRSE#+niIzHR6pTl;s9v>baUGcseA&D=GKtL)Ti3746_<$3ecO${z01f+zf=~TgyCSNqmK#-ud*w z@B`|j48hthWzcXB%}l45%Wx+cc(Q6^jesDYTx&u#eg z)DXxF`{q4s=+E3Qkaf>%Q&TfJTAA^hTi1;K9H$^eYmc|Bjqr3P+OxmyBc)PoJNdgA z=e91Qxs9bsBvyt_8dinu%&O=*aJ-c8f=x1k3KxEX5PPRq#^mB7#;F%viTZKS`~GIc zYSWo@);gDPqvPhS0p;HZ&zJGG$Ve*@k8#4)2R6g zy&`u+ncq!uMh`O&AsiKO0t+Zqx^}hm^)&Uq6NSI`kf&xDoj0!`=S}js5NuC%^y|6w z>+;_2Ns)fI$b~|)hz5v)5lL-A}7PIgghCn-yNy&g#AhSG| zU%pC4W~7@nt$~Jino*E!*<26uuEgu+ap7=sAYKKG1|Hk#hrbfnZRz+Qvy+yyaj@?f z=}M$n!pM35Wp+C0bl+wrVev)yGc>ca5W4bB=?Pl*uh}^ye_Ko8wN>aKo`E9Hqna#@ z7W-Afd8*P&66t7~RB4Pm;X1C9qSB}R5aLNQJ2#r0EB`S&odd9Je9zMEh%mFl{xv(b zSE)3#6W%xYFWQM#crBGt`M=Q4v$0I?m?D&G^-BW8|Dv6Grf0eSqMf+wOs^Q1&1q;S z0SmhK-)LvAIpnO1=B1~wCw=#F&j$2;S_i5AH`@8LsnjBzVGKxBeml(K0F`FyIe4XY zW0)aQIiCTCI4=}et?rzvA*^*3%sW_=z4F9bxGMgN);OKIWa>2`RAtKB3Kcoywh+vD z8@e}OD8@2zjI%1BPitb9Nqy(3V7pC^+mFQ0o$w5#<5gqHiYKp=7w%oKcLvMO4YPQm z-ZNw^!6WOQY8vI;^Kl@*bYi_}5b}~Jm`G(t@}&{n{8w+Z&*z=g8xk?6PnqSrfk3iP znO|A8fV97KhD=Q%uV^;Rv)QA*jabp+PQc!)(fyK;Ao*jhfu ztU{{Jkn?dA_C|d7kxbA?$4>&gEEkJZWDWCW%|?nw};)`ykJKJk8jj6#Y&p@W4e; zo)$V~Fu$IkO>>AvuC2w!vBG@#B+Y)fI8jXM4SO}Yn$I+TJv%$`WoB&%nuG-;*bPD-@4wUM6Z5f_Ns7%H}2>9iKv-v1`oI4;dty_?YwnTqeEz;q~1EK%eh4!$Ik zy&?;n^i-O>=a*TomLZIm;caBSxVP*`9>Tfi=vE>C!u?|R(7x})0)bkK+S@rg1D(^B z-{a)tyNjR`RoNkoM5n`2hWy(O9W!@&1x5c9V8xMTFO->Yv5au!pk+}W<=^8F8iG%h zP+0!q1~m$wh26I_!BspWXUl$2Ktr7~^B2teuE_f;8)r{WdMP8jf|Ik~VvvC$A9BB#@gYlteFX zLE|;AHEZ7GjOO@EWqN50qjgMkm0>37YTG90R2#LIBqnAK!@t2wq}I14R$7TNWK-=K z*{~;uw9I3kOjn>=pyZS%@LpfhqL>PXFL8IfBzK{NyMf#D^_h{)vN}wM`LfbSJAYkX4}}c zV%DayH=!u18hg*8sH&=3Z57p)E+@b1y3X}Kch0$S?&aZ;+{xGX5qbN(U(fQ+EBU#h z>IxpFBv6c!D%edZ9FLN$xHK^EGDOKXu;NqTWFXH!Ov!t%Y1ru+nsR zS3;`cLb#yfeJ@;k_wY@?Dy_sk%r+gIgT{~KTrVKf?*j6EaWS_QY-Wjyv5RXgxj2KaIRGVNF(nJNB9FYJHMkT2r3*|B3 z&WaG`?4)odNI3_%X)~#ZBl(Xxt=UrY&}dkl7^tBZ&?6<2pqNxWOlgR_N)C0GZ_4$P z6e|TCM+Jxr9$raG_1?g)rKeBk-9HY2_FuFtB>7USz&fkK_eijl4*F7}mp2yjDIF1l zqrC^f#DXDNB={SocR1-BYrb*+$EhWaCsr$tF>B*Vhzk@}Zmz9}GNjRgm$-R}z;iUW3!gdK&8{h*#;VOK7^ zB@->!JG=l%|H@PB_F)z{lcor-U%{>O2C!V3!QjVUV4RMzCNsI)+A-Q$Zt(agfRP zgS>S{Q(|AT%}EMS!NsS>@&il%E^>ZuG!C7MW zOwjjyAKgpt6lwMlc_bnBnAP~BfJ*O)+dd1}`bT0b$Blw=Pix(zXR63oXYNf}CY(r# zQLdtR8xO2*VfapHfv72ll&k_VdD1B}?jeyYPc44M4Ikn~a_?1BH?=da{VP zh%&TL+-1raGLo)`&$*8|!-q$0-jL6#|DiTng(3e%ZCX57@=42qXXZ2uwl{NBV!x;$ z-szzr1OK5mhs#UV+EHJ+DAcBTY7QcO=|9wF$E%VSjM~%8^Z#RQ{sDt$(US5p`V?yu zMP1G(=HxAY+xK^+;B(v0>4;EU&BVA0#k-nR0?`KFq5=W15j-KPB5+nGZkwr+^&(x| z282Rwex7ajgEN+)!S|5#E}?A(g?MgBm}_01)Uxx1Hpq1%^cW}aOB27L%Mgebnb?5% z;OS1S+HP6KJ6nJYHm-p{ZLkyd8mtn<+%-f)WM9WmVecyC-nB}Hy%O#_(5|82%NDEg z18DrON;t%tE)Y=qkG2U^dmx>|Cq|w6pXG6ZWAshja^n#fTpfN(Rcnb^$ai&#f||pP znKu&knCeeFj6b@CbP9!2&zsHDerR25$g{SLeLdMauU(jlFg6cg)0@E8t+)-xuCnS?%c(sZCI;Y^N~(xv<+!eWl02& z#L*9;pnA-hDz(SDj5N;=L2jzK!?lAQigL*|(j%s1fB5rq&WLh%&?U(b~g)QvTE z?RV_XIRfCC4VzIH7&_$Y^gQIle+5(*SZ@SsJ5CLVyyy^e@<~jXZsP&qm+?(z3H-o%uV}H|K(uHs|{O8|SEH`L%VobRwmfGN} z4ULqKm;y8Wv+$77Rg<@{%!S3qkT%uqXJ--}I!JG+LB=};;LL@eUsm?BB$rH9&aaAZ zd^tygnnY_~@axS(7B^s;l`tyQYs0(;smxLL}{vuF~5^Zo*E1+unq2 zUxF}A({{kY_AQ2;+tNFCO?QF=cR~m|VNE*`(>wPMb{;b9MoI6+nC`{}?j~sP8Z^{0nAXW&nPEWcgdpleu+qOjl02k4c)j{0b$pIV)NE{);Bnf$wwH zrGI?i1Hb+(VYPMXkYEN2*Y^ine(}Bg1^CLc8pt}*!Sa>ByBWxVhyqL9j#1X6DK9O{U@qkld_-b&x2lglls^LGR$hHy(@Ai% z(hELavX;@roIBFA(oQ3{J^&1fIM_QBL$Wd07dIFAu-uV@VR3*a=f+i6oa6Dvorp!7 zhwQe7F5k2s#=Vz!bar<rFp{>-mjMxbZt>&wM2d&G=0 zfZ-#{)IgyC^zSB;}%g6-KoHGd7&TeSN!q}xf zmS^8p zQ1&@&1UixLIt10_uaL&FYJ9##rCgwpWL9CA58+LUgB#~7)Ov(12F;5JU=7Vn9qlOT z)cCGUzs#zYrtxfH+Oi(u^)4+d<*9NOVG9LuET4fCTLHWzVq{65(@WZh4K=IkN;P0C zmghxmz^TRy8}TVQJF6v_f~Q2D-u~Q`AveLnF4o-B<$HXdg_@f+kL*iowf$2dlC#?d za{`-}KI`2I^i8ZBm+0y|hBt`n%OxW2SyG|t3X-^>X^};@shfPtTpCoQoMj=vcbE6c zl22zr0Q|1L83-P9=e93V=ChqPQ5jXJm4BB*1y*t`b3=%0-|<+mu*@+zUM7s3ubsR| z>ZEMUX9M{3js_C-#a>Xd0NgOX=!n<6goCC84Y>VRr!OiZY9!TcX6 zYN5(Ym04fWJtrAru8#UJlUg=NqNO_*D}d4AMV$GAh=pjCV7*=iUMr%pQfgb4fkbe{ z)d@$i1x)-AF?xQ?-ys{m=tAk@Pik>DDxk!kPtr@e(y_0q{X`~AUPjGbp}XPVHN`zZ zn%utTjwnaGQ?KQqPr1ocieS7`v%A1NkZp47D$Z50lhgKCGIt0(a_R2aUpo4k;=NDgC zsW*d+mOK{jxyo5xbZ0YTBI#T$c8^#+O2HDLa))=j$I~-qy=u8jt zb=ghhvI;W&{ph*3vjuH})Tkd0*&&6nkgwuUKzH77dsCxUsnQW+aFz&VK7y zon76vJ{fmjeErVPy*e!^Kh9C|+tgg6@!Rkl!{ugO^)4oi?QZ|s zDJPXbzCGKwlSbEKDRm8gA5yUfUBlO#Ro3fBv% z&qE(FwMNz4L_*B9HOL7J&x4?MBnmiLanW%K=m8s*yUZz(+bRHnaQ&$Ej)w-+isYXP z1DVZ;!9$dD+opxu9OljkJ1)1s_NU@~EJ1sqs+nn2>lGYO?#A92sw3%Tmdr{ybph>A z7f-1XY6q>N|HszM<@{e;QLFicrJw{M(H?SJGhlz6Hl)pwXX$Fzz5=Vqp7E~Ma9 zFRUrbnvof3Rw?1>YgD^D^x&^Y7>ILAhJ-drvFC7K+_O-n5zPFAzd z`{K$t^6EjJel!ShaQ|R+r()a^ibODZfd!!HtVDX{yRqGUgv>9tUOYS?BUEE;bpx`0aEU z|BN3sWud35%A(p4#als)=9F$p*3pQDBU@;JJR%)8*AL14JbJG1BPeZZy4>$CUmwnq zCPj%qE_b1Kg|YGA?-Y2pgx^xb+bYhup&oY@S~0bMCs&zF?P~X8D+$U2KGzWF{l*LN z;-F@Dy2gJo!|Z)=FO$ktkPgg)#LiKQb>GlvZf{dZbz!-=oD6TtCX_8G5j5jOMEg1I zIKU537wN?v!u0mXhE6a%++kZWykjmj?50JVA7!`Il5Y=%ZpuHI^*I-j|H#ea2{j`e znO*K@{g=XFQWhWbhrjPF@wI`T+7nT1o^@*CkzD7~)s2pOjK4c+uRJ>HX)2(04cQ&0 zqYHl{DHx<@J4-NPYeDC^end1gjosftga2wJGFzi#q>n|_QZmIw>`1Z6(|>LtZiX`P zbePInW8NMN|1e!v4DvTy_4iJ;S$$RYBV;UD6J}2n2nJ$LhIz2eRyIt>aKRa7p9}J%N`fa_ov{u z;n_V~i3!QlymwA5CYfO{8FHp3$A+{~_Q+gdU_&W9NjY3C(=<*kd?n@4Q6l`zZIjFpvkz#-stdm77&!=5t^ytLU)jUe5 zr0o;DEX1_7*}?Z`_3weWkNUeuMYb02+RF>N_*}Y>6w#KuUgx_gudO!D|18c3l%p82toe&sNzs#?mr-yj z+LUV|Evb{(E64RI=c14xv5GGa9lz-`)%(lo8U!~bu6`!zO1Ia_SsCpV4V-i71O&V_%eD~OSM8vWo-ayjL~817mei>Ug#G(m+V#e-1iLD$W@Ud<^m~J z9b|1A=^uMB6F(50cW1&8D-z@glVdq^e+>&Pqs{qBaHEJj*s39=pv!GMoPTzDH zX`aFCUxQi-18ONl2I|@l#=|Gn!=@=$ga&Kf6o!o!`aCB!&HRUTCUpZGYcE9Va%~N= z7is!?^b7Q8oAzMt77bsF!-P0gW3je zBSCAL94Wef#`>{A7&i~y;K-4XNj>(hff%77i8bAn$x$hg&UMG(Tar4$TY5jgVcakS z4h_AA#(m%FFt>~Jaw84$QnV5*4R{)K5*zf*iv~E(+qAap4YGU2Vk*Xh74*F<4IW4u z7NqD$A@mS!x~Y+dHI{`QlVg#QnD7)#@E`56AYFHLgUCO6_dE6cqgp502X(_WJjVS(g|%DS_jxAjEhpJ& zjOvW_*b*wolS zf6KVhlHA{5^2Ud7HtNSd$z_llleYEn`y!gwc4BeuT$pk)$3D zVe+a$Hy<+^VX1p*a=duKw1Ukrk>+|b&FJ>|;P4yu>n94v>)C@*J^DV8Cf_i9S8B)0 zv&Ew%Umls8{#~2t2;$D>lF?3?{;eSP38Xs|^yt+8dLn*OlV>zBXcX)O<(8cN<3D@S zgGIb3yM>vhd8^B}CGZb1$K=U<&A*Tl+)O`(Wp^^W5McK4(=3?XjMK`L<;8Scl-b28 zGv2pmm%dB$q|R~Dn@@f=6Ob|&r0BVx7=EYuJY#c_`{tsl=3-8TY{ljhFDQDhxzt;8 z=`ZFof6YUajP!rHQdep`}Ucq9pr_cAS+m)uQ2$5t3(7QBz~TU%)xQ%7Nb6 zPHIW@ui?z+i51K^vB>0>(Deml!$&8EhZWXxgBnF!(}!!;6Q8XE{*KwDEa4-U0>Q8S zoTj|D#t#;>Upp>ggRF18HFYnxLA8&4iL}|J(L2tbJZ77GsPOusj*X+DX?M!d{DO_- z+Q>&Ct&%~Ts6~^VqF!9FEgGY@xn;9iv7At>9sb2untnMTc*2+G`fi1lPq5Yd$%&vB zujT$stV!Ai2CR$(nS{Ntto*E>eSUvsB6j(1<;=e0*foFa)07o|ut{qBj9Y{0b0M3w zfK@y8!3T}~{GaV~d0&sw*zaV|MEzZgdAsWJXLZ5RI`^Y>$k1vHuW9gK)0D?kswtMS zPWC#&_EjcEofwmX`woS{_Ae-Yaq64*ZKhp4!x@ilZi2^if*0=%T_=BjQ%>_n74ha} z@kBSz#4UFFKd~#7>UJaF4fEO^-g~UQlYIUBWcudf^jQ0Kr?(D^isrX@S0~wxC)*8s z2kktk>}O+VeJY(SU%ZJHa$44Lh$z;aHg?<_bo$!k5D{zi0%QHUapLym)CVcEZZPKg zi?>Zz*B7VO1;EqoyiP3@_ID~)N6urNI-ML_g2vp6q3JN|1W3YlTktG>PV2Z~SN%SKb=!W0H zoNXYwHfJxE??$@BRgU~o+=zHQ3~0O;`}+<$wff}G^<2N2M=wB;# z-PIRc@84Qs&mtEOgIqy6Z(SM$9CTcTt1R4j7Z&0S$&PMfC7XQyZkIde`Uc&^*WIKK zHr?L3$}qUgUUHX{c9++1S1@%~batl{6_g&hD-+yRO59bO+|_#B)u-LBuDfd-xL^C{ zj%M)CyyT%J?V+vVfid;aarV$X5A@J`;Gs|OFevdbZ1OPb^)R0H_)p5=z~lNq4=jVH z*(FbNX-^9cPfJrzD`!vZKu?$H^lmaVRLaGPw6FYS-z?KTl&bIL92XSxq&K*iwxqi!DB*-|&gF;G@7%mol* zt4HV49g|oQ!Ao5nw{?XyGcY_R-6uK~DhdYOG=_J5EaP+^m$=zK+OuZXd+=ybzm*?f zcvm3UYL$IYmSmZ?G`)xF?FKKtF*`ZK*l>@-*S4ReFpZtBBWzfK7H{!SA0_`<`9KgjXuUERd)(KnLyX;r6no@x$_V=0$9>3Mc;A1~zSlrY1BrMljGHcp` zJh2I>`L$Rd7Fn-Z%*|~W<&W|GJ&`BWakblMDysi;ag6{EDk*^He=aVMG8P8JKpDl2 zM=3kj(M+_L=E%gdp=1tuzroQ>eGI^+l*A*J8K)U#s9)!pc;um<0@yaCgqcy}1v!1K z7sg(>%@hlXcttYT*36ab`R=lHEo>SkOD2IHDYT0()>}kvFDzTuTQ;~fI=vN_|6o&Z z_51JBoOTHS{X(lxDD1-L_J`+npv65gYss#NF8cQ$oP_YYBO0N z7WFmS6S?MjQjK%)f2VIIv>4 zn(Os}+{4LH`S<$i%jLn_GI3jCLbr-=6AjZodb@hDTYJ3H zWGm4_#?IS?W}>gdew4sbo>j^%H&D5q})c>H(2d4QyZ$IV!qQLX`{}*`P zKwn4}SB7~+dHdJ?Kj8WNV}Yqc<-7Z9|3Bb)KIi4b(tufl z^(CJAKk)qF0OYd9t}^L^HvMxxiAa5Bn`-9paMlYgKOnL1{|BBY4*fSg&!q@vy|j@D z$ZFmD^hQYN=PUrC!1J{-BQ9_8cVQc&SrRvg(h+sl@Z>TN)Q`w|U9GM{WSyY? zWA{TE8X~rpj=A8wm7(7O>1Vme*6NY9_m)YQ&o&+D$#*}rlnW|NbDPIcgnXIWjh8r5 z5=|R98}mS_21y;EoPu6w5=&3V!_tP3$fzx_te4xRB78r7x22;@Nj4i#>vd5fs|*qC zC|ZOOk8}e=Z`ZK8Q*ZW|X(42WQAHeirRfB2B+5)bv!P>uzn(OFF8-;ZZON}5gH)&T zZA#w^A7H)XLZ!uVclLt6-(eWBTxaXO%cZd3+Z$B3*l<|w@LdDld=CXF)eJH1BZw->0tIt;l6krv@xM<;@ z#um^QO#gxBeX1LOtoqkmw{)lbp|rYud>luZU)*ot3KPpa4_O`E-}dS+5I0&lcHe48 z#^8gi>Zv+6m)faj4ps63XRTV*>BP!Pa3iR%Z0cgEpChBe)i-V5iiOKu<|n-p9wk0f z*h+u<<4fmG)9-_xpX)^koTG9JMv&(D?G&NiR4(Y1kt=i1`${*R3xc~y$L|;Kkq?+|8LplfVt_$^L+)ObWrmDng#=d!vOfOB2 z8pAbH%TbnHD}tk$gsP+={ZY?mLsS-00MQx3) zh7W0VibPHVpo!2r)L=?4<<6Vyn>QrIEh-+S_!yUSy&lzlr;cUnp>jq@Tla`Ni5WF^ z&CnVXV`LhuulCdyy?2Z!B5O9N--=@$aOpDWS~3ld+eAdDa=uJ)6=)JE0N*qta5I3d#r1)5UT9rf2l_5l9Hn|^M{^c)Qt@w2C;!~$m^v{ zsWli8Lo@fLr)3;dHM&aUbA<4dawL7NzMkQHX2D5?s7bA%&G>vy_emuxwbt0%@KwR; zNtNktCC+Q)oA)UY=YrJ8Q1q3eUm!#-0_8~k|T)kp*~mO#zLL__qr2=0heUr zVpI6{dRn$Ig<{r=P=NRZeT)F26S)8k`G!ksy<@UC%MzXlVMIb5uFI$Q)(#_mh6vs~ zc`Ri-}lk8WNj4?L;9wjqGcXmx%#yQOxe z;n`=onS}*3l@B<|9fpd6oi}VyU0fu13y@b5IhNYU^aqA!5nMC6K;d!`J#Yz66@_AH z1?8h*Ja5)M)O;@hXU2C*&+8^ z!Q=Wys^~A%%Ff%oG_l&HlB3W_m$NT2AZ%jD#_4+Uh1nx3x> zoq;WMLi*b#PhJNQ4|I@6 zvzy6d;0wFHrw-F{qGYLUEhiNZ<%F7K3YqS-coeqHq?XQnI^n{=(oEAQ0i5o`5SNzh zK5@9F*XW)P6}49>20XW>?^$yAYO-}R*oUc8IY%j7lQHy2k_qJF(xokZLc`>;$3J* zy!}|{dJ%r1)wHv}MKGp)-aatsDffEjj^~Z%oOV~PEYr)S+bG9ufY&>of`p7XuV3t@ znLl3yJepRh0mt9-tEmSgi(lR^CcQD_xp8!-x_DQ$Is#Ot6INlh7Q+JVQWQqTu-HOq zFZiF);E3a)*w|$2Fu%YnN?Zpa3Z;J!w>eT1u z81MAhWSvyr@<({#sTn(^J;fca?u*q>@4m-+=7jx)nDIUT?EQ5(ex}L|nYZCDcTZFP z2|DJ&`KYbb{2r9a1Je9ha_x1}Xa9M-9ZI>CKz~EjFe3CU!~4#RAR~Sxfdj&*?0-J< zb=7BvC1-TeO2QE-lg<;g{Jt?GirbL%QD*Vwi4m!PvCIF`nf#0M8m$PBGJc3%i4hZg zbJ)dt(**!rYr!nOIJ=NV|2SQM0H$I%v`1h;NRfb7I!tZBA*XWO0+;Vz+XVW< znw~EsK=m{3j^+sP)QU4~F|bh<6pZ5sc|&YFXzHtJ8m~w?TE`B# zLEN#l6_5x!UA(pEJwk_Z=N*|&Gf==Gvt>Ki-78dFXosg}4nCtMjf(L;yE5&H(L0*) z>(M5wLf-gzllICx?K+V@r}6WiCM=~PV>$_qvWnkU4F#@~x`q^QLcPp@a?Ug|jtDc&{h3Qi<(B*SSE9Nb)| zqhuQOlgmQ6mabe^olhF9Hf~i;85}k19_6`Hm0V_>GP#^0*Pb%XkvgxOx?r2S6q>r6 zm%7@Sy0)BJp8;+|(@|3b!?uL&P{M8=VZW2`d6{r(JN(P%<#)ZUmg}HRDFk>zt#6LS!U z*$I2u{)u_mv3n2C^1N8{Vpa09VQJu&ys*Ulg0MX2v3!46K9MtD+%CVuuHZ{(zQ#&^ zXl-t4ets=y0cj7F>08idmtQlMpR$)_SX)rClG}1t2$e5vS1F1%U`kEQ04C0l7IPq(XxuftXT0{Shi1C@d{^2(|K$0Te}ifyUfk}5cVNZ_ppP*ZdS@E#nB*t|#fr7qLWOC9WfgMf!bx3R51?X?k@y#=I zQ>4l+AYhh`qoA}J58%n*xiG3|GKjVIiO{Oxn?&GW7vtBBQvO+bupix59~!<)#^<=l ztVGCpe)+F-TJ{1E4uJF`;dKdMaV*^>)l612l!(soz|*nWl_?sqzsU!h4xju)!Vp-9 z3LLRjL-k3SDmA_Q8?wyFFq?LxYzq%&CDW9XelyY26A_fNyR7FMw7;>4?L6YoS@>@fkq%k@Lnw#4#$3R@ zvvjYRnh7={Y(N76Q%A%=nPYHnjYi@nXZxuLx`u{FK88i-YO}<_9mZl z<>-E+7};m(_s&t2>q&3lwsC;*7Mk`qjv<%C zc!B~or7JYLFg(Oj(X28^bJ2_16F(k4IYV{WbW?xAGnSI*PjS^Q;%!$*)QhNRBk9E7 z$eQPP%F7v7NsIx{sk9=%_mrE_$|c>k)`WmDb^q;ToE%3QuQ%)xCuWLN^f3F5L(mRqr99>M2!c z!Sf|DI3t3gj7;5(Ef2z~TH|TgNdPUt&?Q0t3Po!H&_6^`MQk9h;egjzs2(a!>YTfG zQ>}L^f<>mjCIHEJIt%%YM$`gCE};F}Y+1e^DH~VgYQ(q50JXVDrqZwdKh-Aac zU+GYr33~o+z+SaVVqQ9qn;wj7A3{;=5x8qNU8-UEI#<;X7ix*)C+jZHcCgS)c-=oc zgebAh5!E>tUX!Z&^z$R23V`Fn+cwA`b~OE6!vWiFAe2N0#lu$%D(A@LEp#b49DyJK z^g|#|+#muAJUxUOgcG+zO)I;BAZ$4T587g?CJh2m7Z}kGUJO*5O#+&@L6=Do9kN>O zbLmAiym@rZ-_#hw0F9^0SMMo$g$q{Ro)uxdQ>RQFi1a@K_v1vr)5{ z&bXS+_!3?2SXdtM2IjC)_Fw{vEu^O-w`(|blF^lCE_4?p;a1~g1xAFv@M?QQ1`)^c zq+bv&QtirYgA}6C6<(`}dg@U?xMNQU_%r0nQ+-c;=ou15dq}@{hJvHeFMP_* z4?&1%ed|~qVo&e{YAyWXw&Q?P!z9SBI{0&`=NgO)4W+e*q)Y(xp41n~h=+I}7CpW3 zW(44!1--3)D9On5=ZPtI)i1TasQstg%9BNZo}`PHKMtoo?;>W_!QIIa`weMUCP)!L zq@M&a{^=*r*4C?&FY;7Pr-OO^zSs%{q74}g&(Q!@tv_iWiXhI5_AruxP6@8IA6Rhy z#w%GK;wLD-NBGbiV*)p665=w_tJW~ZQ#<82OvF+3MRaeFUd-MkC^!z9eVN`~Tlj%^ z;nFi5w$YhhKXqa}97{>v;o!?DBh>d_hZet%YAKc|Q?yrodSRo+KCANF?&ZJzCo3D3 zu7$O!#_bkpqW!?o18zFILTEFNp%njgg*5gKS;O-A37I_Gpgvm6)b^81*Mp=-?Kg%V z@)uG1iMZy>sb>#<)5{R-Oc#jm_X08VOvyrmd=pwP`QHYQ=zLa z-7ou+j<7*NpB6B#ZIohX-+VzMt}%7QhPRjBXC$Ek4n4u zx-%RDrxElwnW2=#5KAZUdzN9C1i)qyI*6qr($=RJ16LiF(2nI96Ab-m`U)}`CO!Q< zaz<&D;RT-l=QyBC=I=CmTPQ)le26Gz8orBo{W1VP@^@*>_$`}DH5(CHe%SQ+5DLaB z+?#qAt+5+h!WwXo>EwKcfQpztNhJga5YpBMPk8sfobF~nNXuoYro#fnzZpu0)Vb>f z`yhfaj;2(D$`F@MgNAkx_d+*jqMUbIH9oaDv(%R|B{mVDD7D{ch9Una-Rpbgz_g5H z1kMGLL|Jy=`PBYj@I0c==0en`a?W&@?hkiWKi^1#;IVb9&S`=!v;`ZmI9%H7gM;0Y z1OJPUL37qD8K2%=`r1)@aN_*+e)y+P>-QkCUw-#;9ZG)%ZWK`nc^W=0=6l?fsUh`MV=3zT+zozj^i@LT|DDo&IKKz)a{Wmc4d@Vf>~rbEN#_#BheaFV|k< z{G0mMlMAvrU3Mqdlvkn{DSDONq`^BZ!}Lb;Qws(@=`ApGS9u|JQrGANrH&2{4;Z%lza!i2PGp=F~B5k|aI6LI75I&)V$HTCe>^YaX-T z{(ZNZ=_2#@FV0N&9{vR5hecf&lF4sDSgPhjqhjC)gr*(C!#^A{I3Z8HU(!w9fNUYD zs`vo%`To$y(Z-XjYae}R263m(u@%E~A>s^-drV=H%P-@JJfMSdRG3M7UJ~ zaV1o345{Cdrz_gsqvxkdee)JmUS=CHLsb$nKAVznxo2KwQmhfjWwmcvXF=uozwkVm z+xoL@tLIdSPNDS|`;LIOy>Z+&2aesrU)G0i<}5i=7P|j|d6aRS?zsxQOA1xaIwSB zz{;@u=a5T|r?}0bR6e~D$Ft-5$ln@MJWeFKz*ZtozI_zmE=>i+f_}_hZy7r)dGSfQ z*ca?|3p<-^Fv8)RRcj;S!x$0e{|Xg$PK!cQHM%vSsTnaSP4Gnzk|yoWooSi9%YSz9 z_j(LAE*^+?R>_)Pq613Dc3a4dV+#&-pqrg8U+J^pC@|YE4pN%p>yr;N!sn2!7CpY; z2$Vj5f>5izU>atg3@t6kX;Eh<#skKmTQo~2c%nstYyE;^f-rHPD%Q+#K#vkyR)$f^ znJ$!aW|6B)l-#IFG@ytsoa+T2euLHg^>Qaa;(aL8hSkn8lsh-mx#-zurpMs}m5`ce8(y@dO0ID*aHnem zU%c|>_Gwa*ON`7k;(eUb_UX06YY-KWjAZss_pIx>Vb3ybyt+Jd?+fyK<$G7bIq?PLnoeWUo}TSTncV>J_atu7G9p*S~oHC)~F|{j0t2(Tp&hh&C|xcf~l(qFWSh+9aUTm`2mD> zb08mC>{Ukg=iP%RfXfXdz6QOg^>`8bAx&vExKhlkb%lCxZyL`)y&T9AFbiu2OgH;> zvf*(n)y3|H#d^^+UMn93skRkZ{U@xn$t;{E-7Z5}Ea{#s&aqUqDbBGfBU~K#=s8CM z?CBNt_xv+ajIl_Y)_afhsqkLTX3kSCJLAU-S05EkoAz1>yZ=H|v17qlrPb?nOalbS9I(VC8`WllM+ z86~UC=Z!QJOkS>S*SDU}6k0}Bmb@+ox%6Lna&F9c<-9E24T=UFq6HI;E1ui^I0zA8 zPW}1|ItZqW+;#aq0=Uv8Fw*IHuG(H2-&a}-Ji)$G*&FhuiDS4ShHxq$bY9z8##ava zSfdRsDlh&jxF|IThZtYp;0d!_d$Z8TKYcrSeUGMsr7uT#;2~=(6QSYmRDDG{0$;^C z?k_D=FQ06~~n53F&VIjJE) z%F7X~wj6M@5N(^WT-*OJiPY&3(M=Hgf0n=u&d)lPP*H9X0#X z#zx=b%8Lu==c_g8OYP0zl~oR${ep&3d&5wGh0Ak?^?ybkUErpTa5cxxR-=x##xH%* z&)3%3UU$vt@(ah3?a%YiR?yw7qWzv{rm{|b~vwAmRL8RN}ZG9ngmaEcP=B6Bsj)Q1nWZ3 znZy7^#|ye(bb<%bjc500{%rpIBJ+=8F$vG*IBV?TvwBIihMg=(R{AK#?~xWWbP^1l z$Uib~E8|)?)lOaP@tTZQmISjxH4)KF5{g^=%=!8{(gNCq-NpEaV}^#-cfdf8$IuE{ zP6u9`ReXZErrieu2>o7$HU-Zhf+?;{1Y$SYds1CS!%j3=Z&%lbl z61GY)D_?#PIWu(CcJo|DEW6zEj@LGJNd4h% z*~R;>(R{gck9$uF1g`EGCxkCFK60$q^9IaE!k04izSmK2@7vQxEDveiYt4OO>-bf5 zX0rEQM;hXbuT8|8j~XHPzGCyBk%;xTi{IGHoHWb7TS8sD)7j59{}wEs3SZsS`D#Br)pW0gCwGe~i+$ZLbl%49hjmbL z?hLKS$=#!`yTLN{!@TE~;Y_vb-&>!(IJhqp`9ZKVbMduDrq7VZ(Oo5AKlfez#`TAP zzVQKIeR87A2RzIXaGZ|#y_x>-=T7eJuf7*!8O>#41ts|}Q$n@u9fxtfk4klaIS$a= z`VOdW@$-SozOe`h$O&jYmkaYN6LEuRfPgoL+A=%l@^y{nr#OeW%0wbzc_82x0|b zfyb9QB$e)j^Sx7n2lZzK+vD#=gByHmh3)I9gtWd*zy)Im5L?>tBJHb#+LX?}Sr6HY zM@O}1fB~a}&emCE9*kvS)jnY`#MMyYMH5-F>WOtJa-G0#jXc#rdN)2py!NcCFWX*B zAu=)dSTixRw$FFK0-;zEFipR%(ytB( zt*a>zPa%n0>=7EeOVe75n&c9j;q@)5V%JuMFVR$)(DVng^>6nN+nfwoDva3tp)4(r z`a5c!+gR!ZEsWOhNxw+Cz*(euC03^r`tnf&eC6AK0GnR0*=R;K8Y`)DOJT%nQad+$ zAa!u8tmXOTz2OqJmr$Oeoq+j%U2c!!UQn04~Oapy9~zAFqqdfA;iw zvjanIkzrHYc(X@76)@K3AV^Y}c#4^5vow0f-Pay8@$4jZKYD@!%y;=rbPrDSs3%tv z0h^jm zEq77dw)Zbg&QBWr$RAaZ1@pd9VLLTa&2Im*^5oyy6M%~dI@X|3jn7w@TK9NG$kip1>vJ(hr=xf)a%G+ znArx0u7w`Jw(6 z^Oetypbo^RqGKZM=8eho5{u?16Z2Bv=ZCT;WY{f+=w8VxzLM8@r4ZM6E@$=X3a{Fg z;8)61-O`U=sm9H-fYtNv%AA4#yin`!#X|TA!r9HK=z$%)2oq9jc4+)VA{lEBn�!NuU|KWG$26eRSX>hXrzOn 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 z zs*xueilP^t)M^@07@H_8v{p@2RDnR#g*^Ypa9flTos!vk@zo-_e*CTW+DUjJ=o`&J z161~rzp-M?j&7teoXN9=dq3!wF=GA4{i%B8^Q<2~nxh_``dyMAo)rvT`Bc#B$wpLP%s%jCIO8lUv-)KQ`vhM*n6_`QZ3}xe$Vb<0;AxSJs{%jkMEPy z?eH@mBOyu|W(>qN+DR3 z^q$61^TzzandVzjc7j;v)cFh;&95B~V5~`G6#zre>$*U~BwW-lzq(~?0 zn$&2eLq;jFq|05^F z3a)%@lY;ITgvgx_!?mx6o}jMtjw9=eMxF{?NfOnT_>Y3?qH4Av@n#|k>%VfAVZw+4A}HP2a^2IiOe5X$z3|E1{_Ee$`9WY9 z`|E1sJem&_=GQ*91+FuH_?G4C^a}siv0vWG4_BN3JGi=Ip&@B1gHO<*ynn~~ZP5Om zrWC9F_41c$=a+L9?r!!GWS%K=<%nIkNgPPCA;seBUJ-?8a5Kp8n`5 zaA@YPU~e66f93oPR=56wMQEIL)zGKqhH1~lG5gBY_)ocS?}nVBn|IEBp03SAI_+ub zQnlneSX^iPH-F9V(+`z+n|q%_H$OyvXF6pM-EFmE;rd{mK^Z=3ljVs2kf^cXOYvqN z_Xw2~PYT?0%g%}7KZ)s={X9-*quKLo-YEp!ySeJWL&x&PnYsM*rKg;<$1Q8q7S&1W zyFJ5S9!Lboh>9=K(V5YVr9 z9>j>R4$5(V4rLlK{twfkeo;qOU<|4ZO@O}yNM zJ-txxm9mo7`tWm@yv&33q1j7TwjSpmhFu-M4!7P_?1pv<9iPr#Fjf~{3O##p+$-4W z^kgh*?669ZjE6FNk|SF$!!89?YR|K)=+u&F!vApv4a6LKu`FWQ7Uy`PL~PWjtzU%+ zndzdRBR?mJHf^OvQEE0%-JDl1nMJx1KBvTKsHu<$wZ~cWs=!Cl*9=sO6L83o_&t`BEW7+nRxu>hsz&>%zh{w+I@Da@61uNC4boHSoo{|AV-q??|~Ke56tNP zgaY?5XNc0~v3JzRF#?>Dul%+*wX&HC$2Z{&!JB_e?5dt7A+rvYw+yC&BkS#{Jbw1F zGEYVRZRh;w^Q>>Yks7Z!`)cf*U#NlGGzEWF|M6d-RL|%4*ZmFEU!oI{%~yQ(lC0H} z2Nk#8>|a?A;wu9Gc6Vbuetz`|&efU839k0~`(1ZL;3~b<#@kOL+uo1HUchQxTR_TJ zAEN@kK<<^cJk_VYy$Esq-tcTdQ~rR5Jz0GYn~;0RrlHCx&j&FUl4wKw2VbMYoXjmq ze)+y1%H&mGsh&9e{8-$%?hl_ZrA?< zenLzzsJz88wlFAvQk#}q=_;tVIBI)Rmori6E;F_``QW6UKwagDvSV-kCzAGNpyH0z z*ivs!a7g9Zox2`-%S-3K*Ba~lt8Bf;mREi^VeX}40VpQ2jbK^*rvJO|voZTmbr^t@ zdM!}!wVk}BjPJxmb?`@F`>zkI!1L=aAwz8+4|4C9OdVH0u-T&%6kF{yTu5MuBSkT9 zVY-BPv>CF|D}qQcC3UST*N2N6n)8{ha|Nt-H#@llz=H1NHsr&|M8pr$Zo zerp!!HSJgxXB<={r-vqv7(?riu($4^DV2@GnsTn9k{5cRJP;{P9<*{!3k#uTKUT71 zRoK3_{~fYF^x3#;4rfn01RwnX<_CRz!x?EbJWQXHMkly~LmKAnqVLG0UEacU=$C3F zupB@Q6bsKW`Xm@LpqO?;w2#`Fc$f>zfIEaZ)4wA1$AL(24_pt$ee7U9YQ{sR|M_H4 zSHR0%`?Smbz0~(f1N?qH9y;5tn6S7bM%NxnZmmdK6;16|=bT%-L!W5@!kwoc2HPws zOdBLYLv)q{$WFECI!jfh44+_%%8~0ZGY(LDiAk1WfKvGjBrq|GdjfkrEb4@Sycb+B zPCXT7ML3rEM*5vdFAjioZMdN#?7P=g4sEn0nC-DjOb~6+$EDyy?3=3;VfWh^kaG8< z@8!eX=6{osR@(O==x7h~XqGTi?|qrNn4i)rh!byB#=)H!<{JfkdZoK)Wy4rzS)!mG1o(>>7p|=7EmHe2QztrV+Lx#~)%o!ZjF&X>Kc*@(xMByV3b7 z?}@ZA-y!?Ia;JPwYo9q_JZkD+9ecyVF7fX{XzLiMUhWOn%uyJUqSgeBevR6NA3zg* zP@ImblU97pkQS*x83}jiq4_W7tO3I+D^Y^>9Y+eJ$ZfmsPhv6!UyKsK41t7wp&j6@ zbP0Iyh2K7xsPmWp6(r*g{aDfdC^{R@Ci)ICoVVzCw+S8+SwQf-(!k3W(5-sT^nvO& zkOunJsSSTwW8@)r=0H&!(m#*ra+V-{+&M)6XY0QIN=hRB-kA%Jcm|bq4?Y?U2d{<) zHbopZM|?ST*E|XAAW)bP@%u6nADf(CXX9$I6ei>l%z~u}8S3H}Di9K7i$xdC`@P%@ zZHGI633Pv$V0vQik|qA-^I$IL0O3<8x5opiAt!S*%n0poxaaR79aF^;(<>OGws%)Q z&HtiCpcdloeS-2yqZ?^*HxYy1_w$%OMFN&EvL)IG9d1DIc%bF+0f8$y3HFS4IVoX3 zJPGoZMSFyJ-eJXqr{f-NP{CB-`Ge4JGU)J-_}w^fa7h?IfIT}WQtrazCF5Ky0Ym^Z ze8MuErzO$m80wPtP*NPo-Sq1uA!q3kyGS+mM&fA(Us1%mF7h&!f2H-tAj42BU?$d(7v^i);oO2ig&?Zw| z+Df^{>KZcd;)i_vP19$2A@!1wAG|c84io%gD;2;!_D+C-WjJr+sld*Uk37)xgNeX% z3r7OXaW7KR88UqtXg!IdL{kR&-IeZ6Iy;ULyzgFB8Z%PjFW>EEPL5{R3NVljID-Mr zKa*RAV>oQ+jI8NQO8q$t?pp{2niHT28&u}R*sb$7UUsrRlY@^LHGOmlsG_t4fCSyM zM6+!@p40X*!NIgb(cYRADsq|BCr>sI36a{q?=-VYvMDuwWm=$uI=U&5gSVm6sZ+@8 zr`A5F0*Rns*&0lt2F46V*=Z0lc%ckn0XSG>(;0NT3Cg$|4X2xv?%|r;m~Z>nl=#18 ziIHS+;&~RuGZfHp->qRjMi>>VB$!$+`1VE05rUxsCe$J(D?7;EYiER5-ds+{CrD@O6sn6muWW5_*3 zC)yjl8h9zr1H6x$B2uMbq4T9LqBk&f(7&uNHU(ksu)Ws**$VFg;3;FMa zAnv7(E>KR1MGAj&7nM)sV20P?aU-M`tBXZ)QZHp{{W{=|mT_)u`_P{-){!hGSG>1! zGhCNI=PVy%BHC55?HsPF=Lv!i=1mdezKFKQTKJcv zO^ja~+Lp?~iq}T&g0T)<=PJ>=jZnQbZ`-zFp&kTfIlj1o!4g2}Q_$s&Mljq%czs3Z zEtS#jmsQ^`Ex%))@PMI5%6_1j9isxRTi3M{M=*)MuudzJS1F+_C~;k_@XIZ)u&w<3 zJgVK)VXmlhaH(?g8*|ki=C*+=HOG~652`%qs%CO+U3#k)epiu4SrdYhYqr%J5309x ztMB7rt4q~C+El#Lst@FA;{7gwJBhowHNTfct+6%#lB(}KpfezXS;;^m50nO91DS@< z5^HGw5YhWJ^m^5l`(QCV2)zgJ63|irh!+c?pahfw5dR8z z>m-N>0HQ%V3Uh!i?Kc?M)#EstZYeam+BLa9Z1T)&y3^j|)vg<3Qk_6T{xwGBi8Ysz zXe;o@JTh(Qa{U<|xtWXR5&?_mLU{f((4y-hs}S057PE>L*@8NFGWr^2z2w6SYEHAi zUUNV@^-_0z0-7pfyBUAC8tDuZBM~9Bo`5k#%(zYx598CTLoGv<@t})E@8M8OS5J%6 zJfx-qi20MkiUF$vAlNpv<_~ZKgEsK%VBG_1?ayL-#4xWfuG|U}9zW5FGCn2D5Z^KB4fHZXOHf`}i^D>bl ztGm8TtPN$~w$AQnos32@Y=Ft6?x1{W}73=*;ma|H5CFo@Mt=M4FPiyG`hV;ua5sLC`APAcy8wlQ}Bq&A2Qa1;!|Lb z#vQQQp$?JO#qCx^YuoBJ|do z|3^0X;awQ9Y%q`+Zf%eDwnwFQHwWc4KU{96^cvbs?ng{RneFNp1)zz^u&9(;R!Y!B zMb8aFmo>4DvSOk%k7zB{J3$G|>O-RxI~OXPrt@pn)B2VaU5Yz~!91gj?cmt)Ix1)Q zf(JYZ7_b(D2VoHB>r?LJw?`bLcsR+wyU5mfpk8rWv=Mqp2{@mn0E1&bgbHxMn3sO_ zy}Wt~(e99k-9=;L-?H28e1nN$I^@zO!_WGc&hsJC-Yq~{TUbgk`NNP4VZwwoyskL4 zMO7a}>N^^r0(NL1QMNsrsL$G)4@<2cH=E_iqmx6!wiP|P zwukaW!S1>q9U@n{M9}GqlYskV=8Kk)j@eYs$=dB6BZtwvN0WHYk>>``bp26LJ4q8@ zK!k<#p={0^gDbi)yH>%74oKUy*jo;CUY!+j{^|#63Kna6?XdY_C3gS6{eK?F|?L1ad8y9QFUz}DVSbKc&=%>h%fpZNL#XJ*&0(lJ4cegnPtUc{a_l>C*r?`H-cj=ikv`0)WjG9-v zwXjbc@+=P+=>4nXr!%`!m-&I$kRvd4%mkR>tSbDq{T4-6!=P7ll2pUQvl3fbo0Qs4 zhn>*sUeE|_XUiKZvvY&>boPp%vkg9N<+mt3uDwikJAUJf+)B$q3P|?f;v0we8o$~G zwjuW0tH}pY2WnD~7{b9C;fFysPQ1?++6chl{KPhdlQuMOXgn~-o~X)Ft2^>_Z4Qd( zU%ys!n+$7bmke;H%eCHOIyc$s_TEZ1+ydTcFAOM{10NEuIW6UHB|3iPl-Memm7(UA zgnyNIdJt)VOLSaO+Yk6Q8nBsD`0Z`~Ch;+=V^7=AWy$1VMew14Gwf@vg%FC-FqHAe z#v@yh`exY;<1|OHt3@)K{j$9;bkn)FXR2AB+HQZgkZr40SML9&TC^Qp{k?f@)v&um z!?i-0UuEd!j{!rajn7(Jj@qnHB{r$=N7uG%Xm);Nd`kw)jzO?VYu^)IUxCN$tiPm7 zUNIQt+f`fBf?QIPTHAj4@5k>#MIA|*@8-L#Q--IHcfFwdlrCyO&CbKw?+I#Z*N;}g zt_RcgYG0MDM4S##PHM#SV}rSOKiDD;hYC0C4>Y;2?`s_G(*_)V7hetv>?S9LPo-yA>Q#_Z(JqRo=XKYn$&_(8}pO-C=rq^xAbl`o_(> z$J^dFb}4*~sTjXoJDoNM?O&hX4>miB-`9Qn`B?p}!QAXF?wmi;$Z+uB`2@uzbDge7C6VAR~|ImSGy!qi;KG05~ebz3S?$Nn6XFPe|JE(C6Drfb?6 zmknO>w;HeOW?yyaieWR`(9f0fUg>{swrTiMDeNDV-F%BwK$ps1Y)!Xr`o`deax8lz zze%}8mE%CJ#kOU&LuWXQ!*bQ6j^?SZMxNz&+a`m7|0ZzzbFkHY@&83Y)b7N~?mq&z z@La3?TZ5@wH{&?jwjCoQ+2G3Zto!bhFBJ6PT((D^GsOlTLDtWcoJOgyyF4ykJoH*> zJZE?{_`>egXQh*)B$(^$`u05W(rD8Q`(OT#yI$Ok=XUrV_`Evtkjv*IVSj<~c?9BZ z{_l{3^@&$cyc}e=yUJ+&pKU3fJ^GzD{a*sNf^W-|_ji?kj%U;;LumDrZ^Bqyi7JQy z_PrJ`T@k7NqSjVOM98cW)o9sft1quf=K@c|PWA^2p_z+R+pT8lNqz4Cip5wLiexcEE2?U8Y|CcP}p;Vd+3tKfXdERY)Tqxl*jg&a9!I zXdnmW*$hbD* z4+-5V13!Iq>)x~cOZ0oi3@K=_;-|0-s(xDDi{K(-vd?TByVgZJmRT%j1U7{_!DyK_ zx)!&(=lO0V&L!WP-Fz0dQAWrMj9V1UQ%Q6%d4&~|xOM5_n*{+Yxaj-%b86LBmhhJ{ zTML`{dM5=QP^&ucf&81-aJ5|5Uf0$Q_#2LIdaK>HM!Yg^>Jc>3deP(@f9IdFz+jl) zN7$3FAJguyI%NEQQw)A@y~WUMH)}y7DpAo=|1qzdzqLuQj&?we4)P&w9d_40u_JA>5ulvrO5KfH?Dxnt_?uAwD{fx~a1;6PFxQa}AtB8EKxmGU{~yxQ>-=Y3c2`m{ zclkdjCV>F4E88@Jxzn}g;&5rtRf@|wSXSA{!u?vpBN#NUp(vsAL}`@GMrfT5-aGs# zO$`Ml+y29^BLUJ(#2*0urEc~{oE!Lg-vL}ToRQLJ0gK@{C&y(#RaGL-Jd-MfH?Go) zQ<@&F@y4lp$iLU&vTazyrK!zMi4xU@L7115?Apt91t6ed!sZ5anb8{B3iG4BR4Zi1 zYsx}V=;o5}soUZP1&$YI?4KmOqyFwk52d4aA2Z}R!0oTD?!5Aa4iMT}rH_AdbFYq5 z?pDs)5)YMq4;Yj!nLzisRwvRN$NKGOBvz9J3FQ2s*0D8}J@kuA`lX(}9dpOLSU&fC z!`i)@MDuE9;A+T4Y&3f*UK}}t_i6FOxD9K`A7H8qi)%b7w>B{?vyWZvozdS$5i%VV zu-v9-)|;Mr)q}y)54)oPSJI$-orZZ@MvvXEh3Rwl--777&fA=V8UW8p&tv0Ch<`?M z{Nt3`TKB+S3=2MvpG<+jq!Eqbmt+Hkh*~YYK_F_vAp5wS#mdQC^Zl}N!w>;fRAo~O zQv3(jvK_xxUDP^Jg$jg$=VAJc&nN$S2RF8V%;Uk*9VIP>EWxRMWY2eORy}`>Jb@qR z1JroZ)d%YePQT~oyG{pu!;jmY{+-OfZQ{6C#46&zEDPNTO}{86MKb7YzK1rB7zOUY zQCKbXqI~>fq&l2wZf^Eog4f3OsDea?-9XNMYVj5wx60xc22d;Aiu}gTQ3MS4`cF-{ ze3ixBY_*zeI^Ck!TKYFMi<;`~2#JpKQCns0X!~*;NVPl3h{7Ih^;Jkv<#F&E(K3K( zi!|*7A^~^LA_qJ)4>3-PIEejgN-%m4wBih*yzrYb3Jk;T?LJ6oSL(02jX<-BuCY)y zX0V*KGN$bbEQ!|Y`uF!LXrgYH3Eud-vDKf3$`s!$2$$^_TV>T{k?=x-;cd;J@_SA0 zAVGjVl+`Z<26#{`KOcmkz(xpD5YjZ5`OW*ppLHrWssrPuu^M*D{vbenn9l%yJJ@sk zK0c`EHhAEjzgUB28V_UPtFhNwnS$6vhtj1Fo52|=rfk-@TrL02=ZMe;I%x=`Duxy|Tf=!diY#*e|_t`++g5I+K%=R7~%lmRa)y+k} zF`ag2Wp^s@!s}1^z38j4944!d-OThuG==GX7m>^usTQCx z^2YNDz)U;8SE5xZqZ1y8J;I2wbYF_)!1IK_8s)`U$Z#7x*hef@<0s=sK0p_MgASu_ z8FxJjfk)bj#gH43h4IY{BAs89cOMdDfVWRR7w!KMCPay_OH^c05(B3l6C&~DM$-Yqer4TBiP&+6Ma=zS3)#1rAGjdvo(T`R?( z-^C~!igf|QeCw~{Lv@v50V>9!Dv|hRp4UD7^*u_XD*Nvw_;{MaFbIxeVQcNCTh@3f zT?PsuMwtu};VooS!3XzbxyZ68;IC5vuL>Wi8Y83Sa968G=+z+&S6v4z6{&;rV3z}w zA=@3E7t!+4SCx0Ws-qi$y=eLrDYgpy3_DQ*h>zhQJ}SRRiQ?%nFG=5x_E zGK{qAGezR##`W?Z#bp}?J4<&<}gA1iSIFco-5Cj`Og=m`Tj{2$746cJ)} zLyox?b{!(w_8A;nP&UyU`@$R&0vf&mZw}xZOZkbBH;B1g$Q&W?1;Q&9cP)RdL|tAm zzX6$r9E%aV=AzzKVm{WGLRl6#W|x;(5rbk|(5>SfO9948Opz%#6edBU2|C_=l+?^W z4pl%!k@fn|khS`Wku9Ude!VpG_(;)sa}Ke$e;i;1GKa_dgc91#`AJj;(`$OErQ`3^ zC#rDxv2p$85Big?`by#n!}{a3DF(|oCT3?RJ~Aft1?oe78JvHJNm@`$7+@SDGfvj6 zO{}{b=37t9@sH0RO)PuSGz(2G#f+CMB`kYQ{x~vxjx*TG)cu;DH2G+J*;v1`e{#BL zqW^62G{z_fZ8RQZaQMOSTan?&+4#<^-Vf1<4=GbX>eR1FDYob_7;BVsw;*5)a>gP; zGOJ_6zzJ&lF*u`T`-FdtB~lTOz0uxm~dFkTnJ3Mq(7J4ix$PJhDYqRqweD8RiTi#I|70$Y;Er zT@Y)y@;Y4Ut<_Gxls{4Yuor<#T$iI;=Jiqufdui z{ZO)uuO<>b*XE)O(oT?sz4yaK^7B-ix?w$2`BeA%Bog9ae(%$%N;2Bt`y9_jS-03v zOc%Dnov3>pzQqM&5k`D9lt1h4g}iMuQ;9T7@cUT&dQ2V%!7IgB+B)FNmfzf+u-uJq z_V_y&t+9|LG^7fxGZ?|izIjVOD z<~$1-GCD>UXGuik$(Y#1BD)1~k*fgSdhj*W1vjfA0%WJq;jnWr%NM=n2MK@2)6aO_ zTb}XO8z_PRYRn>DX%R-6d$~UMx5N0KzUkVyEj1TvFlCXbA>-hUaKYCHa1o)zn2?0{ z+XM@)J(yQcoE2aT;D_5@TO~ET`Aqfx?N0VRT)Y{MDgEz!2Gatg%F6 zj*vvAa+f_i@i9VW62ryr+Fo31{nE^n}r?D+A@AM;OJ)F@c?%2|y4j~#tF8Ati1$jJ4ud#NT|`0nZV#>N_nGC4x~(A_<;n zkvA!Avd|6E)nC+nuvg)ARvbthN!wWRmj|8v4%=mp+k)BZYx;0~ARHC44Gn$W>_wD$ z^iC#nt2u(QC6eKtjh`$CE9(j)$B7^r38bi8mG2cT$zv_4+pB5h%-M|q0Z$se&1@2u<_L?< zT%hBeV;HXwbz3-h4N^f$n6mA7cz3G!+yG~ZZas-pl2lida#fRYRg`5+P#mpKx$2P( za9tzME01qQYz#)izvfxC*>()r&&q?y8j&s5(X5(bQiCy)NT{_Y+)vX3hxg1Myu-Qn z9io2!!ytaH=kegzO2hhcQEfn0+torl81IrLkkFo`9rxZ&r%yXIFmAya|M+Y;D(y~$ zfDT?+hpXXs)!a6WhEvBu5aqrfcL|{&a2gq3;%ZZxOaa|*7!L%SEu>@p7TW{ zyKd@|VBRD|s@Ag{Qp1N`$nI6NLB-R;#DA73C4*^#?KGw5bT-}uI^>6z--?#SbAB{= z@B)R1|M;2S^a)o&^5h=llk)e53C~k6Fv)P?#+kT@nX3kh*S!&9RqYl&>sA}~Herc8@w1{PM<7B$QwR^EVh)_c?R1BFiv%+KnAgcu#K&N~H3f`lw@buO%P z#BEe~V03GN@$|Xgn5pe;(#FG6uQLKYutM@)1bPUULgF zHzZkwTL@=@tsiC_l}qX9l?2&5w~3G4PnzybCGU#5k$6mWf9lLWLtR6wkzm_(%2S`TPpFlEtUFER<&f) zVtYIOz7UUH{eXX>!E&Sba&zi(Yn5I7q}|Q`LE3#qH5EpC0zWB)1Og=VPUsy%?-;5y z0Ra^iF%d*mN+?PZFcfK0LXjpyC?dTJh=8G2=~Y2#A}Sz7DS|T0y)$>#tTk(1?s>>M zPv<3B-}$n?z5l;r0s77Bp@$!-d=Ac!AEdl~TjKSwJN_W*E6eGi5y4jVw*A(8T87g< zVmqS;V_HC#_@8m|RtEJ*U}jTEJbIJyQ|=Y~PnWF|xj&^*$kk77P*`9|FQM!p za9lkwBl^ZKv1O&-?mrVgSIms$o&IUKiF|%1rc?E1ZTbTHT0mdJ$lq+jhVOBkAG5{5 zF*Q*NeFtIwR;#|h#+x$wOoG_%ce38J()C;^4HNXto>cl)>+H1+Y24-vJ{jqyr^+iE zw~0kS-6D!l{=N6THyid=i}VBXcXot9=O)4m_9c-1K3?ngDTzfr%;~o6zL#ZBhE-M^h%C7p03C&hpu^C(4zp!)9{(d{9bINHhecOC%)X3#ubMi+`V zB*qKP(iPpm|B{?6ws~b!aqqX(bosSL@68Wv!nPdq&U#BO_mV*OZ_wF*(?+cnVtICT z9yRWZ+XHa;oLTg;lM%E?Q^mrR7%FfY0o)osab>v!vQ2KWsdPJ1TpP+#`!B#vKxcIL zdf3f-(hkC|)(S707}Q4V7NeF%>BmcV(%2I}3`d3`3hdtCL4w_JcU7C-;J$*#&j7b7 zkH4D#3ApJ_l4@O{*tt4_wGf>b6m;k{Sf{VF{IuOP!#)WV0;B);{rpNK$aKl!;;bz6 zE}7RtF`%C61h8VwiJwf>1)s28^B0Wdx>vkp#CPiqQ;Qf=7%^mPVP?{j7iGO}^vj|? z^acQ5JRhL*B}(8iJy~H$eN$!+W;7~TlpU6ECe%clO1%+DxuAI@lKLrNR?wF3!}-a0 zU~z^VGA8=vcIqi1RzocFc2Kqh-z!p=5%+8T<=(!ktNEiT z`+Dlyql(D1dv9eM7XpvPUj{`EZj_JSiwb$uFCVSw*ADvYMOD3` z&w~^Ppq|*ovj9g~Y!`NaF+yMaOOS1XlF@5livgYE6Qxlr{_0`fqryECTj@rJNzhb%$B=A~9cKFshn zSUn7PEyXoF;W`bGnMwDRh%X6yT<~5RJ9>QHBxGGf^?x=9RVn{{EQj zwQy>{&^n@fP+WW<#GT(L2gd`OdC{PSU2?QTjQEk3gZgw2L}s0asp!5*I8uq(91{_2 zOuYb6^?rv=u8x@c&TZY+kS63HOuGBV{6g~>qi-sN2s8mwqY8`x5@b2&y_i15^GS4A zhza5@qouMsh_O=sLDN{Dsf9$hq)pPbMjw>epIv=W=@TKCWF+8HNc}M1BSAasXe5P? zmvE`Z3G1Qwv;7Uf+SMdHY*{I)oFyG@S3E@s0-|v{Z$*hqdJ!cgMUt23Vc#p{ue%r{ zw+=9LQV(&41PErghn>_XVazuuk7~!nA|-j=)_Zhi*StQ zHjQf04xJI0YQz^a7I$wor5A%otw@~L@-{|`==R3T>Y4Ce*7ZWg?-MT- zFFn)`{%*TsI{JK#6<>8v>T=y5)ne~wMMg4OQD4Tn4_9Zm$jf|_oBm3dxhI(r~L07}F4^y=h^_9Wys#m2y&UKstZhy<~gsOjB zsN8R8`|I9e%sr3aX=tL*$a$xHWU-UIeF>)^=PO+{|4r(OBk|hT2Q^w%>)vWLlYh57 zFTS*jOj5IX^<6ITFWsl#9;%=q6ihX_{YlI(+vf-OY$0hiT1z=2xdNJ7)|o$^L^#*W^imdUWO-Xx6=vr8{M&xn2p0&&a)P+h zv;dW6{B5=`9KC+H?l?bC8f|Xr-QNnoB>j<3dHuqGSfb<0r*YqIyqg@jP-#Lx8l?VL z?yAv+>*gpM2b4UGRR5x7G`Q|40Kh)LT@VIdPQQt?ogWT@mhs7_nKJ8T=v|hQm zEaoip8RA*poL~*$*|eAG_ous++IA8i-(MC!ci3y&www0uWa(72D!`Y}wwH6-Vyz}0 z_#m|HSJAn@7tUAxxtrJayYk)1cc+m*GZSt59?x{P{B9pF?CBQ2ISnojls{SWp+D^T z$-0wL6;#9j?#T6;?ypBbPrlJh{h6-P`Ca$82&%k-R-D4USYM1ki2(Q=7B7cX;U%|y z?t}cRyBk%h?~Z;riGJ`tcal30a)Kc-ElBi0H~O^Ql0%T~($r2KEI?zguUj83C6lvn zw3tE6JqR9L@$HC@z8O;3d*nX2;JX;FOi~e9Y_I@CSvwnLaELfSjMo6!1vF-0=;)ro zlQEDDk{p)-!~!BU)CpcZ53bjjsfKW5U^Gr}=q}n-(^CwKE<(6FRq*!W;nMk|P|C|7 zO3ox6soH@Id5Rb?Y_tG4DnB>65<+CCoN^@e;bQ#!dmc_e`2-Z=y11#5$0vuNS?gst z3D+)!LxMs*;U>Zx2HGS+$fzKMNI_>qq*5X3#kq7y18qw9k1g~Ydm$t zA*cd{T0+PqMo!mBiFxsAP2o+;K9w9{HP*1RNC$cufJH)IOIu%k`8=vG&S}7cupGrX z)M*`p=PC@xoN=nti+bFrLYHFmBoqM_T!*;Hie$bp>JM1L|F&G*OII5z78A~4A{yS( ziHWueadr`8)w`3h7$4+(V@n`BBjQtem+HRr&3~dk9<&EZU{n`yUE z^koGv3Q;B zs<*0m%r#n}9V=aO7T?l!W1 zzbbey-K``)N0<~?XrUlgp!q4+@w=)slnwD?x!N<0-!k}B<(Dy}Sb zDJ(5}$Y0h|`nE-?!X~HuQ|Xn-(n?kS%Kh(`H>9c`KC5Dsu3;~$;g_xzE31{0u2U_m z)0M6_E~~eceto&@wWD-{OId@5bmQ^<8GI|0dox+SY_ zTa#uplwos_>2WFV@sR0#Sl$~V(-&FZmmt%hR^Fc@Gmy5?WsDzaC?9N-8R{t?8ZIBK zEZ-lK8D1+N-jW#^E`RTGa|Bi~$|yUwwlVUF9U)RNE+;#oS}~z3J84`oX(>B(xnjyu zcG{(4+Cz5cVZ}^{>}+JkY=Z1uTE$$B?1!R?50$bX8!A4w$hOjj&?lKr$+ z@o7u;^M1wWf3l0P%0))GCHBfCez|3_%4IpZm9yHNuH33|<*KFJm&=u39Oc$rD%U*Z zzCJvy{2C&+9$C4bAone;@>`DF_oB-0m2y8CDu1-eZS+)b49jg!S8jfi+gkIONu$nQ z29%*4bKe085^g@WU17U&d<#(QqE7vh+a9mjlk%V`?wU%VVzR+gYaHwrTZo}Bsdc%- zNclBKDlCX})PO@^s%C6R=v69owPIJXYN}LE4to~Nn|qQ>rI9dDZSfpe1(Z#|JC+qc z>8dAHacV=Kb(`?x1AtVYfi^Lm2UUd}0@1B@F#GHMQ+2;S5WXzZ34K&?sNhcUz=CME z0daSDM(3WYEPejE23DW$zB_27dSZYo+7&K~tcDm=LqAskO%BESpQRs!vH72F(wuCK zD_Y69QYR7s@eMFaO_r{ulZ|%^;m{$RK^zYQ;39&OhgF9O3S812kPIsAIu*1*rJW>k z`nl5t;pmF17SgJx!bo->wqck>fJaYzRcJToE4@v{p5@LsKS>kc4h;UJX#p$Z+Yu;m zNEj8q9jdqpQLqnX(c0vQgDCjxF^d!_1#QCREBLOv3koRGTh*LP2ADQL0zU3i2AiND z1DV4P#vfI}2DKtpp@NA3e|4w0KZq$pPnry7KK1yQ0Ei5MCTkbvtlimeg81m=XsU}Q zw+iIb71aEE7Hvoff83vBKs0f0rpJrPl_c1?DQPRq&(n$QJ*CogMRBYsC>-i@rtG;> zyp=eKlLI3dd3UbSPBzp}1W{iZ^^@k8zI>A&(xS#ATmPST&2(C*+7MBT#rv%HjlNY+ zC8h3Mpcl=e60fK4WDg03^O9ro)k~r3iT|3&2eh!@IWaHHY8A(MSyuC%$;&(Y+;TKS zU`e-KmPU-xBPAR02lJ6qYwe^k+Dc+#pXz=>281>p7rekNBPHS9o@aaSJW5TrVu z6cQ*1$4J7#{i$4!Nwj^y<;PUJ17-E?P+PfhgK&U(o1i80li#1H%?m-8aLLv}BvDn| zoCZ85;Zjg%$gQgJ1VFS219t$Fuweq$`zC&;;GJtf)w>Lk!VR~~!(}Il%%xzXiAaQj z*I%g$&e2YK#&+I5k_1X+qUbJ8O_nv=L<<#uqKVb)GEj6=f^S48B!sJxv2M9k>D65o zVI>5vU1oiq$gz9(`EPg{l}@Vzu-!F~@>NpTM^XR{Tsv01la3M0;MHkGub{cwsi9u~ zKn-$FOBT-ii!t%+A^LRhRE_HJs>O8^fvT>9#PO|%+P@l3Toj;>!*!whbmD;Nc{R^t zRYWyLp+#SyG)(Y{7dJ9|Y*ZQ1q!whg?T#dJ4M8;IA8KWH^12a4&2MQ3CH_euNs5FC zl`AGRK(vZG&WL91LWuB&ykN&^C%3lm_;{$!>Q7zA8k=((sRpuenNU8~j?^nhz-Jui zulD_W0Rq25IkaWrO*m9gy?Im}GTV#R-^=dZ@3U`%Dd+)xF$rm?!WakGGU?Hgao`LR zPcQZAD-~N>y{v*7+H8Q1^4qqzUfbUQnM!&v;G@LbfP{A#Kr!^y3Q!Vi@-CPFpK+u_meQjY_R!+{_Zee|JzuvP_nzLRlNVo%Y=uK)n2 z0efL2lA0`BS)XLJ!@tu=SMG$g2E>C#5cXho2@s;s*L@EsCekR`xessZVEm}5`ombR zi-ff35S|aLoH;$9iNnB)aWdthya0Cj7ZlHOdw>}h}1M<{!r z)-LKq>mx-ZeQmvD6*~~tU=0!34ry&s5w#lIJ;-W!z~GY=ET}G@tJKtYw0-uUIE5*Y0X~b` z3IhnnuCwwpQNUggnbAo9$78Mxt5fOY{PEw!oer6raA7hoc2K_NogSO#o_0!@^LdCy zXPb8~;Pch{KxhvqUI9xb!45rs{tP@bFU>#xq<@6f4bq{T?_|HS|C8DC`)PZAIu>U~ zf)!^;p+rz(cIsg%vM4YgpCPCIJWmXrw6gGn?T%{yoyi+-M$0GK(HU_`)&_DtdaT>i z)($M;5mX$9)zAm~Nd6d31#3I)tL38bbDvnH40k-@Va&!GV=_sj@K#RNAa^&s$0+-<+T0qC{g#M7$r~E7=x`iF(gDW}%ruBy>ih#V@yH zcscbZ3Z75X&h)-Ln67X|SHb|L&-Da{1?dPO(^wv)k|f%l83_z>+NtY5IQBng2?Sa! zjxH@C7*(jHv)iHmTXX_8eUlDuqucT?q z=wJ$k&s2R6r^ynwGgY~+b*6E|*FV5@&y5o`2<71tweY+n91fL7m#X)bvz_%LC`V0^ zP9`rMX^4z4t~Fhc6sM4klhG2Uh`c1um;&*LNHa`X#K3k@)vqpZcvZRTsj`9~j4z@( z?6wULGJ-{2GBSdv&()ZXx%s&i)OR4JmiHlN|4&A{fuvq3{=_k|Nt9Fs%HTw5`wMFX zZgD9_{!@ECE@n*&QNi-uOQe-04Y#=WsT{D?1TiAt< z(UW|qC!SttxoD8#wHo6hX!%ewHrYR)_YN=DCC;K3&ew!P!7rExi$zWp%clM};I=t+ zWl;Ay2|BFI38Fo7BRcCf6V#($cj)7WOA7HU4Pq?b9~lHcXFYA8;i8x?HOwnhNE%-$ zDIVe9sU02;_ZhC!J#~NZ=UaqUB!MnMOV2v|g;ljHCI4*x$M$FW95qW`Cw(%sBP-KH z9gmM#k0rG6Jx4ql=*CGb6NKL7f4A-)h`uM!Y|M>*8DS%rVPp}TbSb|f*~|(T#lK6# z%B<53=Z-Hkj1Ie)CF)ij&YVqK?AIckM}2EQw92b@%ft$EHuR_n0~r{v54T$m6jd=~ zgk;_FR^8zGqB&@tRiG`n!4mQ88*6Hc1N5oW83Y_8Rf;Yw3M8-&_lKp}-YQ;9i$CGs z0N-pP2*ZxYo1Yt*O~UBkW94doN!>Kl-vE5Tp$9V)7p-pmp1SI}@Vn5NR3JLcp6oP- z$z}6?Gt0~^NW+k?qq@8ld?zN59n1K5unn7Uom4bjb*wThZ1=$7Q=3 zWt=f!XPn5zFyqL)^m09WVL=)LZWC~pq==5A2K+KkFgHoIm@9N!0ATT{he%#aecl5}0)h7)}J$C;{AE}B;E^z=n5CKz`Yl3KGDDrNI zzxBb|Z`LwvL#u0rrk478@oL*d%Ls)@tNCG&-3s4M#(ABs zu`3wL3jsYJNEV5rEDATcZ;5}~;kpQj9iEaHeXBt%c&0#pvK~Ziu5!g|(1EY1okpFM z{m~Z(6FvLEX%oXx>Nc8!+W?~&v30|3#5JRdiLQ9H_sYgkaDhDpZbuRpu8)DrX-*(y zK#Aj4KrRH`R)+=a$5XV3j5Vq%^1f4|~<2xDBh=}n}3g|ucInqHeouw%(MH#)o}e_f6#4s? z5UKz|p>wDaMaNUK*C)lZP9;Xk;i)k>`yxd&?k4b{pbmO9I7BL=;k`$~;&V00bv+i% zP{k#Eh+)=RPWA3sRS%HotxldF&|={N0}YvLVe-~@3(|28 z`duhyl$##+PbYM-NG;VNl~gzkwDT3iz}gZQWVkHgT!v<^5_gm8U#e{0z4++-Il5)p znx~Eu-~DeSB>@>mr;7`>H~s-pm)N4!rhr*i z0cfXJtc+*fi*`E_R~!JMGhs`7i0X8#AV>OpwS!w)^!XKUZK&leAr!=-dFdoOu)AZg zb-ocUa%2@5Mk=4eeY;k8KRvSw=77dW$B|I0Kz4{ZE|PEtbA3Z~*PBqlcGej@n%yJe zJ4z8ZDNX{L8ODOy24_VA=aw)HkQ6K%$#ez;e6}~{k&#l&DlFxjMbU`=%D+YETjB4w zH!v0voPhOWpoJ5Z>yu51!Klje{JG2X*&?>;cV9Qe|n8;p6n;B}^gK37^stQGnELYS;oKs|A-Q9bK$m`6H zu75Xqx|Q`GfE!oAo1VP8Rw1T%g7O`n2n+g7eSJ=N1PwcCwn!e2X0K&dtjOPv&-r{i z%P>~RtqW;0KNm9OmUTh;Zh@p%Z3ZG_;C=c7&zXf$8C60K13qFCHMe7Ido}KYoBP^V zW(xMk)dW7`d&G5B<++BNAO3ngX`%V_gWaGM0a5WMaNZs4InbjNk?U<9Vb2B}1#w<( zxA#OPrz{V7G*ms^-;I6tU+TK%$msT;fSbmkXo}C^`XXyb^X`b!3F48hF-<8vU_{dT z>C2yHG-gplw=Whq3r@FS#W|k@CV6B`Sm>%Xv89AOc;4+sw5)mc81`KK;=e z`>3jYFuqZ~Xzt#@@n?{Le>-j4R=C>95<1tvTmGyA*6(CRP#~b+DrfgucHd{LDFMJ^1@G zR3K=HDfh5j?cYvPZqOI`+@tq?|Mv0(g1=ei{+Vk0_q!%Hc+)fYc;VpRLAyZ6PGauK z7q!!)vD}c~Rk?pR{Z5aU1RfuaxNR8ED8y?UcE6fdyqe(}7XD-#HBNyBZNrnWi2Fp^%xd64HA2lA z@e*|!SxtA<8bYpuUIxIt0CboM8bJk#E$#o!Bd4(nqy z*WI6B=Ou=~#27H)PxBaurxz_bj|+rjV5L8DUS#gc>oPRkS10P)1GG13SlOwFJ5`_z zJ?4L7pg}`s-ck4(K>(zP<{STOSB?JHE^xBMfdZjzmIVIg3CJjND%%Jj=P}!iqV@3f zR~Bg;@iYz>S?^Y{wRh~}sI;sUT0=a2H<9(x#e;D)RFMjr^g8qAp^jsWKdM1#?aXe@ z3>VPcEC$>@HHY5JypO7QCdSw^cMrxTIQ9Ct!pE@L%*OkA0^dsy1~4+cirjy5d8je950hchD&Ry*>MNRV>&CHa2ZYp=ruI!TWpHgP+ z)*h+Ck*q!tmSvou(TA1+;MOEyx$yhsMFQspsJ)Y2nJBkLP?Yx3fB#casg7B-j=4dP zM~)dCmZxkwp?nE7r-%U;UifedoaMb-WEP;lX&7bcwx(AAAVXNA;6+dmR?Le9YC|%* zx!A0TgGS((`7ksM+aOn9H39&;rZy+vG7=wq6U3r!ix(r{na_@1uh-D>V<7xEO*6cS z&KQyj0$47xgG0ficCmeg?(FtdXgP0_;MpFv_9R~xz(ZjK-rWt1D%BZSyx;JJSwe=d zbk*vykac4O_b|Yg3ZEPh9N&*{!F$rS+<`k1##}piH7bMv898-*?!JzvsXJpI1nUP# zoUD1=qafbuMUkR%zTs8FR6YJ(kk|>lXE|?J-BnPWuAg~PLvCETr z=_XLS1Z7=d5>THAOj%P`&rR{nkJ_|Casd2b57}1OSi(XO%}SsUFVf6P z^HT{7);NChl+-_ElPyCo$qk+&$zYET3%%Qdhm@WoJ(iqMv=r$g-*4?jN&dcPP}DOq z%|2G`C60S;wU@n^)>Zh~eQufOvDyFTqyXk0^w^kFP(6BU?|a;3 z{ScUZUJ&nna9I<35#$JX()iIBQfO_ILhD6#qm&^TIsttugp;d%!y!Zxgjr%& zeA!n*52plGR5AQrFx)I1^BN99ruMHd@)O%FZYvgVs_a^4c}DLoBv zMVUt3Q1{AO;xW`ifS;KGK;#H*U?L}rw<*o7S@sFYz9u}*s}bJZZmojPXbG2l$iZX} zCKBzVoJLAQB==#};KFwHe@QNHZ~#}A9t{yRK;olYLhMsqMCswfi!5|x#`PryCguG& z4DYa$9v2Q-eHR=D2>inX<}UZqB$i16BNfk?_7Lz?y@Nox5j5N`qLd7n(S|-exJiSV z24$-hNmIbfh?<_q@WGAcyb6V_7v)uFXT%1iRZg;;vRsky+>;eM3z7z80u|FpnSe45 z&JO?$0bmq{rEyHYr&`rhDJ{g-Qwa@paY3YN*zS-3=Q`DxO!44kEHjH_`!Y~Wc$B%y zpMi$T0l+XC82`#70?s?Q2X1!y(H9+k!R4kgY+tHJ4z+AG0)Y-k>&b~)J(H4|L;@0+ z{XgMofZ|^0s_)X|J3J)|A`NY0Sf^)iHo197Kzxv7xUP$bxImQaIeL)9SsYREp|W0B#{OS7jp(3&OYOyGOdRKOp81xNuU9M zKjJxv8OKFDe&+P%tOF4$qb$Osr>wT8WZVs-F%Uz*_W9xB6^86E0YzR zYA$Mvdt-)4)A##;@`IrOh=(FI?|qgyh4xT2%?$tI%1-Obqw_OYGYe#&hckZ|=gPi` zV}4Ve{n45xbxBxPNZ_;&4E$L$Ep#>u>ygLyWz~X!PpdflWvjp3P_mk4K-?t&m!CqF zBp(!}nQ_^unl0zu&k#2jX%^)c{=49S5x7-xOpJ)2;@~aFzpwsvbba@ijZn*? zm4H;(YP8^BTmd-tw{0CQ&Hg|H7ndf=+QF>2OZuA7{zHmWJJGSn+A{XTihc}>x6DU`mec#^b^O23ze!un6 z!mAN|@o|CaG$9=pcLTdtpCoYs{1?=J7Fo+zyXx2^_?V7l)?@e`ocC+OR8BuZouAqH+JUEH0N$V+;me2A^m+ky3h5~pO zfnhjb)H=W4H2-D%m8u&)ko7#cZ!}{Z2*}Qx{HVv#f&qPhiMO$1(sb6kQ_z&+GBd_| z;WQ2-;|ad}xgGT#$M~H}Zx_q|GLAHmMIosUWh`(?U^qYAFaF!o=$-^E{`5V5$;Y>O z^fT!P+lyGLZP5n6_92w(BeP8G%r|+y+lwyXjn3Q^1Hpz8z5xPv>8gCPOp&S3zQtcF zzxi)Y3vp*vaMI(t#H2#KuXWw+(|-SkW=!_me(>K(1RiMBRFa-O5B@)MSRX-s=m@A^zdpESo+CbZzTk9O&nT8*f}pv8;7gpYQ4{admSN?j#`f6qu`6 zZn}Kdu`yk~<2mYnQ-N+^HGrPyb5LB2%C)<04_xB>fYh2yCKsxC$t+AEtcSa7Lh%? z+8asGYv8m`dJ)wxhrhJ=G~n)yy`G!wU@UjM>r~U1G-Oae3Fe!#*uxU+skp;TZDf)9m;!K5TH`gHOV*iKufUv?Y_ zoDmsfXncVjYKYlTJ_TK&!@dUBN>aCRi0jEB7pv9IAkPYAyGvUJbQy0iUC=13*1yQ# zZUTOKCF3c(=-8VF-TEQLD=@ghua4<7Ve9<&3XEfU!`#C4?Uopk2!IEEU82t305jjt z5BDEZy54);H~gy7bIE>2VbMNfzobI6MHuHgX@VcYpqyfkRBTGVfV3{v52Nc`ELhOD zzcy|u4me;f9*VPRq;l`&87CMR(S7NCOu2|H&=)7_TOzel9W(kEs9t}9f zFRUAgXV8y^eobdd^hP+L(KpFUGH!YFH{qR}1H?!rH-mDI=*~+o^)xA=rVLmB1lPtv zp%^M1X&oSADbT?z9OA5NRFK(AIx(}affOy1M*_rIFd)+`nlP~Vjjv3gAcdpk{5fTo^E~69gHZ5s~bDo#@NLJ-tfI;2KJ)ekP zoHaxf?zgUedy(j~8eO>eP4XE7Mu590zb8kJ4|g`JqS@dpJhK=Wv1yGDwZ3E|BGG}} z?&*}L7-%ciL8ab+I9`RG*Ur5Nl7h}8IG5|uRB-k#40~bDZ-w*QEOzP6F2xWwIyp~v ztPHd>bIETiKrOx|h&Bp?No0AjFz9iK&!T`4mxPh z0l0q6Gtr6%Aejb0FBM#u3fHWra>~DujQG_zk?Fbz!=gz{4RNpX3+Lj43Rlo~Ze6%G zq6{`)1>-|*QNdkg6J@qiOR$zL&8Gk+X9ti4d@({{u?yffdsv%VQGQ$Aypwqr8Bl4&FM62^AD+1 zb{Q=}z@i+UlICmvrt-!)yGG!9tRId-|}^;m~|(f&uyc?*t4WR0-X_gl5=4je{IrRK<3e7qXP zxn;i2Blt>rf#8Fto)?-=pM+Q}S>!c_+%cPY-eVTi`<QHRJkh5G}=Ivt8Pe%^BAFT?{d9oc@Kc`V87_s4MMt681M>eWk z%#GvM3sPF3-Qzl2McQ#vMf=Qe?>yw>|0gVEc>VHtZZrL9$%m5?w073zXD+{xmq-l8 zb_d`#Yoj$|3M}VEY}ux}vi&bVh|0P!lSNv)cxBxE9{pQ`H}93p-upP?a6H23fl`G; zTEoTO2F?w6G}PtF8tC7T9bM_JdGsp&`aKBuNV-`&gxch;e){lZR&zs%DvHTjLa=MN zjifkS6KV4NxOOj6)$vm+%>2rSTHmH#$B)`jJJK#sGw`DnL7=yI%WV;H@4Q?b!%lRH z)@;u-uB*!JXWadu72cozuM9-p?Kacq>EwOvbrN<{%e4E_xpxOlAM9k@bg)&d(#UX3 z*vtMA^i@M%W8^X8FADwNT2=YxTBX~*nM}doEIgYha!(n57n=uvFE9%n*SP&4+BW!y z!_VfKcZ~bhPlGp{<=@VYB<%l3@~Y?CkDnP2ng)Zne5>9r{7g7#`4Rjx=;zzdr;LYG z`jG7i`IaT7#KSJ>w)vBLEh_>){VnxEc2jS*e34H)8gk0n&HmZaCcSYq`q*)=kRy22 zH1W^m`GsHQUpZE;e-4;R=ikpxYu)s$^7%0F?{{;Q_LltTM`r1z`=k89u1`)MO$ndd zXuqSg{`}n0s=Tfo&mrmeb$svWomj9jPB_m0rsvGlxx;B=wjGwAcX!`ypF~}Ld^`O# z=+Vl|-(4}?Uq+`V$GMJwq7O0=x)ca)HAKJ~a$_|h)8-T;KnBzGglw{f#H)o2o`0M( zPI{sixILrKc1U26xXQau>i(+C370~-y8qz_?)`dG>@fHw%AXYv<|hXmBn8t(OCn|= zsy3m_vnb^HUB2S*-|SM{(GPLJ( zL5ieE0c@n-80s1TPqhW-keDMlgO{&_{Sgn4_74+i!q<01Z~YEVw-2b8zL&`vws$2) z5+3?o!Xz;&%zpjR$QM$M1pe5Ml(!yeyM7v!wI2P_KDNmJX``#jpUBt}lbF_wC)b+% zFpFS4YPi!*nBr&@AHYI5jH2BJ>0m(&qhKivgb)o8ramzsg8?!iW=&EcMg{ae(WAtL z_=6=#QHgreIRKPS0Y)M|epMW{+50rbsH@&G5K`=*N|Z zvCpH)#U|vF;zy!~(X=r)>P?<>*!$mJ2Wd~D9HJrO9Zy`lK>XXxtW=N+)*M47(Pxm9 zu`Iq4JS%YU)IpLcHu(-A95zSmLP3!iq4R7(C`3#qbASDd_TyQg>UOrx*%KXVLtVq6j|q5iCTHul=x$E+o3r9Na%Q-AJ7 zFt#tL%OshF5Y9pc4Vi$&iOJYJusdfwOLejpI6)yMiUkGkfwMmf5RwjvV_1TcN11b| zNob9vg5tz$nUB+=(>(fP?@FdsOOTHnQ#0qtSphLHQ?g{V)34}f&-_A~?4P{2`Ybv6 zDPA&`MUq_F7t^GX_9h_I?dqe8-^fG_e}@dP<1EVOE{P2f9&bu_@kx$70?A?^ezWlb zB_OP1Ld+Xbk3C0%DLCMp|CTx|csmmmkoKN7B+K<#g=rW~$@3i32)fMYuQH#FsAT7J z;)?^4>b@nuMm)crd7AsiH8)=}o9!s(#!;rr)sS7&Y=POFt(ff4SIP2yB;o38{s5uT zXk$5QddStY15!}Zvvlba)Mw5F%)J+~AmC9y3W3c?xI*g!Agr*dzqzstXR}(ka$of4 zaM)z!9px}KQ%c`F`!|>Sk1Ln;D4K>YOP7vPYx?Z^(Nl&G(Y3{t{JH1ZO)i!u*$>5D zY6a#!es$Kx_gIqx;lYEQ4&xs;#bImGS@iPdW1xhC3c!6 zm(A`H`U~^^oIW=;qp)6k$#@jt8BjzzA3S{rtVzLhpd!Q6T_@(!AJ0VzprBIQAltmC z=_m;Aa+DMv9AF>pY?4Dh@+VueS zUf4zMyyd7wqd$ppJ*6f0vLKn2Cz-kTHHxy|lvbpdHOC4ri1O7jmo#^IG(EWXfFPWr zj7N?!Gc4T@znn@YL$%gQX)Q;myDxslh7BjHCvAoz|n=Tin&j`xP`F{o>Yq z%=+&9zH)j~!@T}18kbC}x8@0xdsKh)wf?4QSt?Dvb3?-|jyTniUN-c(Mcxr;&es)m zadu^mO2_pU^Z=Pslhl)Y{cU~2o2MbqYf~;aX1s2CFw^AJ6LRHR#I9eX#Pz)7=EltH zF45+VW^?s}`w>}heXR}~^!yre>aX+5n$@o~m;Gqw@oPTMT~%fN_VxX@5gf0RqMtMl zzHR;S_8omoyL3yZc}w^Gmfoi={benKgDvlWw2aWVj!Cypn72;dZ=HGCI#<^EajU0>}9@(iN)2U?9sp8hD7T2j!-l;Xzsk6~}E-2ubB#8sM zjLJLogCq^zy38FqOy)(+hPo~yJ1kp7t!28ew6xpBv0rX^ppBuuOzI{Ky*;Kv9%A5^ zkboipCz7Ef>@XhyP}}a#s)Z6!QjQxmx5|6#&3hgK@Bk#c;SiKW>=}LtA&`3==X)|c zpifRD{abpI%A1|%X1#s~F!1AWQm19oqj6h}{K z&4B?NX3!iDH(%^C$Iy~SY25(!+ekRcnijt?NR=77v)D_b4(9X1f`OqszyMGU4RN3? znI8;6BHjhP??b-t9qI`Wdf&neb3?Kd$V1vF*t_yUN1)#;4n_=;Y$_jg!O+s#(OxCN zp76eJ&K~-)1H}^t)JQNl4DG|}!PcPRZ!H6eiiZ_4aI4Yb+MvGgW3=wb{>lv+;62Qg z3b8~DfEWNP>R7%u>;mvUnB1Gq+nYTudk@(}vR5K|6x_%2-Dq1JdT4Hp2k^f4AwpzY`?TXvVdgh_?A}jY!wt|e zOgl9)RU?6TnL+JQ+DjNnyTfz>?=XO$bOnuW#Yy@*Om1xq=q(IWzI59yh$?T6Y_$x# zwojbfobb%cqo2S)%|3SB6=nWF?5ZtpiuhAJd+}|P^{db~7yU&{8ea6DXQ{GWi>sfMK zZ;H;4toztS_X(2rtjUepBEFgUzmpTZALjl(IL1%oaKnA?XIHZ&uT>9~SbQ>Xh2D3d z>3cC%(K3;pJ|!QFQ<8k8d4kW%Ro+c2G7U+op7r@oi_aorTH{6T0Z!@b@+Dn zK!7YHk~sCmVc|&yO^?OQhxnz7y$fzgXr_~}XQxvb^x z4hPzst+OrK3my!;)w}~E4D8iGu#R|1&sNbN^ULuJgJ`*dDTYqA2t5%Ino-%_t z60A;VMa{a$(j7)5LY&Bbn?n$X%AppwekGlcmpy37qqMU(rmwyK=(^LNCo|FN)~lH? z`TXz1{qixj;MKF7{OwlJ@bbZB?O7Swsc%Vh>+U`HP}s7?`smQ$AKB##a^KE1GBL>x zT2N-sZw+rFhq&Eg^SlED%ts<>z>#-7+F|km7M5FC`u^j|U^By9$BkZ(kkJZ{@2_>f zRJ@0VT^OxkSUkrt@w27Ji8w5)Gh4|x-|a}-5Cj3@hkw5qY0jQZvD{3}>91|+R*dL3 z1qLVn&B;gf7mlyp@Se#C+9>dznX25f7Vo;4vza_KKM=Gu_;Bm(<_DH|sj1ejV>#br z)EDcaZpE%|)c0=T$RFvqhTmncO*l%S-~VJ1@HKJk1~KihCAMDU?PIUn$>Kl({~ZbG z5#SA?0~pS@tUCY*zzA03+xfm%lFr=2YZPXTobr!)>$4giz20HJDgj#L0Hw*dj`{+w z!_m{&WEc&=-*D3Ckti$i}NG2rY^U26Xi&t$6b zG5UobsxFm8^Zh5!RD!h_ zY}mZ|#FF2k{2KH7m-jj%bdFv3kQsKzO`G+m|C48mdgnXQ&mz+y729TN={K;`QIz(^ z^8J7DOufjspe$1`lv3qNBjKv^nyptRJJgjUcmBy@l2jg%^KGt zh70}&Q}^LdRs6>f{M_r_Yjf?9acx=FrG7~v-3YGFe2InDh8kFltsJq=BYp(E2}Jzl@J>oNy*k(=4V z+03Ce`dEfA&lw|yq_`lQ`1h>`6FeN$8ODmkRcZrtIfXM+vQ9g#Py%CqmIX$ zxmP#aKzK{AzGVVp_p0e$ z-H))5z{yWk5;fLVz`s4h{ByxGXjznq!7KCGe*B^Z0*065n}{hoda!k zar{lssQp*IQ1NT9JbPJ4@(@jJ?DkWeo|hOfz^dQ)fwVD=c&GI8Uh;j4C%I&np0DEK z`6%A%%~dR+oB5kS98L|FZBuVQ!G4Bj((c{x@ieMsHP!J7Cq!ZMLM2tM#Kbnau4vIb` zrF`Wd&9IRsc5|$k8A$6D?p%-MXHHL8`)=9H_9a_J9BuI9W(SP}SdzNpmUp2Mr5oJ4I*E zeNxo@9;1fmf!|enbGjinr10q(R@YD{bP(FQE3e`9b$>>zKOJjbAVKER##E}S2IY%$ zv(>|ctJ}*Ml~#%99iWv~)$4H&VZD`(KH38P(*iChw^{uM`Zi|oKp#4(k&Vmo0CEhS z_PrIw?pj~hCzj}&Q<`mv+H!33?~2#YSEieOnb`s`Smmt*#LLTar2jUoVB^c%+#WXh z?0JZRw+wlA-F3aA1yb+IRl{L0gwi)yi(lMsGo-PEu9PYZ)k3Qo{e>7o0QSAHoF>Ch|Rp{Y=%=x_qFmD zuNFi$^Ua$yf;RPe#dN7fx7<10GQo$5^%vjJl6%X;mh^q~-dRyn|Fo=1Y|eVud%#?K z62!OCv|iptX)6wzqy+XCGc#S}#y@bws>O&tFHg{k*QrW1LI*j9(vs7<9=s2jl_M`` z9g;G8HgE8Jv;~75u$sR4iS4(v{ST!BlV=E<$TV`}Bme1-C%kFwlZ!(#zUrIZT&X7I zJspKzB5F&=A9<-`u0GU$=gk4FEu_ob9@a3bM!w8i^R$^Gd)}%OKf11b4fn7g5C&ch z6OuJeg~%Rx4#*KAQ&jipl(R8@>z3z1W{7csZ7^p*=@kKm%Vdtk}mL{EP2`YPh9?3Br}xt@;z-_v zHzl2n^+r=0Oh@q}EulXP4D;*$`hwX12@~gKheaAR_TfOi++7mhJi*m3X}OuNAtg_U-Ds>XT38h#4vr1!>Y1;%&}iY zvexV%lnZyzVK~3O4nr2MJ-MLXP>VH!ZZyg`-Z!% zd@G)^wr;@j!9RDlCB@ye647ZL9scXysjdZere{m=RKGXT!_a!iuzJ*?1UogkF-AcV z`Q6K#$A1DN>W{~&`Qtc!SJ*^?XNp0Gk!GVm0r=xi%gNA5)Ows%ne6iu_T+}TC(Z_J z`sTrrH&Oi@4ysrCVKcP$cdQD?`j@coLmMBH6+hN)TPQD4=7;ukPz+@*d8sX~(%Zd| zSy?iDaOJA{vg6C&fq)&@H z#&&qgheMv0wML%qbP?wdzZ)L;LDtJfWsX1RT z>X>p!+$v#>!djf{F35GtKe27mq@VUC(p@R!hwMQbg-c_cr(kUDx-MZ>w+3nWncfJ} zbB$zpdgzdB6GVb?r9yI1r$w-%V`A4?%Fm60>-5l&Gt1OB2Ela{zT)0UkAx?6)W5IC zvkl>~QP5xl<>X*gC&^=WEOabWGlAgsHb}|im`pbcJ2!~c<~8*!B**RXZawn0Mw9-I zPP#XYi;yG1kN}=(6g1cvUh8z`*DM^Hg(4$_$j4K7ovJmMZE>18PmU zeSR$UUMJRUAVVb{R=Fefwor?ok$MeJ@Ar^+dQGZfD@=mt`g1{+9!ZgX>~Nn>hC(YL zJ|tSbob2lxlI>&48FA7Rl*-cwRCi6F8yR9lr)1~H%J?6d2MGPJAK8YCsXTken)lRN zifyt5;Q9h^R|JJEkt97t=2jytttWSi2#EPfxribgLy+3xC|(lC`ur`D>9b9zVS9zt zxO62w`E+Y^)?yk(&YH3PBkBgMjuB~=Vcoq`FiSd!Dgl*3qz0QqCbeN$9yDfW5~mh7 zM+B9TXW+?KQEmm>Jevfn58|Y2g;9iGwyA9j!*v2S;)=iINH_){l~*m&MbY=te@RAV zgcd#)EzN?a|5D<WkxDKPq@hjn8 zdlq;2nKSx2=fbZGr&f3kgK^L$u#`8mMBwu=j~-D-Fz5)-5Yh|vB?F0_iijm%^X$V9 zu2*N6XxbZCxc6CWonF8AUU=D7ti)P)1P)UPWgQ|$D3Q^(sMC+~bA>k;Lp5O=3&kkb z64D>V?&}h8K zP>=I|9;=zn+tBv6VM}k-y!gq#7e^?R#n_a^hL*+Ul_j*7C0#9*rTi&NYiH1d6N%t~ z?9lSuyz;#E@|+Q{2&Nb!QJytY{>G-_#h>zLc@-7y6{LZX(mxe-BY;jJyoR;1rMmja~Chf2s(q)zb>qvo_V&Jk#Pn;?$&a zIJBxw55BTg-Selq^S?Y(FHOw`YgK4z%~!9=!i4|uOediX#R=t7aMC~RwU;Bc^60AG z5ilg6Ha8TiimFichi(Yfoi5c~^GtuOORhH}bCzlumupumKoGV1>=EcGikhsWoWqvd zt_~QksE6k_2z{-qsxN0+uC1!8-Oz*QkAM%aj_djI%PsvIPmy)K?G4BqP22m7Viyg6 z+NrPnDgTU=^B`emYZNMpI)zlPBfs7tQF4vQm?#TTdvjj;SSX zkmk1oUUiBS`1-qw@He4Q^ZZ6Te4~6vi_bEdpMHfrZP`;{xF1*j;a!WEi?=fYtxtto zk*tL`ds}BrT7hFBC)>8{C-iUCS{O&k{vA{QMl|M=RXG#h6(}?pC^R6RyyMJoE)Z&e z(@`FIpS%7>TiW$hbF?%=zpd?lN5^PKZBa+Z*W#Y#(&OGzztFbf{7&j;Z9rPN_7m!> zHR?;73TF&OiC24p-n&zLBk%o&TAQwtE7p#FTX-vZTd!j0U_Q_Gjn2l4ch3?#GB3Ih z^IJFWcOTfkAIX3J$F|kub=##7xyOCV8U2dl<>u@q4zuHmvir^THu#7e>?t>T`tm#C zDtRcK-!t2FvsN+QxGasd?L1R_&zAK5!nU`v9dUV|QEQa!Hv<1BwB?l|zMuj&0q=JC zUZH6F?z$XR%DeCPC~KVX`&jb#BqF`d%N_44yE#SbHLLnhM*9tu`gy&3%?ip`(tF+N zdqHfZ=l(5>wiS-(F6t`A(!4(NQG6}yph{SWu|YSopnHgXC}p{4GO~L}zlU9^=Z^PK zfL(ur?EogJtTVCArLz2DY2anY0P~eTzP7U5+oqx-zg^+7E4iRNqM$$cKogab^hKlTZGVb@#6^ta^7_Raj`1+Op`TgMwvM%wk`jVx-H|-q{65sJxc6l09 zw2qa(p%^F}8{D}V2^1L^Fc_;m7*wws3i9q57U?!!8JSmlTT3yDJ?OmD8@<;tTu?FS zveL<&*qoct{`kg(v&fjzbcfnyU&Ttl&)8reMQ61B2r<{tht3boVdMX*-p;MiEhm*T ze(h~psZLd@>a1!szmKnPFFZ7uJTVwIq8JLN=$*Q39Wm%-4F52}Hgd%_9mGliuQK}I zZz2w_5)_8}S9VoBnW)UK=QQlOapla-6gDFD4X)um^G|K&is1z}Ga*>Kgl}4dqW;_MdjbKkdEvwBPmVVD;1C-%rQvpMNNS zKC%D&EBy1>i_d?$K3}eW{`dDYz_A8aS%WyN!BRevMfJ`Y&fZvM{IxvwqJV$oVC;9& z6l2Y*wDPLLc+bk}dKBAxcAwQtiEeVL^@^!ArSvItuZ?@^t3DMgsxX< ziooj(Vc1QjU>9B1JM&W5zCdSxy zoQ=lt9c!8?toL8;#730rT~&RMPu_J_+kWtGj~2d1o3vqkImT+gKFBteIWdIv-R1nY zndPvj9kKT&A|_sJ>jlN8GiW#U(|(Xcw-FxFa0qc*hiDMMqJ>0F6ReY5jD~fPbj3-L zi9uro$@_&(S0`u?^5!=QXz4LU7lC|>=w9_PMYFg}SP@C#8YF{2o}~sIr~8(W-JJ#^ zo1pqOU2}WZ_uHZaJQzb>5=GW%g?u)62s1f^sKI6dyD-dQSK6EMx8GI{zwO5#&KyUU zygYvN=ff%6rb_@MRR!=T?1TLw?o$wrV+c__#2~H1=(>_)4bq|lk);~YFC;RFxV|0P zaP5IK!zG7R7>%zAN!^z?tjY?#p1}^|ybrD5_X^1!#7SQjlIncX5_?QMjQMGH{L|O} zr&iU^0Prca9#X#s3oa~>f&!??pFHP3YbBr!c=91SXc%w|`TkQ)^(4y#T7OIoh%b|I z{-&FL4@T?~!O80h`yf1U-Le2%hv*+`i?01HKKLM)JaQxTM;s?(k|Olh(=SmM5R#SM{j+67WnFto3=HD1CThTn8TBY7edpzNA%Yq00E+q z_SNtT4vjd1h@$dg^^jH-$i`=|QQcn+l&C=JS9arntu;_X%s=pJBE0X_yPB`(wcm+B zFu>&g-72dJ0MMPOWUAG1w{hAGs`$X}KyG~V!|f3x5tD?9(_ZX00uY>tOM)P@=3hP2 zO^a93-B}zKOh9S{kkSF1(L8P(Y#RoodRZdWJ8Yz-**9KECbooqq<3SrcaTyBqql|h zGF7SPAz6VpEgRg%U;dA0@>%`=c&2>IBZuzrzyF|YtC$8?q;~NJZ>&njsYYEX zBvu~BWL|{%qW;$_)R)YfJ^`C42yv;`Vsea;&}Q*?Q?Gr)ub-w)uz=Y`g7fJtxMUO@ z$JOvbu+IR<%tQLdkLP(hX-ymO=(jSwBB_>Ki@Dg3pH{I|$JP2P#_5ac*rvpDS@4om z*lQ+4rP{qDY+X+^@|>$`2U}fOWXqmtz7cW_h^Z79yU%?^shX9S`lf8-;7cX!uVPVo z27>(yN~8)kOR4QdMqMgZu%19+2cS2)o~dsq3FK|@Mj2HB$5eo**vD;Q%X?k~GPycD zchdgsXuXw7s+NqkTaH_cwZ~?tbJEHQD{%di?ej2xVD|S0G7Fd|;stvlUM(VR3UpEd z!HaiWrY(L_0D4;3&N68YrNZoGkCygA7-0}|H7xb{m zq~@ywko$%9!u`6a?Z?aq>Q}@PJte_ywNWk`2$3h1mU4n_rona+ z@-FYj*4rnT=A=iRo913-is#NMJx{ne6lr07e$+r9;v+TGoswn(^49v?9bR1rqXnyU zS40%<&=ee7P&%g0rpMQ*(~NFPu>}ll+Ql(ByadGy^Xg*l0`By+%TE{nmuK=DQVwP$ zr}L@h5hq7tK!ePI7$LUGhi~}XYeFTr7iEG;{DURmwai%YmenMvqvxT3B=^|O1eFD=#ruZHsZ>e%HOWOQ1t=i3=HX`7wrJ$P9viPY zU>N*^6F5R3RcY$fne<+k)QRK4&I4DDu3XOq;~O>=^HGetep)3rosW8B+4!4~#2x2JUP_W;0u^5z?&@XC zQ-QH=RUito^#Lj7sdv~}b&Z6OSfM@oM3ayr#xuo_YKMeG+2j~*vMH_N144p_NHmS6 zrxlqGOkU}>mW6f)5Bsi0jQp?OR6RVswQJEU0+%4 z#aKj|oP^BV^ekGYv+ni0cH{+=Y+y(*P^c~zbhBGv?+2+7!A%@1Z{W;0lRG5E&l0Ex zDXCH;?Fr*&C_)*aow#!JuHts2ljRNV|7ho%i8I{Bei-*>%*byKbEhx5$>|!JK{h4g zA<-6v%^BD-yJzAd=-6f)@#}i4&D%ocfz{~RUqXRC8cdg62jR9dEuhAFYGOa&bWmz) zv;$?D)j+(u>%-#n51wL209k4v{d13V&nuw)Zg$IT$*rJg$iIOQ*w|fw7L6?FzY~-8 zYxZqVfrgmY9^num+g_W9;V_kw8bPTMWJf1Tz&f_kH&ii^uE2XLI0 zw=+Y>V&A78vIwmAl`CI|S+B)>L(YJA$`amc;?Xa7FbZrUplCClEtC6P%g4*S!=?k> z(^zZoEt9wu!+^$vN<~ZIYzP~tUu&S&7b5quigp`}Of5soBDWm@A`0Xhh+EE`@;c?l zdTf!SfG@Yd?!9$^Pa{_oB(H^10xo~eSHktX^0 zi2pJ_A)*RQlYfkh|2j&oDL9`y&CnzN0KIDGtVTjzcPt)$jN#b|?2&);HJHa>>^tjO zZ}_%dZKFR_s}IxsT8y^N-~-_v6A4GP1fMY!$VcA65-nfAw1WH8um0;tUI86 zVfFC7NiVIYSnY4@`kckts}Uz!n0-#kc9y4R!u3;3U%9v_!&%BiW;DhxV%b626`+Cp zw6dd)Cd`+rmDP){+fluMxf9QK5gTI1sFkNu7~Qv%Q(P7+|CQO+N)^m~i(z2CQtsT4 z>I+8O6UVwZQ3R#+kwp8&+EIUAcSlC(low*-C;uKU4E4^#g}-mS ztmBNWRZU?3OiQjfn#Saw!--h73kAV~C)BQw5d?)072>N`5<}JwkigDjG;*aT+eh4cy9M=hHsQ;;m;WOwRxXlk4 zZL=GR6!cd{KX3e7992%<7?iK-l-CFoxivegPC2GjP^k)?EfJcPp;&3ZlCbM4tgM6e z*OM)0(yZ{)*?~7~xmd><6$+!)`qV@Ted>g$BmDKzS1$TOh{LsCdrO=%lj(@){nb5i z=8flBl1hUW4L^%(7m7+HABcf@8t>=V2%f!rHL7&a`5njEyUDM;Xt3IY#%iTe%xa*9 zxIw$(192z`{$+Igh@uqtFw(!W@fFXlRhO>tj21T{6^+auyM`X@{Z_*O#rPRMl{eiU z8ya69^!uJ@&x6&w6Qh2Rzkk-#%*~rPt}ZqS7LAr1_(%s5_HHC;t>?k_8n{UAj>zBQ z=|HD9DUR|um3p3y_4vhn$Tes-Y>N)ss**YeYptk*DN0}Tw^y3> zvy$t*3M_jOqeUsAR}5)PYtVM~s&L6G*t+-OwbJbx*5Mkq>Pq_2 z`W3ml17QKUR3n9cgX?IE+3n^P;hGrzcY&krP5P&Kqn)$*ui8huPex~#M)C9p0e?n& zr3`vl$NJ3->=nia9~v~;j1AL|4ZRr~$rKVoM@JtsHlZ;S*Ou~$0RW6YV>Tp6jbHOj zvu5M7yyJ7Oz|_O>Mczb$Qru+K_(aV3G~0L=0mIQa_8DSy^;9pWCCzZz+i<0Gd~VEe z&Te8+(=gM2e2cDYfvCuR=bC5_uHgNOVXac$`2DHRf6MwvpmX35=F#I&&1 z-9@G8(<+mHuG4!`sNE!!KmFtE)zgw%rmO?gSJHf@f5yhQ*k=B@PHbjQ>~xyQyG?Mf zPTvZekvg4Gbu*PoHvQ&3!`wWxt2Fhsa|Y>Vs8}+iiZw&J%?dXg=}1rO+t02Vn5poY zDZMb$51-Lxn3H^ArWZ7ASUoH2GixY1V_-kKIX1rWnPbC;V@vHJe~LX>;_&CHsJtSIGCGPex%q+r+Y|SCz6l+oE z*b%hpWrV87;xth(9!598oh5kMrhXiHQVNriO#D#Xs-F)~UBr0}6Z;I~96dkMz5K|1 z7$YiY&FdaRS+h)MgcgLwmW~tKlDL`pFNLPWe%9J3eqj@swhcYrl_g-VM#o{hScfmw z;A-|A#PR2g?S${r(Bv0@x*Sv~*UE{5Q~}twm9rts#d-}xy;^pjQxS;LaE7rP;lt=^ zc30zH%aDHtDK)!~-Bz-aR}^y>o(9>OGZV=WqJ#w_NlA!|3M~!XL5A~g=;4(}UsscP z8;@b|+X+Pe1aTk=3|>TAs;vB?j2(A#LZh71-PXR0$Akm0fm2wc|Dx4eY||>>i(hti za0mRYq6U|fs~0tMp`)+A3=SIa25pp|I+CM31hSVNSKCdxu6wuIFxIRD{#(0=w-j)| zP!8g(5bK)an<(Zr@4{Wbvt>^~;4u{Tihs}N5CZEao_Pkliqmpa5?>6p`1%B&FfF#h zGl)|~d<_&|{DR$h1dn}>g+dCQ8^ohRn0KPfFit3985kf=1p$~hWrwZ$-M^5BxDQzz z9eHnX!fo;Pm{BY)h+#QgJa*C><4drd6p0Sg#VHNjfq%Jz9RS$gN`?>4wKYmDZZ&Kz zHV7NF=S%E-24bYLqv`(k1Oo*lhUw3^Wd9 zMJy1z5h7>h0{`s3=TOsneai{y2wIHsC3Up=to|CvmhnsW0lciDPag%2KAiZQY%?k1 zPpAr<=?oM)8iNi8qOs#bSAo>qV=7tqG8Y%Gpt(LB4vDv){*ZZ!TF?Zv7VR%qEx@7+ z`p~3zkA5n@y1Ov8v>hI;@Ec`4FyWX@!F#p8oKsJYCWGAB7i?9@ z$q#1ZGr9~fY) z0jLKta6*+B$-edk8Qa3=t&$cyXAZqe-AVX!=t9;S^WKnnuyir{rDeJ~$P9^#E;%l2 zh84^G&N$L0YK;{`(E@UqU|rjWv|#jSOmI$c%Ob$V8LvO_DYyA^OD={F9nwS+VkJki zJ5Id#i5T6z_1?|5SCkx5xX)e+y!F8?GPq^7TF!_PKOA18oFKj-V0WPz`C99|(W4)N zi7kd=@^)W0mz~=NCL)FppOpbFM&GR$zjwB7^-%d=FxXP6u7`=UNu=TO_{jU-1(>T~ zg33OU5?rdOV=1)bxk6s4{#)bB1N3sOntosM2?N)iQ*E+|V8D@H9H&)i3o64{#t}bS z3;-2IyCSgxAvm$P3(PC4E07>jU>YWSH{tf)WijziwBR>k4pJUOtbSZlN=U4qc)`)% z?`ncri!zv(_+w|PgQI!BV(MdWlyg64m=&=5R5Zjb1gGY?#_8|SpZX~{ElRxwy1^cr zDF=F6y%2S|v^kaI>%Iq?zb@^e^%kp#G) z=nj%gj2Zxi9{&Xus^McFLmj@+B2buD!7?yCHr?GDC|66s`&e6CS0+l^indF~OO5Eh zA3Qg`sqmSC2r$CS9)et@jwIe%{R+V#i^G4897GSfse#?jud`UCN0SiM-`@4j9(|aG z@PmjC%O1vhbys06V#DJ(x7c|ix!S)i6N?q&$?kFm9v+-a?h%S~GQ@oTNbS#7ST_28 z`y+j@*x=DeMfh0=kcH(1d=0Eis@Km;F^S5$PLn>XgY171^4Dm0n)2puGEBo+KthJ= z7m-Hg4*>0U4iYAro@ynt>Ob|TD|>kO&OptK04YAEX=}0j2@#@336BEqaH<+;Q9%0!NzhcW+7X7 z-Y(upsdy>gPou9C;Aux|h8G$3S7TSlXlVfa5%E$l?(qHrjn)3tMX5<%xs#f~~)Y*RuC4brC$!0{q*>2m+a^8Cxi9s0nfFDlw{10 z^^^|egwtw?K=)^0R;61wM`AAVVY$tw&9Rc4R8uNh8|53Kb~8fuJ|<8T@x)Pb1ZZ7( z*iHe&IejpbBJ?j7F}Xl=lX)KU2(E;(yI8zSqwr3kZvf&-V#=&|tvtud{`(WI{OjZW z_sYLl^KE$6C?v#H&cV+d|7q;qGJ{C_iZtPiS^IzOS*+RZEC0JX|N36_>hCW58>H73 z9Hj0V29^JOKJ9^z9mamgzX!f41Iz_^3&Hm zlifIt?V=`_^?o#)5(zy`!=PM4eS&PvCgV}ru-5H{XTh2q9+`qPDuH)mNh4ivJT23Z ztuRT9f5pqeqydAtTygi-q*g$d&bULv~A3A@JaQNCk{^izwI< zf;M2dDEc^5nO~8`M5Z)|ViM|1&?pWEQkGg}vU#2rMKO@F=9EdK%z{aB3G&r>h`f2ls%D z<7Ci7ErI(6>Noa~)*v|=>aYTJk>1u-3B=n5$e1&e?Dywmu5KIJv{ENyd{cJq$hhjUa!1yRv;g8>9~OqAxP0fwOmxq-qc z9g;P0x46E6``wKEEmwhS%N=xJiSeenI%0epLVxE)PZYQn%%;JW(F_nUer4)J4%|~F zM8bdyS?i}D2 zHweDVBK*SnBD$S%N2_&^8(jEC1iEJX4XGn9#)=^?1ha3Fpn!^pe4k{V zA*N{KN99gRT8d%}xfFc7k{fK?!)01N{S^!0^IVLPo0Wy)W1c)xyyn0)c?VE)u918q z&iCe>3sw^zbkeC!^`^oXw0!ni(J!aFq&s0M;!*WEW=`D0?pC3;cZZ|fWU~IBbe+@1msqJkITcA#4N%cEQ%9SH312>eCzRjN zrX5Q)_D$*u)oRpY-%~ODS5CsB$@ghKwN1~f$hP-X`ncTrk%M{k4k6t2;oiHvg>EC+ z?w{iXU(4GcffK%Rny9Fd%*`9y{)1<5f)K&UePNg9rr>(r77(8Igz%(Pl= zQ-8Rva&>DjYy1hnqyN#7OAHi%DiWuFyr3+QJ^Sh)u;Y9O1cm0N*ZH*Gv%+Bj;83ct?I?E_wW0qCgG`(2Ch6b z$PXm_uJT4W<;Btdu`vqG@oQTAfZ_v)awIt=$MExX>z%*roG^FvFA^#Mp0bWe**Jro zD-$atjWSG9tQlf;u{EUIrNpPzUlsCE#r$W)KC{Qi=UU0({}R8IZK zyno{;>OW{&+AA(P8K-~s`3(%CzbIMT-85kmyV1Ix#(-U~P+P8c z;$kgz@}#i!IQ~@DmQSNH#XP^Eviz^x=^lDnkxpBpc-Nkp+ zGBt1B(XzjqZSbVmc}%Yxp9wKoz4J^XjzzPnE>quaq?8w9FqLVzo@w-*zDcCd7@C#e z!=ttTNjhQRE>z8Q>Vv7)oSBolxxKlCkGW;I`A~e;+PxKP`5c@2thiPNyYVQysVw^- z^m7Qa<2i#QV4?Mk_b{H@hAZ1yp3%2@K^ke{W?~WcoFlJiUXH7=tnn~K9Y5p^Nq&SRV25H2Y~usTN?mAwWPVQtEyTk=GWZdAH)V(<3K!C zZ$n_n+QmZzse{LXMEd^96(hqopYMDr9bf}Zuc#ODXtDv)-k>lx%-1hBi}v#gG%u#y zh^8agrlnVBv{rSqW>sNt$nts#J;Y>oYfpJy4Oh*KzM1tZxoVP`7nN3++$&X^wbu#L z)J=-{^g2dQeBkp3&9!H9c{XDqp2)Xk5VEFdnXo>oR(rc~bNkgfL)HO@(>cd-p(Zi2 zFmA{$rB1+cd7!vXh~4}bbK)F3K-#tc8y9e6ZHOtZPpy~t-gCyFK}QR1;m##f%fYL( zBNo#1q>>Ee9YB{f%j(XL<{Pi!y@i)vrP>Sk^#mT-yIHNdb;@|lj8~Eey{cYIsF5PA zU4-eP?l=p*LfT=Z%!r2dG?bdy)lk?TZGl9?UDr?+1BI$l3%Y(^(kpBdWfBGOQZsX} z(kCVZH%AipWjI6>Un>T%woXFA)Z{3zcD?TQ6Fa^0JEPj7q61agBt}388sL4gF{?Ys z;YiAF`Gf0iI+Z93%34t*y1{_&l29@tBocW?bR~s$Zo-EGB#M`TT!zJl42pi1pRwmL+{HgllwHsH!Q4%83`jS+ix*RBBM!TTR~K!SCS?d1a0dW(Tlhjtz*rEocquK8F4EPG$?x6CsAbg4)4O478~Tu9^-$k3&_Fie>pV{ zz!-Oo;yhJjk|yDiHl=I6yWnT4(|oBSZKmIBzAI*)b7bxpbmD$`5-4qzQ)Bt`6d-u2 zlVV<^1Z`3wO*yixE2THJrM#P^?Oc564{g`ygI2&|j=!6)huY|&Du*LqG*gQcr;M}4 z0eZ&QS+>PRwZ-YyuLE@%H_H~c;1bu`LpQ%G8IRyc?v=hCxE4>vUml5vp08xQu7d92 zYH0ZYP7)#+klg zN^erAox1c%lu(>0$gWqwGfG^9DD7bbx6-kczqwze$m0jma#=GE=e(Xgp|Lm(Q*U7u=CII`S#OFv>uvI<-0t%LmXWjFbmsp5- zd%fSS{^aNkfoaRlc>FwH{`2j34~XM|t+oV#^Ptqn0i&&nj_ z3>@GkPkLKiz!g($3fT8h1>4M=nqA_M$7x_ zp*WM0Gj(SlYiBQ$Ot0)guWF~GY*?qd(!f=9f8YI1%g(_`MtjGw%>6%uxXznh%|oM} z)`?0Z_wNklD2=}B9DT1eHq<#bsWiUOIsRE`Vz+bRNNMtS=j2rYYy~xagniTu!qr6p4vLXi?fPyf6KWyKi%(z+e&n1F zH6)}a#mAygDQ1N6(N9qrzAd7|Ox=BcWya~K&ofah%71yrSnmY&o)bVhDXDjuPnhND zH;?Mf(xxCNb%}y0a3-Dvebp<`kEGC5*|zmG>ja%20O|h^GZhs;3ipE*#i{u+?P!|D zj(#X}|5IMed4?Njjb)G6ki^WXc2mp$qr!G?$*ONpE^QTCux#u6;7NlOJo}z8X>Rrp z%pAG@wn45l5`^sIOrqXc=>B%Zbx5o-n=$zx&Qy@g$^T?(eAk5b!PDtBIk+bcaBV0Lw_D>P7g^g_z3fFPKsO;)D# z(gr!@Tu=Ih@Imn%4BIYDF_Jc;OF*b&DWt`YT||nXJtn8hy=W|qloPvgu<8n&>gZNl z*1ONK(q17gYaM%H85Fa&WqQeE$>_Tq2!2 zKc7UJr#vl4GzwPuNj4Uxg(poMMo3S8FO`Tv@|>}871Ag7R=SX*NZyF!)b?dz{asE7 z*h(t-!awH6Lv9fMBvu;q(mTI}InrWDqvOqFC|ODS;|uq~ZQUbhQuj!qW3uc!^Mcb| z_t1uq2-@$H8w!Cq@@S&^a(B)8W&iLCE27^B*aEFMhKbp+Aut{-b_CE}beL$_tUJ*V3iD*%KF~82W{ub*MoellM!i zfo4dF0&$D?bzJE8vb4b`#r)-k&g)^CslrVN@;UUGEmxp=86zkKuf#?8s4q9FfWFdC<%}Wl?-q8n+peQ zw!0&Nv<0H$cZn3ijefLmd7f)(g?7@FjN?3 zdMCJ7;1fl@36Uc_>nd()SG|K#&egD0MK_Gw?bWYchO6wwv{oXYuby)2r2E%< zUhi+gq+uKq0w}ElZ+T4@mdM5KQW0Ltl)>jWnC3NJ`bQLQrHgk~Br>1U4DlJHEE*(| zlS_Cev00|vA$B^OJm4>dNX@+p7V3HqWwie&BwxZg637uW{GR@qjl8@EQn-XcaQSHj zPov!>u1{j+ay|35puNjjcfFJ82G7$VM}&<)Y1NM zvTNk$`GWt~zdu%=ypG=a{95+IuM?-a{deDwu3I5M79@(>~k z_f8jyDGtlnOoT}5*MW6$ahzvF((P;w+6-JgznnAKO_(NQEiQptb(O-IMT>R3K0?@M zl`1e#OL-EPq;_q2$Fd0b?BJ4h!pA3lr_a#uX;wZ5kzbZ|g#E$O>{7YX zQ>3t=74|s0CS+6edalfBoDR!|*%S|a{!x!JmP)H+Q!+RL>;TWo>;FT~RDb=;L50WlWcG4b`wdbxbIb+en*G%zFU)u!CG(&Tf~(Ar=}XwbIGD(mgY z@vMUL=54i;=eMH|vtIu_+r9;4L&d_g@euhP4cZr|1c~fIs?Z&+K~2zO)9fO~mK`1G z6tgDR>|)LqA#Dvd^URFw5`OtzgRV8RoZ7P@SLa=$yD8?+GZ@M^@^?)FUs&YlmX@iF zU2}}&pY!48c#U*nv#ZP(me*Io%1!uzsQhn}rKX{;&GJ1QL_}_w0+fvTWj0VQKyG+bb2=TJh z9~+Y3xyc9xkh;ZaaWb}v^i|4jmXREw$ObL7UmLJqCXa+Xr~xb~WFw~VbWmMn(D9uI zRP063kweq_tL55w0!J#9k^e*(To-05&Tt@sPTgZR&7PGX?#(~Q`b*+SAB7|(o$n^~LM*MMl!9*P1O53a>}q4q#Qs@Pz~Y-%sR-*- zUm&kcU=#q}d0wa*SE`Gf{Uk{_3B?zyLx^K6rPwPB&CzueOqW3FMvP;z_Gqx}l!CiN zaFZ1T4cK4)W|Xahd~z}{M4b9?0*=wtKDj5lf{UIrTtxPUiSBzGC4dHHJ`N*koIfzM4jbyq z0NOd-g`T{>#UK?+8fFC2e)I67^KKBeU58cU$d@vvbIA|AY3}!29~Mcs0TN@<+^-Kk zhHjs0DOfJ%P(+3gGw5h2ezTvHnOG-ZPzMSY0p}^sikSV7Vp)O@fS#{-+WEG^tWU%z zWg_Cf(tY^RdV}ILLCd!2dJCOBf<0LK_tqdI;_|kda&X1~l0-7`yie2=n|hz4SO`tZ zYiG2-6Sg^cD>^W(`|szU=-ARbkG%9gtOU%NW`mj#3DD|{$ERf+`;J}T%zm=@RcL;> zI#?C>vHXed?%OPWu%qsK?J83&MH`@%6R84Ilmastzat505%+6QjfEork?>1}=K|3f z>J0#x`xr@5whv?#`$v-aLx`NvO?pGTcO8Mm%3X%dt1aE+8brjBqiE^Fa6DhUq;%O! z3dvVDiaFolcVI$hjx_eyEwSUc)997QKKH{oPEY|yk0hz?sz!KKUPkELL_T?X`>g*R zzMQ4+MjZF32DIq%z0O_clT=NOkOPG7@Ta?(uGPRL&Xv0=1KsSM zfZRRS|AP8c`xEd8cpElKi}2Y_;r{Y(?7B(>83K|gcC#8HnkEW4+G6+g3B6Yn+&9L% z*Aexs9Y;k-;+mKb*pZvr4*qxG=u(QCT@JsQ;ZIX}^ouX$AAs=B&~$KWkNpfdEihus zfcuBp{-=+>nY_xl5zA=_gc{bi z2QKgp3ECsYOtD-7oE!*HZ=gX?0($V&gafE)19b;_v3wkY1&j)P1CN_Pk#!_7X~)_} z0ChYmUme5}358LU>f(@IjP<-pe$ zu|HgqG%P18en<4x0Pq@Md<4%);D%3%v3<(`UpKZD2m&i$@LSX69VF*08r(;qo@?OL zK(D3{m^&o7Hcz+)V*LYvOA;mYTogAJs=-AR;20E?y_@tMs)!BG8i0ZrlK_M;^oPe_ zF(5}a2K;LLZv=wAqx;~GH`u8mHth=hd2PFe? zh`=F>Kz8z2+FcAsHDGQCxHN&Jc?$Rmt{1%H!`nRuaCSz#j3sF@;rTLJM2BLk^Sbf- z3HtwlIa4dmhfP_ z)G=QQWq|WH7b-Tfi48)=Xh!v6-yz+w*uIR}q4J4h_ZnHbsS*Tu>_;Tq0058(%BQ~I z4PujG8i^{J*8qPM066$1(boK(R2W0zg?|5F{G%cMqX(f~!XNZvlFffl#R#nmBrZ3L z6$^bnY25qD;!HZ+@C}JK0bt-$s}nIkLJEz*T5cL$3{BdqdbGwbIg*2aCci3a=+k1s zJA{c6`Z_=%8=Q{Y4NqN-fL@{537;#8JvZz?pC%XTB?yJ_{&R5>5zx7H`X zN9>uBY|{tt9G@2b`3P{XX~y`j@fngBB1w>z+D5gYa_(KR!0n6G&g0l0$0uA7K0i-I zzObA%+KqP=g;0DQr%U%kgf?y3pNWZn(~P)%{B1%&BIK7`Hh3Dt!D7bGIH|*YqTU-) zl+6InYaRz66sqHBCyfDdHF}eNTtL9+A5kFbG{-O6ftYH!o}i*j6J^HiKT@%`3#Zs) zj^9;^iWH}|8dlN&nzs00Dt481^J~h1lvzvs_cmQoo6IJg)+(jF1k z8@-^#+SY@&?}?9Wr`=wTAd+_n%a&!VYNBx<8B3YQs^@phkSaRsrxIG8C0SR<8S`FSFUqs} zC_(^71={g{1CLtR-@-#&k>7E&x8#Xx>#}VM7<(YvjZd(&>8Ld00@xG52ySU7t~ z1)fIE)e}7p5rWx9UthI2|D~w+0+Clc(;6UjBaEzqiyB^VGZfS+iqa0P{8-By6IB=s zvC0@PX?puYgU)qY^q&dcs7+CIZ!mvKS6VZ``K^EkxBfZ<+i^Ih0S2VI3VWcimtnpf zGQDsGAewaV&&sTF^i3MeM|ghD9`=xeQljL@0!Ve_sf4{q6zCIp^PCNeY86m=@=|pJ z{tDeLBp>TG)-Y75A3e4+=>iPO&fXXMbl@YN+%UWd`l-!S(LFl0~Ddmk__ z4^fAIpg)L^ZnMYOCA30Enn+lP>J`uK@*A=~mwx(q&Y*bij1KcaC1qgx;Ikdv`Dkog zpSzvqjLL-&^?@ht+?d1KS=WHDy;QbwD5sU~oZCWM&tOD3Rd^njQzRiPay`>%c<6Jr zTYHEod#5Quc*VLDVM&{?_f@~}kATQ$0TBUtE50ZEdb`~8l6*923+vjgLo&@QjkWQ0 zKlHet_O0}i$#1H^)X0DGoYrCv+S~ZNn|p1XsW=Ug$e%KB7_yCho}8LH_AOJkRANtV z4z(To)xP)9$J3u1$D*eVj`s*UU+GG6{FpQ~0x|D4mumg?_BrqREi{^1?bUvh(;>st z?7xbnTb}=NcHPHTd=lM0eA#C6K83*_&y(H)F1OgN4HlCl0?Ld@L(n9~gyU?pGryC; zvYEBf&r8Zgs047i+%%4B{N8uiCBK2~3(0<;y*uS*-oT0L;i=|V#o29N)3Q)s`sDV1 z64gc0Y8R3zSG&X*_v19kJg8JM#oh>iqnH4)&J&@zM7lrqy!>~e_J!>%DlI?^olq@U zE|}`Dk)1~6RX_F5#zL?wf$k7EuY9TyiJ^`#cU5AiKsGVK$5N=Nnxl(Fhwv_PfSj2z z>gm`IiFjrZx3H=y#Gc!$=6#a``>`(FY?MTc&2PP%t>k3D8(}7lX3h@6Um7*2T7!dH z1qs^$8YB|6WD5$wRhd@Wj+-M@jejSU0wnI;{Jgm{x`iD3aF7ch={CDEB2pA&ypxRG z5HO_l0fFBl+49C?<#TvN%fMw{Sdm0{A1(M8%{6-o6(3P}lkw7f_U1iX%sESA%wic< zc{s71zPF*@mG~ryak5s{!)B5K^X){P01m;lLhFuA86&FBRW*)Cut)R@y|>%>ALZ?s?V+QDeoFIf2lr zzw2<~yCygQ^ThwKZo_?oZeD;D+I^u*PahUr1djRo_Y2BufE~nRhO$;dvQ5DcraK#KKQC&SI1i)NF@ERHp%5Hrm#O)ElMme=x#0mYyYfR zqiS$ht3n}KP6B2BiV;soi^f|Ss?@jA{_TZtyn*+#B!B#({`Mh8Jxm7p8-3|~cI-e8Hsd!3pDCa2?4i__LBFcQ$f5_&}O^>tOu zca_(>kW{gPv3B&_JIB}GUytq4)$jGLElgz5{4=}*ym7(lH2P{KUG~xOjr7j>f;x!| zOJ9@Pj2J~h>+&4+Uz;vJC$Ya)MRrmP4uMEg)9!X-#iMeCok!}WofN;dViucUi&+xs z*}BAq3?Y`z7o^%P6IhT};osjbe^0u^f|j;`APgZEOu?p_9DsP7z;{iqfzO{r;X?3K#H ze@ixvu4zTSSoL(}mTxoFci+cQsl44^X!icnnXb}$xZLsZ_dhDtw&T^lRQ6jLs_#xW zM_;Nuf~&Qk?M_$S?aEMle{rz%{^1o|z2ov^mICfycrPJqi=KQap-MBt|@nGt|jFOP-nRzw2Dnmv8b>5P`ZSHJQ}{MdnHu@F z%sQs}zFqXI@)4$4irFh`C1$)VYuhL=Dtot6`@6J#zq6|J{qgwS(vGv0_az@L55E+5 z{`qHA+y$VnE+T+$a1?dJB-09eC~xiKdueXJ!uK&a8RPp|{QtZj;CNL1dJyr%{`C;w zYs%NdLUkPlBVt`kcaZ;I!0>;`-bX+H>L+sG)f92|_D{!=SNbI7`T)(th~AWN#w`F6qXOf1jhi3yWW87Z57}$d-n{#{+@i{Hti5G#uKwP~0+VYR zYREF^>p%^AaJKZl@jTZ5P4+sCcf9+)JyCpX4%;8(-#HilW6Y#qIlTXC%YVt<$b+TN zB(O&-QV&1cmHaQV_cMFd*OmOM)gP6>^4ESKCTsLVm_RuZdvGUk@cM`7KV+|FL=xjN z=MeG1f>q+z|B$^r$s}POE=jQmtuGQEM-RHB2>A)Er^(GK+Oackm9KM$3&r-Ql7klJ zleL*VH!|HL=Ds>gmtDTVi7P0vWGh&EZsyoR6ZxAIl#c3ZY-RZxY8wS8l%Q{2W?(LB?)>deiWHtU7WS{iqK z^aDu(1_-83Cc5OwBM~5~*Y4XS;0g6(5N&qn?q16b*UP&MQrh)=`L@$|0By<^*_)6D zXK}ZA`eQRwZuP-_*t2zSSdbTT7f9>h{Cn2)Nj4F&yLJumHr-Yk{4;dT4$D=E=f|-$ z*)YC}$U3N-vo;Ezr;8r0UL87Sd?TajdAB7uPe?M6EK^23(-8>lm?N$kvwKQ?dThlBDLm z?RBxRXfQu?>)+*f3ZCSW(@(elc5sd^1^=)xh}R6iV(xFE?V#Cj!@F?DgUUd}IFRI7 zJqYY+ow{6-JU}Mcy0S%vSzVvneb!>h5e~i41>+r+EdbxN=l#24^onKgvezUeaKCA6 z|8i~H@7AB@(~nYriGCa5W;8*j+>Z0HAD^LY>Gey5gAe2x&6WZUOOhNvh7R!>6&eqH zN1G*x{wA*LkNkFy)xIhIXis30^*XRy9GD!O!sw}QLHRsdV8r44jTGDSAYOpm8&ep7R^eM%W&2>|)4tXmbD(msiG7c<_2|9evb0K7$<%@L`VV$v9 z#R9>1@(wtyELpoEpSRQx%Iny<Pz&(yxT1i=-)v)^<#DR$b{?_(w49>2<6r|+5URnw$Ri`nR<&m9t0nRiQ6{A0;=mk$oW22xkD>7^Yd zYuy~thZ1+Q-@Ke4eUGR4tPm2%+#4@8Gn5!R{0nw^%~@Z#x#<26{XBuh1;`sT6%tm=ae(S~>_MdYNAa@OAO@eclO!GqH-;(3B{+UxZ4tr&>xW@r#@RPd z%sYD6_qWUL2^_QKee>ky z+Q;oXPDrJlD95=26ps`)CgC9-=^t9bz4Ckw%zXRO@56Be5sw=Z#WCXKzUV<-0N&{G z*J&@}-$(y_RI*SWkS2lnk$z)>w5q0!Iyx^7jahj6h!DVDRSsS<&ot(el+|2#2-?`_ z<5M}mRveD4;Y@mQ9DvO0gAhT5hk0?Xksrxuk^xjtLj~`eX3JCBa!_Kaw7D8)RDh~E0dlc%#8 z6&h}PDo@*~=NRv&Kh|FlkbAKk-tS$~2j~?zr6s?{7Ad|c4)RHyq-n=H)BnyJ;KQ1E z%I)sL4 zTR5N8PF{V}kq7>1$X4(twwR`KiucB|>j)u=n`-NLnrpETuB*NJY4-cQ}vqBfB0 z5w1s!4~m&<=gwY^1pPb=BsVsG`LN|ZsSp4Otytb~%^FSW-p_>QZuZb^K8&wZC8Jml zsNp=lBKU^_>ywTH2g&nx4*Kk4sfTORz5*`8d*8Am&2f*6IiS)hw%L$||PLi~blo%1$XL)3{Wck7_ve5xQ?a$*j9(If#4$ z2OgU`CQAA&_qCKu%`YP0DBK>!5mf_`)E}adS4d-WjAaFPGfPHjz)7F=a?~HkGm%2` z9%yM(v}TUpyI@I_IdgiI-Y?*3`kY*q55!b2hP`nP0L@hkG&dU81Z^b-YkO?x*B?Ka zx{(2^e)F7piIwZ~v+wVu^ev7nr2eSze=M+Ia^cG#*4FX;Me&(ML{DupxfZ+1m>;8e z)$*A(COov6_CmS$ZF;Y@-+VVbInUEd)Zw#bcte#F?1GiWAtf`{%u(X}kvv2X!vl~DL=Twn)5=64nsipIZP2jYQ%`4J)E7`RXqsZKfjD=C@Q zezM;r02`Yi0A4TS+9lgDP`ixV4tDs`6D$J*G}9?qBth_|Tsb{vB za5_1@+pHY`z>uC1g2=U#ABKeD!2sJ}v=R%I86F)(3V<`g+WM1^q{!9~Mh_bx-(@4- z!dwjN!D*zj!c0(pcL>fVGJ*~&17*pxAxodo6e!UY+t5@O(DGS>Jc0_E(1=YIJR`UT1Ot*%zavV;2xWS+e zAc92^51)S)>?j;(E+Y-x;7NakTUtmLtG*^q{A-Tb!@aU={nsQ~CY9d zUq%S3O(-Elz_w*>O7-|h4W1Ojy69}(djM!TikvwTD%ek@^B6}agiS&`>f`q6yvWL` z_g#Dje@O`ZLPMS&26d~44+oHjqe*cH%llGfRrORkWu$!k5V%m5$BfwxE8j~gGTjHH zi3mxpeyjZ;9S6Q_GL|x@7e#x6*1YA9c!W5(-ts~CM8C~)x?9I@v)b2j#S-=AvZQqk!ONgi3W0{FS{xCdV?-XE>3W8oB67p zx$9X*U>FL3cwODfJVxsDmo3^zu8@*Fsw=5p?d+i~(bx~lf*8SdqjY-}R6HlVghh1! zV9cFIEOwa9HLW;Az0BUaV|uSnaD1B*e{j^A=f3=kmZ8Wd=Eq+A5!nx_ z8d#Zs^n;{E5vV0Ii&VI*BMBrqz9Q%uWJ62AYF}eCi{*q-+0k+|6Z-sz5IEyNE1Y4kw@f!E%(4# zZ9p%-9oD3eM{x%1HgZJ=i7ZP$eeU2sOA>jJzPHQFPVVkOhh_~Bq)4sL5AF=fef{$D z!btpVzp5dv4+{mn(i#X>c`4eJ_5FF)OnFD4m z7TI1ZKP`SdP20E+RaT)YtS1-ahprPS3Tdngq27(T*C>lij8 z4QW7KFC5^gD+0WZ0$|Kb4nUJmVII@-CRuM+H;9ncRt`81q1l_ExACx3qI{TkfcY`` z1`!Hxr1Zl;+X4b|_mb}tD&JI+&1Kc*5|P7k`;?nhS4<2@#^qPx3JTab{V}_KPKFrKus%qHi`Q!w3;{0NAC-Kui z_S+XxCcj|XlvXB(8zeV=7v402-zQrCL6IYTb$%!L_>^W*?&YU zNZz^1wm6lddR!0bJ+^2NutW#=e9)!5nc8fr+-SpR{ns3xt5E!VO7b^~%0wl*HxoKC zA!)2KW725&Z0KFljGZW_Uu1buNsxl>+mUdbJ*U+>dPGPk9zlae|%& z4#&H~v0N6N*^HhLT-Ib3s zRTJ2GFsKVMzNJ>n`5SRy&+SQs9CH{@9xxNym1fLBzkm3U7c2NoclPM>SOz zCT7>+DHKJ^$g0HA@f7-(mE1?2{z)RFz}()I$SAQ2S6_XZxH{xal>1F&HeKcD%ti-r zB%LfI(GsK0IAf$_0i|zy{(k+gR*waK+JEja#BQ*-zb0tPB2>U~Zn7@QW$^C<^QTSb zPLtu}z;&JhW@E>V_=c|{F)Nk@9lvW>Y+^+Jny~gyLT89%vXx}PU`~S^GxUwqyL*O;NXVX;;w`Bkr|02fltn=v4%F#awwwWIAM|9jz31NsNviCql0R+iZ+xJCQM@>RMd2&&{UeD|n zj<05^(M-8oIaySXyV(oxBSI|=jv@XMq_4$A1ZpZ9q=HwNU1{bLu2xTHfGa0f(&Sh< zmj%|lh{TvchZwVggjIvs4mO1wtlHlzgI}@~@0^%kowR;w2t$QcS>=#EITd!=%TZmf zh<*EL(m84MJRYVH14NR$wC8vGNq%CE0K9Xdk4QKH7vM!|0?%;H7hbPep8r0Ja@w~I zc>l$v^`XzX;FF^}b?0wJP93h$@0yJg3;mk(FXXWorIQz20%r-drlFEQvon7rcfXIm z@vHaWkM9Id7l1-wyX@iwe0Rd%GJv#)hT2U$sP5L>0fzFej?~TsdDekY5Y${qnvWOcp{#2SSRm&l4iKT~?^O(3$IPu3DBz?d?V zSJxMAQUsqY3&%OfNB_>PW>n(8Em7z_IA$^=JaVxaw+$oE;ZI5u!Kj2M3XwnBY94D2 zed$*FF19kMP=Ad9uLbGGiq>%|`-?AN-&-;*u*KP{(e+|=QY38wZ!zyJd^!4C^*$%i zS&MBjKa2;SuQ#$3GFLn`5u_UOWDMaB;BQm2|`D z-=9O<?b$P=;ldP zUpiiEF}?`_q~Bhu=Yc<5!nrWr1K`#e{g*s=jOhQ$cu3n80Y1;j7u3gdeWR=dJ=IJ* z(`gc?>?=yKkt;UqV-Nl#DJbJQRit4DB=Fw2zoaPhLa3+DGS$YqWk#CI;BTfxa6ud> z-#!L{k^QvhQC=C$0>l$;JtgwJxc%mo_hs&{5M!=@tqINBVu);<<_?T|id-ciQ>IS{ zM}n^m*N=3ei2!k%8MVgqP3iOPYALzp*toxQd*+m=iM_*_3RdvOM*nZ z#Mj-~*5T{@hDmE|2B)fD@Xd^*KU=!*sK8R+LYcHUZ@#TxL5wrpOZ$=A7x3_5x}w1; zB9aPp;9*WF^6zqxx=|ag?o_2JSU$E3?m7SY_I*obU4c{3i-t~nMqY1J)(eIbV29wS zuRC=-8qBY#L2f;PmFoR(Ac|(}>Q-1LR%TH;BPg4fs~?YLJVdc2qPxlC>OYtNp?^Y` zfx3~;!lt|bXg!A%}q^-U_J}b6?GqGcpXgl-aQ?;~M zZ8=Y#95-di5HGLi9U~4^=D|3QJ?cre6^-NiTwKT@V$xhoo$@hUAm?KS0C0mTkA_>U z1jrWt#rXaN^9x;8zOdFS3qIa$G)>DNS_MarO<_lt-U@qKlNPYxz>C=zb3Q=MGT}j% zE7|v|rKi{*efQPR|3mf$t6CD`wpH6?Y2X3)+r&NEIb%Dlk@@j$Y9>obqrVNEZ$=&lE@$FQC9{G$NHn<1Js)jF(QhWGb(MXm#%faA*`o2 z!bxB0n4fjxu{l_c7512y*$jJuQ_dHVeim}ap5gDQkRjPQ;{6`mgH0GfULSrhmNG$` zGih#DN7UG01R+`X00+OPQBj+)Z?ViY({fBDw!I$kq0)ePT|TcH=xt!rWx&aor$xt2 zMlbd2YEH*sAZ)Pr-t@!0xA*cub0anbLAfm)8kjY~cK|OEm8b5BM*BUbPbNM|?Q1{; zc3j|=d6uPOg&7Qmh0KiIJBAjD+sa>AsXSXI2NBs6Vv79_Y+F>vIWbHQDp#CL&7hADE=mml)2+L#U#%a-2+o(!p@Ls(gSRrubq;W;<_q4Z=DcQ@w=&t9!%kZ-b9*C^3V^EaHQF?IhI_nX$j0J`3n*vI4q@6 z$&_%n(qQoHo@Y&%+MX3kTd7kz-C&|vsvM`MewbZNcx4z~H>Bq@JsxHTsydlV-A|L| z`BqO|`lK7WVQutG-+4zzq>EPHau#qo{Tc!YXBoU%Z$|E}k0qNzCJNd~Q&z=Z4IgRx zk|>`As`TdNuin9_C_D`$^N&8^D&GFgIXj+VFV~%tN}_5`C`^tLY-D@qDs(nb`-Lrlt@Bo@&z&W1Pv84As=%~R5 z9eN?)Aq>M%ET29=5fr066z_`Rz08QS97@X8OB5S=Tr!l>Japwe4M?6Iig1lR92!ao z8$fr4Qu&6T$_;0^Y9&z`;IfBssNtOHp%+jOf7L zm>;~+2Fjx}dU;LuhJ0)dd0rQyS6gCKYi~dWBYWqgCsCgCK;I~G^bn~ZLN#`;FLqQEf;rRh;G4*qWbq*Q})O&ApaYw7K3#Uej#lVff97v&{yiEf!v&*`1i zp7s=WBjaZW{qTHLOg_2rz_`(t6lKWCpa zLwo-DQR$3Nf;m#nHrChNL1!*G$2Qx*?CM9knc=cwxP$4v{6X`48|idC_pwjEmc}y< zrX#bhy&a}w_-$1gr|*2Q%JsF3PoIf(m}fKAcok}EpFdx4zVNf$HZ|WeqU57&!d#BF z*+{cZW3|bR=pmodshkfB&u2bA>z@spneum-E^k>#x=wQcwbuW$(7^xY95YXRptUPE zRtzzqs@L9)pZF6sNXWjM9P-6;-=dDwwn~2T<-wwVvt9KjDq$Jkxp|jy=8l^9mwq>U zS4;bwuLdGUV_TLD+3*(Wu0W$RGl|RCj|qb}gznTwX;~l4RUM#wtt?~D=L8+h;x;n~ zCJUWv%LAp$iw;&PIvV`Qp|lMLC~o{Jy{~5rY#1YEpP#RF6tG|enLH&}Z!GtJQMzF(BLSOaDs42ggFrvDX|c(b)!Z$16wqgU2S%ccLB6LYKayzMd_w>?b&ViT-+l zc6h`8HC%4=p#JM`-@!j_&j%Tw{DjywL5P>~=%3=mXRJ>DvT-Tt8uw1k+13K3X z#B}L^p9U~_pvC%#q!$kG0wS$|vwJm>PIr~T+IeZSpD}Ee*>A}!ewDS%IV8vV&;(76 zou{u|<+^Y_MZ_YQ*0=@Mcof!nb=UZ;*ZAGn1j5#?WmBQtHQ}-~k+*B218ZWlYvNmL z5*KTd&~+)Mb!mZh8HIIO-SsOu$F(R!-3>Pf^-%k2-{+iuUvIo!SDCF*$XI^`A>t3d z4-zn6oVdk!%$x@QThWB4P0@Yl1&b@6Dr*BlsTC*FM(Ni;wu5_yU zWH0~_he-%oz9r#!yR%pCt?q2TYY^x1O-H96oEr(t8+U7$O`-Faof1qij1zRrX?#f} z;a)?6gH?fR7nPY;zImOMx#8A5;J=M6hp);5o8Bu{iSo7;oEm2EE&p7Ai-3cp`hc5+ zZD6z;eW`0#solBB9V^PaKDpbEqZh2w7u5vVoc?V^b8Rt%f&>5_f|n>k7A%tokdtP| zarB9REod5ITk9|-NCUpwZM;L>h*idCD9a_>-$`)XNjLCHxL*#5dagwvfe7ZsJF=*V z(4=PgmF&sv3eWFkaHSy%`7#AO>)S}|aV(aK3hM86GgshQa55Qu;=(FP#^KH_Fq4kr zZni**NE^7c06O<#yQ))nJ?D-eS2^$H77!B`&4l62s}*3u3MwY^qd`T1SWXs@AYzBx zW3$`=#Objd61LI7DI`!1;i~i~iUgU^=oEQ2<<|Gf(X_eKwJ8~sI2>aQF2N?Yy9)a4 z*Y~pN`6dU#;(7+|^mS5wf~N8(LvFT_IOKs-fPhcdK8fBwcF#d~h>)6OkUH9{-eZ3- zW4}Bj;g*!hTBS&Cm84-zSED(*hi$QWFQdj{s3Wy?##zlI{B?%24Xq<4H~I#|s*D6oi^5PW26zJ0P)+tX-)t zShPLZyC)!y!0f+TxQzJ=+tS2XYEsFnM^i`C(Zf zVTI5jPE4RHvj+ebuAVozQp*A03 zj|A8GG7*}6Z|*qhql8x$Wt|rkQXV0~$q9PN-^m5ZWfViw%p)YlBKo*VuB4BCeDU~! z@jv6k_^aLz$F!EkKDx9Mj);0fSHTqQh)NI!N5=W5e;;SLv$JE1NEl&1fBvIiC;%&s zK7TbADGqylJxX}zb?D|lHz2?)F$2pVamYKEvkkvEKn2-Hg4n*EGY(!PZeimd4V#E_+M@*ZWXPTS7$+{w6)xG&CmdVcy5~fS zVND9G2mE%t`pb#`<mE?m*UjZx@z{;g1pm~7M@K5f5MXloD#}}CRTEpGq0^xGyHwY_+Tnjl zyYHZ;{=L!nE0qv>?+|+Ey%!TgZ-UZAq<1vZr5OlKDFPy*B8FZBrFStD5drBUB4X%8 zKtx2Wx%~Ft^UQOeduHxEbN1hOu%PU45w>oERKb@k=~nyQ}&LSAP`4c zF=mT$Jw?a6GFu#SsU}OgYk7d`4+P6OO(~T5s7Hy|ZEG+fj^_&yHqH0)_jJB{DM_-J zM=4aQDlUaL23>kXx(i*FrM+;Qlb`p6K>`Rdd8tCSMb*R7x3vLf$lK=g@lx702kbPy zSOIZk0ZSCtTG9*8)num3I-O27Oj$(CD6)D-w9?z9h)h(M+RBzipUb#~0pm>+x(v-G z1$qv4)>|T54#$);nR6Ec+TLuuZ)Lv+f7d)`93%!9V%}S{o0EN=Ek;?&<=U5$=w^Cd z{HI4%dYmGkqDmZWkz3Nx5&)Tq#vbi?15HiC+yJ-I}viH{? zjW>>>5l{&CslW>f24i5WSvuCqtGO2XTI+W%By_HZDJDpA7RLVVT(>0%YP~Ld;MTcy z_a*y_976YB?cK&v{aD+i$5Gn*ou7JjK0o{QS7)Pz?u_o27kp>B_qIUKb-%vWq3Ryl z@t@KA_U6L%_|jL8xK7F5Sn9K{^BHIKf2=?FEa;n7{apXoUiZ_#0uF>TXa1a?5$pel z>{V+0q7Oi#({<1#VaGZsFk46WU!{gb#yHAB1By)Bb(Z#dP(MC`25sZqIwU`5d zF)!yT^FL&7wZTo>hLoM|MLKr^2%(FC7CZ()4KWaA<8d11lVyk-p!L%T1ZSz{=L{qf zgbNfCW#UBG%g`^58=n5J!KyS);tgH`Iwa2`s+}f%TARWC&#V z4Sz$i{@fc@x)gSZ)G(GcRPqzfP;{i@hH_=8&|}P?#P2Vr@`n(Xy~q5FMmgHjYlO7y z*j31Hw3d)A1(Ern4BhQGsB0~MBjmRzm`-Z+lVOeZs&1z@FfSVQ&vE5WK}Fd26Y)PO*MKb#z+ znIv4b(v*Y?9*hku&D?WBudqBM5w1)(JiM~FsqKeJi5fq+UsG4ah2~00ju}VN`fU9U zl$yG6PBqe&n<#)}fvEtewU!*5&=_o5e+(qiKDcs5x--k(&NHJ1v0;&RZRnLA=vMw$ zccvIZqUeTuw(6FcRKS5%uz^ax7TH5zm>I-NIUo+%Ni&S|SVt7(Jy_oDWE$mv*9H8VxQww zA8T8K%}fyKJ_V+NC`Gv)8040s(zLwA$`v|&cvysAg+RaFRjgkVm*jHbYikNKssN*3 zsxOS6&gV@8Z!q0KYMoQwe>66QGIp_8(0{^7=Z5xM%XhZB*5^)qT+x5!Ogip*-gJJe znuLrT&HRWnh!I`~=0BsA*xF2umR4(74w(0rUKWqTc3$ldyo{SlP!aK@lmN15rXEI)6rnxuY(5uT2 zZv5W4_=UA|*w}i4Rn?_z$VJ%vQv_rak+(Ej%arSQL}S^wHC%wa{XAD?1JbswsX#^* zX-(n-zjRUf8#-^>X-Wk0ZF=x3Xd*jafN!Yu3~J}M4zl?2-+N8}QZzHVFX68{liRz+ z26;7_>rY(S_RUjzrG@EIq75P+XS~fsBq8$Kj#yRSIu3himzwCS)t(o~KV%hqg}xRV z`;qTs0sr*fwk1#T(DBZ}+TK~{ zzPdBlKT$*47yjP8ZGE=&@dRIr)R|6I(T5Qhe>V+4tjr&IxD*+TFu_5_B(jO(G3GH( z!o&10EQ+JUxFVDstRwig~;S_$}O%M14SQ5E(+6oh6S4zK>Dg4>yTKj~e{Y;QJehg=cjMvnKl?PFf37NA|M2Yj-{ZSmzrQ}EQa(JV z{(ZFa7e)fq$Eh47P$@G<0|-k`uv8>K^ifb_6tpxM>Og|Iq2RtG80R1qOn@hlATbn3 zmm!?h5OLrAlx48>CrAm9=z1xC#geQDBxEQBKK&2b3z>JP-En7VGi0cwFy15~|5D<~ zC{_vKV=?M$%QzHEF@`h_j<9{kx9XqPb0zOWFl18)q9xp$EH916KIh`LX);TI9+6}Cis z3h9f89X{Y zMOnX0w6sir-%GT1Rk3MRpw>e#W|`yAUFq^FgPVsuV>#xq45z<_`6()ET#f*jW2RT- zhgS8>*Wd{rI0G@$eNmGY5gkDhH37rI^Zs zm&Ijo76~ux{c?`%Rg>{@+rOSxLn~Gb!sbJ3FwRnJZ;7@wfghiE*3HLOu)?8h1?qk} zMMBijbO)mc^w%J=aqh633jN=S+PvOpd4(ae0P9JT?k-MetDmD<>?~s`+?pUXpTwTM zE>G_PUQ80xt#DfE7m_8g4XruoSIUSG9QOxwoO}@BIA?GroELj>W5E3h)k`Vx6p1GA z##IFD-~_jPbW$ZOv`H*EMD|j$<`|9}jDaNM+#-d~a{30AF6$Ub;8Q)p9sQ0=10Ds5 z0bn8|kPNfNh!$?>Hjp98RaVD?Aa@BZ_dcj%A382YII>IyOaM7SxBxPkf|XLl2{Icp zdk=ue3_aw1Ef2(_;Z^aGD^43k-8Gz&X=TDbPF0nlyhRi{S=VQWKr7evTKe_t*1`Bn zz25{ug81pC^}@kmda(A|fNC8GQcYGI#_1Lj(~_!nTZk$RgzGU$>cbGt#XiL~qDBK5 znvHpli1QA13PAfh5*&vV&-?-x%@EW zuRn%^h2ym82lTxM@Pb=TJ2(`E#Ij4&v+Pqpkc7Ba={JC`s@E!$2lyuYPB$;U3(?CY z$l(2D3I~K-0p~V|uQAcPz(*_2N57@^>ONVUfqzM&p{~cXd-(-Ch*al2^BCY3Cqr8Tb zg5rqQNxC^WFcgx=Sfwv!P#sD{HITt5zi??OU5pHY6I0DO;Issh(%qClsqkY>4pXjW zKyN$7;2Mf?!f14AjE@ot@Ci+9HjpClZt3`sj{o8MkKHWJb>RI%H`LgnI)Hu2eKt(o`^_l&F_Mt`+WZ7WM5*Q za?NdrzA!;*2=h_E!_vXK_xnAq0pTLBV7e5*9fF+%y;=f4IY*YY!^yjb%q=}Z3lenW zf?M_>mHL}~H|r8aq|M{xmlD>eLVbx#eU48ml4|?88xlEPv9y*LZamm2RF*EfZUT%; z;k`U~e>Y4U$7%X>W$Utj4w03aP&u+os0}z-+SLaJnjAJt^#dfvo+ic&Se*FC;)zk0 z0S&aojP_pPU!S;MKM#q1S!SAj{RT|`Z5q3{OQ-D))x}?Brv(OzRDWp ztp5q@FRee=UoDZ;m6}BI9r%zikYJ1hMT;vUJ}<|FNXqW$Ii;!28(=K~!CCR8uFrb( z)InN4nWmKiA<`AC+4bATK{|+mgnlsCF35r#tIIEkwIeH5LwasXsz8%EOCNhmEAF}x zW(^eiE|fO^B}W`?f-TXyPU{i-L?w(nB%J81@6NM^(_X`-FxCz;N_+XDecN!#u6?K$ ze|UEH`ysON&vFRHF7oQ})W;IN`yro4C1vB^S zH{j591I@-ky7LuE(E%#nQWma&s%ih5pW>_P$qJ;J`{#h&(l4IsfLa}v?@X9b5~)~l z1ETB;b!morhNiZmOm;VvxDyAt(Ukw+t194^@X(FdJGx$N<_C+rDwu!TWUn-}=F->Fp!) z=5TDjs`(m?#aScEUDY6CAHLMe^BZB-8fu0}H9NLeiz-$7=c>woQ8t&$&wUrOv>~(# zj)0RBA8D#NTU@ex&|-78)r=Z$4k7EhkGaf1k%Ks`IGmQ|0Aqi%YCc4)xl+f#9Xtj` zBg9X;tgE&Wbbl=pq%ts2e^Hr`DvYG~20m@y z3a|uYP7ep);9~<7b*eVcThFdWei5nGallN69>{ucTlcQ(T&@E#DL-`Elz0%RUzf~1 z3<+!-Bz`=6B1qOIZ9q>RZ^0pzrGw0a!{nISN=rbzT0ZvCNiGLLHXZzuYzV?jAqxjY zTB-m$vdkbv=b#Cg#43~eHG6PeW9_H#)DDu0xne;8It+OVPeR%1HN9laxj{;<{VH}u zH2bJV-CzOuD=ObW#tx!ZbUe%25PjGf$`qq8rwRi z*!~4@jO_7)%iw1yn*VeE|xwJgCqzl zDgpikU@QvM(=RWk1t17Q!^akreHty=%B~Tp*sc8(FaXls)J6FT#0K<(A;|Cnm!Io;^Wf0^T2PL-&TlZg1y+$;p127?ecJL# z5Z64>>iD@;@>D097Jwa4u_|~R7rIl)HA#XWB2Mm)ed<>sqXBFsg4hP(&_LBfZL0%J zJ_-<=+=kAZA;L~K_)>Ido2(XUQ|DyMX#}EZ1Be~< zF}X_UH2|T#zrn$vrY0gOj|iIwiPiPN5If<|ufYnzO_xKl2NK;v@xm`!=?3fin8?gM z2B_|2Iy_Mp1JWq$7s=f0VFSbuRDcr@7hRmbE+prrp)1vYx)jhOLG#b_fP^qFBr(&T zy37{8hX&NSpGz*>7Y9h46LA{BhuSo~&mUA(I;a=jmYjA> zK$)pwU8zW{4Mz8uc}DvnsD-E@MpkgbaoLg84s;|>x<75G>p9^Eo1q(2^tGSuUC&Pz zO@F_C`RBz0u%cgF-N$&3f}oFVr^gix{{Z-OttL25KJXlXIs!3;9|!yefyrA5g2bB( zAl!M((EU#y2mApnAb zGIP5R=+biBkKlEJ7ePS~kfEWC2`zyoxd`NBle)+P4jgVw=bkccOQ-4oxVA zvb}C3$#_~SG7O?O!nYIwLXmIj@v(?o@T*>S)$u#yeM{ygL&p0&$@5Qo?ri#u-DA<1 z?=kb3^T{$z-Rn-H*Ra-~K5jiPuAHn}Wg}X^ax*sRp_WGbescwwqigq7hb2l>7OmsD z_9mK*_TGOZdnd^d7E3%CK$Hi|!|7y`cr~_M09^(_2osQ6dwz0&2}^G~$YLftHOOXb zY5OAAo5XC0I=)~##1l|AHN0z zOZ{~2KV&bgBFx%|>&1)>APBRYHj}EKowhh{H}h0VYmXh!(8+pj1+00i4r>cvv>TRV z@v*nJ^vJTGa}KEIc&hdb$x$lkMGTqkz(>5X(BZfkF>c`4YX8pXUdY@#mxw!=j)?e_ zuTSJN#MR$;(OMn9yVA$txOnweWS|NL8xIi5zzQxud3)rLyJ}>_E z<8=_Dllx0Xs?*EraKRt5uHhd8XBHp@0k}(W`2$!!Oulg4)9y2WUXm8;UKAzQO#a>4 zZ6-oh1}Moi#4+Id#nVOG?D}tAOu76~{G8nAfpQ^R5{-TCg>T1eWx3Wa+s;hPCTfr; zuOI-V?w%oEH~~ZgtXKLr^KU1=+Ao};H+L_%D8fn*OqjC4R+5u+42CL*@en0 zTYOAS??}v<-KVFUxW(+7cJ2eE?Ih{Bl#XU_+J1J1Z!z6^q|Bfemoe=vX&uo;X)0r{sGs zCqbT@?C0HmmVXD}nd$mkpDIt*9pi|S!-~=#tR*hwPOwJs(ATCtqZ{;M}`c_6simB6}m-Zl038lErO4Vu||Z zP6N}M|B$_1EnlsQcS>JlVB`0({>4Z9{x4bWC$LhdWUqa-MuwDanpukro7#qE=it*2 z=X}$3_5u?R-t+egj(lcgs&LnRxPsimD?saWXZ?8XN_Ltn)iTDqs7qQZw1X=0=yVgU zURy@LF$Q;U60MF_nlww zHvyGfP~=!+I?5Xd6n)7IgJROWNuK;Q_sZT)w%u=K2ov*3-!ru#J*}R^MvE$el}?wG z2c{daq>sXYo6Q95saO1lG$f+IcX4~DA-2J0Km#LupV1fxMl+|)LP<1nvgaV7MQJ-u zch%4I$jgVH48`VxbXT&xTQrpVqtl|8&aI8{$|t@8^OjJvmrjDaVCoIj5%)|B{}t|h zLW+CE9F}b-F?~Sv(T{_E;t~pA61FXkm-)@-y5bu@_~Y}{)T$!L)4~@Mp0aMPWCmwU zB44Ewl=1b6-kD5aF}@CW_3tux6+Go3afG!<~mC=sXOyWi;}3#jcCHDO;W6_tBc@JjZmg5R}itFy86g4L*?f zfKZ-Ey=DGX`>I0v$Kxba zb&azFQvF8e$ktQMHLpWLn;$Hhy4=`HgZUTg$BjS4 z##2@}9?S--Hddmhm9VDp50wl4*zUc{@t#MG>!qJE6hi5}iCF}%2uZQi}~ zf)M>$MuuxWviI2jonv3omeG|5eTRvPw=3_gU6%TO0VUBGZ@=rw^S*HC60+u&c-)rp z_pZ0J!^(I2EGZ^dccGd0XCABt`{|9sbCQM{b2{0n&6nDBrUDRZHMXw($h?0yt^U5P0(v>H@`A(1yY0`u?OjRab6qaD+A%2nb^jqv=bDp4XU#?Vum_(O zHw`qprXP+UY=wM?@u0a28P1OHZ1&vuZS4LhyLVMx@GyTYSrtX1htrCmswP+W7d`nK3BM5& zd?Wbj4a9cx$mCaBa=Y*4Qe?e*UmK8umQ0h47{3fN59JBJcw5gxUIc?+aMs{hC$74HQ%ZJuY;6` z%k~{l+MN5<1)7Q}wOhY359pnPe?xJFmkoY4WGQJfF|6Ymw;LGu4zM{nOTB^by7n3W zC^n{)lc6hs3EqZ)r>D8f?k5_r{HXR*SJPwew#VJQL282;J!7NaW;33x zl!~bW64y!vT9)}L`{*OmqT*@Dpm7(Rm%-D3y(VpfB8}ovbMbT(b^_= zb&KVrJ@ct>Or_*D>xM#8i#$PWM`Eac%?dt8wj5Qp`gVlrf8v7*Q&`GpYPa)?9%?)| zvRMu}%VBTz&^NFn^YaslpugimWaHprW3ao-7rVo_F2h^F88-A)T(ybZ!xWxWVKHt3 zBOwu#Ak)#C$j+>0!WH2_g&d(@o;J(EG+%~cOu|iHhR1;-#)BfMzU#7 zH4~&sZ-0F-gqt*YIKt|_?Be`wkDCPAn7$CA>Iy4RT&np!&IH>049tB_Y#RDQxrGwE zees*WsmLdj>T0*NRkyG9_TRsmtmV-GZN@Z_-pgjC$OAxOJ8!lrEg>D{a^Cay9nVh^ z*4=O1)+H}}0WSP_1Hl~|nH~FyP8C|(WS&z592B$WsRyt-rEjfb34_4jR=hk^N!D}T^-c{dYQrcr+oUxsb!L3oT z-5POT-(m;s<6d)}eZ}QE5f%SfHQvwODvzB2rC5H@FsA|J8_@}$?wWkz%J>;|J=V^M zYI1s?0cSLG9nnFMQhjgmmB105Xr_8PkCi0Vk<|Dlv3@p5Et;s+L0n}as>~COqLa-E zaa62kvV~@feRPV#Npe{IkJ-3 z!$EYH0R};AQapEhisntJ!t_g;c1Z-jVA6Rsg|)VX%g&<)NnzE;vL$=)0QCSn)~>1~ zd@@q~Lm^)Q(zB@$fcJ62F$|t1TzSik#UQQ%^P8Z~lp1a$TnZrH$E{t_yie2%?(;td z$~!3850{0}B&KQ%p&f!MWQ zAaX#r1e@p(?g`NTw9OMno4%|l;lh#*=-ocygdgnt+?-n-YzPbs{p))mp8Gy6Ikcc$ ztqK_CT~X@~*6MqA6EzWI^ZsuDZaCO;L<`WrmQ4fV+~>YW(|5Y8q!@CqnD*Vx#%OaW zP;%wR{ek((;*+~kTA44MQDpTJt(XU9&i6!rK3u(5nlfK{b~ZiN>Bc=xfc*!J9hrUT zT@KYb8%D~%8%T4%r2K;^r{Hn|tQ73+ahr+6JL}AVDS3F2x3a>i607Qh2GSZ!IDtK| zY6)-ga>i8`mcO01r%rgHm)Yd|sv#s;^6AY(Eg+J@6O&kP##2fa|5;J5o!a;l=T(^Y zE2e?EK%n{6;MLV2%-zWIAcZ5gkx!?|@1&8%u1PL7S*p89A+}jVvzaNs8QYzt_e&So z-69&@^7?#(RctG0|GI4~>dyUEA3n;__trO80mJaNhzpLc@7v;kwI}Lyq{MdI=UU%oiU!5g7T@|riHQilPQlR@KR(oLFP0JSsfFO!VK5ky;LsIv;C+Gke z-&&C&gJpX~M56LDOF)$% zjt!75D~ZE1QDBA0x>k!|6|rIby~^s^C!8XW+>>`}=r&pDmWm_FF3Q=s4)B!>SS%)N zd4h}i<+m=#-;Wamf>k-m2+tzm#WDjuz)Q+t46Rr4Y<&44i*vq+lR2d?(VMOuE2vKd z^^(-Pn^}&91$Q@1#`yP-rV-;Hj=?X;@XOpQAOUjs9o1Ld(q!pc8W0>L&;oIj)SD8b zGgV({fu@Xn+%Z7|9J-{_r@#P$F~Lq=!Fu zY+35Gny5jLO%R(0X`FqtxV*6Vnh7?3-4IS~a0e+9v*zE?MptR2u1#ErzG~(Gp@n(T z6b^P87&-RS;Ki|lBG4e_l01d?FB!3vNQcp#5vOA#dva+%dw<;<{2Oxy^Z3I@y(JzD zT{dc|+dSfsV-^dCO)oRp#F@6azWx5-^b)Q2gAIms0skd|_*>7G^8{>>45ktkMi(sK zmayakmTSXZyiUNhA^yV052I=X`^}OS$#V*wEgZ=pA@1JiM}Gu+v3v_9Y`-b>>aI&) zM`XK7XZV(9g!tXG`Z!bdpK2C?&=kg)624s%NkLZcL~jIMQ0R~lxI|w5gL>=J>jrLr z?($)h2Q~k6ZeFAKqjti(<_k^eL_R9A50UB#D#WlXEVH=bO_NZYa4OBwUz0)ijfrOg z9VGf`EHeo;X6yR#m+OvD#zk%NPF2@L~p(1R~NV4FAz|-#^QPKf`M(6N%yHLOv;#LsM!mN+v?2I4&F* zEk1xx{*|S4kZ&m^8gIyvsK5<6B3y*UM%k(SopJWR$zC)>+AxDx@1| z9?95Ez1TGMRZ!X|8i;7zs@AVY_q4VvJ=M?N9oF+I1YKeF9`U2=Lxu0<*_PmI1k~(= z-iH=pq))6CE}aFJ&OWl5+T&UxfVE7i z?#WMHn0Tacy8Vmn9rj2QC{LSwknWtp0SC;xxx>vSE z%86&RDF2jKj=?F}dsbWUhIgL%KV+}zp_6z1IcqcVo7VqD_MXK$`<#-!Qk8fAL-uwG z7JC0f_Bu5Eo9wN6czNVsWN*ITzsX*u{U3g%H+uvAL-rolxB6EU6i5YB-oHE|Ri5sK z46K%lG!Lw)EM8czDm^88qpSZ#_9j#ZK5knIT&w#R*}MAYW>7=@PxF7sUOtY7etA0S zrhmxZruURK2iCd<)pG%UeV>St@m#iF&bB2M2sCOlSIzyE_Ph) zq7NhGjIz2}GJR#PA8tMyuuCKGXViES_k16_8Y@SasO3%5%;GL0|3i~Tv}04{GuFme zxx-L5xbNB5iYxJK$vmgm;^}`k;TWYy1rp zXpRpGz*?Io>aEdUO*gb$phjw{FrNtX3M>M;OJu#F>Q&@pl_Wh+f??qc>r@3Df_4M> z+4@Ba$1u5{7V#xpjY@Eb#@WF2(@%c_L%VdVs8p^zGSo;D=l>_{9gBx#7p;gGv#XuE z5E>o=z887Tc#!3E0w&Iimhntg8*{BB3OqUhGxbj*&lTE)&+wf_pA=PK`zyYx z<(>;@3Z2dD7$U&io8`)lQP-i}Tbk!fl_a={bX(dWq8~0#}Ir?c4Olob*z%u{rUH-kT zbyMIRD+!?T$DO3A7ksf|Mqzo=u6__a;JW1nU|`jlG~IdZ&6%1$?vR{RY0ko1kq1wF zL`jA}FnEx!Vn36~3qMKgs_v_U1Bu$}HLAhFeMQya8+-s|)NQM$;0B1G#NE&DjY4Lg z`+Czk*&yUp3jO*ws>b44T%$77QSpH7zf;SK9;J1Ql^bl4ys? z+1PVWd9u?%$YIdwDR~*R3umI$fwg5WEld1p7_8F9tHSKfMih-Ul1FJeh)FB*@uJsI z<>vMUop*Gu`tUH$d}oMfZt|fW-gN)CZ~?};%AsKo6AdU#^;i=XpoJ0q%^q9s-|-N9 zMcob18CRnD*?5&Z4x1`-I`MyA%z%Z)Zd-AokI~-!bNd(4G!6B<@>kS9>vtGUH+$7d@GGn-RU3J^9Z{*#nf7h}Ut_ zOtuGutUZj-R;v(E|Irt4)zwz%Xrmu!GVRx5GAqRf4A_vfTMkidvEM`i6AF?ZsZ}t0 z+gRQcMKsg(95zb$0MmqkIR-r$u zCS<+KFL?-ij~sp<%1Cy0U)MS-@OA!W*^KhtXZ#RYQY83-zrs~H?RT#E?wN1TQ}hg3 zAY%wlU+zc>?dAR^3%~oUu_?+N!bq@a5i#DgxlCbAKnDc0s-2y=Y}T%L$yqP6`rF$ zq|$Kk4+E1c1t}P@HZf@l8%6?)cap*`9tyN|>!P10rrtZk+m=ytEpxxB!4oRV4-B2H z3Ia$_Uc|%EELTt<{n0h#BF*1}<;(1gxHMA+F+lY%Oi z2F)#xdE5e_Ycgf1V?yT7R_^y4#<#@qE%A@Ef17$IUk7(QyMBH2m^R5r*;J8@RX0ho zu#wRmz|s~H%!wJZ3xFwE#$5@lw&unAhQsY$k5QPPjs z?hU%jpI>!mbSWu2YbBWG?CzhV{G~A6a38tVD>jN4BN#RO_6x*S43oYi{ob7NR=G7` z$mbAciJC;vMqeMvxju6qG&&h4m~WL94)Mmj_!-9wQ=HeyNeGVcC<1aF#RMc@S2j|q zeL>5;c3loaa9N|n!N9r)&`Sr5PN?g$=MyAf>h-T7dRlB!<}Py=5-h9D<=CKZZ>}wU zza$C}l@XY8lJGmnoU%@g9|?>eZtXj-=Uu}0d+X2 zSV=^YY0`(!$IU}LsmYKFY4Fh~XcZ(rIt*`wzE(~!lEl*t*jQYLf&rrA^^Dad}c!0;R<5G^213KX4$?<#7a={$S;*hv4{15py9bP_7$?Wb36 zFie8_L2oF{6v%&tZ4u~$@l=Qfo_23NFnR;|7E=7%*>27b{H6`wGRH7nm~q9J_MHt_ z*`4+~%G{;RTbE1|8%c;}ek5%}7ub?>&oJ{?73}Tq$BVg*sbSnzfj(nKAih{hQ;@3B zrC)eRx=`QzrGACXr*E*T+CCQpS?I-5&%vn;A_xv}Ei3wu^g5lPit81w6 z4yV(%%Khz*gBy+FvWo&x(#+mf_jO5g$O<0{e$0h7bnpa8;w*f|_hO@6j0qAvEmHM6LGis4;hA@MZ^Sqpn4gE3NN;ce;KeeS%^pUp6UIHdZgO0@fWDVwc zeh$8nqu-fgtuSN$C@d%93m<*AE)7tcGIu_W#gU}x8<|T_bSDm;+?PGyBoylyr^FDd zMd}SKpx&WcEjia*w9pEr!FupT#1X;2uX(Al8xOJ*WL12w&npMMu}~;;a5c2lU2AA+ zxTL(+_SLT4CAr;s(6kiMR;p>GJkvfQ+UEXV`H>3vE`SufXPGh=#(Xzx@jMuHpnc!3 zo#UXL&F(_Xh4va1l|#ck?mJm7cU`K8fd$tr?Im!BQ0HWKZI#uhUypR-EHZ)~=Y~p} zFhq37-?nO~>UOi}dBLZqjYqO6#`EcDi1}LUmw{^=y6`wq>p_qCYL6bVI@DP$^PY;N zBC>8lL;l*cp`hN)_o^u7bHX;h0}s?!VpV5;smIEw9XwV?lk|lPRqz?Te?C1oWIyNi z>p8>{PKH^MK_Ec*2NZ?`06;bm^8WN82mlaJ00f2p|9Aoil%KwW|b!+DFrHS5W zzrG)={-D7Fe*Eux8Kn|C4>9n>ic0#Qd~)ssj7z!qBk=h@vw|@NFGdCshbxoBD8;$ z52WW8-{bM|<3bZf{hSs$#P1cUkxz%ca+EUl%n6EK1)AW6<+2=O>3|r9!h=Lcn%x5| zgY<|)AB|LgM-fYG>FIp_)T9|0)@ur0fIUS~A!OR*p61kAn)GI4#QdPLWu6%O7Y2DH z#V<&$>^w}Zjd2d!eePX$Rvhp2;K@7M<{X1Q*;n3qR`jS$46SBZ=47f?CLvW$syNHt zOs(SP@~*!qbEqY4fo0eYJ%h%bopOLi_JBNZ>z+g-mdAX#zc^u%2(RUB4-Ty2#YrMK z=xYOcjux+hC2QJ(ZYVv>BAexjx`$@MmHhEom4^h>I;=fM8B6ce@Z;U+F8l^qHz<6> znP)gjr(C0`g!FhSe5_fisrUwHoGUgdCv5Pcu*YjY<=YLQQtlwjgL&_}A;(vQ3AI?i zi?{KKbi$Dh>Ii=N#H0#={1cD#2iDX{;Gl}y1_2t}g?J_rqm@FFP( zU$WFkJ3#WR?)p3ff`_|o6G~#$1f;e{9&0)8P+SdC;@8^DxK!~)GX2}VP)|8O&-{^h zj61HjA_yVLYarD;D384OI|Kt*&@{~0WVkqlxhId~q+h&vOwm9>Kzn96D+9&vKF;gC zupjSdM1RwuCfrH({;N*YAFVcRs|m+{e}DfdjIdxv0DAx+{^7Jc8$Ahvt-z)TWEhtD zO)H}W(L4g;YeMaNop{iTFZ9 zs<{I)QS-cv@j(aStMfWQ%KXHdArFopFSq&6a7hnNz=IN6pK7BNKmI) z&OV0{ZyaQ(LrG@9&23vALoTpa&1Xw{zP?^KRxYs$NsQw1Pj(81p($O-<__h&Y9XQYT(kM7Et9E7FXK2k{+&mkMpCj)zY0> zxAIT)Y)R2nqCy-o((o%iLQ!d4#A(%`nB@_CXX~EKP^Jpp2u-`QPLB@(Ot1@ntd}vV zR~s@^-v9LX+c!yK%U6MK2G2@3j!YL6OPZFfS)#GzWN3ye{<`^MlkQSzBc9V@bg-$X zO(H``5TPKn=q&rOp|I3gh+=oC8WSzk;zcsoEfm|AH7Vm8E+K->I@OSELY-z_O*WR* zQHX6s;*~l(@2BQnHDMlzpGTM%T{z;+Z2ZF=4L}yt69QHY9vMV_> zNr!&G0aeJ2m_4m{yO)5x>{eW5BF)CdFtI}KjULv*^-MSv;4dwf(0nY)KXZMZ=e)!p z>mz$&c9i0#Rx^Jr*_47acD#P1rzPVwv?nbsVZ@)cZ%@{%zu(MnMKLy}-x7sr66n(R z%t_W3pUw+|xIDz)hseHd>tpZ4)V!$2%IlB;R(|{1{rbSHTaOc2ZK6VGDU^uq8pW>_ zUoz~GpNpH?pTS*M88k0kxoG0Gj-l)pLm8&kA1ywSzJv1l0R>Iic~@B!kf}^dcxM&; zTBFm3?}aPSECVK~Y)rMkX{gwe?BOC$*^jZPhu2i5Q)hKeavkg$6lw06SZDhtr_vBvE2U=MW)0xkGe9p69LI~5!4vcQ^$%Ydxag)sbol$a{a*mcH+lDF z^Nf=8tixT`wH>=m8}QSvM+RIcQS@MaJTtrSGg9Xz`h^2p1$Md*^wj4qBaY*tG+-7_ zarpu(Hq#^+n3P!l?mdkq31a(9?lN-cxe7o zR0tGM%=++LzE5hhhbFDLGTUB0dr6j{mDh?G8q3$x$P~$J@uB)n^SJrXO3l}=LVAaP zGkgyR59#P%Tp3A8{Q*^%NXZs(J9}-^@9Z!0b}RD^N2gfaj9Pkx#H}8VX&dZ;-@PAy zo28`>KWlll@HNF2$7&qy)7Ae%GWWM!peWi<{-NM%&W$Y>zy9L{lU$12+~l3B+{2%TdT z5=TkMEJ;S8l4N|nKi|*qd;9%&{e4}p>!0WA`FPw9#Xa?ZT%_Y*cnnGOh7J#;L4?3} zSN{y{@4dUa{SZWKDCT%Xmma&~K2!&yjfk3up#FRnZPNCw<{^RgMJ2uD_8yQ#n@K;1 zPt6ishlk}D>qK$qX!)?{2ZDDMo1<07qSd#eHHBle^ka0KWAwse^s{3On`4Z}VobJT z@WNy>eX@l!*|R5*$p?HKm-*=Diq4 zx$GQ!9~65|Kh~+2qQ?XE(u8@t!$S0_H=L=r!l+@{)QDzkzZwDb-n-kl{5<4mqZ@WW%^eI)sd=JBtdb5-I zV|$ttF3u&WN+rWhlbgbl$HD{x))FVjlBq#(TO!m~KPlciCEGcrMjzW1ma-BT|6Mrt zM_kG`MeJ9_)Q091$L6H%=G2|B)T@Nl&EAC12eeeC+QAu0% ziCcG0Td<~$XQzp}2sqCsOMFYTC8j_{XrOV33K7JOPr>HU_(amw`_eURpk}iGxgy1V zj03Ze&EWc*vNx7t@;BqIK8=5zX4aCSsg#~A4EVJco-xq6q+s{R?Q|As3a>Wen243qv)rpg)QfX95Q&9Rls+Nn0P$Z zoyNI`M+GlZeX*#&1WtTTlD8&eijCQ2V20cgd)cw4gOH01%xVzoQ!nJ*EG)())j=Qe zk`{NCem&VB?JPa-55Q&b4$UOU3==3}bh)NkN_RYDL-TfnV!E9sYWl$Qa9)GDgkZ*P zpiWG9G8NN|OZnBDZY@IT`-Z-NOmlJPEMV{k1DF&Bws}l8i;4;a_)f2JdK+Nv@yKDu z{T~E6qz=9b6bHE!UuPvIuVMCPF)xUSI2z~v8fKSW%)*vPO<=MZi7GY}dqOtP8u)g2 z&W|{#868+x#G23n4K{}g08;8pPeRhV4U%X|IYv2n-GrQ!iUM~Zsy7q<0*C5k-U?Er zCgbvI45*$A?mZ@&D15MZfWA19`%pi3mx-xel{|Ky{)qDa^8(IH)!8_H!KVSLJnt2z;JV z5{%=1!`1)K>kl{z%w#8r` z_~z_=LDw4py2QAkhgl4yVlL+{gENkS+yiPQ`!R9s#28NCo)Xv|Pca~*oU|#$(6g7E zu_kMP6)nynA&1+b0%cH^eJzfblYY{Lk`?5WwXGYM}x8MmDTzJk>oL`UGF)gMdbrKl-s$ZCaI% zRZ3q7m&u$!>mF2BX+jhB4KXT4mA>wk@NpQ%HqMa&A7|H}VWXPZwT^4hHAcdZrSx&p zqV(3P+E!l6ylR9gY~U*2St7l?pZ>xX+kPhFrD2mdsiwcbCX0cdm@SUOqDEb7KlCTY zT!U&Jw4T@$#yfLp24!m#p%mY2ZU8tr%OQLKAu=C-6-@_%9%qkp+^Gg8IRPgkNE!#h z64OsHo?-AD_G6Iyi69L7*~N-yk%>@CTj(h|kW&3Xhgeos-J*?aHlsa=wR@a2Ue`h3 z1gaT1?zvovm`WfwI6L<%7KIdpFT3YnWk5YS>q54mQ+U*QT3eT2n^SAE zdzXl=*$;;S40L@scYF}corat~&s~J2Mg(Dsv8Y`drqCTZ{hGVj7Z!}~*uXPF5inK| z=I?nb*FWxHHk#W{u^!n0eU9A5<`Uf71K8*`P1GI})2B&2mz_V($c%4>@*l@IMw8qvbV!sfRO>{SUQ*XV!g=%WqZ}++Pt0f!3D^-i-Kt22M-%1t6&3cnH$EJI%`{AO>p>(!W8lSdh-z109=Z zzfW-7=W5AGeC$Xom1c7oD7Shj4$iCQJhB_wYXft#pWUl0)gTO}KL>N{f=ygAe0rak z=X5MHFbmJ2V?iA(G59XM&0X}xf*9+^Y{x&^!yDFh@wmE>{jKNPCv(f=+qwZ*9_`?> zzo7qxehlKy2tp3g5FQNVKTUK77NrpYih@$I^A z+7|p^9`0}=aTINwcL~kh!E!#pqB_Oeif!8Baj^NOof{cA3RnN}f}}3f*A?gDkne3MDG3<; zfr9&JAm;*|l=Q0KeLTB%{K>Ti-zVVJq=#!xQ^E7TxB{)e9dr7l6OBL(j z-uUAv_R{R}6FX=tE?9><$~g$uMMK|ega2S6c8{Xks^}MupuOgWzCrcBqd2?WxSsCN z1904QV$LOe-;1_64G*ZlJB-4HTy%#;Yho|XLPM|M2J7P}K8hu`WXi;g21%%QEaToP*|%s%JcKnRlLr zW-e3aMdss;o>XvUlPkXH{C!aSeDIqx)*1ioS8KZNF7zF-gSCuI#xdR3IFIad1Nq$H zCb^v-5!Kx&{+uuA#f>pn6;i+kbjH|>CdzyKzuWLm7Vbq|vP`|g`v=IR=Ci3}1?!5L49E1_43ihe~(ZJuinf~008vEEP_)VX&(nKSfY>#G6#BHb!EkW zdfgg5rS*UGI(ETxLSzOa z?ddBetT6<*vyZJ7oAF+cYAk*86d{4OXZEM#;~3(IzHg5>JYHwuxRVIK3i{_x4DlY& zb#j5duf$>9-h#Jm0)wsP%A>zlC%>_yrzms0cX@N9E+$Y;YZ z4~+g`*{xq~Ido>B*CcW{g6DOZ3-dNZ`6?NoBj)S=J&HVyJ;~mTuz^@|0@0Fvn)nQx zgwjRMzbf>39wIih0$%>N<3GTTP?t~OY%N4Yti2tuL&a-ue?7>9d@K(YhSyGG#QMvj zV*mahV7K7+wq7akp>KI(fMy>F_lFDv45p5|C%ns@f{f$2^K8u=WoUm^iqGu+J(`#M}p}B~u|4zIW3#V}cnry>b#~dwA zsSxMQ0Xrm}^N|nYX2`)+UV;VzUhEutnc^^{q>hPhkw+4X{AEfnFB$NUn(F@TkFtEE z&gMJyr@YePQz%y(-h{_C?dq~Szn=d@Td2&#@zEH+DR;}UOh=y3fP1il=lsk}zm((B zJVMqQGvxK}1rEytt5*jLj^2YEa{cA=_TkB*;{~q2uZ`B8(@^4v5=Bgb{hFjk=~u~u zrb=D>OOe+4CCB%7{(hz+?5jTSI-PYWRBj1cDyU_K(MD^9ggGtdu^U9h{k6}_czT+7 zR7%STeAxX{Mf4YXoX1b)0TI+k{4w=>~+Yq%*Ub6 z;upZ%ee%AsOtJ(E0V}d;ZtJ3HoA_%9A2KyQ2=@|Y*H4@N96!8&N_dFy4y72OW1e)g z8I(2{x;fBSTO1;#R3Foer)C?}uR-rCZdNGO6&_Hob>)VKZN;qsceC7E4Azz8H=0_$ zk`beEr5_>_TC8(uck~3pFH|fe3k=GKg zCM=DlRcD7JlvOZr-O^5=cT05rpbY|_?=@SSkGpzy(HZ`e+hC1$Ya8EMyr#>|y3luP zj*rgeTUNaHDItEkpo+U+BSL;}`<1s2EBYyW-R=M9ndCIeUE=`sib6s^$D2gv$u!Sp zau_b!#&V1DELAO|@H~yXQuT(Jm#663#AD0025$@v3eXcdb-3sE`vGvC$ zy@@*^5r)@P)%~$Bc+F<&L9>8tr@!>t%TKL;jUo0HhF<<&4%%42pP%d{Y@$%4h^TMlXK)k2yih_K0R8YSSui_kf**TiHz$qj$_9>$P zaz$}5I=V)+Df^ab&*Ak>bQjs7sza5rul-r9@04g$jvj-9|ANYx=*uf8@(M^Uj41^0 zZ&o!XC3;qsxGTH`=iuYmYUjh?VycHR7kUQbtSmfvvzjgBL#|$hhkpw2FU^zhE%r7H zjTuVW)Gj>)_Bjvn@-Bms;BB)|P5jV13oJ#*9dA6(gQ^XP`c(@4NHU?OM64Uzr60z_ zX%`vzKhmzlVR`K~Zi~A8jEwjs?=l+{hh&olPe|c#g2%5Ig2BY_Tr}%VKx2%}(1`<< zcR1C~CLxbCYegHNWvC{ZVMD_@ack>{bbP6ztw^>2EEsd<=5_VE*6&+Gd@^q<$x5zR z!({aNRO6Y^oDSBbCM)*c+rMqg0)s9!6!z3yfGkVoJ0u!ZJh#j3%u{c^ z@mU5`i5J{=Y%xPN*l)eLAbPPbKSDKF{XSP#9gdv7yC!YTmfvCrNzYv?sC zE*r0{^harYD%elwfUr)vGJ#Z_OgCrbR^agw zLfPS9kwP4Jv6t(~p+ILuO>2yf@WxqOgdeiQeNLiP!1da>j}sqWZuW2Af{)+&=i|E; z9q=vV!ZV-MqcZV@Ej@~Xqh%ITh@8jIXHH5EBb3k``oZ*HZxlvA{r5qqdW_!o{n(#G z`p!g32faWB+C`Nr;xhr0%>lhR7%HpY&9coizV$G2i_zU z=!SZ~(QaCua7(Zbd0i+zPe-=+vR6aW-x-~;?KQBAPRPv;m7lkK^vq}a=!XB1sc)yL zh9h4uu{K?+b6mCAP2n2|X{%^|oWT zVxvyrY<-?;a!Q@Qpi}z$&Mfp;Sf|kR8dV{gPXgClBPNcj#l~t@ttUS2YImrlfBC5P zB7E@n^v|IE&cnKaFt(E^&x|6x?ljM@LFq&5PpDBVSB~A82#w`@_A2W8%@=p3|LjZ1 z7p+IFQHK5vX&%*eWi&4wdl9*CWae+sv9K?VggZ;;X0|`Py1U79Eb{wp$*n2)iyse! zDTk9x{|;H*+kXFI597<)nxNj>`Eu;u_J}Q)aY@qFwr`uJ2EVR z%PhegEFrQf>I7@;XS|3bOSqdQKEgUQ&szFEr`AC5Qr#`5_FnQ35`?8>){XxST`8W8h|Qn2x&z%3l?h)+?kX6Hwh&V5~e6LMH| ze5IazHtg3rQp=})m~Q^dSMS*v5}eSks!6fbE>Y8jzY0nW3+AG7qC-iQ*Ot3Rx4PoB zlJy-sHM_e{pxO?1BUKn2%o4CfNSEFX^L0L}gC{eMQqcOTY z!k|P>7;0pJFb28>ZL`>6;L~A{(VsQem(i}>NlGfou(#db&LplW^d;gftE zZHiXPTiO9W)En;Pk7u49gnUE8ATi|nTliRAdbF`Q_0}v*=d$^}h%{BIlji4 z=tvH%c5Ed<1=xPfAMe+3--*G4tjTs;vBAx{fHj9K*XtPfXpcB*7=qfg^_nSu0zkxA zWl-(SPk6AOUiN{85Ww+{wnej9kwmJCOSDxYHQ|>zK7)E^HdZ$~{!&}~{m8zKmCPo3 z58fOe?kg39tG^Rwtr*dW{+Jq(W|QmKcdgj+)d}i80SmkIFFLJWC|}bCkBtrSf!CD@ zF!=|X2;Je|;?=C-C)u%)0Q}eChR7i63#2N-vR(0rwNgwhVXH3d1XusB6CVlW{K-DM z?pJ@yhO&oWeKe}~M_B6mbL9C)>k;8PjU4v9IVZ-fLk%d~TM)Sl?V2jE88bd{GWxtr zJM3l*ya&0gdfY&fobCv_83a-G;czLAZgFjY-e0ZOOHF75+u`8K`dPQ#Aup5>McK$Y zpXhw)3S>|0`!igeCk=9&35Y}SAT+)VWGl}`}Y?issP7T=k|6mRAm-I>WwxD;?Q7nsy4q< zHH$gjtNf@x(ZyY=dgI`q0Za7|{FDU7vn8%O$0#r$5ze;3B(m9>$|X(23ubiiVFVvq zr7Zz00S2@d?bI~cCs;9&L9ioR)rmP0BzE*UMMrK2d%>Ib>6&l{0$34Gb*pe}(K-dJ z2_-FR1G_Oqo_LdjgsL?M3P3ergHPcHuUfMYJ0Gac5Ru;&ZZs)eme!RpxC275iNUr^ z9v0bExl}!L>*j~doR=rLPI?o2Lnh-dG`Fjc6)MIZ7(PfA9W2*;RJ1d?6k(q|7OlqT z9Ifk2;Ta{j>?*ns_ewJ&W7v8h)`1@N!u~LZ7Ir|;t8#> z{1^u+l-Q;;7G!}?+>Jk57ayFIRkuNRf~fQm$KT)uGX214FyN4ucoS0O?_8YkEH$KP z#bt;v_Ngz&Tg3cEy3IRMi<4X&Q9&L$Ja1Ey3dZpS1GBtS+xjP+cwz#_qkZ|DQlGvD z4#lUaotng3k`)J~TOTA6VGeXfn>DE-2E;o{krIwk)TEx$6k@*TPA^F8AdHu7WXEI| zdb&H85xF`T-M4>F4qZTp6;qq~k4k)syCqD05=Zp}tk0@Y>m3zMI7SFb)F=20QH$ie z$(D&0(Gyn&_k)=iUTbkRD%w1#IBwe;+s8cm!l&uu^WzRjlhl9W0HTyg1XX2n%6on4 zw_B%oaZ$n_)PPexSfdJ5Lq&{+ ziYJ_jn+gRS1q-KFc6Dvt9;o5r3##zhRY-4k)*^K3z%yYBcjdC%LyU!|WD;*EFfe}6K}KE4za)WOFFf-U*(idRoL>Z0<{jS=;HaDq62vttm5jRjmBI)7J05mXB8M`LJ*L zeT=1CW2&#ZC=EFP9bd&+$?FA-I0XzIAMn7(e;Yql+fR}2@rY(6rfJ$gzZUDWiBxP3 zES|>nWUn$@Jz^cRrQW9(mY<&cZhguw zaX!}fbynz+Bkd!J@)3-axULapTzzvy;<;XU;##P^n9i}uUwzTb>mAvKLb~T2{lZWE z;M?Q(VD}}EtBT631tNxA#ZH!s>-j4}uhPh_rm)kvM`}hB)~SWI&l^dz1FJU@ReN>) ztz+!~J;4rY5b`GeI#_U%|J^&?AOZxf5OZzuhZ{TP4Md9&7yXReTUAl&0I<2Z82fEi z?5N*U8vvelH{qpbdr=bna<8;ijE)T-Qhw3>{Kr(SXE#a0T9x@ft@G*os@AjYvJJ1> zVMpMA)jQpb+K9uwzAEs)-Zz-L`lcc)?RR&*Qtrkp61{}w!oQox{V8pF<9Wnu1tk~M zUiKOFAtSlyBWiUqH&e4ElVFf$bYz54t2mZQv)isTHw5NUr{8lQ8*O6osvtTq{DfzMedDURIV+szN|RB+tc<@-062fnKBjGpwo ze!4w$Z5TWf4H}B42;q8T_;J z#CzvQ^}iv7-M2@gc*UdG*1PY#cSmpUexUA-J=h(8a`gXNc#|J?_ou$@PXCFT@&*aA zBPu#}XO8TBp4pw1ygT8&w{ZKepAmTe!QMA-;+Ku2#YcN9f1;M}?S22V_rqHn&E8x8 z5hb{YT36Wr3x~8_P>t~1nm8-ulsv&gfbY;tLn3c>m+ju zY56a%Np;8ZDmc{mtV_R45KvbZkOOOw_HEBbE8_Gc=eJMc`>zwp5RKhGr3_SDOP z|2&fdjsNGFJkSbQ`lU2d;qb_z_S$dd(HhrhA*)Njm(K|5`C2IZZLW-qixNLw4tA({ zqjD^Auenq2(RoJ1pN-Y!KN_>lXgHs`|CXlbex;vZNkG!(_mgM^p?C8RPYS2H*t2OCwe18uk(y^Cw^Vxa92m|C~Q5_+9<>n5i=x@yT zKhVCO;wnWPtAG9d-|bq}L7vM51HQW-We4A6`|fq*|1H1uecI3P|9U1HU*9PV5L#FK z_V43DuI2g%b?tVttIUS$r(|*PJQYXP^U92q}m0&y;AgY$z^GF z8;dejds_p!th4tQ*i@gXpQ*>Z|H{I&_DyQD&qV(Qp5e~BZDOdC@0r*rbVk(QIs8(@ zZ-w6qrb52dE;INq`=Qb&a_MOePxrX)+)4jN_r7w=@M-WZ&W zmuqY40`D}@8|9B4nYwwtMdzKwUWDMtZdy_XQ+cCWBnW6gsm*Q|iYpG+^(TKFGv0U8 znRpt@8T|RjV5HvHs~>j_7K<;1sh>G%o-oRAz#QT0*Za0vbDh(3+r^xCZBYMvzLi_k zKyqSj_#2O}kM&rS33rV*`V{_zok0)l7c5o#5oMCEfZarrskgTv8tKq< z6qJGsEVsY^`%6>VH`!UgBy!gg<&n$rYu|gA*c=d^rDG&?m-!-hhkm7Nz37Yo%k<(m zN^X{>GvLt6dT$M|(H2r-yAuj8&>@VN)lSYEnLy@KTF{+|*CRF-J{h1wg5~*Q`kK}j z?*j6)QrJ%%cW8xqJDcW3ShC>?2qk9%hvR4>pSS`PyQ)=#V^2;>{pXpyJ$hKBL0kHi zSw^~w=diqMzO-)ey1QpAbPbX>3>DncA^4G*=#6K&(D$BB0v2_O}Gb%uWh=+2w z2TFPVjJ-18t*6C0P9Nv4?6uUYPjmYgCnKZOXX8*WM~m!7bD6gqI@MtBcN87Hdqhw)N0BtqkkBYBkK&ED@Rs$Pr;E9Ml^Y)&9g25w zys)n`^Ou{K4++5oyzWe%8%?JowfN~~w$8?ut$Jaf2Fs>Yy_ESj*+EW?<$4bt*f-># zkq=~hlikYGF0+ImwV`xRKe3+vS5+A$Bf)BvZ)a3I+wvTqR*B}G9nussj}GFmxR}LM z3V2{&x};4~f{L+cbDVym_ivVwL#)?pls~jMS(pC^akq!RJ@aJaY{iREFdulCe8#{7 zq2@#4&L?Ol=z1VOxmIuG=GH18z+4$(aRN)Tuwy4#{6mYD8nikXV8P<94LhzToK~u= z2Uj~@<$F><_^8R!9gRrg0@jhP9xmcq`Pu^xln`DEebc`2E z_6!ZGQ6UC3xqMqrl6yVQ&W+-{0|H`nH10PTn`XTHRt)hV0(T9G&fU>ZK<-4-SQ@W_ zDyUfvSTldy{|J=&{?-rwbklOiY`USdAkTLZVZ{?z6~&V7_ivy)2`D1`nCEGGA2oq8 z9G4YBmMa&4nhKw!;Dr*{u`oaqTe~PH=8C+m592WoNGB+?$|mU{i!DJOZ@R|2>zG}4hqo$DS{HiXp3L=H*p zoACphvt3%Ilhgo`U@m+0=r?BFs-C~+T)?F)VzFQbu0ffIa=mEWiK=D7G)@hZjn+zp zK}g#FGAJ%(SV|U3_>C8MwVSP?}%7 zll!y0NQ=$Vlm|ec{37~wt1rthU14Z~v%v}3|KOu5U`_h)!lX64b{3+h+5YX+J;RZ_ zyXi&u>0|GkBXm6v6E*+oxLr#zdj=u|02Sd#HA4J&(d^2TA9(n+>m;w;kC1D}q&QEo zO&d!YF)T2BD(Xj5!)*q{x7tOHuox)l$%<3mazS1U;^5(L8&_n4o)Ko7`S)GZk__C7 zIkETk-=L=xFZ(!eB)~WZ<0TTUobyNJrjIre055at)syOg^vc@l7Ns-1noMm&`%wNkqq3>FyJk;WgSTH~Y$C5psurXTF(=z-n0w+XmjX_^^LDatdk_#8p)N=Ze7jCf%MGJC!a_BZ; z?gIa5)+CR=moWBtI(wSYPDhbyFjEMjyWTQc2P-%+zmO9KeyZ z$$8SbklqWPOIdC<{?fKv;)>4F^3yXzU&M#|%wAsbl$S3V9(Je9xk^3+C9*yZv|O|$ zy9#Vd8-|kv!%R0VDd?R{AjxH04X-+4nmvveF&qU&h>>!n#ssEAeqU%&bKRp{m{B9O z8aRoU&xO44IO6Hm5Bj88NFLaAw^1Y|OdW6)Oha4x!`*$6$Cc!-Br{$gu#&8YI)LX} zV?8=KqkR0+LranXV!n3$v&p6x$DYiaDI9l+XtBX%EV#@}zL>QR)`%QUEr7`Dc^nTZ z`B6aT-Ilg(di&Ppqa)+NVQ!{y&=lZEI^Ay1mznphavXG3Myx|5Z|cHnL)HXE%%CZ+ z6@+We`Pjxf6vet=u;-yzRgBvZ<=b)*;0M#bfq`t&_hdJI=UGvFN%aNS<+S_aKG_ND zxwK-|QlGT#4e5%xXfc%dAanv!cqL(qUzFuOZokvQ3!nNrOuDrY2*YpaADePm` zG5+N|cY%oeL*u3^woc^=hYQZGWNil@B1jSXAMTghhMjLJE^cWTu?s2PJq+@ky3lY` zOmxBnyji>hlD5UOv}!=YD<5?#Mq@f6-LaQ>q~4VO*J&#ZLs+P zzRot;quQz*GFXzVNqMo{ z*{Q8xblU{%W8xZ5F;$Ip(y}hU0$Y^zC_jtH%dsKavOlz`iO$5sL~yR{wJaPG>XU0h zQOe~#p=ej;#ecboFi*F?EE)G0;`7~tA3;$}ub|%Vj^l<3q8t(f6(cwmlerhSE>Nz5 zvw-;?)8DYrZ;n_=4Vz&>>4Dwi2#DvPbB?qHKf!|f_#3|Q0B`iUA6Qv%aUnSxn*Uv?XtT1& zX}AD(P44Emb32vA63Qi28FY1J6`pIw^2%k$tIA>pN}ZP6D#xTQSCt1UKe~0Te3~S1 zqN*ZgsnE5m^8Puou&S!ZOHz|0-U_A4msK^_x+_Pk&U%2V7p^_3cRQ}b;&dl+3{+wD zff`;5j4`vOv$9rw`Q*2%hO0|vZ2-z0fEW`IiWX=*5y8gSags53pxK1UMf+BG9E7sK zo4K2!6@eD{Wtx^so5M)C-ah!jR+k12EM3-l_g7attn2QIpPbJ%GYi(U<4i7VmFLnB zK?Dh`O2#NM5AJ9<@p#7l<(6`lu7iHb1dFy?D<;0m(mH0x96RNc{FQ%oDdRfjx6Jz& zRF2`mkK`?y5{UT@rrbAF1XNczd}>U$m>j(yx;9p5xqdwoH!9%n%?x4 z-oO~WSrutW*Xvf*fk0(5XAlavjCah&j{qq0GRmFxELx>bkA!h2f-QiSZPh2+syUm? zF=gP514+Q0h(LfKKs8^|DhHd0*8$CVGC48>OyktXJxK==2cIG^bo9cW!(cD-f3*;YlfYd_|$4ldxE4AmhP0Q#~@ zUC8R@TVQ5lb!Jahz_HdtYUFI!;0b|M zpnX5e?=_V73bz+YXfeQ)So^y~0R>s+KEvYU^J_Zwc<8@$NWic%1foFZb9EMAf;fu4 zEonLDI55Pn(|A=u@I*1ESkjU!%O#yrLLseIbtBK2$uRu;%2)cEYSua#reUUlqy?X~ zU$eX@w}LC?jLK6p;;gL(ny4=5R)hbgi6#b9-aMIgFqQqI`RS#Gsg4%*hyPAOJsw-j zbEo>RA&V0@+^R(unH^mkH3fUIghxo?j!x=%rmNpWz~@OKKxo!A5{KQI*s-7gR7p#L za`5w}X>)aAWxh6Jbx7}Py`z3>3+nyb>Z8&jra`M5Ys#i!Akp?htzAF58=nvai6cpU-myV`9meN9?xhn(q7?dtjBYl6Yw&{-~xn(H2y3A zW(pR3vYG-qnwwH zJDk66e(tanS2wr!3jT-x%W~)A4N6uTAPuW>+s^@q2&U@K8wJ~LO8I~STW(kxF@Q@B zC*Vy}1C7mRf9e)p;fq@<$zn*}QGOqx-LX*BnH%s_VrA^qlcwXWy@mZCSq`D?P3GRk z5JMrdWugY-QveLR?3@wOdADhII&@GuSVl~pM_=d9+W_t-%O9mxx(10qB{bB3+>}~V zwv${tTpG-Qv2fGYe%<8ie$p*RST5_I*b3bg! zQ&7u$WdLtCC>W~BpE1}WfW$H(B2C>(KAd+Nhmj;W!MLAZ4WZC!C&aDkrL?n%2q9m< z{gc5%6)S0e=y~Do67B60^Hv_7?bALHTn{0^L58J?5s%JTpACx;IvMfsg`uBYj@{+f zQa{6rL}cx~?b^?fM~}9>X%UqVBA$HQK51-N4d<;t+{=Dj6xUaa8ppsGZ^*EBdb*&in*>XhlqdOFTqZY1z!TI}lu$9l-j11aRx7%j! z-1zXX__NWA>e!Btccym!75y<{<;5~Nb|%H!0{D%)Qe!*)Qr#Wzcr+W8AKSU0WBgJw zwg&^JpD}tq)Y{zu3FbGd^8VN8-QWDc_{o_af`4SWxAA~g+iUZe3Hg!z|H6A^w#)Yo z8A98SsfMp(b_Sjk285z6p>})tTHh2IHvNb!e(=0M^?AdwJEeJ%uVs!t5o;e^hK~N* zA*r?(-!|^&iyGs*Q}@L9aiYzTQTuzYs1N45qpyxSwY5+DF?^rb)}0#pI5ocVeA`q^ zt37-7)$5~kXDGAYyC3Y1PT$#?%7~n9i28hPx31K1_T0|gv#2@meG^Bwy*HTWZ|`+X z8|{7i7crk2^|U(bt5HPBSL4A;9Sbk+z6-s}K67{cUewrg_Gj&m&NF*UULC!mCM)n4 zXL>u9{{Tb7~IMfYp1o?cq=Pw1|fhF)G?aVN=@S>O=I~D(l6U&DA<=fwxnONd!XU*GSeXA>dCw#!3!fT8DFg-*_*3( zUQtZr0{?A{FZD9xjLV+iKYn2=Bs!OV?)Mjam!SRmE~wHhab2!zP*_#@T5!&{;kj2x zGM`l04jF$!U^inqh^NcAz*F+JC`8 z{PVAx3J`p$R%9$t#%bbM?4Btpd@!IeqSdLA{oJ z6!tud_7R#I^?mrNq}LJluLS?q7SD_eX%45jRq-WV!rg&=9H3Kgc`Opnjr}d-v@HSA zLDz&uSm_r4JSguXBzPUiKe0c2B}HxeFA01m$i|Z^cp492yxXwCM6Ss21wK_h9F6oJ zGcpxwF*heru^9J()40uL76>xAe>K;joVbvR+zeWvay(X=k267|T^Ebv)h8B9qs$x8 z;cLELlAaDyr#T$I9#(FP#?s3SE_^c>Zgz;C3*-&&wZUaC^1f3@JJ-_ZLwcq85i4;$ zPi6MKXTozT|La>h>XLUs_)!TlJyd_5_mC3A=+_N37__)~RY(4PXHPs z?7W(+sb({jc}j3UNL3UT?Xw#uC`i}~Em5b(D$>)qFYT=NY0N~H9gR^)TWK8rcYeUk zeLH;~r!`v<#tv3C1XoncO9pJLC%zb7QXX0A7EB3IJ5E;?0z+sa5Vdo&XM!=xJ)5Nu@3QP85faO8}ffTsGpfV(-IZ zF{eoei>4NFl}a&(d4m#3bltu>8wC>cI=1?mA zhIeq4@IHrFV-SV+1P+yt!%RBkp7nvcjQLTq)2g47RPh@K1k1~$w-^GZF9orKA;7>( zeL5g>-n@@LPOA>%?l>qW?J>;7q44s30T&=vAqAjj+fyv(W3d|&KnH@8R{8LfT7-)Q zIgEBWqAuaGj?6%c0!=~Q{=!h=G*6cW!hh`(|L~pCLvtdh!v`}8KYt`VlW0)nB0M^u zg6w|}DXI29as1p|on&=6p*m0vIJ*^RS;M=Y5K0C+jN@jH(D;(U1H81R-t!7tH!%m; zvS<^&7qT{+32xeuu=Z4ZT0==amR~P^3N-v|LJBp1;OBkbj%#6`9XtmHFid^xSNqNk zQcyDj6iqL-NV$#WWL(v?D?3mO`N{s8KXZ0FQQ#3xeko4!&1uzyRl{dT7f8_{w5F9( z8;%CmWTC?Iz1b0hUG$foB){T&||x3DnyBwlPcPFhai zEI~mFBsUo=4Z)GsO3oP`i9XFby1AZ8Qhk+z!}B1dt*-j`i=jfBr4_=HZEjo{DVA%I zUN(b?56}@h@8x?hY;HVYsaB*o!pvWn)~7!(roA)%E6<1Lz>$x9(bm2Uxy2?ca0cgT z{>T}M+HAPAAil_T`m!>7onf9^vk=>D&rW{%rbz0Lug&QR*_5VVr9#O*=4WK33Kn!q zHKMNCrsj@3Owr+h1*HQux$oh=Ppe&auQ|g3psrlk8bC0A>1l@ogHu1gs?|Hv;Em?- zdFMKD%IQY#lhwIP_gu$j$`7}!7hI1T`Z>9B{7>TtbP(A%ZvtUzYTUn`9HK8nw^XNk z-+8l4k#!gJ|Dr3t8ucUUdj97_7|BL-P_jCLI0_G=qUq>0ry+CjJHah0JtYZ1VE zP4E!8cw9i^K+!R`ANVHL67*PBP&3Y)0&i0{OElm0AA-;K8o!1<1J(#V&(5n_2vVETE>C3m^iD zru{q2r359(!s72{o**f*E`cb416Rad-`f8*Wa%06_n?;#(}{r5wmI1V-x@49Xl673 z9=--lu*7(0g_IfK$5I$DeaGp|FV6suMUSF-65Mia$qcsKtFG>#xxbm3zk`86v7fK8 zVWFC+9`;xXRt|MQgOC7Ck03(ouJd)h9ejUE!WZS)<`RVX_TzCz8FwZC94BJ)NjEmX zBM^9W;CD^~@5+Qk-6l=o3Y+T%L*``=uTt9Ert5ME=9Y`@-yCCB$5@;o<}X|{GB1z= zulWb&JKP*J)> zq@)DILb_7~5m7L?=jZeN#ktRY|9zcn=i1rXIoI{RUhn5)ySpAiEyZ zaB-tVnADq+&#x*hxpybHM;m$fM&|4iR>mv@+vEhNK97bl^3s{`-h0Kn|Cw);`rRcE zZKLI81b(_i>OD+Ka4m0&rJnCg9^WuCKUb4LvYvoyKZ?kp?BXFPk~c9(E%@}6fNGP_ znGXL+9<{R>6ZIqh2U&s){X7yO^Hxox=d%FktoSa3i2BMaPst&5kKS=KEaFY2RRTt# zO;S(>p#d?`>rJA0^5Th5TKy0))24x&%pyWTXfGDuOcw4Ud5KmQl~YSG895%;gMkzr z0$@ay1VqkgvI|P0Is0Lks<;GRq1!{GcbjAxamZXjWNy{6%_HG6p@dDR194MduDKN{ zIlWM6@>)eI%8}*S7VZ`4HRf>>X0J(JidBpez}JNXBmf|n1wZsy3Fpi3x)}VRpM+cw zkQe_V@7R3Kc?~ew2hui^K;v0jfXK}I02Kkg!&SB;b40)?$F4vp!ba}HG>ZB}qEPu7bxreeFAFOZbm;tcnj6kx^*}JQV8FOK2%QFX58g1bjxi*bs zMd~3uve=jE`=_Hdc~OO9eaBGbML`9r2H2x=4e=?^m&ZJFK;#%uSq92J<;J_wSF6+u zzVK%EQdpe@Q)h#jo}t*WVr_Ux*x87*o)nuPG0b$YNg|Lz=?My;G{yl!89GjwIWl}r zC+@Dv0!pT6zRL+Zg@xC=EFt*nOPG|{rJBUl0fRncHTN;IVOFhH1|lG{{pXAYYyz@V zh`EC8Fn!$yFv2H?ao-~GuwhpRPlY6Lacsy6B;58iw+wFa3bTofQZ!}Hw~A&LS60M+ z4(YfVWX(LlvL?V+qbU*A%h}+@?LxFMY%Do5d&Ok+>K)`=1f!tF5D_^iZpUz%~sOZg7puhmDhik-@tFqp4(r=oiFi#jHhV|W|zxg!2 zys{enKEZCSqh93uJD*Gr-@1q|g>5{pM{62^OUJD~;cb3>93LMQ%XLnp%BH=An$gdc z0%q-YhNc6qOndSt_Pi6p-vL+|t zZpsl}?;qK5~bUA0l z^~mVDlNUFYWh<0pTHlAmPD) z@7lxXPvU1!;?^UtGf4tZv4{qssjxlrQ!B7oz$};(^*NGQX@C9u3GoXCXrjs4umYVz zbTywj}Ns5j9VvkO<1^>76r*LjqtE{EtJT`Qfia0yHS@gKgR{Un}RXag$*p zIL4OnRGFz6C;s0KNtJX`c}b*xM^ar$Hf)83{u641ku#oPWHL6?OQg%5;WEeu{UFdP zl3~|0SxP1d%u(+s6p4`sg(4yS4@J_KQhAzFtsMOzDtFybdBR+k=@iw2&GslU17p>g zJ7%F|()=mGgMlW$e3OJ=%bwv-`kp?E$kpu(yH}EH8XbGzLNF7+b#w@*EvE~hXS16i zSY`tb0PL|*oS5%f{Lx%Lg7VE*N!I@G^F#n!Ijf#(scw6rd1qYOJz=%x@-_bJueHBa;Z!?IX(Eu9aOm|KNLL&DE|4yEI`64iBDR zdq6e&VB)4kXUM~MH>o>+JY1ZUVL`;azxJrSzJB?~qb~`M&c1Rd?Q=i=eO_#BF8bus zO+r zhx6ZTwu})qn-fMAi|DA~4R4hxpR`y%X>)eJ>NxMArs7#0=xHk7<~7$MHs6*OBT-l! zu+FhGWru!a7x<~IqkO(2j-%7ot~0ewL`d4@onlw#{9?^z!PVl<^657YA=x!;PhLI2 zd2nu#hp$8b`|E*XA?Wf;{C$#Pr{%v zZ>#x&Fq*MbD;VmSa}#94+rleQ_(>D%4nh;b+U`K3fAJK;b1kj$QC^WAUV(^S$E^R@ zDVmoBIA(}+g9|$8D>gqtW0kSZdsAb&f;Q$2&s}e8cRC5Kbx-iDPR!qSiTYI~`df&= zJ9OjIfc$T@BPUgXAwa+u%Hh}O`?jf!m@y{zf20&rZ}v8HfPw-BDGvhw04XV;2~`XGZ*Fz2 zNiHsfOt8U^dOaI*ZpkexFq1oLSb<(fuJxZH$R*Tk8kslG=@ifa_*;SImR7#ok(I$lKtVH&S%hnBR|qXCeU;vt;w;A8*#a~GPk$OP7u4E|(P=}y;E663Klcb7_Usa_Mm{g*niK}!sws>g~X#T3)eibE3k zLoPCwuo^ZITimG!gAEaZnn3&g^L4keX=|LpSGlqCwIM`)5Am$=%UB=_CzPQ?kgU^e zpin7E@l0L-2y?9g^B^pp@}q@L%;MbB4CTj5Ph)@n_)VwMu+o=A$D@&{(zy0KlSeI{ zUbSgsG)4kF-!8*1er~y;^=2x;qC0A(!^A>JX5L76V54>5YvsyL)MUKmO!+>KjNjF^ z*at%|=6E2bN3&Y8Fy|+pHfSe5eFk>jE(5okszVIl6KjXN`Z6hmH|@T)3G(Pr{APAY zZ!+skYKaen_rEAAc66=)u0AYYYqi$3CtRm8z!CA5DYyh$=@DEaqXz8EEs4dDdDqkD*z7_P> zGYN^W^Dc{6`d*b4#iDZMZu}n_A9BKv&MW09eAkNakoZ*oAIb2Nz*ak zdw1e#G31-sM)&hei@E(Mugk~$96OnetVFIhhwVI0)Cb%zeo@xkuee)Ucd z-PP{RN5+rK_b*|t_WU?d3wZixDO%>qFGdr5U)i6G!&0D(AAW%G;=+}Fmc&lk3iw~o z#NKQ?_|97Kec}+sGdc3mZ}=QMjNx%;EY~mm>zSM-Q~kA22rd$z9=g4V@;f{lr+6lc z00)a;2>burGbsyy_17~=boy`4#547;XChG(@vmp%`|Pi0vgh@WXVS3{!(^sXE6n@< z_e`RFT{=fN|k>%Wd(nqU9@ZTVR=O}Rb56ji+kpoMpv(GO4YuSF0t5jNS^{}dp>dG^H1 zzsf^|5QE~v?+IQwfoozV$(Tdxe)y$NAf8hK&1DM^I3ydSH_`_QO2txHnK5))@WOUh z7`XCRKw~3AbWGc9hA`QXU#x-~+*1tWxGjWyArM2^Y9G|0NsuoA(}pxcWOneu+CItZ z-x_a=hcKq5D}XMv8R+<&A((>&Wc9(E${-V*DE~HY5UyZ9sHS8Fy+KNqO4-pt8(HRL zfPq{|pLHX4`e|=J!Je^10$n$>z*#&%u?S6rq4E`^J$i{o`ZG+Cj}d}`(}~BU`)%&C zL^H_tQHS9P$XGJ8cEO2CBNxQ}dNQ*jj2HF`&Y%?p;Jb>bU|uWFmOxCQV00yqmXLJ-$*Kx6(U>y$}Rc*1*sS^kkA!$r{HI`+%W}U$xJSM zMgdsNRIM+1CWf&43~?h<6Yz@G!G8f3NEFBn5}NuCzyhE63$UCItJxe|VYqOlNH%-t zK>=8x&e>LXs~+}R2F^|oKgWWLb$o0e8znppvMu>3?1rReOz!Z_IMesY5)#Iru zr&ZmmL~`mZz|egcC4P3y1?7j^Q-mPCYUcaVC1xs`TX4RZZZkLzVrf<3CGicH`$M@;?*{K-~*U_;Z=31Y5e)Nj1vEc1ljS=I)!b1{G4`>NN@(cqb`seA5 z69oCc-IU1U|Y5pFi$c- z0y*NAoSLsq^nvTJOzy2#8#ira(8)=nmgsru8dlDnc+=wI4wGaSvV<*EUpVt6sxU@_ zE|ZQ+#hwfl-2mLu$Xx%+y(||-1#doDr|AvpXu4a*!Uye?=b@u;FCOXcD^;}0WB+h3 z*{mg_8w0#IiC#v!@c+{J>nZ}2p&|GAF7k?`K)=E6e09%vGOnXVlg2*zItow ziafPCd}4BqkY5(Q`Zd%D$oLjJ1RwUI2{KHiA&}cxd)qpUJASjoKKnJAa(mnHfnK@1 zdxpBRB#e8-JuH`zLD$GS&yWFs#z2$>CE@5<0j|m$428lre4P42SWwuCDV&;tV8OsX ztRg=}Z3hRd59$2Uj!k6+nUq^^CDU*ifP`{sD*G6-v7qas+NcxgttzX%2$xr~*6>p| zIk-FQohGNPZE7V@gbe&OKxDTEz6CfavFZOj)ID8^K~oBQRW4uOMsmvqbq^*XI}+lm ziDCvIaR-x^Z|Ub-wMi)^_OisIcL~B|sD~I9)eaRu^%X{0xG6cJ@JSO%l*=&3eKo$I zAI+y}IIaW23>pi6yS_!n-_S9d`5a-R9}XQ!@YoJ#HZTfX(RRIug}&7+1NfB~gqyZr z9~w@-r>C2Mr+l?DKa(jxAvkp2hS~@K66u8?TUEGMKytbUPqu!S4|zyS`2HGLzD zg_dYxKzpGv{7T^djX+j7=&7F3sDa^YQe>Kt)sF(eV*s5H8Twt8;V!`8_j}zr0^_M{ zxR-21CB&o@685CQ<@DHicqIaqloGGUNI>MKdT{L!91EJkgH`G0lM*FVjKoq^CaJ0X z!d#?FqB7ys$Gr)3dEVzr>@+yRGnz^IRG=(e9;A*QTBpt4K;PwH;Mi)ejD;FSgw{l+ zXxcHVIy%TiQP)~99^v(<44re{c-}tLr%O{|_-@i;;ezS~n;%krB2b-0sT?^FwZlBr zlO1f{pT&sy<|%)(B(XvO#iTtI3IN+d)wWZnkFvS0ThWay)?PiIuhia_)cDt)Kg>^RV9 zai*JS{LCp(3I*;s7dTuC{J0%2-2$BRxilD_`Aw`S-hg%@3=~O8k}8^`$WZ2FDraW< zBeb^0xlGWgab)dZiY8`;HJIJcLoA2%#-_ZXAm7^PWvOkwKFFU+b?23d!>$cGI@ZkV zcBY<3I+q_QN~K#dDcsBILaoZL#Ih(o5JjRXF7;AWic~^mUm0QRf+TLQoCV=v3@0?I zW}-=tSISmK6XJ?hczO+ee6X(e`PWLLvT=z@#gxw8>o7Tdk#Y`i8?>TGHf9F;x8Odmx}Ub!3as5vKAkBGAR}rlHEM!a{(L z->otT=p;=4w~Bd0(Jkjws2pXU``y*FH>?kEc@bZ^-YeS3%p?$QYJ&!`6tw0Am1_AF zCVN|3`LLpbO4hJWZ9fRfGmWxmSWDfZg2ZZG^)0lp+AR9^1ZbYxCh!F6!@1Y-8E)uzenRHDFMrXw`wST#r6h(Lyn<0g}S*UuD z(fpY7AMWO_c9XXislQFD5!T`v<6>tEs&0oSy=OQud_>g$FYN|ziD?g}Xg5asc~QBo z6zztAsuELu=bRGAQx?vRvO2D1v{wA9-6(ywrD!*r-#Z*{wJA`A!o2J~{o8n2f`EFk zQ3T+K%tB`E#sbaglMLXjTT?ZNHXzOjG?;h745;h-ogFPw`?u*7VW8QWy@8Yf| zo7Wf@Q?#24_8?aahvXO*bc3^U=S@ zo_+oDI3d^ClpEY)a2D690HDs^@N1smAxPLnvE!cH1wDCV|hk z11Yl6s7`LU*4(1lIDaTM4&32tvEBC1zcfy_KyI2fBz&u^vWmL0 zxX??~LoAo~a>T<8T-g-dgU*0acBf1P3$4%drAtwn7BR5)%qcw_NL7G5BQW9=8Iq^s zMN@oOMYY9nZlmPqTc`ifIDn2XE!QrK(cZ?Ti>4Tyklh0}2j!;p{v>J#C?}WZG3MGA zWOxtG*!s$hP=EZF#(@Vf>%h#%pC`S|NlkoiDXa%>_t^?JaBbm*bc`67faH3)B_Coj zs5AO9ij8xq7pp=?CS%oy`iV6R7M`wrbt=HIiraF0`I6eygbte%=SP}u!>XZW6c|Ug z@aU$>1E(==CGI!a2mZ!`wdegDZCifRO@0Aiiqx6B=%AYMJYs9GG_ASh;;EKSvez{o zBbmY^l80G}L6GNV{B2KDoz2`-g^+y{&>pX9(je}Yj!mV`Xw#u@sqoarA5-kTQPs%1f^4W@l8o-2 zvo@%Gl{KOw%<(d#QcGSB2Ha2yNKMxdoTU2w5`IDF#OXCtneFY4x2q=a>XhDfQKs=I zT8<1Gd5ZO_cabWFOuI}{XS1NgIiGjUQav<&(D3D9VJN5)H87)4?c@9|GV6U~sJ?(j zsQm@?P^t5taq3J@&l8;0(QsX9LsdxG`>g$$hXyk_QnLl7vqgckH?n3+>t}CG%$DuX z-es68mzt|IovRL3nP8ys5QHXZ^fO=={_D`DgV? zPox$GoaK817vfm|lQy5&UwFx|_)2Q=jp^dMz{U4ji?j8M^JR;awD};z(&|%@Vbi71 zN~(pdrQQ0ay@{p$#lWA$i>0%Pa|cpqD`2w~Dvgz+ ztQDFAL}0@*!@Y2&k~0@stOlIzdQk%aoUKHgAI{6Y

6;mRT(SxzQ@Cf2g=$gKwE})B<_gL*XpST10-vnY& zGLXg=NopmT(jL+rpqQ+ZG?v80CF1|YDdN4bIYkT6f-k(m+-K}$)@jIhUZyfB%Sc|r zLHrOL8~X@d2>WDee09_r9vdnLquO-eIp|%U_7r%&Ko+b!u-5Qk!aNODrVXxpF0^}w z+v&ar8_p{*H#r9sQCaAFXcMqVB9RNxfiWqVsUip~@(X0cR$+Q!k+@>8Bp(DrMFtLs zGhv@%H(&=~Zx$5Y)s2K zW1(KGm8qL5C)Yoy71XS$(p3y8x0b&7TUnA>a^+9&KUseU{rU4}Ps!}RBT7e=Eh~Rq z!L9nYnp%6m?qI_QRaI_#YqV26)HDTu`=HKq^#228+v(5^B>?4{h( z{8d6GFw<^G-zVIb%}$IbE|cr!U6XbsT};}UG(3rwbQKuZ@`$}{iAN;GhDP3n<_CubCivI*p1?c5-9z%MbNgMZTzM`Y zID<#RdyjG-1cudG?>%U-(SyxGlOXAIBl<3;j!TgnP-4tS>{Mv53@1G$3#sF1#q^O3 z8uL6emGvL1nnhwKv0Jmpu$QstvgPc}tY^$Wj4zNCX-|Da&LI6q2;nASKcE??eu%xX zng}y22x@#=J&jxj`)lh1^E;zO-$Zv&(_U>;9s}l(tWH+jt!8s|WmTi9tCiznw|u8E zzpB3Ke)XdoeQj?2;)YvFyQ-(AOuJn_-q_RJ!#dmk+S$od=bIDMhr7itAR?$1*ug+g zA55M~-B16()Us>1m-s`3R?&RPbLrcJf5FYUEwLsMC&$Y5z{5D4xG=Fv;uBdg0hyqb zTom^cmB3u2i2IQ(XKi7aXdS8h$rfT?!b2Pl+XmeSIWoRJ`YD_dIu=OtS9za%O2N80 z)OFs8bgly8PlbJ_y}ca^9jVWG9a{@Nd(4l@OJ_(SMLuq5E~r}-y?cfHX2$5RBKwcTzKv`0I- z7%nf^x~Z-)E+yCyT6v6~ecrA5E$KbblB$5Yb~aob8XYVM z*nGRZ0?!#&2dB$^1;`N1EN#qdfD^OINHp$-hT>mnNvzi|(9hFv2e!dnpyy*u5xAu- zw0dnj9ciw+?k3(>zPW)8q1;GrtUF>O%7mGOuO_ylY@_{Pkl09I%PuYtu$4-KI3dAJ*#C_lPq>tUFL?&U`EG`CFvK^N>WuRFO#PzYUG)TLlQESs4X%b_rBL-p^;BJ=q3U|WS^u>0oylizXFX}dI~F^Au0x&y zK5F1Wuub?zL;w!p7l?A?b70#fW4>b+W7+T|#o?#n?*sL%0>2fH!Via6Rs-|drsx7x z5>g0u%xq{$QzP2Y`PfcGaEAM)4!(eOwCHU zu3#pONL-dMMB)|B=htyMvQE-96dtJ+eh%g}GCO`Q!U?VO2R$oXNXH7R-89vJ(bcN` z${r1G>qgXaYvNT&Rr4x;SM;jTmtQSESN^`7STU<2T=A_^U)7_gq;^I$gLAzRRWF?j5I>}FgKJc4i(D9ks2!+jTSq}|EA$eCmh=@#@2 z)Wq?`TEYZ^0XAbFa3ow)AY?2-uLj~}BZMY)C^{+9C0rP49_${N3(Wekw=?i|?z%nj zFDY@(adMpB9H+sS_|uUN4Gbap;fJ~To_fz2?@%AdZ}mF@NznO!8%d8HizgxPpxR?z zV3Y7G2;Ybz@;J(N>Sfw{x|NX(M2OkYAj$=Q&nW&IewdFH==qQM#r$00r*!}VM3mEq zvxohR^^m!SA*G+7vMEbRUkP0N1ne6)dE9}Y*LBg#aMRFHV3@A*s@xr2w;iqRHtRdf zJ@aQ1&NR=6GM+aKGN24E^hfmv!GsXi_b@y$3;;fqz?^LvY~5n}X-|e*+iy43+tODI zorl38ZFosE99xfIqfVn0!0h=9-%c?in(QDyp^T-Ps0(N)`Zju;-j}hEv4Am{!3T;i zo8F(cjCvesSHHm%^p@hkZq1gi!A38o3eK$Pb5NAs3&XL0(m z|6$3PZu(_t>6|6Ii5Y~>Ku2hdq9Z)8o3RHyevcRH$#xYvs%(9%@63g!M~0sIsP-e+ zhHtB^%4tfmG6c>Ijbd3| zW6!mwn!6e|=)Y*inw6?h!;$*Yb**Y!*UYW1ui9BPuWD)4!K#v~!s-{*GitJGL$x)K z&qXPF0@b>eR;#;dIBGg&xoA7>_!le*O?|Y0A$TV|Gb)LHN32H`Vk)td@MVNXq{ZX| z(35F$NGs$3*jhVq6d%72O~G9;L>{#@@yXEUBqO{6Dpm4pJ8I{<8Bgk6K6?D3AqX9rFcmnVIN*&&Q`XN6Xg8E-Oa(X_cK0F zPm^bYNi_rwUO9dp#(=;?D+4_}nU0gzQpgK=X{Xt0`31?K z`_@TTlC_Ivu(_%Ev?b53a%Ot&d;js7y$|7ZwALRCtO|L%6|Oy{@(Fp``3G)#hZ!Du>68pfW_6Z1=UyHr zzbyZK{;Wp)+>KfCjBqMDWsst`e073F;4wm?> z(E7gTX9f?39zvI|O+*zQ9#({_!dIelXu;pWbjM}jo8h%^cFYA6O;h4l(n9h$awAd} zVK4SL>V5oLbTKp!pM}=Iwl~dN;T&n}V6HTD)}PmX(7APMb+fcN8lifSs#}$ETkj zeV+8?*O!)Ge}C0{i~gAOr?jlLI$ya(H`%lR;a@GhX^A)D0B$geH+4^!f$}f*)8}h&?fi*CdEDCr;+Pmj<_2m#au@( zN8dv4!eGF#8^xc(FTm}_Ig z9Ff+`lyCzcob+5#oYE1_^!^M}#=LZ0iX$l~>n2SX-xMwr7;%EkCVT zo5(f_?6A8XI@bp8bAM+b>oG&qgXe*eTnUWYc&G#9F;UT)usqa1@XA*L9Pp5DLEs@6 zEO|;&2r@)7B=f}+MDGMscum<^^nD}&t`jOdekpP=bR#g**VsMO z-q}1^&(=IwUT-+vfKrZ97Au=8S2i54?@({7KdAvz=3pKt{lkwX&ZO43Aid0P@ z%W&|)*E<$^hr7Vn(gn`vYXUrQq2CXk3>F6D{#!taxa@xwI2-%~{HitnL*Dc56xT5z zYIg;S-9|6dhX4bc$Ak5Hyr2AYLl>j9h{fnm*d4eFcmv@*=>WAma{_m~&?EUJyDPt& zG)R$=ydi5^*xBeXeyS|)Iy{T@MvRHQXZ7H^5Mll6?ut#4(9hnG@Lmc5v4DB%Og( zvP$w>Bo~x&_A)^0o^h%1_(@(O${cz zMP{Qpw|NQzo8UPRj1NK{KvCfdx|pzxyq%_CnpiK`r`VM&6LTQ53-cu8qDHYWtlNxX zv>s$9{uV}${1Wd7o3D0Ze6Y}`b&ax%Er*Q+u)}l!v-lmbyw1^$*PhU9(aeB`!VuFt z>p#x19=U&B@ZYd9>PBcVJBUYUr`bt@H{$gPcM>lp-Ak6FVKdw03~uzHpk0%*O(Kmi z7c|LdG#Z`f2L`4iXIr))^L*;oq{azE(E{F4_5fxI{XFFuF@mi?ZjYV_uJE;ixwqHZ z$=TYm&o;qAG&%G|Iuo#e_Cmg@J2=aGf$6Td$z^N@)&R6MZmV}>x(>mtX%(;$h>nQ; zsN;h3lB5K{R}m{Pgbi_VQKvD}owe$17nncqslcJ_@lB*#SKpdjk?)OGzh53dkBP zAz%oN2xWvrL@p^tjW&?nolpW(k@Lu&@o|xdfr*~i_MYaEdb#F~QeQv5?s3htsyP+J zvhtF)e@_3V{TWokFC~|&Dn?aps=i&*v+iiaEA<0?3rn3t;r$T!9-a{UhM0lg30>tu zw4SU=?q$I$(R3gmMZ|q18p&(v-GqL!ESWH&k>tFvgx8dl%DTxoK;J-XLLE$gLD-M& zglZcviR6WcheV-ZaBuKkV6VT05AU7qx#;=i-R$2MTpEr<^wA+;r|lk}hbTlHz_h{N zByNKHp@s5*(vd=;45Oq{b7?TUppT_DrwyW{kSK(%I25)W<|!Kezq{AQXnCkbAln!4 zZ1J1~h6NiYs(6nUn54~}1D(BH0?#Pl5nw)5gg-=PMpMC%P>4K>9*Lt9N0LoIXub$3 ztr67!;BS%B>5SzJ0i&Guks>Cw#$UmlKp~L3VLL*M3=JB+M3=&L&~#isR@(sVvP;U& z4SS)r_Ohyb<&6qO<=U#XHPv<5%2f3$O$(h{-^|q2+Q#Yhx>_&+WZ+ku z&l@ug)AYsq_J)PV$EMrn6iZV}u4S5~!1~?R%{jyK%5Mr?fi2@hdm8 zupN-%*oE+^;3fY;Z>_7=e#)}d$k*@I#x(7;bX`Pu2M9ea46EQwy4v*K;$UoBIRo`L9sRQp%f{; zBzVN*aEsW>n4f6F$kXtH(2L>;k?q0j{>Hx79;(~w7;f8Y4#L?fs{Nwv1SGyXFz!$c z2lae?ovyupj-k70fn~Xk1zUq2&P*rX*}`dX65U0f8t->s#5c;941CyTu69l>yw1a% zi=9$uHspGqS=^@cMuIT~zMp=YPMVRHkomcJACPA%Est!2oP#{${Mn)6NS~M~{shUv zi105+zzkze=k*XS5M!im6PCy#iO!_dln!vdeg;{s%jul7zA2X!t&)~D#R^j4tlVst;XIl77ZRmRSi z$@YI;L%mo0TY??KZKC(%&rt1gXwnhtX+{HU45ur%j=PFCpZ`J7QM5-qQZiIBU3^=( zk>8Emn!StJhQXrWg?x1Zd>73@X<~1}gMuggvwaJ^V>~{W#A&yEvM|gX_~tog`ejbG zwzS!8{ecDU22RL$*Llw{e`4rdO`A|gukAkA@q8k_wn75f<7)|MYsf)?=#M6W`_`|qN zEEC-v@j9Xp(7b1ym9~?XmnJu)!Z&FiD&N#$Yf37oRn(O8Dn5fnqhr<6Dqi*e>H{?s z>jcUJnuCU~mTJd3&lCTx(ETVE8Nn#mX zZchR`#w=%ZSFwAd*XnNwRYlhz&ceGj3fm2KWA6zENoS$uTF4MGWz2DmWcnlO6v|lg zc~XQ(Cmz7hz-~vS$9IP1fn@JF*G9))+Xf5Q)JPv!|82n6sjBx?@v91|w!6$B~O$?V6Yh@{f&$;3EWP1muBMKqsjV219?sX_3;f zGW2hd8_@WKzN_BF-U9C~&mi|oXM4NV@)Jxg1%_Apz54OsSCHutz{dThnydPu(x^9U z7wP*Ml_rIylXZhF(=pdI(OVnX9+4r2qN%uc1UU&qDWJV$oM)foEfC%p=Sr)jdg%da zw$vmsO3+fLWQrtLoF`nt8_v#Tw4z=kEg($8^~F>n>tpl6@BLXGpJSfwp5>^y!ju9G z?tFt^SFJ4oQz=__N&nrLWtn9g@1VQRy2k(su~}$yv^g@2DJB$?YpFNrDbQEU;?Q_$ zf_9>~m?a%3{Uv!U9w;&h?tyP02z;0;?CVT4{X2O$0fS|r_Qp3x-@?=1XN@yqdvFbW+tZA1Om{7K`xxg0w;Om896u{CHkce98hsweqpawr z*dN$3>^|&h>@4gz>>OMIehYpwd=lGZ51=*3BE&jy7k>qVT|sPSv}xpJXgJs@b$$W7 zANC+K91EX|(4w=zqL~yoA$p>TxH7^_@&RgB`T^K23D_?=uXs^G6ERcTE8z$56NCxF zq&FokfFQJ5{93e3xSaotvykba{!5yPzlkYC{fK8o`JqF;53WjEiaDrXr+uWJrs}QC zZP3*%fiw}R`b3qdie1xC%Ws&VTBDhw%Q8faNtQ}mp^NLw2{n%CkX-B|e1OQJw4t4Z ziM@@xSg=_1KwJQm!|7mfa0}-O9=}7;frE>30ErO=+w!qil4?4`-o!EKs}N`ql;>h1x{_#2cX` zm>Jk4+$!8FoDBaMe~_?`I1RYI$4HgL3xsxfFE)mmiIHGb=xyj))N^D<#2s*D&xz0? zZ6kG&Q_(I!_o|QCVx0Kl_+7*l)MiWrE{B){rgIM!O;2HLU>33qxSb*Wv`I8TTr5r$ zFBH8LHU+wTu>iifd2P9stl#vh6dhqRb_wcUTo@f4>gxaC*4zJ@OZCk(jg{Z)%r)bx z-Ox$@R3~vmE^Sg;ZQ7Ld8kmqiPQQ{?m9kv%DUp;=EN(9xz+cRr z#ilcR({jl={4}c$Ctaa={loNdrIT!g5QATb>u#qnzLeS?^dw7r_KjC;~SK7W>ds!Bn zRu~TIh+3lNuG*-cr@?EN0&B2N%h8?DJ=HHZzA`Ve^>b1^#c(3s5!x2{5?h4ahN;D; zlT&D?8QWMIb~)z?cOUOE|Cc}~yeujOx&lVniQfS-Z$p_k>B+QzDF;afgaKG8>U3;k zcxRxaZ-K|<+V4z+&;1EYscEQjk|D!z4}L;snYq@h;BH^y^t;ME-TebYwb9$i9oP)w z5Q>a`o+;rx=baP&kdPB;iUTQ&!EGf1zz`<`pTWz>%8+K9Pv4q$Kc$^wV&ZV=M9~d? zM{b;TmBFS-$*=Je%yL9>v~P$QXzTmnflQLKgJZt!x+Tk;Zu(`EnJgv?a98(Qk+#;h zmB79-+S@p_uFIaDzK#Aifwh6<;Fnw&_zYxfL}-3!bqED+z=i&o-ekDR{&otTD;;<2 zJD^#RXYOU(qwlC&4{XPk+7fUDG}oo-a5}N>Ds0U57=M|E+0>3!?*869KRSqj23r!M zKl&{0C-F4JM!N%zo7U`UoZH+4{(eE7@S(^Eyz}8g9ltN{6K5gJ(NIA4zDVs!o=fNe z&ogQKaAar55m@C%`$l>;xK2CjY)h>dEn6)@Yl3Z@{k-F=^N-8q?(NO?pADXg&=5z_ zSbQ=`O&LHx%yhB0@Ma2^Nao4-NsW?Eq>M{Fn|dO3ed@B*6{+)5CE)G8s#qw$lWIVaY!6;1ULs#V5vL+ zz4zm>_OYTUnC3!zfGqsczsrBuj|&VAECa6`J@DQC8%|6!gE+8!A>&rWAru<2V@Gkx z1O{n5#Xy_Qyu;qe{mwhY7YNn~Vqmc8CaM+*#NR|E!j^($-WT>%m`RYSKGJJKGhAcz zo%pZtJAawGg@a)oW-`KQUasEOP+i-m+EI~JE-iae>M7k_HoQEgLRWFG@?}-0nrU^- zl*cq<3~enT`$G41-^*Z4qyV8pSK(b`BYhovE6*V)6HSwpOS{RwCiY1>s#uf!A$fRm zFU4m$Epbdjm82MM_16UTybBx;(@Qgw8N_|Ky_hKK6tWMZG?o*c5xx~{8EE1E>GQz8 zs9hiuSO+FzM5r5d{ma80qWHKGaS_-?9PE883pXBDf%^|X59kXrQW2>KX$Nr@L5Tl? zy@M%7XTg^LFmfp3dhAwYS7=wjE7j-&w0Ou~uMmCE2Npcf}iB|bs z#jNCy$xD-uDyAm!>9n+0WUQfO-9h*5@F2h#i zC9u=Iw4AW6v~96_93?J^_o07I2p#Pi&qw}1b;i8LPRDm4b|7a`KhSja<&1wB$&BrE z4YaSGQB$ZRDNV^ciF5FFOlQ=Ec&BJicwq3T|A)8Kea_j_9<*ppoxo;r8FCIk^~r{X zhGzze@s;tnX`$u1ZK89kXT5)8Xhal`IE5OH9ZEPsPNRQi_2;b>=1K-8kP_$1A11{W z15*y9?oX4av(iVTAyW&JTO>7x-b^P+cM(spp1XvFqW2`P!>iEy5!a)f@aaIhZKRY=)*4Sb3Uiu4ztcsrm}TE7M6!FWW79t;6bMyXSc@zPB(x zNeNGg+Qy^0+@Ed(Ee2aWO?-1_;PkT?6 z=ReOJNRV;@8-w*BHuOX1#*Fb#NDAfxZWb|yWW99DVj}mqhr%AsgU=klD zRwOP@{4R?pWF`!UnfU@SS(GYx&egEq)3;Nm5m(?oq8lTR#45t7zyt3>*KqrOOJCDg zLq|P$#k2(NREOz~mFokss40Fwd|L zai{R}g#>YhWJ5x!>_TFB;Kg>d#+lE5o_q%hH zTj9n0&kngI+&OYFQW*&X?exEB2;88%Vhe$4$B*LS=@|nqH|*g9GXsf%8-6yVb<4aP zyvMw`zI6Wt$c>@HO(OlG%VSmXUC8rjA?_RSb8=|A88=zGI3sx30;woh(mEk4@q^r& zv_o-0FX-Cb&XIRXOp)!BE)bUqD!9j4Bk1{*EMgm&m^VY1Bcp>?ym9AtTamfK za8*~Q>8Gv)R}sJ7R?Dq@QG=;f)wZp7HT+e*(OlCVGu$*`p$GEa)!g?pI5GMb@fUp< zCn6S+e^FaA?y|VtwtSqhyC^Cu6bpbHH&a9sT?PYP7Xgaji<<{Mxo)&d@=4+;{AX+w ztw8Y++UVslSi}5zz7+4jo_tTt{R8r-liZ!%W5KQZ%!Bp)=l>`8H`F=uZ}f2NZ2Tnx z4_zrHe1Ev`7Q$-c9pXOXKq7_si*S-KoS?xc;r-a@nEj~ph*vR0v?$CAy$Ou;fAKOr zg|2aq2arHqYe8E|AQ5LUyUhrT)G`<7ymH%N`vm6*_h4@eeYoiSFMQ*84cA6-VnpIX>3Y#6 zzLnFD^_(uE4j>K3cg3_t&WIVqU%@GK+qcv^*z*s#=Go4Uj<5E2;7HRsluoP5=Kk$D z;+^R07+PD*udTgn~_T6CgW7&NaFxwXJZ%RLZb;>065EeYq5Qjv#mP{)P;gTP4F)4 zdpz-0s0EnWFoPLJ>`FqAk3hfe38gu83w0Ycnc9;=CKnMO;{(_Zn4>5+q7m>1JfVew zQg1tVrQ?+iZ9QRLVw!E7Z&;~6p>t{vL-T7i^b;vYwMl33+j!2Q?(g3J0@uQ6aW(1{ zZZ|2PwwL*obB}*Sv{||;alWEc>fiKG=ArDHIYV+U=0456oVzHOnma0|OZKD8+v)96 z*DKB@zLXk8GQkk;Qr2|3lyZrXj)nIo_B`A%DDbBM-*Tn%jy-H$U}<5VX`+~@ropCn zrqbv8U`8WE*{tW>^@FX}f|Y!_&&#{RA1F`?x(gQz`w3qP zUhv28ZgDDDKA^Sqr*0x&CtkwO!eY^z5R}+s==7Vw)!Wva=wY}CE}FBIfj#W8375(&HmEB*idSO8ABqfkj3aKOayDgeZ`+AtOtuq7WqAy1?H3^WE)9B zYEPU`xQ@5rSn!=+iJpb3LbQv&jTT3ihaZHJLKlMR!3%+8XcH|5Lch!ZAn*Zx<_Aa4 zL{%{fVk)u%wFWa9H=Zz$G?9`-J4*k?_`p2Hn#pd$@o@HY!(2S?I#JT~WKMwxM=-owM$G z{qcqxWs!QBwg&E=BdyIHH1{{}hCur;D~3R#F(kOrU!{=gWadrS(&vKTGl!1=ub5R( zAs8;0$8W@Y!bxFoV5;e@Xe%juNQ($^+zoUyWF?#v--SfM3;w}AuD9BK({>jc$g$g^S|ba$Szgo9`PLRDP6_n{)PFYiecF0Q8GK#)JIE zKEv@0j{V`7XX@HF+GJ}t>ki8$^A=Ne;}-pQsN%Mw$IAw`!m1$dS8|BSLOuR4Hy8~L z&$&LlMYt&R6RpxMX_tIS@t|w0F|e1>x?B2ph8xC(rWR(eJ-boF}yW@wmo;Q z@gyZSO|F{iPn(kA&sV#^;DU_{JuBoZw4u=Vf^Q3K&i_lkGwB(a=?5p3NZ9Ir=6GX$ zW_qQknNH*jb%3l1>$%GDkCB6+(*Y;6s}Ippv=XevW2A5m{W0^$`yaEA_`f3Wo{vN- zQZZ;qs0bfnVg=*%*$&Xy`uMKGd|{5z8=2Y`;UiMg8uPa}hI6wr*!&xj+u?YqNeC^V z!R3LFzdgD|=lN#&p81COe+zU7&I(-%>!baU_ppcCF1(YPs!xfw(C8!>o0)&NPP1=x zUUq-@cZMg^rE`9^O|#?} zYw1_f228+6ff2ug<6RIP79JTq3P(jMTEO1qtjQUdGb(3s&fT2qXi$~%n)qJ^mWJ9z zJm@Cx4GrU3%upuvg|?QgL~mqn==SMb8=f0#7}pu0?lP4z5lGni&+tS4pY9a1fnG_i zCHD}=wS(#srK?;Pb5tc^C0~YDI2P>`MbQvFoBzNM63V0Hd9wUTX$pONj4V#KWhUtk z=|hHUrYYv#c;9PbUfOG$VXJI=Vr^+XV)<--ZW?cVrvJd4rzR6sRbIL!tl(P51t9kZ zFl0q@*euug$zQ8L)7n4t=BGNJ0v|toEb!^Urw^I!zZCdZGOPEGbGaq_XM>}FZZG3w z(i9{!Zqdu8a<(!~+H)bHXR<%FN5-!F+X~h$Ji17OqGyV}Eqb@;oT3RuM-^FBct{~% zf%N=T#<|ok$!x+FPb-(%alzWb{0Y7+DOY&M`?y4|BYQ617@gPU;tS$M z*brFyyZlLEvp8FtD9=-lsd;FO?24AJ;>>v_LDyV2O1A(`nXUM1ZQW@GR37!1T!ZGN zDrypXh`yt-ZyH~g%Z%@d{T00uDHORLUJ@Q2?i=m{KJRci5h;m(M?c1DKwDZ8T1r-G zr5sjMp@=OBg>iZP48t1ZAQOu`q?zWm=GEp=Pz9_)3jKWal{aO2Q@x2EaD258tD%YX zc(hjdLZF^6FXv&l?|a*CkuNvVd;jrMiBG#e4*R(3W1&xJnWH{?kSg~nOZ`zl@4SCV zs8@6YYZBKh<;jywqOqf8jD3o0g13KCom3%xQvSaS&Mhn!Nh4UDB?Dbk`0KfYFEWBzY@=(cSr}7 zO>ZEf7r1mZ;|NgCe-!$Nns`-OFV99QdsQUM{6@?nHV~VM6~uI+KT#fy<#n~u>K{rS zSrJd8gQFR8q{Y}(B)uktPot%~n!mnpKHBge!TGX1cYp4EC>fXMwe)rOA4k``5`GZf z8XwEG5GqTRmA2Yq@?YAkuVyT7&a?b!yNqVTi1UZ*s(U!PwGG}kXbBnX?u(442ligJ zFP3KJ#m4pe3DDtx)K)0Hkcv5;yA>}T+Y|XI{4}^Su*m-uJH!y*d0$I^YQP#y2{jMz zi_`?eQ;;7nX37J#9I`WWTpuwOvvje|cIU9iiD(8&LSE&c;{MCdMkmm8|H@qXS%K2tX4sI9tE`Z4+m`i4k= znxjkA{ms;4ZqsGyk<==39r2g81qo{&rnr0hf4$FsHU zxyTMs&=dJM@2js+U|FzKI1srX+liYo8I9a2%4GGGR-Rmh+#`iyF-w(2irsX>c=&Z% z!561DT+lbsAJzq!d`we%A@Zz-k_Hm#phN-UwYE{4q|MVFXpISi{6=O{-)U4^qHi;5 zD2`LpXBlGqX0PQO?HcE9;t@TIytagC$oPyUyh->Y;jH(BXQ(^u?BLjGdu0ilJ{z{` ziqkWQ)5?3%%(snSh|~||`uFCI&FPrk=KJ`s??123-1W)z>FLLpACo>U{8S;+{rTrF zhrW*cz9w7BS>-*w$Fe8cByJzqk8dF~64UgSXw1K2`^4qw>B!pf_R!6s6sQ)M?tkTLf+^-%9-G(S=kjO! zKL)-Bg;1HuM6iKd@bl^`4wCz;&4>i*I^9P1Qr`h7gEcJ+t-Ed0?QTaWG;|krJg~R5 zUqq61DQlwTuBnD`x_%FHmHI%?S`B5obVqRVUD?C2WUw)lL#wf;?G7vqY!18&)C)cj zZV2rUQ_(%KrR;H@6I&~%v}9^A=J_qAhSsQkx2u--aN_smPpKQz^FeE|zrfjo>yV4G zqVRGgcuXubv0%djS^0)#yi6;YS}VC|;(JeD*L{0o>pT-<*vzz`(ug=bR8NJM9Le^N z8Kc2){_xUJ$zucx+^Pba8BDd^(buD)UC+y>LQ=dK}GZ?UcWi zwO}xdD7WSAa<()`dLZh>bb;g_vUB3KVrK9we8?NB85|N==YQ-=@h!^xDK9CncHZ{9 za_FJi=pPa25gY;)Uvl((Y!N$>?;+MkcE-QjG~7!MvCrF$>(Dq;(OTU0*4Es<*uLAo z$v)Oz+J4ezu@$xwmVKsT#?^X`89=`RUVcq2r7Xb>+nN8!u8z-+oro5RK8$RSY>1qX zkZ7u{8LJ&%!4~1e!Vig8l8L(1IOc}FxM`QAiv5wZt>;{VKPfNecv?fG8|LOW6ch^n zT_|7S8ikcY0}4$o*tvj`Z&Jp;X~tAX^1FnQo`0Msdr2tQCg?6vsl)<>k{0kO?1kv0 z@bAIZeyi_M?$aE5&IAN%pUYke7uuWbPCr~YOA8{&aGn2oz!v%~yd0j%X7MX*DSp0i zS-dOlle+*3?XSL6t0T$hspiw(g3tU}JFk{jw<@X1ez~rkiS9b9v|cPBJ`vUme_=aH z5Z8z$rDxJiV$drc3?UD*5p7Po~wO!rO-@Dp!%@j41(066# zQ74HYl8$am6QGx0#((2_aUy#ErnBSOjV#B`;eO+P7OIJjrH=UAx~u)PQN(KUI_0Ac zI!6CiKMQ?K&5YB5-<>cXF|IJSK(|s&cp2vF7BKVZdDK$eNqe=E>M`Y@yhGY0-Vxlw z3cfC%#=D@Ks0)_qK3`pUDeMrpNjGFdZKy3I?veyL(gy2x=sy@drkdsk=mETd-Ls9Y zqpd3Pn>JY!ti3HG%)gj!p?|zC)0nDHRL7_37PGkh@lH`SbRsatHzIe{k6_lc?=`;_ z{#y1+&(GH~`)0PvT%K7N6=4;=F8Suox|Y2xXGY#O`1;323dD1`3*v002Js4dpl61k z%rmW*?H`=a+zY(|I6^6DZL*YHIi+4oaU`MjOWvP!1g<~3caA&D*}$>KX0{GB-!M81 zoprltjhuw6Q=RHTgUKnWqI6FjE7liliNnNy#9`7;vR5&vk{Zx5i5KKQ)JIxmlJ(^c z6^uH#Nz0jMn=hE(n!iKta?(7&oNcOVYGbTxNYKA!Hqu?FRPqJV;L0f1r8eSMei7G- ztrPDRI}lBXZi-Zk1j1Lt7sA=$-{2768~rP`CcXn%5Ks9S*nlDOHKmxg3<;k3>55E> z?v-w=p4Im<95Z}21Ppf#{SEi@oK9rk(}&@)bqWOY|B?S(z8e`!(;aDTQo1BnObEL-K%f4PrLXCSz9(}J z&f`sTKJgy6Cw?v}hkp+#f#b;io`HTpPwpk?NbcmM<*v>xnfFiLVqZsp?LdRz?>Nz8 zk@~S2@dNA$ZU;X}s3%g=WoeKchnIJ!az@z>Mx?0nQl2Z9mtRYxB~_dtegk@c16yg3 zJIC$d_H*|*k*mfp=QVz{&{=FE4VI57c5NK-iEKunXUgc08S0qcAVGz(Pjx(W23k!U4_>`jao=~Nck%zFC{6}o%%LqFecHfNz)U_gchEWuIY~PwqBN+CfabG=|p|i zhGBNv&JSVxfWv+YKkps?a9@?YV!6$c0Nv$B4d_T#BQb7B4i8;kue^G|H2w@639_M@ zkPybr?(Lqp-rR(@ ziF=cJBuA3Fr>sd?js(IB$-bno$erx${p4=!T8q^1GnOT$Ukx9bk(8M@qfC(62=%yz zc>kw|ZwIRdzWJW#8S`f6*32!P+a&j|Tua{hye&|yWdu)z#sHt`$oA%kh~wo&>Mr6Y z^_`&&7E{o?*IL3p7Wt8LT&>(u_Xy8r&p)0$o}WF7-FsYXoC6(&><^$``)q1#+@OES z=;^BD2<@u!lYB>S{B6(f`|+biu2)3XPYw zq^e>)v@|Z_uCg@S9SHQuShZM{SdZ9#_?g#%YkJ8p$9&dN7$`1zKRDboAG6!&{$ba2g)!yP~bap{vVuglD!A>C@(_s@zYy4DH%vHWr%^%^!IZS`_Sp zM$IO^DS6@Cy}285ALUlgdz`n@HwAr&*}*@;m7=BNZGp<@ALp1PP&rNpblW(Anp1OY7se|Xs(5UVaIW+e&PDFTx?ymb|eBV<&3~^ z|1{styoP!DJOZ=Y>bwHJH@+MGuYua3H{lb}8`zP0iVx+6+GDZ@qv?;Dez%r#WV&W} z<;3PGgVI`M81kIXS>gW?pYkj7nr|F5g zs&%RDAG_jUT<=|z++W-&o-|L`z0+O6z0UQ>`OL8u&Ve1498(fhB(0f#)EHv6x(JG# zwfsT$zgVH@7PPVz36>8G^56Bf@#%fRJhyLxFTbC{?KB|tDBL*u2{V3czN%P7Zl(?= zmQdT6)%xF!>E>gWVz$}#dyenUH?H;WbWblN;uQ0ocNcSybZv1Sa4fYqL`P0l^JL@S z`qzw+u1_x2vXy4?X|bemlxxP?;|e-9M@Ix;K(oV>!^^{W!UZC`@a$BKRgAZWqU0o> zBjl6XfCIgv@>)5vCp8i2u&psat$mE@Gewz!^ie8K z)+Xl>kF^A?H#}lBl{fMhdA__tJ`TUxPuL91>MpfAnq|wAb*LWnRA!Uzxc;``Ir{!` z%}`TV&)G)W2}f_oD#u30Bu5p;KWH!C11#^BWudu%X}RH>t`O6anoaCcuglNH574ZJ zp>}T+-W@Cx`0Tp@L`KYMnezk=f}U`EW&-8ymHS8D9-r+0Gf0GwMJC4vBHd-7ctn1# z<`6%qr`YM+8vUk`me-t(ti{cHc_+smwzv zKi>1#@Ma&xRR*yqR%0*V@nhNVhztmXS zr#gsfq@QYu@B6O)iQ%MiB6>iNnoC#~S{{S*&9odrkI!p!ee+IJ-1sY0CAamWuBfg) z(-Une2gy958F5$ZrMa|c(DBVuC!(Y7ikhe`*YXqBiQ!}#b&ncNXD|<$X}X{EnV5B* z#%abI#&5>Y#w+-zLdIi;G(%7QSY3an1bvOH1Kp2R87y5DTzqGCL+nMw5$=NKmzuuZ z+*>)1(6G=s`*qgbtUg&Iv#w;d&o1}lx10@7QQ!5i4o(j*kKT^w<2Q@HDBrc!|C^qA znjcz=ARB9$XG_B3q~?4(kOt-XWX zW1JK0qpUs5wT;R8F#VEzqWw@x%6~!aagSTgPKnQo9g2#P3Fw?5Bc@2@$oR-3bSP|% z=8GMQwTu(cg3RQ8;lH3oBY?T*h{%dnv2!kvR!Y;P7Lp{cfS-ARAo0WacU%+hIGe~$ zM$2B)*gZJ%64BC<8Hq;H;LrOTZ~eYl=XiOx9M_Ux1PwrGd8raoyPyNP27MBE=w5@# z)DB6h)2%&iY4)@B%8r$e2adOni;myHVwJWxvemZcvt*mL8S5LK>zXk~D2i;YZC40+ z0`mOt4dA~B3#IN#3#}VDnck$kV0dPF z4Xo)B)OZ6tDG7fkCMGvVn<|4ewl8UPdOYnyTGO<{ssE+iNuH5pOdR6f=YHzUv-4Ko z95BAppJbL$1Bg~?OL@3>f=|ah+9+m;=7#e^DWN}uT;OP6Wnfd_PQZXph#SFbq5J49 zC>>!VccL3&1LFnRM=W|oxt{z3zL+pt*pH6%%fcq1t3V2C(TusB%iz|qI(B&cVXSa$ zdh|!6UxW|u5049XL%-;l@D8|1nna#PCPiDvTE~aOfy*LG<*HOgIj=S*vdM*XCEdUJ z{&?@JSteUI*>)hmq9@+GZ7$MX$z9GJam_p6yNnTku2hM(9TPBbbmr z@k?wzel~PTrRDy>o%U!)i2eU-)b=tv8LO_fZnSQ)ZiudlE`evMD%tW?mRec$qjd?*Wz^1fIbtnU+!$KZck{uGe94Q_?5NsF_ ze0R{By(j1SkBUDYX72zl$!CxLkuOKgk#kG<=J?&gXQ7Lc2Qi-gMVKwUR>~9esn5(W zhJ~gl7RBa5N97?;<%Ic($I!GkHYFi-PU_{WhHEvY?IX5ks_o|v3))zb)lFLp;; z+ak+3Q?8+qei-wVYD#|4W~((6t1OCEC_hf~EwF9NV1CN;f8q(ODqO>SSX2_EH}Xy8 zkh)mwOOzw?&_Udk&ZVa?ai)!K5qOZ*(A<~ReZVJaV^-26-GkaiLR+Hc*P5u4(KcQZykRo{riJyA3JR5a? zB5yE&7W6FLPF;Dlu)d;Y%0LOk3t)!jl;cvGIEuf)I^w;em%~3{pKpYo)qS}WbB6s` zh%>Z%R%(_rt5(+9tbExovM>DjH>W_}ZeNc;{ZNa@nAqQ}z_*r8D}{-5lt;JRK$-hm zciOKxPq`<1(-T)GeM>f{s;O7fnxt<^KbF2Ey-NDgG%BrPYVDK~$$`Yb5)!>Lkuy=s zF$TT!h0RNhjA4;3gSkYFChNhk{i`}rc`bL7Rq4F66nU|eq_xsxsj$36HbNDa2t93n zctDNBNa8tBnw(5tAit79@*R1A>_P^K@q|xnr5%RXrlQhO9w-eLhYEl2Q@OS5)i?`1 z$zM@6G7PiCf$;e7NOi4SSY3Xk4AETVO==qRt6p!sYnp6HwQa_% zR@K$QUB_efp72&mn3u3OVFlVMu6a$~lAZ$4jvROVX1@ePww-C5VWDmXy@uSboks$L zTkOqWV2j63MSDfchl_`rVDn81T=Opj>$=Q;#qS9$3seZQ!OYOda9+d^D-^GbvvU@| z4S$yBQYP5SJL+mYPw$9MV9ro>VdFEjf{Nk~@gX#3OBxX3+MjO`*>_ z3|~_VrJmAJnXTN1%X2mE_yby7!byIC|LF#Ok9nobgCZ?z%rm_*Ujpyh*Or7EshVTG z&N=b?^q=QjhF|Su0Uf|h^b3;Ax5ez<#S>#UxFJK z|1a7)@*}i6xFE3B|I$|*yYJGxq4-(>FJUuZmTwn4Mbmg69ppjEhVo%*`$7)-0rScd|?NWez*iyv{gZIG;MV zqkr-r%%bD%Ymm@A*pg`8VYC_g=(f>MNfG_qwdC$dp0onHs+2<*_ z({gN>qAp_&t)BBFXD##wXMOs>_@E_xHL@f&4mim&@g!7Mda?*z5%-hB^d7xSCG6WA zF=s({n&+!$qW6v0m|#oz;oamdh6JLU?*CjjoJ$?`?cc1EEN1gMV__uO{=(d)x{;E0 z9NEyL<(bk6krYP=0>2eHkXrca%CF}me0Sj;l-shnTB-_7=q#nA`b_;(qmV8AgQ!l9 zCfA`2Vk_jG#mxw0BRV_p75B2T8G6COnH*v9W3GL!VxFPJNc;Wb7Xm6G1%jnJM z!>BJ>EH)8t<;l>X`JqU;!`~C0h#%1@N2_UCd7?JilxjeiW<=(&u7UoNJ_$R-B*P4( z50*0A(bv>3)7^%LCx`k>J|qrnbI@OwU(Oa!qRo6T*9TANPHcW%qIpOUofMf8IS?VD z3!-&m#n7}p0P3n@NNMh%xV7)Zed-ExL4V13+PuR$(O$#pbItP@5(XshOgfEr%C>Nz z6iaKA_Dh4l!=e(}}u5BUFYXH&Rs=rw=L{~;ua*TsPniE~wl&aNwpqLhGB zp^jQoRh0`$58U8Ak+zW`4G~Wu+phvYh}+CQi(BLEVtb8v}99C1x2z6;x_=_-wf6X=L-mweWPHba1eYda-w~p(;SHm4R zOgfFM$W3sS|3Hq?9H`V9no9yReBoH-s^fX-Z3pF@5ACVBsr%pnTabP@eM@?e^k7>1 zv_+}QQ~D*_l9nfgJmuW2ojvW{tj*0;jA_W@`b<93ZYdAJ!j$6YvTAH2QcSBthc`HQ zG4ONXo&T7BzyCIH*}j2SfdN5F=vrt&xL2fpv^?}Ub~eubi@eZrLKQJbT!OUojp&PP zudGroA|35N<*9N>*`Z89H)=jKNgk8OfelHJBhowRrIaZVas~MhX!%PkdzC8cTXiK` zPpXnB_}pT27V}p3RDa8G%DBli&itz-Xjx#@*#3E_ zbUr@^@^qgpPs~$a$OWX~!ZofiyAHej-SDhXmtfn#82=?-S>MCF4fwi~SJ-#N*UMiM z2=7SffvQ9w;En$UPOKd12~=_G$dPn?oz8I1*wOsl@)L5-raA^YOSvw&in{x|$KzLF z_c>Q#ps`CG%j^>{1Cy4+rWVGp`UyH6vz#hM-qyycwehBSkr3NO*v^}HXqP#H^RYZz z3eK;0TrWNh1h2iACWWM@@+l~qrz7d1BIcSCR8Lx>=P@eNOSez=PRHv+Og5KvQ+1_v zkD30+R|R`UHK2}@spJAe(I#jbJd3qeQMnB?dX=&TPjo=32{%qYZJ$=32oT%J&QuEh ziQb31y@sCGA2PHszBhI;^;IeVvd-$m`b8?=ZPT?>CM=@=?+wwn64>s?7a{x z3QIX(ykqQ9q)V6&9)ee?pKo;Du3RE_K~Bw_f;km(M!<7EEVo3S%U96fK5#IYKYTjU z2M){-cMtt_bCte$Y8m=A^C#~7S*DNXBx_mhX_@wEj+mp7bGUP;v!*l4F~afOo@lRa zYh!J0sb)?!g$+0Ki*)Uod~}%nm$Wb+*Xk2MbHzHY1Ut2##cbIuX zf2TO`@?N5ZR!<$MY>?kdWu)a`N0$qg1V4X;KhB?p57aL7!nRdaJTEqqzDV=ss!BF~ za*ecHZ4r@z6WU4rNnJ);ua{2z-yJuOGEy_huS5l62xifJ$_e?Hv`1Vc%;bjw#~T!% z7~2+o8z}`AJ74&IXnkl==pc}@KFGPg5ZMkU`QLaEZW*5@UXdn2S5=&J(i-E}|7$#D zUSMrzR~@TeHcx-=`h=Z{Gm|POpGh{Slm(|0N?w#4PAZjDBQamXd$d^a&TfwVwk+I^ zHH=;KBbhnK0)M3Hly=g2gjE!*qCgs1{sBWXKk(7MdQ)40R2E3(t-G z9F0e>#1_RnK*jup+k#n60&Y?aN)W5uU*3=2B2K2!ec+d$!qqSg==~RImDEV$u=NiS z%ZZBcLAZ}e{<#oD_u~lh5faVNN+72wpOn+;I&C&FnH-P&+u6(#-Cz2}hAGCLV6Sq` zD=h`Ae_2D;3bt0z+n2FrTW45#OKZzg^D)zT<59yheGgqh=09vsm5DFvMraL6OJ-5x z8NMh!-`h}ZMPv73cVZzh$tUBjSSK`V3T~wl(qpt0ywLt6Q!uej&=)k`G4;1_*3tI= z&`{mL-3W<>|9N{QTuBHfL=&DQOhv+J8}DR%mhD_7bd=?T4)2M{gM^R~%qr?Q@my8p zs?v0@Fl~`JGza`vnQ+-qo8X3kC9vDy!(ZKB)!)KD-T%N}K5!^dBltGBG}I*Q06Mx0 z-BYFFS@CUbOU}nl=V@V_@LDK}RG5Y0Zt<*m3BUFODfvTeCKeYp%n8?oBf?(cD4xR~ zLWVd%ye5{DPD#y?%zjMUzH+v4 z{q4#{Pie??!PV3C-ua7jf@7_HHM*)BStQd2V^PCyT>>+g5{a>zs4SF=Nza7k{6MZV zJ2bv4M#UBbll20jsUDq$=VE!RdAtlxwfew$&WU#UPvtGrnJGh1FYisz+c**p@ zd>XBO6>JyaL>p*dXrF0sZzt>vY(8rlYkNx%b1PFhc)l*`{zMDJEwV3R(k>|z&{9=e zEQ;jluG|h*i%*RIf8xsV*qPYtSaN(!JUc#$^>B~4wfvt#N4$@<;V&wSnMTvz5eLX& zR3Z8{5_WGg8Ss10#tu43*A5D&N6c_WqQ@dbs}6LHUx>2AMD4bktd7DSR!_c!XZDl0 z49Ii^v9MTPY$?tX?}-JZ`H~E6qfyzT{HBJ~^%|I6umUXkC-s4JwhksgXgJth@DWS_kwFb$^6B#)mGPW&sotu(X-LJHsSZg zgrs#z(WHFIDamC<#O=*rYPnru-uF?wehtL2f5>!Q6< ze^pl~&tysRN(ICMV3kaq7MEgV%n>aW=@?!c$_aK1ehsV&3=H%M%tr%g6ZAUo2+f9K z<7PBpd=p!pXN9}cVP%)Lmpn#a*S$1kn^+5H%XVCLP4pxstUzCSMoNiPYuc@}7U_G^ zAEjSSpPinRK0WPb>ZggC38Z{P?$$R`LhfrxezPl|e}u{1^6E}aIa zd_J($92m6(+!5!wx@i#Hu zmJ(I*i}YN6rF;VyXd{bYkFU+t(N)tIM#ko2bXXNPA2KIcx>=@KW?BYV%2@tG>d8Y> z8fKP(hJpH)x?)gkohK&}RW(*QEl&YcpC%ab85d{!p*Od7>|=CCbZ&HBbYC@yc#A@6!--6?GkqUe>@2jmMofB(VtH@fV*Ay8*`DfX<>>0D>EP`v z?2NrDHnl^R1Ms?bMMCs7U2jI9mXjrjyHIr&fL3guFo&PW&1a9rxme#=Ai6O+FxoQO zE;=rHJZg+hg@@r-d*?IA$v7^yQqysXeCWUK;i-vy+_YR*8{~URZeSRW(zh|&p zxo5mb93b72OJX8siT2bk`U_)6%6>7U&GgnZ*X*`Twp_IQXSr`#XQ^$uXRczN1Lf}% z!+rfB-FT)dokOlAYJ*kUB)5lF`y2kx2k}|{6SKvJ0#jcUX^fWm(vfD7g^{n3zELrH z9O{MvY)?$2e+cu%-C%n26qi<+XhHVEB-)rMp!=*_tS?~LZEzt^_n`3wCaFy0ZR20Y z)<_{cY-nM~MGAyT|CcUFx1A}++`xvPMt`J^QVW5m^rm`Izf*IlW55E+(=+I|bX6pl z<}wYSj`r(n;=jD8&(f=U0+Z)8{X~5_zKf!|Ma+M+g)UB&Ad?7Ay{Rmcn@F^Hg`dDx zWrf(?=+?-*@WRlp;M+hYxEC7w8Gnv1>MP(M=jZ%81CxU@pxX#W+QuHodvQkLwzygz zrFJ6fQ)L;e{*z&+shuTY9clmQDD7&GROXsqpZAZ1Te#~Zxa;R8BqYrC-u2|Tvt2ix ziyYPMZ>>WuoN0!UGR)Rd%zVmAp3wRrf8PL1)F%EcOyvJXd;2?f0o$Ifg}1#AyPAE= zmVv`4%=JcQXgA?Aw)-HG);~k%xJ}N1*0G&3MwzQDz;@JADXrwno8+4EJ82}G&YN-f z+z{Fb+5B|4KOf?E=>`=2S1=^Ma~r@>m*+R|NtgjTicaahbWolFB~^7T9~7it$-78T z9>Y}CW$WhXjnJSzG%&_u#)`&*#<<~cLvzCeeL4M9-B~6R2&tV)!o2(cJ|0nyODxdc zDnJi!aX)bv*uKE)b@(ccvnHFJ3iV2o;KIDuR$d3*r3dkz97gMPJ8>tyGW7yC*wTI& zeM_a>4Lr3F{(V28W#Ylaw~5~p??e4tIB{cw-<#mg@aWx-ozooY_Wf}DY&SWK6ZM~& z#`FoY7;zZed>9U`zl4SSTJ9n%#yiEI;O$S0KkC+QxbTn>-Nn!K-s7UI)M97H$x-oT@;XS5z*hlvitNErH;V1iw0#>8Go!H-TC0 zj3>XPX%B)obY_z|WIAi=Z{m#;jNc8v8W!sx=wzkr{KhOA2lh^<6NB8m8nTYIH8 zR_mj6)_Q8Av`yN-S~@X?c#TfAQ)CHhBjuu(&_-qsaHc`JJ4ilgt)H&npx>cii&nLU zdRDhwXVML2j^W%;NE2x!1oew@OLgO{FMmA?YT!5T!Bk4AoM#f7E4aN42c# zRjq1*T3+q0u18`{Ic>g{r8Oju68XuMq>dU-y@eiUDt(&%4Ag*ORCKAIr$^IHdKIwM z#?X%J)K05sp+sFK4Hl~kGIxcY7XK}l7L~%jkUvNTOG4*x%U9F)GH-3(q`Y}~7m>)h z*;m(Z3`7H3uxxlrbDKEMq=m@!ERWXFBFO2f0eQ zpSoLk_Io~hes~^w7J7hCI}URG8p6O>WPRAs$#O_7v(NX1dqLI3lCoFNL5bIJE)d1?Z6lKKjq$cyii z2a>#$sz$vchky|oMtss5X**R`siQ2B-$=!!xtMV~2>0p+Oe;JbXtDzYiNq*EisITQAk_GqUTwt;t;(cNZqc0=%B2UA!kWASi z+#)hE|qM`2gkOSYzH z`XW6Mcab03+s20PhKWYiIL?%5s$?EvUTI!$o@H)twxP|vD!iXIV<*E>{W0Bj<|ciP z+7F%Yd~K>aP8koxd=)xwVtfOfqs*A19?-us7ns_>jr-_j{T4$V<434cY-mnQ zw=A*5Ev>BUtWT^#D{K8|J#6h^Wi6vEZ_Fjly-ZV$^9$J*nZer+c((H+s~kjy0txQ8)tiOEotp;nP^^Mnul(x zp88+$r_gX;+#ybb1-Ym^2isW>Q;tL2D%6K&;T3k6yZlGqDzp<0YI{_dI!(!wJIjxx z-{5WNDssXep^s1oK2en?1+P#;7=vjcRh%!9NYr=9dyr!IL79kq_@GvkxR06l4$hZF z)N>>m7N*P41u!Swr)E&;)B&;pIg@w;-oK~1R=F?pI9s}k%Y?hUh3|risS_dUL zh(td_yn1{QbeePVG#L00&j|@qBY80reH#(y$%^zD<`?}1_#*e4?dYl5YJcQ-?|h2> z&jB97JJI{tOC*>vF&*_b_df8H@C?SCzs5Ps(Z^oS7PRa{`Y&hLqHm&O|EI0~h45&! zYEp{HJ*2~8inv9nE<~`&9|L0fke8sYT`lkeJY!-f>4P*t4#*P}8Ob4jwS#t4i)p2Z z_ISI;pqI1-kq`IyGHgLtz>HkPRJul%(PVlBr`l|A@?A0IcHx4UZ5y#R_DlSJ{8s#F zJRC2;4rDL0g)y5Z@w=c0cq$ARZPFpBF&ro3(Q-T&=R!a2BHn?@L_4%0_aJ`5R-dV@ z!cXiv&W&42Rb{vAkjG13#kS&Yp)Nktp6FM)!L8+{bF;Xe|C@QY^JRdG?L-IRLV1Vs zR81fTqJ6L$b4*vsaNbzQe8W-$ZAB($C-jXU_nd+MYha=k?ysz*)Z`M$mgJjBU6Sr6 zrX)5@XzH!xkzD(nbsX1hKUr6qbCFiJO!o=c$O`NlLzE!yjH*bu_`tr6$7AJy=CP3l zkzXPPWM510(RW9Sk}EntYK?7*Re^GJ0e;H4=s;~COr{8GQF zX$mX760Zu!;m*Izen#tGUudhEM6F{|MWDX4yj9;YW^C6AH~sV7t;G{TsfcFZE?5OamO${c6b0>!JwD429c(CPGT zoCgWi6>>OPi2OwCC#DeHz>zkJR}F9BT=>Uu$nb|D1ATgB^{b(qEXe#uccc1}U5JKSag|iwO2@=`P)wtVmMt7F z56pZP?$}D;=Gy`<{}KEeRD(5u;(No#!@ozWMN#VzuZB%!6aPghD@}p+Azd4co}ets z%~Sw0QORIKe*Yiv@%A5DYeX{H~>>c+{2Bl?%R5JNC#+6!%CdHgy0 zpd0tT?8V#SgVP{QdMGd&CU9dCh>vp8#@KNAnO_#3iVjdecRr4PULR zs=trB_?#ih*w47pc+q&vc-FYa*vpt?yliL>rFeJ!Ke}qVO-z(-PVd1a-jlq6n}3&9 zNIR-FRP(?s^--!Tg^=r7K&hg1P$nzK6rWN~9S^OvUF!}dL|FR?XUSUP9Ptzw=`-RC zaJ+Vej==ejWzgu-xb~SM}a+Ij8i=pOe$qFohx|;g zs4N8ENvq{>qIFQ)sP*x^n$%Cwi~OlnK%4OqaGgKH)9?y7-+JK5bD)FVCS8}JQZ;!2 z^2~aGFYTsgtJ9zty$x*HLtO;=BBRqKUAI~10y6RpO8ZuZp@va#mew^Gfi$((zt^=v zR-B$`O)tjtpGPDB`5C0HhQE&hdcIj?;Jma$vo(?L#JAxGBgfd!cfqEzTx=til9QDT zwVF17*aGKi8D^^Pi9W+P#B{{`&0@15?#^L!{)bQ0<(`eXUUKU_0ryFFYxh%Eab)jK zg65*9t&BAr^HV9~Y5gy{b2ved6P1A6x0L;8_~^q|{r?bRXjo=MUjcRc7noNKWG*_8 zS6v%w#GB!KK%;cgMbU)VL2Th4<6~GIw~9*vzF`+83vYz7;uvrruYg9orGio+DZk{D zC^TX`77vSyu<3Udn~L?|hH5De0%m_rq=BBEk_yQ?aP~e|rl>z@5$!y&nCwgaN~dG* zdWoci(T2*#Y~vDB5%WQF0m}@_9ZSrDsCR1)@VtH&**x8xW2$4CYdmXsr;q3~#>7~F zzbB(zTh0Qv2oR?v~(TjlNUx2dlDcOO1PxK}H z=&EvQ$JEZMO?{|rRL0|-?}}ezp%K5J_`$J^QE#IGc%t@3s{~*EH?Yr`^E*+DqApJXA?6Ep`?bgAbs%=Ir*kEp8IW46u5K370C!Xr`}arHucRoQyw(z&GKo~1b5|-f;_6cRgap2SnK?D9ysv<9yzso->)0N9gL`hextIgEbY7-j!I+t&**x&0%{A?Ml$n$+Fj+Y0{Yz@%icX z+00c;Ze7UYL`;34?3I^FGsRiL8vYU|KtFvxUN`;~trO#817pKuOJcWT-uO6d@e|lY z?kYEuF9$y9tT-Jrw+q|&66{RhwW)-WoJWS?*V+t>$Viu86XO*Z8_*nr$j&I&Ca&oNBnH4+2MY0v8F9Pl*HC6tx9hUkV(VFZd_i z8#W#<8XtlgxK8vn_>YdjNE%0mM)pL6$lz!ox-eD~u7wxuS#B48l+Ef| zaO$JUmQ)7)fkp?P?kG|-R_ecilj&%fV3=>1i@$a;lsBmQ+xofsI{KLIfUY-`UeBRU z?#z^AG&+;MOP|9Ody>9HKc#bM3sVVa$10q|98(-x^Rc?Mx?{R)Kt``&(x0yTO(!wi zn4g(TbOm}Hl}G+W4kA`+f2-G(r+6mciQk1FuX0|b$oGz~2Rm064YYM4x#1(>h1lWO zhaX{2Ssp16eHPson~yzoGk2TUgeKA!Sw@1?6QUNipSJ3T>Mt53V@Y#8O9N|Zn`}F3 zukASH&^v#2Hg;Ba>YW!HEgg^SqTesIal)pg1- z=-2W~t9>;kW`kZ+^V3pM@6U4Y3AP&cDcKFe~j>Qq}3|8?_{m z&TZ(p`Jx52JlrQ2w53`nIF#O@_iMlV%+tQNy=|H~v-jF-{k2v3phjWLcYlx zX^NDbblq@qBo(WSwaEN}BWezJ6_nRDQCk-TvIj1)$0Pjt{5kw3{Oxe9*#cv6?w=3N z#QodcXl?ej<_l*ZLuL0=*YLz9G2F%<{hGd9 zZ=!qj6Pz~|DP#qhuUoNprjgrp4K}_w{q7ymv~9Tb&m_E|nzZ&EqywABeFW&Zh5Z!miww3DFc+ToPO<sKjLR8o1VvD98_4GvXWO3jH8 zBhC~XfCWDjwt}P77P1I-;kWgY)%=)sDDdls!ffHXkc71Cy|Btnq^Ntag*Y9qs3`LSJ=awD&Yl;PPBgycg%m)G8$3l3hRZCL24y0QvOpv+XT`9 z0?tRSRqpDZ2cEhi8$+Ij(4K_6KwlXV^3+q;v)TR0m5NM+T8>D29a}}UppsU0Nv066 zG`hmvpxtY=Kf#v4CxJfjAaDFJtj6n{#RcI-UIgmm*Jz@B*XD!DelymZ4Pm+V2u8RLEOd~!P)(X9a zDnce9M39&zlM1|niF3qjoKUr;iP9nI8BCR14wY3-x$`Iisz^V@Nuq-k zg-0;`JZtN2@7;kdDX-f>sq*I#pg)%lg z=aj$K{;v2(|8s`~+Ed=U2?1aJK%d}AE!^mC##$-G-qJByR|?yj*?T%VqYX&!zU}Vi z`NUIQ6mllyMo4VPN^X5JJ<~jvyNml6vwbONchLJoO!j_mbgw24k@kxQYvd@%+X4C? zAE^R!z>bC+vze~nqf{Ph-ay^3-byYk5JJUyyw|BxQ0f9coKEQhCh|#0@Twh_DC}5hRXTbbZoc;JzOJ6mC`VDdV!Niy zl&iBkk69zl>PA{!cFg407u?H+)nzUKqVmgLJcQSlLKv+QQELBm@N_gsD9frqQZ zz0fb{(8n<=l{Islna!Ns;Lr}L^?T_dg7(ybVR#QrWKbh55^^4#l$+ zuco#Yv=y~wM?LWfJw${WuS{o(n1fQK3r_P?VlUyem4sUzuYe(})l;$SifE65i-H}< zldm3(LY;Otn1oK+(iZDw4HH~+r?s4kcak(g9*_EU1oLhuM_p$&*ALfvcSX-dPl=FO zXsADid=9x8vLvKj$OTVf&v^F{*F)TBFX-J?pvOroU*$HG4Ay15J-M~7fg^D8;}V8@ zXT%>sy;Aef$=@}9|A8-9{A>2FmA_(sS-%>P6;d8gVPCx7cjD)H8=}2`<=cufuLvt@ z9|(!VSYzZix0(jCH**V?W%$XQPOoZc( zy{df^-B>ZT8_zUaIxF6w3cNLc8YVjQLY%NO0@u-4%<_f$wj?w`t7Uq9UPnUFgdqtJ z5*ng(oq-}CY4BI@8JOs8y!tP=`6WQ2j0l?WlGOPL_L7dLj&9DE&I+z+WUQQb9dpfd zMY{aXSx%>Ou;Z3JmA#Q|oVt-t?j{_FmkO5Ks&9@mj>3X8(M|7t)D1_u>V!52TmE6T#0FOnmi9?eWcu6&LMxI z)2}E!75j)fxMvb!m6a9TuDE%D+e9^e3%p2b{eu>xZP!+4%hB#0*IvSb)I&S{3dCdv z2z43!Oh3#@?CMvn{`SY!3zeaz=WLg;f zzcZ;ZJ@K7XHPZ4Gx8VSE>btah+V|j0)GHTBI|&b*rjIU&)AFJ30oMd4fDEq#!)s@-h6?0+1ET|L|@aO+!`qCb-) zO`1BXmE?Squ1S6-Zkc#TqIcmU$3{ZP9nVU4l*{8hZtn>CbV%k-1(Jc5n>|7DYHAIG z{R3P533P@}68a~kNqE66l6$!AD12~usMXb;I3va})i+kNsvnhQN^uluW#z4s z4d2x^;fIyq>TO1o9G6Y+rXAwM>l%1M_xu|*K%_4Nxv_#Tk*^F)({o=V_Tbn+!Qk)U zIc*KQZn!zb8ZL}R1u$M7sq|9o*)rR|**7?9Iv+b5vfF*GtnOm&TyDjE*)_=JW>qFQ z>O1DxPl3z+QgoT?O(dJ>5fWKx%{(Nh_tUloe+44Qxu{7t)0Qm$tt- zOK3hy%JI^6)W@l$+u~fYshFKQ@EkPx2;J8K;k@t)tx;KAq-R8r)Czr`Bsb&Ma67&G zA;nG)HVc=@6K4D0>KFC48cl|24)q58#4A+I`*AWfgqOc-IjrJldvK_|B=Lm>>joD1 zKlvK?{=={If^5`k-c+D^4sUMQ&^_Ly2{Snf4xlZ}82Carx)r6t1f##%)~XFI<3c^X zRqo8ExJxZ!+XTm5mgK0x_I~yzr1w9wO(ey8wi@7FZlasZ0y;87IB9(Yn~5@(Fmo2s zRx_)u4^#~N_8;>vWcN+s&V*YetPc*n3v@-lgV!fx@MltQV&ZDw=qn;bix+g*#?J(=e3hI9(OADSnuQ`oq$31J<> za)jLr?HKx)%(LO1X!n1v+s?a=3--OXrRor+ft*LOi=V9f;Jvr>PgTyzbRWatufYS>#TL1 zSFE+qT4eRJs#u}cJ#(R1&$N?1G6sE)ihD7dKC=fCY8|~U^>F~x>NP54BV!}lpD6Pf z4yI}7al4`;t0wLd!(jf8GCP%$doV>r%lqV=@?yD{Toh;ia;YQHS}KbtBO6; zk;ED1Qb_)Y@k|XV9(pY_BJ5C@2rg1Pyjpn9@bBcHq$AJeZAj*j8lL9vHm*jbMW(cW zS5GJtDtp!QZ%>Hl)#tjVsYqlf>g-QYYM$w`z6m(88v z-r$npBzpQ0!7;&EuqL;IDmPiL)eNmW_pP;Lju>pZ@PpMr(X)(>>XkHFPR7icOWlOi zc9iXoEtNgeJ_s~xlD$7l(d72q{BAmuqY9}zP*IPO-!euK; z|M`mg4ktt=ND0@xyS&>tjb3}xqVc?vP!@C~f{gH`q>9?;btdUmaC;s>ZTK~mwt`w@(yu-P6w#fNy%_ z%IWGw`pHE**|Msjy1C&rhg4QbW^J^jowzKC{u1tj%jrjYh3B z>{3|B!;opAnZwqF3E`E)dvIrScYKP$u=QaD!lFY%$%J0*x#Irj5?rC6_^IvLYz5RZ zN`1K}euh(^={30(EhVnB0%)G5(`V<@!*#cwQZGXK>H_+s6h;^00NS}$tSf~zw#oWL z*WE&xC~RcLJ}sQ!++NDOSy^xhvDOe+_{(N*Gt}Hg0>L+Zrk+oKpiKd>v_Jqi1*f40 z7|KMrl9}*VFt0X9yNI)9Iz3ev-h+<)|Yn zp-0<~$NV>$+hJh4$*l$aYcmWddoL09JeAYl2UjqAmOv4dn7g)1fEa8G8r;kEP8&$y zcG4JPmPF_CkUX>bQeV3J{Nz&Iur07xcYJe9ak^ZiNTST-ZsP9i?#<7c+z(xY$(9(% zS(MJv!al>cT|K2-lrMoT$Kr86%j9xb|DuK9pcsy?Ca-^kuK+jCOkw92LZM!e{&O@s zk`%si`1*$UmB4ap=H1{>Ej@`WoedlL8TqY)C=xdEo;!&L(AVXpv+s&avk%UMYV;I- z@shZJUMUUu-6~j+%w%euWd7+34qby2y^0lSb+@L2O#dMZZVa+ga+)?qda*JyIDIo_Unn%1b3P?{^d#FB{Y?WWCITA&Mf2MIbq6i!xNHMfSB{z9%1)SIAT3 zA#z`8!bm39wP>53pjytLRD+A2rR-JiD!<9z$*AVU>zrP7sGpP*%6O%m@=cyDrAr$p59U3E#5EQ8VN@e zDxe~p3jUNd_$T;Md#FD`jrYT{gbZ=?D|Sh>{Rzr96lG_ z(e7dHeE4jdx~_0H0-0TQ-ImAJNFAswkoTZp{stad3BUJA!(;S@sc6A2nM^{f2s(Np z5KA7dH;^URj!)MNw$fs?g8D{y-nrzhjpeg#1*)D3#mqj=%IusQ9mW16gtz7F$iO~4 z&WbC}id<$@wmvem<}vTWmF8!f*{t`}OHogL!yez&9%vsmi3z7MtLBpKA%|!ir(RR) ziW~m@DA-+nnEFeCpSzrjpxdu1mWKB;L11PHm4r{6Vc9|HtD4uCO&{nj$Sm#wSHA@I z_8a=5fy^vr0>#lg^km}w8mPyFQdj!~<`ik90V{i8#nM@ykWR=aa4{US9r%~p(ZyBV z?Q?JD_QCTZg+j-M9uIvT`i&naLnnq73cVCkH{`G<+|$fG&vn-M+TpW%?5S;8)ci_m zxu(<>^}|-Mwan%qDnM2J0)4U>Jj~hDCRmqOD=OU@_DUjtf0Gu}qVxlLn9;+CF;E(i zzxLD&v5IpxcL%xbXw|iHTR{{`V@Yg!O`n?AxWE+u1?Qbd+rWE#92ghKkKbmqzc1%V zPJc39S^eexoyq2S;7=Rq9k>K*y%>GTNc1|r@$a-Z?irPsWJ9bjJY89+02;jnWb_oT z1E{CR3eDg?{HPv>fscO0i{Bas|D_SlwA&i)qY!68IitBT0mdrS>`D4TE9OyPR$n&~b9R^K_~ZAh)qrJ>h(lHSmFJj)TG8AA7lWDl9( zx#kw#S;6cplF^gLmPCyMaf%iv2_3AOW)UMV{^YvBfq@;|J`?GSNvM_Z*}KNu8!br< zZ!^5Ld%a$71WZs4T!kh5*SMwA8QcLD{1YunX5*Mq3kVyY4j4$BtcFEOI?5;$>cvETvOnv--_eF23Mg?T>|bjfS$8BvtI%(npk+iZQSQ{ z&|jDLw9VJgSKC+GSC)4-+;_|u>hBNUJTMS~DrdM>jU5*-o|_lAYk0jlQyL&gDTP#< z?J5XfPRB_{1!uHVa#eHncg=83N1tBK73W;&%JE41R&0X)y;v2-Li`V1+#F=p!aZGK9m$-r4 z@UBRx;geY*n*)=B{k2YdGov9Zqnc0>->DP#$$mQh^tNNRO8BL+I0icQIUbO5_=>E< zMUF-e(XpOe5sumlkW8tNb$D7%!EF;as7fnYGm|VE-&aBNDLUCsMm2Du!bT;d71-oC z?%j?u58&^fX{o|^+-~F0(T|gS^cgSZs!TEoN?CP?x)u!Mih7x!*MhNERb}-sOuNP{ z)X8C3(@CpA)t3vYg|$??{hY(E$Z;xcd?y8SlHOXcNG3@x`l_4bN|(XZrtS`*C4q*)Q=eG}s$jPC^$ zbaUb6^Km|{27`JAf)Yob$Wsz6*9ZfJYCwRq8rmuft z{DkAl3p+B8>X8(EXtZTok#u8zp`N&w%%|eg2)Q|L3k55W1b+f9eboyvomAFnCi_!R>8*{6ZYIbHmsij}0rUvNe zJ5yH^aZ|P(4|+Xxm2ZMoaV(Z4->?H&^9NDnWEb92Mcbn(ya6tnMLna|v0b-Sux})B zI=`ctqo<=A-ryW4g4W`TyoJ~AoSI!7sa!#O)R5We8jNH$lIL$2>5RcBVr#=jwh8KF zS`0+jl`oJvkO%i>f7~FxKqNhJ7HuWjLhJM#Fr!t_ceJ-YT0PMnHWp8d>FDK8PzAEW zc(#B!kCH3O8Nfl$zy_C-e!s`Bk$pz7N<9U&*XfS9HyV(X$|!ZxH!IZj+zwbcF9|n_N8=fo2Nrr1 zS9NYZ0e0?)xy{-rY!&xPr{x>Ub94?|Bj$MJyv%(XgFFR79*6V^{Sn$UY)jaSFtVY; z|Abu(n;e!0PPstnd|V9aJhk1uToax19Eg&|5A3;y-hCWC-ddzo zHe;>sL)8=&s0{WTf%Ghjx5 zt%~$e$MENN6HkaXw7cV^ebQrgfk#d(hv3tFBJGj};L?4@DVa*#DU_myRmE*nmbtU2 zaYQeN7Pm8c%3bJOv!P{v6u26=g$f{XFarF*hpPPrULlQShuWrSo#w9hYTyMorFn9s z;^gM?R@9jBcFW!sC7$Id;H=|}bXId_bN(hDfNUw$a2KgLqty#=6XoQ-(kk3vf56b{ zo71>4I;q}UyN%-Z3<($CNW$w(ubs!A8{cFLcFINaG5Q4Vp{_rT0{0@G^Vc}RbYrR+ z&RiY}Ht`#8=3dbtr+SFA4t>`hc!XEH|0_6^7s8E}!&~=E+$IhJaY`xvWXg#~fir|t ztTQ^2F~TCc)F*hs>)=WqNt3ec9kh$+I8IY9U;3wW#)tSnlP>nk=ksMowYJ;uLU$V< z7)2kt2Q9N3&nBG;v&LE>k{)ym-kN7}Pvw`=S-qxavURs@u-&wMvjyQ|esUwrVcRrY zJ)4{Q-B(S=r`cCYrks-7%TB(V&bZ?4iK|(IrMVwC0K0TWILoR$3T_pP>fy2Qm2N?R zLG{4!J4J!&^q7fu9`iwZ;Q^RR5g3R5ri3RXov}gBs&7K$zX|^H0Pp4%Y)(P=hdsW@ zz8=0dIB3V?*Zt-zOHNHlU?B-f*U-CGg$Ma++<;GBBTU1q-x`H`PF1o!w5_$baEQ+3 z&J3=(u0Ndk1Kji7%iJ^3_-Aw9arLCX?*;0V8;9ay?mkNfZr54{4HM6i?4QnRN_YQ- zJfQ7l0&Zrdz7K2+4EQ$%mn5IIGyDB&AUxP9c!slSmZotZ$|JpoanQ(Y&M?23wXD@3 zd3n&%&k}a>Eba>TcpfK&b<9rn=sn+{4C!p;pl8|&!kHI!_%X5|TGBhFHXH^*6Mjy| zubRS>>@?mP8O`?QHhB3GoM17eoKzPk<7)Z|CRAQ*CU)kAtkxh0`M~BM(WkW*5~CXF z$h5Q(HOf=;`$@@kY0X+WP7N+k2Ei}R^f#n&C#Rat1V5;#@7GHiH{j(mp#a_{3>E82 zInnyQQ_icKY-8+=9T`X-nd2(NO}jNbN6|623t1j=7=6=z?rW~cEjkN4$x-2bc9kZJ z{SfQs2$LKOv|Fmj=q=Q#{0)?TOp3Jz2st{Rct1 zvr2J}Fk;Hu#FV0oVIJuMYy#6pq+Kaeh%ii(&A>?}QK%P*bV=Q%~n^WJWuXz0lO zIHGo!9%c%uV@-n%f(_BA4TMEG5&RL%gX%t3%cxJ(|L7f!Cq^Ui{90i56k99BJZsIgsttL3B@y%Q)OIOeb6 z{|IZ;2@X4lFFQO!3*Q{yeP4FC(0G5Jz^}ka+?eaNa^#8*25Z>Ft%@;LW!6t#&hk&( z8!`~aAfFNMW-;6r2Mt?_r-W<@7)0|K!iY-n+}qJ2AofOic5+HOtRwKgy~lq~skO zm8QxKltgIx2icO@w=mDIz-eCBIfKmNGtRTl9nNvis?IpaLPs{oR(neODDE}QtaeqR z<&UKNcA}g4#ymIO{9x23hq{b@Me9i)_YypI5o(NitfpHqt_`(qXgCI=#_3`F#y@Mb zmU3^`aoja`#ir6dsTSJTB%ql4lpiGKR8!laZ0w>oQ!A<&I6=;WVwML3-2oOX%R8kg zNhZIsGriO!VFgbt9}~_qkfaTqa1-bdhSK?uWEPmgb6tt@Z!4>53sq${{X-N_-$F?- zg-on(baMHOSmvNRc%SZYdjIv$o;gGkNw}dyDSC99`Tk+}8!_D!2 zLZNjtp{-9qnbSW|j>*KQy{3n{Z2o7R6HbeVr0t})jpdxrW&dPfa318QFoPc zw_#H1LzYw#w-481eb-xOSLbs_4M((HwYMO>$FI~-mdRgX7q+0}8VBDqjf#58>;-!1 z!y|VDSL^{J7WJ=-vva6<*c9lDmUAYR7G}WoW){10cE1A`Dhp;Y04>fKlsRoVbrVU? z@qo8PEf$M1FDZ)27U)q|;a2`YZbStmfjTyVNhYH%fs4N9^-EJgMQiIr@!x&cbD_C8 zg!ZQnyY(%n;6UpTK8LhIP1I-;(K;<*PM89Z7b#>Hepv_j|8s-YPsNS$%$P)1_86yL zIXczTbgJD@>r~LnqIPVDHvJe1yht#F490S*^#QX!Y{5zt!-jYO=Rz9g5|~I5+iqJq z)CQ#-8(5#!oTHrUoqO5+i=Exc-hAX3;;`&v>_2SvZ5!1uN^YeSc*R9_PG+G2xx1%1 z6>93MK%068f6#lT3|yvHilS%AMG|`@dZ^|8``iaGI`9gu{mEb{X10>#s+GlAQ<5Gt zE0g>mt1ffEjnKCng2=8a7H|G7zOgT4D?Hn_Y znz80RPNBqBIr`Tv?Dovu$hw;fR}mNfZ80^P=#?N%3i|rGKlUS3Gx** zd{tmuwljHZFfHZLLyZ2Xh8O|f^PRJElF`vphKCD>hA;neiHJ7Gm;jQT$pJYu!2u`YA4}DcXDIaVJ4B!fu(5EQqW)D z_uue8M4O!n^l3ZZnt_~!!^!R3p+|wv#G2#Ch_%ClO_M6gUU>s9o2P1L+%1vZ=BB~c zw_{}vb96={lh|>cZ0Mx+4Yo|Sg?J;|C`Xy-J4gpbNo-6yw2JriHuK6k_TXFmtz}rP zH>jiQgB$qy5(!w@xc_4>cXqYVPm{4UmkGG1`N*unxe`ui9*Y*ZDXBIwAVW@6Oc|y0 zV4fP7_&%O+ZIm}x$k8Y+e1s3FXNlahIEYh314~&+npPCJdSQBl{6-0*F6iMzV+T`{ z0*~C=+<@04G08B4t#wTFZ$P1vvahnEC{My21W&EKATO18j)&0ZBsPyT)7oI?A~>1W zYYj9PZsi5RZYXZ^Gnr)ieEXQ z4&yf1$E2AiaxQVYUH#C9+EI)(aJPm1Eawh)-*!!MWpW)Lkv7_q%rOY}d^%fC?u*vs zD)Iv96{zR}!Hdf13H-rNro)nWI*!8~4?}s`S0BTCe+ut#R`82GWDL}SQ~zaFL2vU0 zl)XQE#77|+j<$w44?55(Rzr_qg1pa%4U+i08l!2LV`hW7iZtFcv8K=uX^k}jJ!dc8 zX^@Fo?HJwqSxzM9+4Icd%7o#rM93HTa+Y1#G@Ku1CMo0(7+|-m{K^C~oCc zwUeMdN-0hL@c~;8ds>nV2RReDwz%@TH@iKaPM&R^ryiYLXwCBs6tA^MCWj!Id#dX_ z=*V#H(|Kys)l@J_mAI3vi#Q4l@d$Wt61)_vwRkwB+ks{%$5#5A!}-|#K3~A+@#m)- zT}!fH1+W^0z49JU?*ml0*HMNaH}c@gG*II1wRHN%p*TMNUmb1}RnZdu2rq?;K}IEZP(TTAFxuEBD}Q8AOUN-L68Jd%F!8EivaKA&*Xc7p+l zERYJI%*TSET3?bsqQE2iapLYG-N+^!B0VFebOG!<8QQV362Tu34NH2Y1vN@83| zM{!41u=)#}NIC3hxJN2REkZ6pkUn@VezcrKt_FNloV}UuomvjYk^Iy zm#O%pqXV}CDoXXGC@PzS@00^j}qpHYgEJ9f|Mhl{( z&;v698R(?CaEw;(! z=<64w=~GD59j2AgbP&PCBX5sjt+DBp+sV*ZNW45*d9-1FNsyVrEx@hrvYZ!1!hkWF_6ZA)ck9Oq{jR&`4S@ z?ID`cL!3^_sboV*Z>kC2rZH>GW^X45X;E?1qM|nB=MrK%(MJVX1F}>|@PU&}L^I+> z=eQ1BqbASGZvH~e^a!^21>8i?NWyv5l8*9-`4wGrlr_(~L?tPRmu4==$qSN7lETa9 z23^X`++^Z9I}7IDUdY8eiRKoP%y8n%&E{0t*PL!+IrB;wS(u4a8M$CZdh+aFlEyKW zQ=khD;67;ESBPc6#<=>!NFn zEAzkeXp6()XpMW~wVGHht+bMdaU=X5GT1+wZrJ3GbfPagBc|cN?nScNY+otgpM;YM z+@pmeXL`a0QqS~+Ql$6XM=d&wtoxQ|Aqoc9p(Gg$;!s9kN|rBAMV{(seh&Pqtn;saye7yR00cYaFiBp^LgrcUBNR?;s|MtztQx!ky>>7Fq?s zd544OzeIo9*QkT%hAX7O8_F9UVVxe**$;!MuY#f|H@=|k{ELO@;mXRVaF*{>Qlosk zuV%A#wXMJ#{S<%7WB$9+)`3jZ%iJVo($6W%P`sx#&`}Cv3*i8o*j{)RN|Mf(5q(ub zu;qbxTfb6S){{iAFt836!RJ6mbXF%pYes{fw*hHtN?#KNcMxUWqlTVCH879#g$h)* z7%4$Y1Cv%qZa@!S8YV=SZs7{)h|=kWxE$VC6b}kr@NwLPN3F%b|Co${37jT1@P}nV zZ;=%rQwb)d4(4cc1?%()+Sv5)oNbv$7cz(LLkV?^pEoj#_M>8CK;3YNJ}jU4p3J^l zh7Z=X2`L9hwLV%d?KAAnWH`{8^h_nu6gEdwxD!olQE;a?tt%->J=qlt&7vgyO%U>N zv(XHx7%uB^N*Z;KS{m-E05=)_v6puYXU(2*T!BU2isPp`UH*1%IDBU7WP7PLL^YOK znM_|?gPbmpI9PZ~X7nA_&Q}svi<7jpA6-lzy*;;l^uPhKo6Lcn;9|Fof~+(fT-!IR zJ%4#p-iA)H3dlln5@1SlUe};9M(J(!4rJ$dKnvIi2BaXm zz#yH*ZjwT4lXCq4E}<1VyO*qlE^uN&aPd|CVK^7+gZR{?OBsn?<%K^Rwd@&=%Uj%# z<=0l}waB1}HD_~|Mj=%GZ{R`}lek$7ME8ijyCWQ(LunY$tmHsn!D^M=Qv&~2I#0a& zh`Sd##%os$|R znL?5mPa(D^+3Fci ziULwy{0Uu9$hYHMFAq*>ktn!J9K)VZ4+j#%1lA?&v@W9B4;sDUHieQK`EXYOp#3ek%aXiEM zf!yS8PDII)#Qz8X-W%U9Ut)h1e&?<~A4zyllK;ZsA&WAF41!TRX|_YXwO%ME-XiV# zBYB(;AxIAu z`A^+Y5(VB6J-L1a-tCTJij>s*#*gYsC%V?DNGD@D*j?gkCT!^@Gpyk{&z= zKHZP1ev%nsrbi`z8$@U&6YnH6a#P_^H#2?RGvfKo%Fz`p0FnAkYGY|Q`f=>d!z8^t zWR8DE?(li0vxS^ZHSp|xK$SAkEJhyeLEc9Jrsremd>W(h3DM)&>o52brzIhgY#{j& zcIN)ecp#6Wa=pu)2O%I%EkVT(gWNmR{c0oIAKO&!l379$`!MH2GNjt$_T1w-<2vlx zNas|;WjVJvt2l3?bGl+LZeMQmqKaR^jXj0r5%317#XiCnT;9u>i)R|iQRLRKH8U=!<>E)QM4}&^q<3yW4&5Hqtdqqy*Yx6$;dn-tA zYgi;7`)Vw{8cRQ;kL8q6^h??Ttukm) z39H>2w4yJyV4HQD8dV9V>W+{D&Ds|+>felVSkA9RE7{f6;Gt9D7#rF5*=0vf?#0~g zxaD|5HrI2-Hn+d0pL&AJZxc*ReS8G(#9TbZgVfNP+%x)z6X+!QO}m*W z?ty)h+vvaNKLLAih`TAj`%?!RGspi4G(snk4fRc8)~N@FiidechKK)#d-4PGd<8!Q&iM7Ku5!D?Z8K0kXxRO*(j$yncZtUZ<}H(X?vj#BY}E0*=D1_>6({WzC*Nq{QEWO@oad1-ji5&k93DS|1>0j zvr|h-oA4!fL)UFnYQuT#Q|>806-iY=3x6}2o>Uf-SWuEg!wYcfHJH=Rq5vx+8F&(> zh>;+gabN;#;Yqvjyh_1~WJYh397Zz~=d=^lN=FIxjoNbA+C#_Hm#3c`4aFuH($u)* z2jQnZ30vsZ=F)whM|EwWSgrxevw_YjgZ~@)kuA*evvGg!!pSD$shthO)PTE*s?kGr z;-p;%lj}9MnQd?fpBDzBi@7ZI#4EG_ea>9fRNH{#$C3S0mmI$C_ReJT<$>=!V;gGA zV!NoeRsX=^JC%iKS=UK9!JR8GUvxwFzmGm89ccO?Ft;JpurA<|V{j;(K|!6HRkIT; zs64&VbL!<-@g{hB2TtJE%)!;bkSEb|Y~W6dRXn#|Jh@cdGqagLky^S&ZOw|Nsz2#} z$Jl{R^1i!LL!YujdKzU|RezWnF5`$eLuYnVf2#+$cd0aZ+$3WcQ;kZtcog5sI=+*q z=5JF1qe{*z6g1!m73K);n?B43De0z9;LB|R#%l2u_tXE40fp>BU)@6=NvC_59wjX` zbgyBjH@k!1czyJj>Ov|_=!_0!97zU~s3Cn}uPRVaO-|_@u*3CO-QRdhlfek{^T~ZSZ=0t$ zv!c3;darQRM!8j#O#O|AuBkSk9#$rOO4^EOH)ac;k$T_dv+2&8;`xBy^DPm z4!$w4BGpj;UB|H#H#GD&_-KHq4xI36Lwns44gA=(z-xuCTSUI>&79Hmow z=9`3zLQlAnu#fEWO=OrqMk`v`H_P{eTOii?!vm87GA<((4lD`JHkGl;NQJ)BPAz<5 zl>*0jE@a`kF2P~+L6pGivPt>*R|~Qi(vf;CNk2)uz6HjA0E~AH`tgOlR?)@o7yo1a zi$iCW3$1fE^o`MQC7)qbvNFlmLW9;8_tq6?kDYAvAlM_YMK{lpE9K~(v-a7GVNi{ee0M`s@kzx9aD`zh<@CKdi7 z%HUXTihX4CLB+9$DKDM=j(>BRHjec;3O&OXoSQnSN`pDuLqQf!8|lG1KF|$sryK7` zA07f$zm}(z1U+|qlG|p+k z0MS?3s}GH*$o-Nk}JTA)aFN7I+Nt|p%1`_ zCo!R9;%n?@+A3oh)QuTT_1VCAZ=)kx3}65Ey-dendRqITWk<_*2yIa>c3BNNlBzt* zW+ijB=~0b!u?4xS zD@r1K;h%pWmmygjZ}q53O^q#+ZfS z{PV!!lw%(2f;VC%u8_N=@lQw*w`$-xo$ zh-ZL?ULeaMAKmByCi^DL$##(4Q$}*LA2WS=yzGap0BCJ@So7m}{=SptVqucKI}cOkF2VqQrG8{#=X9nk+C%T{WtuNV zGI>9^l%*i*J9%xT_nOWO+mIPn!cV)J^E9jV+FS#oniL)C95C~6IQot3tU7vbeBTN) zz07~}fl5rEXA0=mQ2A;`Clor>K^#hQ3X~w5rx@!Y52tGqZYO_AGV@60zTaf?l;CEP z8n!aF%&f^6bpm_-i82DM?J`^yOPKe@^Ca7XiB!NzkPl3xh;fE<=?`k<`t)v3StF}) z$j=L|1qFK&bZQmJ{k@~*U;=rk*W{E;Mdu#J1bT&2awUrO22{b{d^ZEgAh}D9RZ>v; z;&KTX<(#-AB=TLZQWGbkg(yKb)(7z{b#o+Mv1-hk89_WkP};arN2CQmF2}0w!I`s0 zJWl@QZ_y!T;BVCe8)!*a+YHvBw3JyArN`vC4WhqK;0d?Efw2#SBmm1*)O^6+PXm|R z3pc}6)^}ap!Czr<_mMKafuyM8WXj9TErZCBanS>uqMA-+N@)!zP@Zlk4ZZ&t^f4Pb zu_|$^_EF|*r<@5UJQvST#ZP{i->nG0y9@VF03N*QLu+=yPJJK3)hyqaiT*m;|A@P(29y9|7!F7+%cUq2--=`8G_l62LL(Pej&f60B7 z&q^zFPT5fOU$jZ~V)ho~l@G(O-^yOXu24Nk*>cz}sLjc&=s+e&0~Bioq-7uuz3^$& zB!9b@d4%r%4wy^|KGT_6Q)Z_ySo#1+LJ}s$=A7ymxJ|Vo$W0vIOEhR=d){G6Sj>xP z9a@1}JcZ+~Kvy#ncgAM)9Gk&BCcvgu2j57*W4H*cEhn{fKZtQf%LAje8%ClXXHO>6 z37+r`mHijeUJa(O4F6o-z0u_FVb%O(qAx^tLwA%l6@>8XbfiRW^m)L<{MnxS}lczKgm*Nzj*LYkAy*Y_$^O^m9ZgJdGae=k58D453 zYNrL@s~bTDuJJ6xc(+|xm49`Ib*P{>=oXvtNxfzA?o21~5G1%X9KaN5J$vJ#bd5R^ zOU+v;jiqxb$O(Ik&#*01#221aKcNKP^~*f*UaXm%-1uUeUS<@M|M*ocR>F4B!)#RI zV}gsXe}+uNp&*3?*>kVJoW_u9s;H%5JZ9krK1AB?9+0$QYJIef4{-SwN5#^Il*nm3 z$JubO6Tl;fSieCI|B!j-GFE}UzS8FNoRWhz#Rm6~`ndxh`xd(N?5M3aQ3on=k|vPC zdB(^|?-?{Z(Q)KQZTpV>GD*CQma{DPR?VSWo|gXObwb)HEoHWkqzl#PGiOm9lcK#_ zNzKcO;`bn`yT+VOvh{}ZXg~XWDPMOE`*|6=dOzOLyXGe{Et10Hm$xFA`nywehW&Ht z^aAs!O&^tl_iz>TdJtSy2>stWwETsPAPR>?ocLArwA8TA+G9GXTm1Z$N>-4nxk`Ty z7cmO-r8w%1P`;0#H3%fOJKb|Tbo>$Aagc#Y`Z0>9-mtn4=|L0OGV)3cuX~TG)`Q%m zhdlK($|1QXy=W)t8;rO{^$io&@qOKZi%g9!XAX{#Y$PXqMG5d6v`*qz^-1BpV7N&j zIKaQ%A3muJr)?;B>~r)?t8f(5mY2cDq*dxG!<6O90hElfN(`LHDo(;i%x7Qa-OQtz zVbi8cMNq8FL~rtz`KJm>g&pkJ%+^Qt_6QiIEI87h((P_Vvp9h?@op%d8^RM*q|WC; z@0^IK@{4|t6p>Z9X&P~Yn>?vCs4+{TEw}|A(w_WM0oCgcc2{?9b*hGjxE#}cYmlEU z+>DzR@6vIwt*Kf-8w_qX8F%s_v$5s3R-iNZObU8I(s#Qm227|2WqZ)pmdR6cRKlgW z$gv%#Vl*nz{*HQr&-U0tvlq}=w>UpIC32sI4d*_B>$|oM>Zi!dmJXuh%&ovve zLj&y3E4UP6;LD#I2}Vk)Q-9XZb5f`qp}o5X8unK)5{(94VZyA5CZ`{#_gHZhj_d5*krk)A(- znRFf4tJh46Lbo#*+!Z-N!aJjr3K(8H%0%kmUi7Q}lR`FTRHy!49!vuvfw23^sZ7{CC zKt5XrvXG4M*ni1?0Ttj|GK`CoHn=Zf1)|s~*|nuwh(48U#+jTU^EiE%po^RLkB@l< zpS(i)A{9Z)xKnW^NxiL{%+{GHtur z5m(erddiASpvTdtRVRhyEt!a`Nj(`37CDk>dJ`R$Un`*ZW5Nmt@4AVKWE-~=EaC2x z1#~KNP!di=i_?|VwBk(c@07eKvsaR{{Z#!%*4aC5*V;vXTy<3hBWVN5yj0FjH&~fl zjq8fHg(e`q-7GJ<{!C^pUq3bXIWE&X<2lF(LTcAtFg5wW@duHw_#9_X2YMziEZtJh zi;~QfPdF+2k?!za$VQht4z+Czp7)n%yI!$_E}->V2r5()X6Q3W**K=qB;;3ZfxjqA z@AQ)Xavt+m6u4|bx~0@+3UIhg^p}4*`nGfnGdXdNgSCBSHzcAvD$L)j!mEN+63i+! z>+dT*hixDXQFIt2gtF87(gP;P9eA1UdIro#Coqy0Fwuk1;q1d7n21Sg8NQmT{Jli< zz+Ucj`^oJKK0NCp_@fgggNnQQgIeDfZL@3<_E~6dKhsAgMaAeL%i*qlGxK~d6nK-E zo=&KB)q6@SM#2=$U(x`(T;p*=IG-Fvap!%D`V8 z0#~m<#^Pg;^{I5<4YW#3E`|9~UW;UUpQ1%`))=g#j@-n02acc?=;&E80}9hwEuklf zhXXFjY}AwIF^AQ)>fdV-DvI%R0^Pvx>Y$4*3Wkz{9w9GWKy@&gp)djGsJ>a4gtwCL zQ2|`+Cs^nnDG`a}&*jRn$R1AcOC;O(#BG&;-k}XC`YY|x@O7KHnW!&!z1ZzBwjR{e z-Bi*mFr!y+N*`o;n*)y64a|#KmOlC@`{Wv(hD$$4B6>kmfu04=F@x^K{c;=aZEEW3 zWN@%VdOPw6Lcm00K%Pc1eHTWd|Cn{!2am=N(7V4?bTL!eNpRa(9ESUtA}5O7S*2O1 zqqp#H4};H0&xCXd9mp88A(iNzlCstUph`da;iFe_TB+z4%i#{`4AU}~KY56*@&TXI zN3!0(z>YlO*GJ&wC-4=@peFnP9x##5I>Y~v=H?*HvhkdbU?VGYB53n??u%H*B>$4{ zzarDsc_W-Eaf;7oG)1*DUl6W6FgkEq+%0-vM9T z$X@D*@;izAkq&hZ9eQzk;`co9CCuba@V;kao)6Luz7p>9+^>?naRJsp26Xu(ywyRf z<|>%`p`Z|z*$Dx<;FX+Wxw*e$3q4)9c^*b2D|usM_5Au%ZH`uz6qOSw?`m^%loyZX zS!R=cD0{Dy&X_n@72GE_7_N0f&nw@%UKu!nvj+I@!m;AQTLzY zT*|_&nC(fc=mlRN#o3UQb73#HqZF3Ez@jw(Vc3aAz%K3wO;7!g>uyR{l(b9yTkhXg7+2y7Wp0IQ>!7ZX-|$mm+EFt5Tkc^RAi< z2hC{GL{Br_-?m+`9kDH?wie+wrwu&O&&(}$ZpoA7;nHXJ*eM|g4vIhQmP>q^yU1wU zrIpsM1-l26!ih}8p`0b4pmO|*hSA^_>@ujaRx@j)#}9A>XI*FQjFuKm_pY9gPURVj zr*Y;L)QJr^C1SY8FBSQp&Ee)I(pN2G-dqi@w2(=40{C=4IGxs@oDo#hIy{g1bf=wo z-c$Hfhhd<8zyuT|NpLVo%SrkLlZsLrHmn<*=_GdHY-Z@O+_B!0tc(nJYpycIcM%JK zO73Sm&x4C+15Yjk80mIqk@98|>gsir5F6;ASF(clGO>I?i&K+NGsbkVQ#Ns`HiVf{ z>D##9mR|ily6o0esyDb&swv&MqhJZM-wIsGgW2!76)y=CgXx&=lO>m4I?l@Z411f5 zlf4qi&oDZcbKGp0i#@U%HA5r47=2T69N=kKtyR$lE!5BJCJKk~d@spJSUCVM|M&a& z!+o3+$gWlB6Z?bR#PI(8^m1H*jk{YW8{{d-!*c3uZIoW0SjSUXQyJ+WPjS|C;yg*g zY4i*&++J4eGIsSMD$NRN%|R5)4^WR7^foy-(<9K3^yGw^$YeBwbv6NnqZ24pF}^}P znwsrYj)rL7;yG;=;wvd`gz|1;I2UGs;dG~a>Ig0!O{8qH`els7ZE;ccV3PF)O_*Bsu{RL;X8mteMDj@#!w zStxnPqV9#7?HGMTI4a!9RyFw97woiEphY84%8q7dZ{$<_$l9xdg5xN2QArra3xb;! zvF(vK{eK! zjY!NmkE5mrHwZlClblSqSe?E%4Lz`psw2?}dqCRLv44t#bu{A4?1}4cERMNZXfEdQ z^At|+!Tj!DHDm?OqqO{8OZx*7@>Y9|4(Ai8(J~3;CHNCF@dxR8d1lXNxYz!AE_%}O zMNyCHGlfNRx;K>{;rhIeTj?NaLu1j}=A>8JZhr``lmbpEnRgx^MSaECOcH*l9OGG6L1#qVXcmVrn3MIFD)+Pp|@|A3w`H|WU( z`q3XCP91q7ub8r%qiVcL_UHfcbRY0Om+$|;&$5d$OOma~%uM!92w5p3*(9V0m5BIS z8Oe^UhLx4eisve2RZ$7 zojLE*nEo{<6r7Jb2JM%sUi9VFC1mucVB7@fk=0Ojdq5ZX3A-MzpQl$sXIR=%-&C*2 zE`G@ecG$;6?@-F=gI!qWWL1$3biG5)Wj~2Q9ik_!mdlT!*}RNx#__d9epeG%98XJH zKqcs)gSkjVS4pW7nLa)z%WW)LEYi1;)11>{mHwtWdDG#2d!kB3Pw`~4HZdFZ`@Msy zWHNy^QP=AoPs}RnM1qRR%FvloCOc_%p?6%dxWDE7B|WFEiwHj+FI=S3vlR=z2>-Gt ztf$RqivN|J)MX>Lb$oAi7VBnl>A2cwcA5T%qU9aai31^0R`eGdYor(FaTvZMcA94il!;9=6EsY1C_~Hv_VA`ylECNYSkGou zjVE2*Ro1r;Z{I+hUqLthUbo#Mn#lrjeh&6F7B;<$$GwQnrH36m^``~=%*VHW6Lq>` zMj7JII^BFaJZSIqrYi2Nv^wZZFryz--TffW*?7S&vg!8UlTehB^1xFncj8X7bqBmn zYc5V}J{Fr#FVS2TyEyaQW%2vU$x1mpGMbOi7OC=hlFVS$rmbdy5_ERtquRYl`RU_{ z8sEz}V-l7+g}+ez9krx6u>3u}EZr%O|MRykczsUZGRAWxGvfxco$64^jNs3nj$X%< z^5&?!GL43G*0K293jHG+?c*{!&@dBRb)%o9Y8;>*^`#R&W)5tn4C_@L9w{)jwWiYn7uogXJu24g4ynen`wUO6zYbdipnW0>he%5)#v zT{)8=3Hq#mQEQq2pL@%mJ2?ljr}g^+S{*jSQr_=fn~<8)wTv~sXGX1-r)m_S?xr&b z@o#MFxb1Ppla15aP(jb~Z^@IZE4`+wG*oBKAZM}H(>r!8`QqdqlV?o+gD3V}He-}3 zZnUgrf@dU*^W2UJnEgyBv`&BG9lCdYYTjmtN$%AAxsIG8gie6)GnyOfXBcNrh(!Zr$8ZmoS@= zog$OfywTH8`(=0jDb~GPjkloH8EviaB@{C^GcD4MSQ_6NjVb);l;%t{*rcw;I*_3} zOV957jBC(hC5HCCO4ke6M6A8rLq+{g_Bs^`&tm5rpzm$f&FYZmJ6%5caIOW>H|S*} zV-CjT z%d+W56X#QVj?*8CIALw7f6dPh7fc8MOAMf9%&dRDVOs3jj>;Yq(0eCOWy9O@?< zFq)Ql`c0Gc??c2ZQ8}Sm4~pnq-AzBiw@r5b8)sV&pgxy}b63^trs?CXfrnpa-Crj5 zr%G1PXO&Ke<>iEv7}Oyjf6wWwP3+9M$A0@V@ zrWRtm=UmZb>Q!~>&JF%N-zg{UDE`mEwDg`&mRg*B%2a55_BvH1E1CNq0x=#F^ET)J z=plmU)iw1;bWy9cCnlRbdWH_|l=^P!s&IC<#$BB5S4|z_jwkGm)TbWfe4bV+x!=0; zOL%sns6F(VY);+pFGsoR^nroSRVl${Qp?=paJf7zsUvUM8UHYE{6gd{i@TprEU&NO zDqorAxm6XR;x7JD%T)V(O6;ZBsJL`aMbGGrkOVpXHYYm_G51~2S&JKeZ(Wl``y(~A zysqL0?4W^8*}I9mt=Dk+`^)s994c#f@z5jgcs=C)lG68)Qxaa4)ji|f&_dYdSB@xxWtAkMPGKJO*35ofZXjZOfjdpbgh~5CYoEBmF zU``pGZMMc!1bTRWNC}9Lj>hNljyh(d)JQX{f|QblwzJn`VaG|+$= z;&FQ4kGey(4}v7$da7x@z9>4uY}_NtEzjce?AUxz$5 zuX5}^o-|V;W}lOOQ$)`YS@-E=O(!b7o{&0W1I@BL#&$$xpF*AQFDeeip}*3yP z7)=w&ruVOevoh+%pOlklG(*u!HDt@ZVa~MA=hUHp?jD!BpT2wR?xUUr(Z*!*KDuZ- zie7p-+_nb=Mf_{=jYXcvWP}Gy(SL|rW>mu%FS~v~`>I8yd*5z<7U{_wZOXW>6UQ3q zV=17!@s6&&pK1D&>7_l)fizY3s3gvmuujFTQn~0VqD?(te;pg`j4Sk{^!3DL+xuEg z-}QuEw-Z))yuPZ6x`zL-a$ORilQH~E>u((CeES*O>%}rl;q>7-k7@jwhkDUeO>j14 zE4_I61a~*50Jc`2Zx^XUcs!?mfhc)Qrx9bM9pk{mn|{ zjk*Ujzrmi``91j(A4$9qTMpqz+pNdW5smJEGc^BkDr<~?%|bMn&N`ftA%}8@4QD&Y- zz@q%7fOeU<_|ST@Ay80?_S#-)=5LLEfus_~G$247M~0ul&MiLt$e_odV?|QS>a^p65Q=-~vy{RL-cKaozVjF!q zz1Y>mDxXhy*7dJ5xAUqVf*5-4{OEfs^pi~xt8if*O6x5*eNqv zvC#Fhr0U}OBE3LSCX;KJ?)ijvz7`X_uj*e*@6lXc{W(0TY!xNvZR=Ru4C{+lv<_Pf&TL$# zD|Zq*coVO9h5~ib(>xcd9`{jouO_Z!W77%Z%~_M2CuA?je4LQ;pNl9hHzCShAJ_ct zq<8zt-H+41*F>E^PQBqPYhB8HtQY62L@I)VboQn+;qry9<>zTuS9Ra5R@M2^Yo1=$ zUv+1tdQe<$>gfUx!V}pSU>Gl}R$Nc0O`*6FnbJ5A^&||R>b|1Xylc9%?jk{7Jw}}< zOJ%W{tJwBf{rqxnc<-1vDNUe6~~quq4J0G*SUkO_8u zs^9J*dcYU%_Gz(XCB3_%ldx7h&*CXH_xCWjr7G}yJXc|_TF6Sw_#^%C<($-dOou>E ztNlPu+7Hi9Fs(HNJIEyaU5t-4VXKcMUN?dHi)?GY?u1FS(Q$OPDOl(?xM=8V{D}?z zhMR`h7KpTpsxj5fLofMoHS=bV>eo7e7fr>eJ3-MGRO><)Pg<`>&EXV;8gHQ@O?3n=gFbnA$pZD`mqn%w@cfP*mb-czo-|+ILT;(>x7xqI>gW3`|I8r&(Mf7t5`+% z>NpkUg!`qPPqJ8VPEp)>fto^Iy$R!4&FzGzsL>tdij!RTk8FB}Ro{d8{VHc%DOa5q z;f3#tZf)3QeKEVPKbxos2TgvYO2BH>p{w{qakbe#nB`gskcfAegDXAE?@W~4F2)~z zpua4j?~Rc?w9~s$1nWJcQaaIn)f5YF>V^82UvZw#erq!>Gg8glqS;sZ}r*2B7uVpCgp z>uxCUs?|B66RC|V?;d&6a}aR=Eq{qzeut@mz5d)}|EKB&dedxXw&>ep|6H-ZA&#Gr z_&aYOVIAw?kJ(hy?y#hg(I56YX5!@Ega@khnPo@C?NX!2n)kI=!~E26e-5$(-F>w# z%gbrCF7ljjAwUN)p@4kryr{H1(vj9zPSV+{hume9Xz>$YOM@eH6%Eenbm~eijH13x z;%U!`);ppzsaj3b-26dmksHJ$d!V^5ew{{W^t@faSw=xnF;ETWh>hB>Sxr+V64 zSY2L@{+FJWaqhaYj4i)T?=&*?n-BiMApgcD|B|nT*WW%nCYJ2Mx;E-$S|pd7AhYYO zit>soSSbomcG=3qdN)$)Qb`-Bj#Z}o4D^J&gAY>6L;t69CmW`+NpI5twUDY}N(ShD z+Sxx#oLbsn6|}eKz2gHSeFs!ENR4`KEEeZ8oSmB9c=^1A+mt$E|D8cv4 zoWCMZn(O@E3o3_Mv88fm5^DOWAoIzsqy7f2x*ThIizjAxR>p4>gLm0ge%O6nq?seP zdDpwWEFMj*&gj)hi3wMR*rP zt`#!|J9(6%^f5m_sM8@mMY*`DTrq0X!*StU^r?7N5f=J2=Jb#!Ew!Q`F18jAu>V;`Mi((+&m|%Wsfa={1PhDt* z$*jk8Qp@41v;M9W{r+Bkb&S0!U#w!KGpf=b*Ap^_D*YCOEQggoqJR4m@FNCL3W#0?ogF#-O%rB>d!jV$f6=_9Om+yOl-KHs2G)n zLih_r>LXGW#vlGw<=?>9zQi2HU^5eZwg_i9s<-h8{bwB@`&zoe6W+0(tm!ZWdsfA* zhn~E-`bd7p^R_$bY&FjwVFJ4%?>^0{dpITJ65iGnLT|)23dq#PtFAn1Zea>mR+gvS zRR7o|@@(nWE~AiCz$a-Zu}H6}Q?V_VjnI%yTM1XH8L!pwr}1JOA2wNEy{W z+sT2a>1|x0kK{*t{1sg9%O{?XoN%#Ozjzm24*0#hS?7KhU`+iflEs}m`76DmhFa57Dnpj&pIv>ow8-JRBPZ zzw6Qz@?-kBRP4)Q0NwR!ui`V0$(cWeJ&6fbFpv#Sa(P}QVUcQQCRW%PN-m6?S$EpW zjh9u|!i47F2h8Bp-Sy%SH~0n_Bth>JS;x`Or@FUy9V3q*F>b}+{Z&yyz_LtoowKj_+RC# z`(>;*D7%F*zYo37sf6ryV-~wCqmTKB8Q@QK7L=mhpK=EKFrJ=6el*YA=VQ)n>zZt+ z=b3$LZs;35qo3;3tC8#p&(Zi=eaVy9SWsboIUndd`dR->y6ENV1}Q}SPNKw7_4uy( zG}Bn!t>WZ7@qd!nS1Pa@pwSiCQeo@bljp3Yxy7sZmQ@4oCIXMAtbc=ze5b#8nsWv| zviGm(Rd_t{78bq03ccnxXGpm09_OlubYwB5Xh4}|ym5NwQc~8l(PApngnEfkOJQGp z{L|j;J+|~4c9|7!win~S#>r0T`beuk=V=`4d9#64y(+8cmeR44Unck%L`kDhCr&*i z8BHp^uNC$^E!e+>JnEk?rE30RqpSJys>MjXjx)5vX_VSioN1AwduOpFIbyeo+z^luc(gIpEF7M zjo3L{^c?R0^ZfmU>U>e>1dX%G$zlE{>?NfbHbm@Ckb}OZGQTTwa$7mwDII7xAF2a? zD0X(|)wL*G>8Yc8kl@`#$tDDtriBr|Irv>non8eHQa=oZJUOlw4N?A==Um<-Zxp2e0 z`lu?}osO=4w)eQo78&=6dmj!MU8@k{K>Fv+@#LSUXXEQ^v6}lg);f3q8{zSN%{;6)0|dY-O3{VE;)KT^Mc9r$StMOz0V4ovW6FZG-E^WQFj*5 zz;8Hr;6=*B95u!`id}1Q@JIFk)G>ve-PF!J@-XMbe?eCs#WBtTjIQ@PrG>%`tZdR-e#*>dl<)9kn@p%bs>P5EJK^z?@N>9_x z^gUH-1J1QYEc)4JYhmncdFlupx(i%+iCsRU9+HmkbkFI0SM|L7OV3S-%M`=`-(;g- z^MGXPDZL<6c+x=^+3~fgl9z0T z%;8t*#Mgc*3THg!_a$s&KK1UhIi_c*A1}*oTjNWutYJ-jG`sgYAN32b_=r|kjAtB| zos4DawdLn&`1(PSZi(MJMHEWv8k_C!8~Oif+{PKw-mNu@oyc~#vf*p0TaP=Vv8*oK zIx_Pns$z}sgE}y>9LsnbUS@)zF(%z^VhG`NLr+68NSYhUzC;}wu2*HR8J7IAltHFf zPI?wtRTC9oI#c>?Ob&kj8V?(VbIw(Vn=QJ38ry>o^o*yyY|__NLyyB!U3(8Z->-@M zXfoV6gga-`i_^h5v77Zb6g8W?%DK#~%+%yG_nAx$G4=f{ovYwg2M)uhu-u(_owAB}Uy%D18+#dSSEhy3Jn z+3z68TvIh5S>&mL(`luR^_QfxSHIJxX0YA&Wn>-LW_wD|J2*i&yYPVB$|?K(0KY$- zm`zqZ5}!@3BJ-Z=<8dooo-dDx`BvAl3ytDuQL>c2u=I*m zY%6MpXL07mwBNE~v#s8iNIt*YEYUhEIn5Mf$B6QjEc!3&G=_y#6QOV6@(Uv;FE-?T zx%CP})A26Ku|r;elv?z+9O=5eK81gKLfxPMmRC})R^ID*`clZ09;bOc5Wn`Z>2K(A zeX00$RL7riZp00l`vJV^7ja-SwA?Fqx~(~OQ_p9R_YN9^ffR1_btN2eJR=@a|I z)Z`^+i2SRvd;}w1Oo4ryt>%orCX))a1KLC5 zo~qFIp(P||Ns5HBsopXoQor~7lkXQAi*Mu(G^?DZ$;h9&k^~CrTbb<8x zUURCF6vOjt!QW0Q(qo|JFZL&m`)VyW+9bA@G%dD-(o~iv@@sT@^~||CL`(4WHJ(IN zN>}|fJ$P3=-J*aFr|SAhs`>vj$Zpujv;$Ok%*UX1>i&QjR~ z_H>zAio{KP_$7Tp|Jv1sGO583^gVoPm@3{vSANm%mSF+oDJi#AlV8I(e@gtxNt z_%upy)BiC1i4lc)x%jh-#r~sK5T_q2yPbXxVpX6+RmB0q^inZw7S8gvsiaq8U(geKSC7Y3h)_TcVm>U*61`1Dy@)Qr&uNBqZ>j-mV{J+UWLV0Sy${(0B9fM-5~*UXDNE8%rKVF!eJPCWXA$83#0 zWuhvj6LOx^{g6`^M0)3m-hlkOb?MBE9*%7_iGIO~r)Is!AlE9WIhBesl6EX-R{=v%$bf8yg)`CD;H zP)`^h6y#^U*KGX_xv;Ex6sBi&O)T)-ms}LYkxod7edqnoLvshVEX7)(z zRec1r=!dn`W6p?=16)}qh`N;46Rzh0jbsUXe+N^m0%x-6$%#`fx{iBZgplXd^ZwB{ zbJgoQz4RYnI}2qGT7_`#b*PAh*wYEhSCfI}!kVJh%TCjqerE?eC_9HE8EA4{v87eV z`m@!YRGuQ3;dJkwUX^<^yGVnZe5~jGMs$9sb9^iUZI8Ja6XSVqPnoaGr6!*$_6EK5 zN8GF???iH@cSd@v(Se zZ9Q4j)Fl&i2qaxaDp_AI*0N7*&kI31xt7V%-_q1KdL^BWl{8CRGg1vX!M^_I)X=h6 z&2gAK0_UwL-rVQY8+iOEInLXn@oVn5v9)QAowl>r@5%2+TC;Dg-Ou>cpH}m#Jx?xV4&-Q$9 zwRo0FKXX%TZ8nYazNptiZ_ysp)-CDxQSpD$@Mb&jY^a&E!BqUoYRo^G{(PX*s)`QR z3G(}Y;vbibc8jQ`r`%m$_SHgOI0)06PJfsuv!6rdnxxlkpq%jy`CbL};U}!XefJ#B zcHAD}=j$U}eHYE+9JCB)HkI+cUFA%3XiTU1V0J9CDc$T->})A5aKHNK8T|JupS(ij z`%9GmHG&?KB52SCH+?~cAPeOx9uvK6KIgpG6g6D#bgn?y4x=K_}%A7}@}u z@5DEn$WAVcnv=z$=DMuH{C{4$Q(<{%16=BV`2Fu9X;D$>bE^<5{`aJZoY4DFO~jl* zO}ZHM5LL92jQ$OH&>fHJ?6bxy#zDD^iatrH`^r7MM$yQkX1AB0Pr%t*;1z|%m9%W^ zHjlodm%TbKgrreEnpd-jRd9%`RHpyh>AE<>&EIoa1E?DQO- zxmlc>6^Tv#>DWCfQr%@HAM%6gk#7Er_N@p`^Rc|`sJdLbvy2dv+&D?Ga? zo>uCcW32Ais__ePfyt(Pdd4*8?~ho~A92OzF!mqbH-OJS6m^h=c6YT|60cAt7Dc*c z-}Ua@^aS)3Z)eK&j*75_-Ob1d<0wuKS->Xp%84ecASKs{(q7*AgFcL#6on@}iMKF3 z%@dm~f;b0Fj7)$y@Ma7=9{aCev@o+znZtyiKYR%~J(TNAe=P;|>Sd7eNN-muA zaufz`5m%P!w3tWdo+Ni0uETDCzMBrJvh}H+rS$GTuAe@^={LvCQEkG&mQZ=-Izemp zgRj*US3lUUiW2|e3B7NvakVva()`e6uC;&Go^CS3&`RV>h^nm`cryA~dD9C1|D3wc zB)s!tY-(y^J@xe9VaUtwc>BcL{E8IY#UthB+Kb!W+O`P z=v<7ZB~}q;snbHHI5?HwOjU7=^bPhfl^S|l&9J!MxDWl@FV;4NUh~S7^iJx@L!F2* z%4>+&(@lq4_2`1;c&H~eZ0iI65>CorY;S+Ti;k$qToRpb!N=SFyw0BgjwqzRL^`Nv$YKYHQEx@?xG^2{ zWxuakq_#4^j?I(p281f8>N;2seU?tq2)h3q6~pu2li!_oep(%tIZL<3WpkK$oJv&< z=X^=u$n$I^y{^~2Y_=c8B16n!HP}|sPpOzsHa-7H)J~RHAICe4Z}-NApVG&D0vG*( zm(TZF;IpkP<+e$*k|M<*yy*|>LU9;Bnth!UBg?^~L6nmXu8`J5( z=x1&3#+Rd|Y^7T?6K^h=R(cnH+)>@`CR^A=ugM)#js5qL2~T!H&@3NQA;Kq+=XJV5 z0cVX}Q#bwI^}frhOUdgK^=pQin=kcp^@k{LxWl^gw@Mg6N$Xz>+brbuERK~2`e&8d zCC9d|Q%m+*n{QMALZ5hjxzOW~_$>5V4y{I*YG}^}UZU)jmpfGuLmROCx83_}c6u6) zmWiv%gEV82;)#-n-zY8J!C~MaeD&)q9|B{PNrJXktY18PB z{f*^}Vxujn$i-kwdN^}8!a)P``I~>AkHn~J;(t8WmVhs5fe?G{M7;eJZ{H!;3Oe3+ z+D9**-ju#vR@R6TmR@tF$^1IRyH1rw&Df0_DdQwg2qC0vdF&~6&Vjo8Fo=rURI(6X*))P)n zu4|ndV#&2|mLfbmg{KAV5zWUy%sPHD9#dKZ6WhqSGSRhv$91RDU)~lg8k*n_>UJe8 zqpoKMbig=9$=O#rOXpJjLo)QLer_P8Z>9Y4BBt>;#lJk(_o}R;CzKd0@{guFgg(7# zsxq@(;UaxpYjMxL7|Fjf&Bs}MeO@pc1|62+6%wU~vBnF|WPZu1o!{b0$xK`}cc#lU zT~2#F7wD$dj4?ZKOXuT$+5XhnK9u|%I>VPxPs_yIvW}f)+z07S)p^cx6`;a0;gx0t z>N`v6z6@hM3;jYr;4o7!quuj&*#61*a2C)@^5PlR_-;Z0cKQiaKdv*d7}ex`sJxu! z_7~KA5S0vm#$ijhpz2|WzffH7Cze*FwZ)1{o5a7N7*Fui93tOsws_F~ZC1%x>$QPT zZjsL%!dEXlAuO3{NiNq}Ru*3m-n`CN--Ndv za~*AT29U9}Peo&UZyzIiY&J zU4&Z2;%2kKq1fk}xaSLaLTZd}pA7nQF|~=fnSs8u$Nb?qXEwB;MHbN0oGkwCgDaj7 zcj>`Zop8xKRlJB!lJ@xZ64sLj*7cIxgh|%MV$3cqrV9I5B6rAyM|X9t%f*LtB1CF= zQAQsRSxFgN{I+pRyWIQaOraN%=}em7UjQIU{(_ zuQH8v>OXD7j+K<(N3e``^t%=6^NG<_}B^#fVtPpG%kU! zZ>l$@i#`Q)M?&#Z*6fU0*%bBu*ICWec=-d~e9Q{{%0h#!u2E6g$ZogFC-$w#!c zk8OOd?w>{lu@`QCjCE9p`SV21L~{c*uEtIsbr`YpQ z`O8z$7obRAJCQ~&&X>HtBJOnFm45}b-@*p#iHVh{4YeS78+>qtJ^a}nJS28B7gfK3 zOZWBUyhObVoa3O%Od6fxh46~<`aG)Wv?wKa%F6Sv$P9mg2>n$oN_sNLWxEq9pRM^v zURuaCceh1ko+oD?N4*;@qZy}GG@bJCgMZmCI^9i3O*biG-5avbUanv?tegjPR*Uq% zn2_G4`nAi)4%WO~wA&i_|1MhgA$;H;SDOMuC@NaCW`C1JjuRMOL9z5Bs_wz)I9}d} zXU%dF`z8FdfU4swI`6wtdwS9E+B)g7tjy(sn&cclSKj#moBZtal+d{_?;+Yqce?lQ zG{~y-yY+m$mRk9FQ>arhs1CZYUQ|P@Zd$pSv&;uRm=>9}N#p5S?PUF1^t>07iv|=( zOekt~hGTn&o%J0u?^mF~2W)wQzKpMM)HztuRDT(eH|mM>P*E#n9vh6xJO)1 zN@v-p(I1%6S9185(!QgCs%TgDKg`e0lB2D}5`M;OcR=gyGTu$rWr27290TnEqwDaY zTz=P0R<}h&nkga(#HZik_e@%2XN=%E_L3ko*aaKsyT2i-GTp?OR$@*QuSTL? zxc_#pv6qVM$FkJXl$Y^%=OnL*J|88o@58@ZvD+%LjT|`kUG}^eQhdj=|3_zjTYLx_ zR&#&0mW}m>3KMmaY=s-CsWl;^4?Ux4aQyD}e|J=@>u-z4ua<|V#5rrgu6MS9wG^^c#_m)?T5nN1d~S5N8qUkAc&{`0x$*H!lUim3S#rq;|I_H@GKAsSjN6%|FJo8(fRY#+*Mo0Pm>0>fQP&+2(It6?SZ~C9QQWeqX zlGPk3hkr(=#b)c$)(XTrjc-1F(G1pw?(RSAUD(Z`@`#>vt&UB)pI8Em`WBPUYYk`WB6@-qe&*Sb_hJg@Gx$K=b0gMy-SlN@^OtGkV&iT&nPjUj zuHjIiBn@w`%2LZnWob6mDz7~18#Vr-k-Hqr9?I!=y^U=I#bz#!GzFH0>h5~6BJjT) zm}qMlHU~;w6sJPhaZk+dYq5I2-2S$TQ#fTgO(gTYC06cdyWh$S--V&iv&(oDs@3+W zKgLs?niQ<+B#snnA#>@jUqp~}l+1gAcb^4Ym&oKd!(&5o^k>~8Fu**p0qmh1mepvH|QnTWB!RbMt5FH@#zH%a*Flq^+&zTIuFT;Tj?7+ z&F{NK{C*oe>?#)JGF5S&=Wo&}_C4100}I&ajEQ(YUrL4} z_#HU3l*gQByJ=~5PxJM%VpK`5!n{7KPM~=D<#8Ce+WJpq>3#Ue>vDnWdVovfvqkhF z7015I$PKDtAWiv0XFD-aMm7!#f92;wMPw--S;!J*Mb3zL*KcWxZxvLvx}%$ZBMkYU z2+^3%nA57?un)WJ*K+GR$NK)?9&Keem)J;FY`CHCA0-RfEdzMOb=3F1gGJKC5OP0d z@2u&e^Zq=lO13G&tNXh0a-L;!P4t@yuU_(AckKFH9@io=$sbDAhsdO~eF(|kPR9u2Xgs>s_|(`V3TI)*XTUJjGn zxA*+9QYOtG@Tzs{^nKOnOX_?I(_#y8(BAZ`%2p&JT)iSHhyKO2qTC{UVh;8>gIY2L zdQOBDV`1rVIMg4CcY?F^+1qohF0;J#dSva^Te&H?!a#XQ7r6gA{Cw3%TYu{#SDQ?| z+A5bw&Ocs)X;blzn<_l@Fp;mVU$p4|iffz#NB@9jG1%u*6uNvqo}@%3>M`7q(XP7uGk0Ue=D{RUW1l(Z3S-TO}G;N7ptDFFYn^DI#JF zbf-t?KqW-sPwnDC(I~6g)&@Gp-czp|Yldl}*Ju3UeVJKf^{qU*e=dp5->|k$B5PiX z;W0dPhTqms|4&i8D20x_Fyp-&TME54Yx!>AqM-xl7a7`atn+}YI3@C&wIXMI_ZfdX z2^o*biT1IOUw!X7uOH!Mm?_)fckRJoFUXxU%U5eeynO-B3iY_xMCfonMG>re8or!H zhxfbcNryeRu8687nED)n2&3eTx4FxcOZS z@m=e3OZVvq-s{oCV-Rb&y{szJd|2J?Umc>SRJl%?{J0e#PE#wWrr6Bhk7C_Fxw~5l z>FJ9Vc};sc%wSCAa|k&Dznvi(hv~QeBHioq`Lfu126^FSzV@s9CUkfX<01XTiyo0z zZ>QvZf}aNtx=yq{hks}A+e3Y&7rUHEP2H_tcoVD4tiPrxO{$XJsf{tcfU>{Yg1jCi+wce?CUj%MVw|v&7c!dL;h15tomm3Rf0ShS73%=`Y9(Kbwo=WAOPk zs>X-)jr`+v&Q!tgo-nbHe%6!6JC(%Cy=1^20!L%e@FCpJ#E4u#cuwz0vUdM^ry zi`V0ii{=cyRmJZP}M>svTJ$L{^#5E zbvGMm3QvjcL+H8}qn^T@`pRjy+1pGq&AJfAoP{9^KyOV0dRrD_>Y_X!hg@A&BN^YN@;6|1_aZkSsR(@G8G zOLqR3nEr$veHnW8r_6>Y1$={jE|6`^hN8n@YJ1h|;-dV0(Q>WG*~e}amCu|P-I7$N zdNjiv5cH-@{UBf22mu4{S%85rQf*r9?;GU4A*0_9FOK6!r+n`zuM;fc4_^=GH?H=* z-^l4FSocq1$$NPG+iGOp`CcD!V1#(|weQ*|x~GODAtHUD2j;SF!dfEZ9Ch(Tc>0`p3wQe2G3|i z6M2++v)lDggKeRkC7b}(+4a0H$Dio_wz!uEbeVEIZ~*K6$*#t-uNUlWQk{3PH4D9S z`y&3n1LFT^c4CSs*oU(6B6NFPzu|dte6{F3T6bheQLZ-a=s6ymm;ROq(iZf$vM{|d z&Koj|@$BbEdHM;Ji&%J=SEg0TZ)?QU+UURN5P7wyxwLd84WU^zSXl~^J`Fjusf(c?CmO{}+8f5?{3iRtHPdWZad1^@bl z?Nrx6a!nRI-a8f+f!6V?+E(wNm3x!A9SO99|KBA2t#-z$wt)JEv#Soc$FH_ z(7(P2xl78uvWo`Ms;I|c*lL*ih1l3bHdH@?&3Q1*bPzO!Tp+doXVfS4tS*6<<@E!_ z&($)|`>J(Stnf&9|GTK4RnGh>@A?dyt&itiVEmJt7_{^AO7>m&a3i<~42?)Nrk z7&hv!x6qJU2bJ`rUmfv4x^Bj&}H(^~guX zyFsVkD!K)v84P2($bOq)4KL9WYkJl2s_ygJUUed`+WuCZ_Edpa6%x6!i99J1@5!P< z&)6@K9-SFHf4I8h0GQj`>wT5^z#u}`*l(~RTFk3#O-HbZQ`me(J~k8n#juAr?EZR6 zeg+=@8V%}mv3)b0>A3#;^FB^Q`bEFTvj;-a%1(B>CE|aDBi3Q(H}qY62|-@A*Qt18 zcxqwjb`NUBn|!;KKa(QtNbz+M&V5*9OeN1M!%E(?J0oe{3&p67ep66_w(*8F_~{Is zvo|lSjPoYbp|;V_zeo27XN;W|X(o$+%~XK0>FNAOJ!`A%Y_Z<7>AEhy&_O>_U2BOa zr)=XF7u2@W=uaw&(FI+zADo;HW7k2?gYfjKUYuks@iDcjXS_o=VWUPw-*4+a2Eepw zI-!52LtU3K7hv7p`1?{7g%~4Qtl*E<>~>5B(+g$wiZ(KV`I;Hp zMtZhN>UK&Mdy&rf6*R4@C+;6sJWzB_F9V$=%04H*-ikeTgk+i2L$=E#!VFMvwYyd_ zj8>)_!pz`scd!EfU!sdX%YM4a))t9W*PYu_*cG;yX^gfv3*qJlNV)}rZm`aatoLaD z){g&{g}5o8_AaXbWFFUnUzWi#(qV3wMVS4p=O=mhD!hCZX1|HOh1rm!IBDp?O^#Uw zHWi)>S{^^G#bWE@1uwJchCZu@vsCx>N^rg`=2YCrb3UHOm1_B}a4u&gIzIt%NWU z^(No^o@(+yE?S+942A>i&9eH6vluIfqn(K`&c zzipvHb?+MbX>+@ttoA!R-!mhwpT*y@xvD4qnbqsDNG_0y5*T_QFTut?BAIJ=zSTln z$P7F>Y1;EM>fIzU;9D8#uX52V5aLOD&;rw#=Fa}s{G3x$^R|{5V={ z^gp8KYIrbEzEm}GZdIs$eq|>+L672ACgh|?A}oHXTw<;)V-_wk&*w{B!_Tha5T1Vx z4^M4%^Vs{cB346sO;DkGveFMQtWQOm&*VUp;q{mN?>idHY6|R58QodW9Z88f6?fll zVE+`@bXb-4N!R;^z5Yf8@uoiJvQ*LD6rOKQf$f!X{-bm2jwz8F^3Ol8`bE^{kbM@2 zxhJEU;%5tqL@S|Mc^S%2B7Gegc962wLzK^ya8cGdk6z!?6kS94MrE&>db66DWB5Qf z<6u@o+cz z>~7z?8V=8An^Ua9SRdnjOyV27JwWXl-<>YIXFtm=c&IyoyEA&Z- zdJLxv{W#%yW{Xrd570+1S?7CRw@nh9RhQg~_fOKn)e56|Ec#!ucnXeC)fN7Yt%Rqy z7GfWNL^|MlSfz4$Ez&0>SEawD_Hi5PB(u)XU`|cs9Am|-eN^5g)-l>|I83<-*?enP zHqn)Z9?ny^@(qkGoNjYNj(XZNV?z)4Y>a7u`1CThIWJtiL#+$!alTa?#>Rps*Or#j z46AR9(^a^m;2jP9{-+yeNoS5rs!Mld8Ihe3zimfU)NyRYIZV$X3#OBG6oh{Tik5y{(3;r zN$`I=uSrF>tZP3eP^ymN9C>Jh;rVnkaEBeZ@4wE@iH%7i62`^E%kWQ&Noz!-qzoe$ zHvc;w`Z0E17#shCeGb9RO2g~Rkmfr|eQ)YzRbA;hJX0a&zvy^I7u#_P?Q!h*B)$8b ze#gt==1pwso*hpVY2w&G3LmLV0YB_#Gs^R`Q_TviCs$LyZ6)Rop;0cU=Ut^u6caW2 z+l`%(Qx%%(XA5W3WR?f@{Qq5|)y%LD#gBsS^p}3Tn>_!HKa;fkmH#o(^0eOT>~^Z& z*lG~&0(AR~)w~cAO}Tv>Np#%@=Su#O(?f2ueb);30t@;v|QCCbf& zN4x0p|MHqNdZ-=~2_Ls}X-(AK6(j$E+6(O9`;_h~Vs}d2RolE%IHkG?j*~Nj-ha8* z(5v(f{0!#~eJ+xOIlC{^m}km87K=4M(P~1KK6Hazp?cp}4~b=aDZM;!#^=%ILZmQ> z+D56M;lC*Vp4y&e5#^o}E5dU}`aziQXv=5l6ZxQOS8K3bb`mB3smu1Zhx_<_ z9=cCOakj3?M=eaggp-ES#@tp*TPG9jLkZ5uQue4RgeM||Q+-#vvo|rjOtSf%BGE)y z(OWd9M*44Fr0TuQYC1y9VdCTp9{;Z$E^38)z`PY$#ywpvWn_qNsccNA68vO!5A*X2 z&a%4f&ogSG2eJN*{%w}LY7n$*DPJoGS+n9p2{_>y{lxoW<_;_Un^!pP;jk>`AGjBV zF=c|GMg2rAc+nhEcJyw&Fs( z`diC>V%SGwSU`|3AjeX1evj|HZgsQ5-paV!8-DvB?0*uR`WBKc@mhr4Ot+vNV948wZsIJ@!Dpn?znUc*WvQz^@{)~-e^f~c_O1!=kJJ}(M(Q7@sk5OI|AsE+yL>wIIL1;>Lmf2qT_^j`SN1Hd+s&4a153ncgE9iF>7@)kHW8-$)(cATvVa|oOV%>>T{AUf6T`7vx_~V=EvT# zd_h5aC&&CiTo{N zfB3(5eN(RXzsS3WCkPy)c?D!hfhA>-S>?nb^23uNkveK=C=hBTVT!pq&eaX$8Vb?p z;T_wd(H)kM*VILGQ8_Ou(Oel;WtdqwpD{~3P&r-SF-T9Q+U;v z_*0YUBD#(ai4q^cy*zqXw(yyrqIzz3dCUsVrakndL9`GZ>w499m#t*brA@T2lmEXa8 zr@@~dn9@sP-!n3xa3WYD>-(1#Ud7~Zv%*+57P9y-pIAwSqlsPYW*-OI$?;hHOdLL( z)Un7boPxiAUCgIne;KKlPJ{E8lW?=J^==6@|3_zD%iAvN zA$c4lszQfv&ENX!WE!k)KLlF6$Ln9GER~k?CD$>s-AWFl{?#yHeh+4c^S(NX==otw zn2p{jYYV#f2o;gx@O2W-|2>uBn0w1iX=n=9XR*bb{JpM-Fw<|mNkgoRKMldqHkzWn zZhs%4#N<&!$PY)eIng05<|dZB3yYgVooNR{pOz7yrJ~GNJ#7y=3d#olWzTD|%+ExQ z&KTT_FrYA;dCW@3iQG4N$GHd&?1MpDC@3o;S>`lX`UxxOuEy9@EUkbm-7wN{?)!tN?C`R&RsA?J zyS^A7UF$j8fsuZX5svruw^?^(Sz&6Ojk{RV7xdxRc*QgB=(fnZOB`5YmqP|K#g5Ll z|I6*@KC$?|38iQ4MqBnh8FxDnIiWQVz55jyG9Xe3`AQXJv3FZ4%b#uiMyZf=w%0FE zMZ?JiNhdCKwubrj%>Pa8`bL)3Q*U1tXpjXC-hl^6-HN|N^rxiG#&8<$Hr(m}-gH6@ zIccit4kRGcKulv7KEKOOC1B3F7j({6ql zPFSfdvfgE@%P@;~VO$mUh|IA3u4_8yT0#vc^Z-9dNF(zohLg5%9V2W7un8O*?qa5!ZBFrS=epww32Eq91&U=d`qr#j&+`Ty~fDoWked_O7*9e|}j^ zx`@x;kF3i*YZRw$nI%%M3|-)_NA%56veQK{d#Crm>C#$*PladGmJ>gQidBaal3C#vGT6m(_-IkPHeVURi#K7SSEHh7 zG1;PX@rD8zz|%f^oc5J0`VN%(6IQHMbDC;b--AKT{A@W~Gvxf4X!)rkr((ql*U>%; z=LS85U53g~PVZAt#3|ub+8UPf3f(*feLZL7r$YvRAF`hjulDoe%~Z9OylznhU%&I& zGAR0!e>v!TZYCzT!v*7zbvy^WariHN(5LiP_m zqzI1lj>x@Q9FMaKwe^Gy7LC?N7+kD(%Rz^EPVa45uaZ6wJx-})PUqyG8)%^8DKu^E zZkS(6PQyEFk3%hF9RA&xCB7jmeZ{-ff$-rKY9LAHTsM#tH?xngVz z?4Vji;~m3JgN}DVetn)^a@p$~P456LZ#i5WEpD~;y#;h}UE!b0?aBartSYXNLFRHn z{SUVifdFJnwSog?;%UV{;8%0qnK zOAl@%+V9hHmMb*$Z?J&YqSvG1>^fM|RVI~HO>{S(7=bN@v!nCjDq(&-OsfC$U*;TU zGVyFnwue%~qlEN8shqIA9HDKjWkn%g+HmMuD9qX(e;kQ{u_9*p{ z3)sP7YQ%1@ojwl8z)oRJcf^9sEGV2%+{P}9z;Hq*RbVygA#-Wg@+vIo4W&MT2&29J z=ZZfRk-Esg>%04ca5z@Q?038MHUCTMI;a_W|0iX@K|@R)=?V&*xO1Cf4wQKdG@-oRM&wJ{!(>3Fi!MlF27M53Hg3){~~0 z!t8h8pA*BQANoH0WwlWAB6>J{h0}C%FcF*h{(10MtgVlKfGau`#Kl#wqka* zt+o4{F0w&PI3IOi4}Mlw8~R==$S_~<|Kk3hopr{G3cG1`Ux}_=-A8eG%Uw#uMmobV zS<7oIuOz>I%&rDCENEb1HaUZQw6LoUy6hlmw8CEe3yGe=O5e0sljTI)S=_BiJu8nW zRKmMHZ{LAB!VZ`p@|Jba9f(!im=WdT39yB&P|FH@>_ zBi(!9ywY0OcMqA}bbST8#Gp8PToz~QjxWvS-}~7?te%^^CfO^ieKs<2)zn8lz4v9I zXC^}7X_0Ea9I7>SCgCRySr*2yZM-Heaq_ixcYxweXQS|j*9)LkF-9Iv3pn9 zVK^CdlYHSDdo$+$$MqJFFg&f|#fatAqg^%?$veQre)#HGcC?T$ZWYx|i;dBK1Kw;dFURc#dc2vS?u~ z8%3(_^{s0Yc~mPAr;DrX=X*Z2x|1pHU*nd`z56;gxEY3qkD&Mc$b&);^lZN~Wb7siatykHpY6xfSJ~B@6w#to!ZuJe%q1_7@ovV^cF5AUh>SnTLZ{Nfd-IJNt|x8u zB@EzOtKNa_-&Y}EPS|~wX7`imKFu`P$KqpGxpzx?O%sYu1Jh{@u;T`} zQ=`b-%&Y#b4Ib4_hg&DHv>S!|Z8flW{A53R`5>9gSP^wLJ6Qus4v4FF>EwAKa$`6* zidSvN7E?ufA9{)R%VpKKqaJ6cjp?8x@X#OK%`rOE9ToFrPE1Vh&j)mibJ*x6HN8o4 z)Q;Amh_0h+xajwgtuG&_$jTm~cAu0PuEmSO^9zGc^La$6nd2*~snkcsr5Na2*m{KB z91>xpKe>u?P&Cf(d>pIGEt=;MuO64{hHA(~YyUI7aI)R+#6L^oYKgq}H`X+s&9vw5 zW!z;3=zUYZdz5PT8hl3Cp}b6s^4da z#@QfI4tyk!7!-PRO3=v4ixJ@*laBI|Pu$g9_p^&N#mk|KM0#KbL)9N-A3f*#1C$jGw!<+0bde z%;+oqPLuJCL3F&gWYw>TN+sncS#YB}*w3GMN;q#hoWT&Zw`qRw4E$wo#IINK<1noq z6xXZnK2(-N{$5?x5;F?nRO}@gvXWqWHYGL6><%B7vo&_4EHfx1~A8cOm|7+hoUNW?_p7^Sm}+ZhgoJ>_TK_G?oEXn2-ye8LBcuW?R5**m6JS!iKL+6|A`eZ zbCsdH>`e$#m5x|=T6 z>@(}Pj92Z2ljqGd-J|CvQljGN(pP2jf683eu$QsaxAvl9F*?m1SF{1X4;2d<;+tWL z{hloDkagS4&sKSbF5+LU_%SRv7NaW6Z`!)P$#&%rygrM4eMKHRk~MCmi(JA?)ARWP za`_4vYE{v&LS&{kCuEO>F~>yIWwe^X^oUnj(Ni*ve_82fv1yu^^Dh4n)u9Ufw4hA# zN$;8gKYj$4Oy{#ld3Q!%dko7=^6Z>EI}g0b7x^=H#M5*6$()hT^5N3Y;xJ|SUJbGC zHSDjyRh|yRezs$`>5_$5dPmHCwiP@{8Od(fTZr;wWJc>DdihY1zaA7#8+}=j>_inz)AW?5&!(a2=mF z7l(stK3L^&x-|&(k3)9&IxZ8=lMb3tejfR>S6;rE$$BNKiJW9xKe3#tBJ4Y&UTyan znCC6JS{twZ_3hj>L_G37%sWtg0OiXNTic6O(Iy+r#=>n97!Zt7Z{&TMX| ztDONamSJtb!jC_!(@7S7m0!oxJ5z}bkKh$KT-S3LR4qIC2G;a(M0Z?)AsWZ z6**sxXv*U-($%8t@4A{Uia_^t9>kkuxJ4cLTfYA*&15zl?dki=v!JNx<1BWPjHht~ zMK49_<)d+v=D2b}YRyAf_Ep&bcSNN<>rQT|D5aHw6k|ONVfz5wV?G}{Ao|C!`20|% zCeLk+mG#C0KJXgg|L^$wo3N=alr6#{AHqn(89D2$!9?EIOD6uZcP+-k9%F&2)IyRJ z<9iVeImWs_>R)m~_s{`a8)pq2itq50VXk3Hghefc=d1nBjqo_UwqUJWBY$qQ=R3Xj zK!$^&>PdOgzcSbuSMs>4E&*{HyTW(bXi)He#D$LV^KjZvdaIoGKlQAP>PHDynAgl; z$dfMd$W0haIJqL|OL-!d$L%ctGZxn(vW}TVqwqA--&w;}oHTT5ZjX5U74bAYm#aEf z*bjcLVh8t}6&pCzhcfxKP~{E|RfuNze+_hZa?$7ISOx9G z6VM>FtG~wz&d9d*MS4zuz`wrHkM|`jpQP>)UXx@cb9{Y`?>Ojt?<72AuS(m~PIh;? zJ3GL((^~1uB3h9-f$TIf$o$>m@^UF5tm_K}j z&-Ai(11L6qM5gdmi)NJC@{l!i^1TnG_j*mBD<0;dJDQL7j-!NwNq3oC}Wz@}@UMhKBCx|2p04MRLI;#G1rc*ND5P zT|rvhp%hPVfot@`DJDj;q@~t*HB4D0TV8@WO&1M^*zI?j_aGpy89@3c(J(yq3gyE}1v)y9a8D4xZ(rXeX=$@39 z=7X4p*h*=UuYy%dswX$$QE$4|-n?RXq!zM>cWq;T7kF3-JDwM(ucBVmf>zc&(q}MO zkJcyrYrucK1`YgsA>B+-G|(M9Y}9|5XY!~MV)f@dsg6AhdhZq*949M%;oR7X&9r1obdf!_W?!L!P7RYx_ zLW0chtyZMUJ%pXkmVvERq4|xTvpe#!)!$Zo|M_BJ$m@Gr@rEp?nEMVLN!P5}?|#>M zJM#_eo5ps>;SwWY=&;CZxJ+@B{9~Ng=U!jHqku{Qm%`NYINuvG_W>;U9bBYSq?*zM zI@ZNfsz)@wYJ4tq=r?i4?cwqVJU+0MpX^}J@G@ffm1PP&*zYWU7CItsL?yF^c_4o! zo*tf|(}+6qGS9Ec@1L{AS@7zla{@MqQWNDc-9+K)GK(DUElU1*Rx~^yGKTX}w)u0f zl|Lbx-(rCo?dx;CyQwU)AIzRc2U+Kx58=C4_)QcAIk}2loL#&_K{#c%f408U?0--1 zQ<3eYHs7<`%1sb$TDjK(ukiC!9{V4myCTe>y{U%yn#xu)eQU2qCMUbd)cf+0 zVY_-@ z(vUVL`m~D0*H-0izq1V980St8VZ7htBI9w!kKJ)^JK2jheqi@U@$I?)kE-*4+j`vp zc#%<#Es9er94U%&WEaZbn@C28M0CvTo$VkiJ7i=fGa`G3WR2L?ni%)<+u~fe=Rb9 zHq;-)+HZo4wZ{hj2w%UzCtV9y4aEv=g~eV8YrSBJv44#uOe6X=8GAhm?{^B1r$Vur zco_Qd%|LKV7Tv0+z7F!`ZV@{|wSMUT1JJXB!O=tE)f0+mG#p;HV|o;{90xzX!d0e5 z+Pvrbc(4PI_zUrm9z-vGNPJ`g5K|Xo%3ESl2T-Rq1Pgmy**oe8tgbr~Zwd`p0L%Y^ zH}w>fcP@6??tw+{;5diC-L_u)NnYfs}rlf zGg5UL=a`81Tv)blM7;MRMyJf*0!1H3+fO3${XXCL5gO+`c=Z+Z&P2}o4kT&>a&;m& zbs%=t84v6537z2o)VD|RLdxjFi3Ex2h}bZ2_{UIYo>3?H7tYW733tOXib z7Mt3<;JKfv!I=$8v&P6>WI7RF+m_Woo_E|1#`&1MdtSev+FYc#@a_~2zIdlPY!6R`+86`j+8^_`2X;pgC}H_$du za*ku6%bi@QbxUSqM|0fD;cw?z`s^t3(AT0(uRx1jh*X~mRv88j2g9rTp;h((rR>;1 z#~t8>-N``@;J@xCcpmQ>1z$XaEu4Wbx&T&kRW#{#;DP_(PhE_J-AZKlQLgcMblb~V zhG+1!t=}3A`aBOidvKAxZoIQOIC>gqJ`Neb0BRiqZMTC@*8}Tz2ibY7&R?lz9*hKC zNc8r>hRD|vSkn#Ai-(k`?Csd0mp~w&qd9*?m(GnIRhP~|uYU!PzK&#!MI)S#bPYf{ z60i9IJN^v*#T8KDKk&=;1wXAs1a3*>+U^;gSMnZKFgnL&0oGS(ZwEegH-@#q8L4C6 zop&F=6}TJd@lfhatklI=tC5^>Izyf=7;7eYZ{7yqZVP1UaJ2hKB531N(u$yAZkjjw{S=##TS1Lvb{# zdl=EA^}s_;Vwi>BGzGi=JW~G*H)mE%A%|$ja9+51IlRF>toVUkgB{^Fp?%HOPG*%p zfzQ4K?R<+hbHemTL}Mnic2BT+w`0dIMSD7(^#J&12jU?cpbOUkZ<=N6SYjGoN>sxg zy^SL5i_9Lww_F4-I%V}Kkn_7}a`BWkjX_f#kkL*^Vpq6qwFXSJCbB82TAv(9AN0~T z$ip72skJ#Lv%Xisf#a~8Z&H8#BQ||8V!5kg!8a$Pyf@M{l-Q^HMx9n7XUFpQL1ZSk z!|Ja>=q=y}8Q4=+Vo$z9*2-ndE(2pw(6a4Q{}BZx8S80SEL4P3_6!?RaiOu51L;6lfn;i*{4aQp^$T|(;I26u31}%6RR?-~%^~kvyxbf)8 z7vVW)vrR2%^;0y_$E@TheD3=kZ*x|!^P1;*<x#y1>~@%YISNqLi1{)9F875uUY z`mi%{vne)qUyzg7|1xyaU1+A%8@z%|5CuBp>~T=lomhfP!PZAYNikqowEFxYzz>k% z@o>$R@aKtK%|Rfz-8gpP=!*s#h?PGMe!7zDp1}EkL2PMh)_)5!87H&8_wo&&U>V(| zpgVrV_6?oMLs-k9_@Rdq``&{U?M-x{19tHbw8A9T^g8Uqo5ZG>-R?m$ynB{0%IqEB`9d2X_+FmKx=MhTl(f z|04f+ok;nIg(uvdbt$B<2in5viF)zlO0>KQ9MK&Mv_Dri0uSA4r&;K)MZr?5;(cyH zoNhaC=uX^kgRJ)^{<{j;e-XTDvA!|h>sX1SSd(qJvX%I}<{6*&uY4oe_C3-Y*0)1UF}rm?z@ zv8q=VM7jqEsvGOl3`)KYm)-+DHy(Nb8e}u1zbpRxG9ZeDp=K(+qe?pw(77erv^fPxnT2Y0{&?S__ALv8|^*%}?WEw`O>*dsWy59ZTd0Z$RhBV+#v(M&jEazur>AvCk zn%^_{`yJ5eGsO=bMTF)YP?LV>j?iL#u5x8G*;44U`N5T`#`%fdzz^Jh!n64mJ>7)= z%!?egU`-ZIqrh8H^Yk;<6RvGuk>Zr)?i&1q;7dS zdgB2w+;FUPx-mMDhx-Re<)>12>(rEwsA#YX__sOpfmgKan?OltDqRQ9yvq4D0cm%K z9(!^Q&Pl%w`F|EK&iy97LIZsdj`|jQeu|EH3v}`r=Vs^6N$~Do@XUWIdOh+u929UYI(-mYeE_<=AGbZxdAozn^@;XGhW6&RFEXQ#KE{i(Y> z0-tFZXz4g<*Ci_d=s?j3(`f$3HiE^nI@VZm6K_?@}yP8)*FpT4gFL>HM^@XaReJ zZs#@zetZDQdxrnL%ldu`$IOGD@=v6AWzHw^vlSNI`61gDsq2ZPt-{$Z0cT0G$=DvV zA?lTV@EG+umqJ2*#a=jL%*lZEMc;;BKZ@HeM8U=o-+BB@3`$*qE)of-BgTWSUVtx{D`7F_z(22| zN1fn5kWcObRp!G&yvI4*1NS+@|8O*^^F8%4wg;caS3U$vr0#MwvDv4Po{u@#rmVv< z@Y-tV6*Iin$L!1+Ih|!|q{8_EU672Wiq-o9d3y;zNXnjzzo56aJ-lzv{7P7v)=0zR zSfm9(_lxmpNi0-5>`oUfbCBsC#n(9&3Z6}*>FT0o?*oBNz{Y6FTB8ce93pu$GWw`8#SJ270Ytqi`F2Fd00(rawj7l z52By00B0OqeEO}?VBLrSEro`hgXa7S&N5H@0_*k!-uZ)kr=F5^pf}^i-E+JDEOd5S)(l(t`0}FL60}Z z1D%Onyux`u%sJoAxn2YAyu2JnX-0D$kAQ{!mec~-9l0ZR(WxUhf^uI37kx`ib|K<4 znS9xch|K2r@0)=zHbR2B@w_{&HEH@SueC1dX5!8#qStrjTvml&nv>n0f$gwoYz*fx zqG0{wiFFM@j~;-wRA2Tl8grlWIQld9>e2pi`tHd8ZphP4@cecSRcM={7gA$xeYugT zU3smOYUFV_{B~CBSyu1!lBI8hPFNo)+Y60we2JosLi0a}rG1tweHFWEr1wR5^f9h| zOhc?^Sg}!C!t))m3iDy5K7(R4%6nbI+MfsS4hPNX7heZu??W?L32fbeGc0R|vVPlw zA`XH_Pi{aQH-a%n6K}nZ`y0_KmvUAo@&AKa=gq+|sU7)|^?8lYyOY%$j;A&V?l+&* z3%=?KU#$qjY+rDu8TM5`m21OwTksmI^M;@^&jeRp1-Dwe{v-%&3YK;Tw*3d_(iBU# zAZNHV$l+g5s5@GxCs=Ar^iW@r#i8)pNt~~ln>$(GXUHObfNcHTWNxx>t&ypf@kQ3f zSLp*!YyyAu;qiJ@8g#)!S+dL^nStN=C~|)>h`b+BsTGi|S?HSwS))_1fm>n4+Y*6r zhT5COK3JhWirSKE$R6H^eYgi?H4)n}9UL%c&Z20C9;|tP*7^)09Cwu1?R2!&Um#Pf zT#SE-_}0amufy*iT-VA-MhC8QF?g~GT$$0aMV7VLMg!{lMPa{Wfg8e?g-pu6q0@U(3k3YtTnFRfvns`05JP{pg7x1dZ zQ~8Efb7Cb8XuaD&3}jE zT^^Y!Gq*T5T+s>|bZ6CfEIQ^SwCJ_0=|g5Qarh)vjzX#K8aMf-xOb_6qRM$~By{@bo0r~Dzj z`zW*@0q-1+rQQ<#wler<0X)O+(5Y5z>C-!V-%0#px@kq`ej}>5C^l^+a+h-C9&p{^ z*r+pzi0QXoi|!nSuXYoUF6Wu^`35WS`oozUp?BLs&ENQ>w~+GSH0QYXMeD2wO;^Cf zNZ0!x;iD%&TGt|jL%F^!kkR(&QYX^AfD{>PJ`rx&3-sBCs}py0VXZrJ ztipP)0~c9$bs*<_I%j)Jv5$K6e~_tf1KM1V&)f|zJQPiRG7^6dXLu+-4$)SEV8fH>F&|9$wGXM*ZvtapUM3UKL6{2B!32}{e?Yjj>XaI zTpBIfmiI1)UT)8AS&o15Y)k&TQ1M~^LbCj{PvA!Lk8i*kFX4SZ1FD(GdQN~-Cqm~$ zfvk=)n)@x0pg-^gtZrWnB$sdLfE+K!_bf|1Vo}b(YQgV0&uQ?wb(zkQxBz=(G-MCx z)SG&ZE}*k^`1eksS_(wEG=G^vG&;U5*rahDr9CKrl={$KV4p$6SnWuE1Px<-(2rE( zEr28}1r}WvnP|)Ewq%`KfSi8f{~usgCxVS{28o`D9vFmu5Q%n$D(<$@glx#ST$#E0 zH$ekZ;W^IwIj;S=LXYR6fj9HBFBb_?mR^Q0tZlMV{4q4-!*I!1xYHg*5v)2m`+<$- zZ!O=$yiT9$P5h0ISvBj-TCgH*icMV`?dFt@U68_k@$wJkOa~IX9Ds)16TN3e>Uzj& zFz8&xAAEE)7rzfAyD$_zr(bd-XE&bnw~MtY-`f!m+njG3 zgvGg#xPd)jParR^V*)1g(GYk&hoiptL{mfr~iN<~&Zoe6P zb`}WTS-?A^0s5c=dtfQMAR(RbqpVOgKG&_p<$7X8H|143fIa&)Nc)kz`y^1ud7SGA zr*ln~#~$=7xZP@p;b^N{u?Nqg^*_S~7{On*#P52dLAOP3 z+WV;v-Gx}*R`~I$%4tay??xz*+mjX@k*u#}^!OAy&i;xY@X8(1~wjTfZh7 z=HBeh@%WZPzS`sKt;peC(CzS17DxW(MvHz4*Gz(g?uM7nM$-1hBUp!ZS_IAY8CPrc z{%Sb!Sh5fMaQ1el+E>*B{b{t;8Ht-fQS&^5!PaMURX4!hwOVvGD_=2aBRcHG6&aUY zlj{?OF2yxB;~QroV^&p6WG%*EJDf9q2K;p#@^BcRKZrFO$o;__Wxez z0}(q-Km)wOrx=e@*JfVFZ>8>=;j*01U)=J8`fYf|j%Oo*`t=K8nOb3sm*d>T%|=(Z zAR1wmbO0#x2Z_m>l5e|b9w2l+ZT^=JEUWy2Ki4m zZ>7S?%Y&O!#cUmOhw@jdnA@TK<#A*BiR*VP8Ne>!2%{`^GVFp^G7uYbQo&NU!0i*U zFHU-&3r(~f{({qn_lBx=HvPBo@fh^hqg?4kkfM9&JP9Ivh-oy;Y`WqVhJJ$R=j_--_#_la%^z~|R-R8V&Kk(9t;G(P1K4U??FM>9v z5&!&(IL-IS%}?CEh67GZ%{vEY(F&CLGbus zK2HuGh88=6XiB>7)UU|}Oy=9ahO6g8uP#T_%t-T|=y_}2&PVItf=oXQ^SysimPtC+N|G@3D?? zGxX-x@R+@JI~V=wbm!fXQl~rbiA?wB*cZt@5b7O@XEYS~J{AdgpY}75yYo3NCT4mO zv^tl+&*b^z;e|oGr!V}_2OY5z-`j$0+)OmjL}d6nu#*#1cEw^ksm3a!X2k8k!sGmq z+{k+z@6Y)Fk7Ncu{4aBwfx6pZC!C42H`?wjuJJBDX)?Ow52ocTizeC#YVL*JKf3sC z7o+vB2K!!%%v_5l9f4$6-#Dy9Ow2fK%{$fxGr5P=V%Xogk&fBO#+RJs47jK9FjM>m z-1H6qS7zoAW%~zua0&Q)X}-H1t7<*t^6$N2k*s>h{UJ^_xnib3b z3w|)?RHqbe!y5EO;`iqm#9^lF5RQXEP6N2U-Js~^$aN3iw=Cy9cd2}_cKH!_UrcHy zc>o&Ay6g3zt{uLj6_H9;q-!1Yn10+o*o+f-{dG`s0(|lb*XFLVs}@<=gVjEg^}Prg zxCz~IH~#K8;$IJdJMSh#GYTKn2-L~Yb0BBD4I0$!o$=@2pyEgP)-U4KjDu%Kp#d)8 zn}*{@oQ$p;2DcuIc6a)k@_Q0E+8wFS;1$D>W$PX<#%d`|_O9g^fn<$<`}O0mMGlDWdD|A6`T>Le=-mRbP{WSIcVq}F!s|RqqmTBGd$+YzC@RO z!6WOp-sXSLm6*wGaL@Vh%Q4VF-*97iWfiWkb@5#OBF=5b>ub2>3yzua#`ipJldR<>WRi#1<7i| zYn6}B(1Dq)d{e=_$3c(1u_>v}>5gP%j&SWcOb*^U#$28%=1J89;Hn|;)`@7#bFu4JprdZYCf!lw z>|SKdzTo?y_-JBKR~1QfD#1Z;&~`=AmV?LUDIU}7&~6;Ea5<7=hw#B*&A!O@c3}A} zInsM=_39yo3zYafK%lR{Yc*!GI?={mSQ{rDUJR1D6=~NxTD$f-_VEqwCm|c+jYmr4 z>w3=F*vZl8D67Rc2eDe)XszG^AdO#;hcB=^AMh=c(Jt4EJz|BzEsGT!)t&2Vr4RI}kIB9#;7VEZ=45##AgylQYY4 z7LU$>O6NiiGYc1S6OX7dFN20wjQiN%>Qn!^oZs#qcxBP2?gMOm)$22k{Tfp9F>5%R zD|EJAYpAtyk-7~*4covk{lEnWfC~n4)*gEH{lN}9^I!Lau-8;6lp{jj&k8@h4ktXv zcUWolD4b|-)zet8*P#0}Z11<|BQvwDSgWpl$|gunUq1b?V%yJz?=D1UMxfD0a9oOZ zItRWz7VaI$Iq6;eyI7(Y=$Efp%@_HGJK_3^kly2vf_*`g;(4bCt<&JE^#FCQ12WwR z%;`K{>*WU*WPT1*yb9jB2VQs@zL>%_sEdr^+E14}XJ;_gnrNO(Bj}5U-yf?z6fAx+ z5`GcZ#Oa}9k)EfJDlzAK=tfcXH=ybtIDR7T^F5yF%wmZq!xv9taYrNl=Yyy05jL;1 zBB-MY{k5?Zqd2!yk@*2=v(3TGYY>}Q5x=7?7N9l9(qs{r2WPJa4sfo|&gi{^sh2nd z?y!noSuuC-+{T|+XKmLaB?{IGxv=thF;>0>nx;vSj&E3IEsu6o*?TFt|*&6NGvGCLy*y^6>$5iQ9FR%mrm0dWM)#sbY)!c+soCPL1wCK?7;Dz2;fVJTa=LxS}{CRhg+PT<+q3GKS zpyO!t@6*WW2T<{MXlO5XCwOo@u4prO*G&Cx7VUA`k9|f^lW&d`AkM=&^+FK>}s+V>LcJop=;YiMFS8RVb&bvnNpMp-; z;Smi(=KB$a*$}VOy}X>(XvO**GFv}!%qD(s-TE*5?G)A)cq7gtXoEMuLW%F`kM>4> zwnNUGQQ@=~=ii@#cXfS2alz#m9 zVq;(C>fS;lyj6}j$Sk`j%47WhZaDWEblurd#QfQwXtzz^D>=vhsYUq)XDKxyI{iEJ zG=9?rj&5G4+NP}Fn$Xq#j`l=5AHw&Wn>!CHKLUxkhT}Spkvvw9o(WR^53jODY*RS2 zGvDzKKKn~J#oB*+y`938`sQtm236-chi%!Sb83fo8|bWOZ-#kI^sIVy>!MfpfqFyG ztS4Zxtl`sgJ8{xI=pV)sJPQS_f&QTQt+R`^SP-wN4Z6bW7yA@;Bhnx;wf5Gku^XV% zJ;?ib;uTLqyQjdZ-cJC<+>Zvhl`Fiofr^<%rG;&au4_UUyFIMWa&^wjxC$+H9{TZQ z&M&?E#u&2Cfxh)M$l-lRr_&ccz`9taZLLdtXtg?LXT)1Ps>H{O?#tsHd1iA~PMg@B zZ#GBh9ISbu?axTwOs+L~!pZ1k`=-oly$;5B1G#<|>VH(w%nuE@3ib4&d~1A2(M%`4 zzcaeD3rAub=g|zl@n&Ri9-f__|NVnkWX6wP>QcNf zH+R9ZQ?GeBV{y_c-M>fURSqPlWW>yvvb(|bM2BwF5Y4g9K!4;| z^z5bBh5hO-rud7@bp|jOZO&GN2b0KjY@&WoRMC75g{_Eqeq1QTm4!EwdX)c0JCl z9iK86cFEr87eH!8^+zDHr-7o5K&S496|_qxeKOnQtJ-n23u|wr&T2)qie`G)Gjj_86Kz+^}9S0vgU!wWb zz)LehQL~6k{s4lS1%~CCVCnw#Vf*!mGZ(}5g^bWMXXyu|u&EDy$yaXN1PTbPq zyIsh-8`CkWqhE3v*I+I}etjIAD-`Oq@PxqD$9_e6UhL$u{$EW193T`5i!HpcBl;Scydg}-0r`SIw0 z+ga5Sc&*BZmcI|&(~+}k0WaI9{0?U{0ZQM&`B;a44w}a(-Z3Tmd>H(B5IX-LXng?G zmgY{s^qG3tpEH=_5Y}c0zYj%!xZ|eTofFVyda=g*Ml@i7d*LCojc>qF_TBtc;!{p{ zUk=I}OR;lkD=1mLgv>f}+L8Max+kvH20d7#j%e4`tfYR7yKOmPQXT$f(fCGn)%m|; zf2?h53Fo2cAx}hNF2&NAGq@jWrULK{q9T(yUPlYPMAR^mklpXq*c;7iedB;a(}SR=!cSZ5uBkfKN5biR!aT)06NQT-^(R3`YBhdrm%m>Nqg33t0A^UiuVF@ zyNAXxMLV2^mOYy*I177lGI{jlz*&bvhkd~K+e24#N=8COFwV631*w?gM`JS~P9XRT zyE>CkGOlb_$X)1EWy9G^@}v1eGloW%&4$E2Uc@W3Id>vY&UY~PX=Et+rUle!RWwaw zF1gm-ZO_&2R4|-!ydV0`sGSk{!T6wu!x!d3hJnmaMyI-mlNrx5q1~Arr-4mR;1xsB zx`TMPoo!o#UDxJ{mV+Ox7ycIBwrAiG*6Uih-)y7N2`72>!C#MM=mNgB7sw7Ek#rwo zdON`*<_(?Me?>v0?u1|mwO&VNcZrsrR?)lAR=Qe)zZJ4!jI>vg4)e?U5t+&AT7CzH znSsn_CYF71uW`Iu?(3YJ4_NKgd;fy9R&Ex6jzOb(jh(qhyA;>tutRZO*0LwxXkL2* z?l(XxHsrig_qZ{u?M~KPaemtsFVlQjKWM%e+Q>Y}{?Ok0eL41qlDqRN=b0(t<~Q5% z-3x)7e#A0N<&5nVybE1*CEmFeB-Re^gC*S!zujF5-K)@g``w}9erP7U?2L$9ja|AQ z-f{-Wm(apl1?KD4w#H*R}Ki9aYQ!&u*8aKMo~a|r+0hx6KwYuNy;BiHKJ&&w*$ zhN5ESO#ODhmDDGSFmAvWTA_Fi>+f_o_pkE#ThZ@#V%hEnd)&`4uK4?pU=@s(S@$@e z^GrR{Q(W(J$b?fCCc^`+j#W*wv1D^0H+7PRIi}uFZAau-@5%V{3B=9L1{u{1-lgcS zi|`JI^Xw^burrnhb8SZTwnZb_m)V{5T@Lwe#V0RPwCa4|o_Vm%dJ&n4-jb_d7F^pI zEV4$4gO~%cKUG_39nfHOialV1ISyiNrQS}QopBL&?XmOoSFXYy$tRHAo6&B=u{npc zV!IL9-jJA#J9)Jy#~TnZJtNbrQ(qpe$$CTnBnJt7L|LKdyKiXu2*mvH~jj-)PCtNa45GCHsqwm1v=6 z^X#7>ix3 zKAhInuOQM*ibuaD7}V)P-OIhZcdm;E*9W{Rx)+fe8Fdf!^9l{q*&W~A7;fTxR+hL= z-8@A*SXbxVu~pCv_SRXkvqiy4dlhUt7=3y){CFC=(@D%1LR+J|mtlFWDZBt`n^_)K zIC4O-MMgqa!;Z8@`sYUazkpjNp-;ynVYebzmveo?@m8vBIHuUnlep&LoVT5W=IAFD z%~|Itw!|8Afd@BYZ4zZT3<+1q$082rJgjIw1ypb>UT3w8@~-)0rNfN!5}Z>ry!&5R zIcxDg#lM+~Ua=N<3KlVbsB;CKF!&x^_W{R;$gVfNRXgd7*nPnJ-YcH#6y)1X%FD&d zYG*y31yN{k)d4T?pVxRzI_{iEmKq-?JDRy}!Z|rAqsgPTF7a!?x%MZ*5-2f=IVk$Zz48n#wFEkQ#j+Vi`HBWiLr9E4;*O^gBiqg zi^p**u^8*HtX{Ex<(U$vnLt$Btnw|$hZPs6vC2c>3abY;Mr*BxO)-mLl+?XaXF!v8 z!GSwtRt<;+S^)#4atzVdl+ zOe%|wtV&4{Os$#G;#rg0nkzNKXS}5|yxj$B;n5lCTmeq{C!DZkiMz~$C+!R+dEw)R z^Pj{wScmC(JqNwj;jgjA@9}M!sFci$Jqk`jS`TW7*R(@+S{z&SclgpBB-Pl%iai?0 zYj(lnn_U)fIDPO>*4JKWvms-_3|Ar@)>$2cJ{%01*tdA|yI~c*?NMx_NM77|42V;W znMkJ<^bbPciF}sb)S?0N2Y-SE)R=1jME2EBB6VjI?2eAlbb{lNtDRWmf z;}MUbLyhnl=b2FAJP+Z$++FnM2rQG8_D6E<&QjSByV4o1FhcPI9PtivkvZ{Z$efL2 zoJ2f$B=~wL*We_6k+XR+cY7O&1~kX~DA(~qxsDHu519DPZy+0M;r`;PoRInp7U5g? zJ5{y%_0PgJ+H#{a`kxnp!xNwJn@@zYdNx)z82dl%q{1#lV`Hawq#%Jfq5QU>a~5<;BS~wS_(PPqqp8=1#bW1ku@^LCKn^FShEC??bP-^&w83; zvd_k@5a+jKUk%aG7>>L7-@`@PCl+~q1MapD)qFy8cz4;gQzQ3nKZ7VSBoScg@uy1{NKE>G>TjI<1$MZS@ zIX{C{GiEc2b-aVD)>!oDIBxe6levw`7N_u@$E%#f>rBioIm6YUu~im7^9{}uw!-pu z?4ln0Nv!3eSn~dO8#{rCHwPhYjP>8J92;TnJ*;Q8{?2YWHNw7l_j>Y&BUM^cBa!ZD zVXpZ~c;F^h$WFlrSeM7qt*J2)Pkjp&<|I3u`=B>>Epcx<4UK$n2yWVdd+BK{qx+~!)+zF_I#|~|k=k(uiz8?A@|{-QWpaQKgxC3G=de5h zeIJDik8nSp$F)YlX{&D`Js%-A(!}p>!ntQBChc)M_)BZ-tekGFoBe!xqRz|d!QswK zYaq%0=4RzxH{@is|NVBpj?Z}W@v0nJb$QKvO4V6PwpA`>LX3U9!C6OR-ijQIz`~q? zcC>HxV0;d{7wrAFFI72mhnPY9&mAM&4Z;5E?4mb`&-xtAsTXgYMjW*fysPizj?ssL ze~;!kmP7gY4=a8s&ktbz`@#oXff0K09`~Md%C{MOYa9P8bT)sH+=ae{xr@&^zTi21 z3@g~oAvP@<(Fmxrq$X?)?=QorSe^nmRLv$tYXtetcmRH%3Ya&iW;Q4evwsQy3rr27|YZdoLxGd!Id zeC5LqZ>xixg<$O22<;_&zu6h9a8BjW?x&{lES^j3+sa^d|1R;I&5AGGk8?a2X+D~BJ{^mGJ{H2bzIolNIYx53g1_y> z8qRfkWk;Y(2Y|`NhJE-fGxhdkEyYSp|2cfC7}e-#?Z>jW-^`X3jz3|g>;-V|GiP+R zES%AX)$G90p5L`A%c?5lAIi5fVlL!WP|w3?x0}IB&I&#S8)El+f9lm@gEmG}Zb;;7 zy>hIN-|noNEzq3X7s*j;9?rYmXT@DEZXj}I7t#}Gt4XX&)koTz`8mTS;IN9Pj6(G( zQ6?*w>=8Kx>ta4ck5kHB-B3kx8+i2IVqeFR^LU`-$?gG%-hqeWy!$Jl>ACRA@x>R_ zk2kx!K3c_@RmOeHJDMe(0TwZmrr##oH#%x1oIaXf+I!GtI@I|Zn(Ltjr|6rBL6kjZ z%0ugxNvZm3uF+?_^PS?SJq{Yq8K?8v&e3tLolYn&wU%*Nu1+lE95OZQ8fesYx$X_nP{#6%;%))* zFwVF=y2!d0_YJh7d>4+LcyK3F4(|<>0NufA!7C<79beV zok-o^-T0WbMRuSW<+h`568hpJc+@&~qxkLlR40&Z&bRi%G9H4i6h+yi=)Bu2%W)O= zsUXuriixdT@6Y*e2k!2L&anTc6_mF+Tt5B~+hb3i=yW{tt7Wu8@HWoPxq_Zu&N1g{ zy^jAI&Al~*PEPUODxSO0`FCMg@8xjsW#fF>SZ$u2ie?;NhF_eXIu)PsWBAF+8NJZo zSsQ)%`H;k#vtOKbazamzg?YXO|5vNZjXxExk+w5f-&6)!8>AI*z0-B+++%0Bv_x`N zXwW#$XtTTDii_jndnX`WO&0cMyoxc%#eJaBOc!+$lG6!BLg5Rc=!xjkLA-Nkw5}1x)WmdxwyJP!(WJ29}bUN5pGrEIs7eWTi0kWojaNsg}A1m zPC3FoYo+NutcTT6;s$-x^n1FmgNR#S^=o)Ly)WvB#nFDwcs6cr=2V1loZ5Oc`|=Z~ z7U_%foei)Wbm_=DL^0Z~1)#Dql;oD|hnvMJnOTZewQ55zL~U+uRV%3AM8DJw8l%^j z*tF;-WhXPLwkmP@jrh*BKq|53EqT8=(An^T8ub;fPU)}<<;G%%q>*0e>1cp>pH|j8 zh4DgkuJUfJ&D~JQxjirQ89|rdbB38WsLpKHAm^*|-O|B6kwoXMX-Pkb*$}J14o7B& zavX_-nGs1&L~L>l&mF=4#V4r}Q&Voqr$twaPZonmn>KL6yYRYp^$jmN^3O4`XH+U_rR;~UygmitUFD|r5=jjywa`}^Ex&uhmx-zSQnkJ22!ubX|;}#3lXofzX-=d z|2r0L;54J%)?$d+eL!bE$LO7r zPrZ1%&#kYp62~sE^hfVU{NBA5w!mZ8hu;9)w0=QPz4)s)&u)e%Uwu#Kv+8>u4=>ct zIXlMO>B0zq7VIfV@U0w>Pp!5Dp_i$ z-h6^JcVdp6`OICpx1Y^^x-I!%Z}g2GkyT-Gq%oPF;Fu5LDsv&@xOV%MFF;S8fKO%x z+unHTyF*d)g?3ShLAN0lLKh81hU`K(AB!{+tu-3$W$fb_q|g2u_g|^C zu6B)?ClxR2qjo_~)Brj-xJS{j`$Ho$W$FGJ1iuXAZ|g|*LI*paSxssl zH~DyLWP-TO$ct3oL1tb?TRp}37)QI0bGy6P$h-L+EBP3D%~-eHQ}&`;YhfMu!q`RU zcdZQFlEG`7peTylf$#U&3i@u0l&p`{bjRaW;Ca{1Y*wOQW)|!a7k8M`mqsr7w7^R3qLdH*Aa z^?N?{uOB$R=VmRRb9}$#yFX>6%(P5_60Z^uP7Fe1;zSv%!fr(~nyt-b8nK5NTdOU^ zadx3iz#3a2X1}Mk4$VMdRu`-cW!8sE+oQerW*rYh3Tmdy?#ch+eK<42$M#Q}TRH}< zpcmR7j@Sn7*pPMZ26f$w+L?{<>3`)qLR+gFLs@f6@0MF~OCrY)`0sRR@6_AWD_Z^X z1r(R&W*O~aODw=zaAzC)e;@fDqpA8R&OMfD_Wfr@r2DpteEz^jxW`ZvWXoz~SHu5F z?^(Rh2?5Rs_>glk2j+ARD+~09(iikXv8u13;ml$u`GoL&U+@#zX57GLh%2q3C zWyV2JY6wW<$bvzRLc>Tqv&vQrh&_#oSPNuTx;uY~pscm3oo9Ma*MlcVfF|^wPQ^x^ zh<>!s$S9{4^R$Aaj5VZ&?Pl<1<_jx9ufjKa@1Hd2OM681k&TAb-7vHS=0j2?W{q%P z^ya?E^`H{DGoy1noKbaWw5Hr(70%HhDk}{REShsCxWf2d_H$bf-M9oi(Tp6^kA>DB zb2h2RF}tLtwadzif!J12Ogaa&txnG|!=)FXm!IjMe?obu0nAmT%Uq^DgwtAP6g&PV zI!^5}9^G;;5^@VPv?52`<~()dAl6QcG0edx7h*KTDz-)>s3yj81>bKa;QYml zlXCi-PUjLOTl2LwbW6rLKNUOV-ciPQ z4r$;JJ2928H4AQWI=|Ctv=Ou5l8-qruhh9HPV~DUX}_mvQ9Zjmh~(VO)sN*rk3!p^ zL#wvs;Lo9!Q&E(WOj1pS^Zu4qPgR zp3b?dWz;Ocm)K41tFp#nJy4kuP&0_utsKn?+IxOBp6CTdbE-e>Ki7*ldu(pYDTn&; znQGJGS4RI=kx6Lds7rl5Z_p=1*8nHjL*^;y>}F#LCvP zrq}=4Vgp=@)ayLJO6YlJQl?p5*F#@nIvgQ~C)<-15t)jr>zbI68Mj1i%udIzwc}sBmTbX*qIK-0I=SGqOl&(B z8+2aD8CU~&5*q2qqDu!reS1%wPOu8!y(DKn4}M!FUfK`#D2T%B(iQj-=OO*4Ks6&! zdTv9C-*yPN^H2`6N1{(DdO9B5c~J8*)V9KMN|iAqz&XXaG@L|mxHwKZIjn3Tw6YYx2?YYdaO`#1FI1VxQWo0nJwJ~3baXM;q} zDmu@(09wjKLu=irIOR_`c3$3N#Ll=$x_+}~lxJjqp&N%Dgxwjb4bTH!g4J(czR8*& zCI2I2!X6Oo0`xlVkbD?w*ct2ngYayo-Wl1*h9x-@(-Dl%`Ry*;Wb9rR1Y9=F@`?CLZ9Mbay_{*;957D32X<7wg zw@cfCn$we3eYTCUrdAMo1)jfW{co<;J*HOROzo?P4>~ti%-SmBGeLcsIwtA~@^MY| zcHM{G?hR*;)eK0@U)#f>d|K-f*>rBWnPIa@{?00Qf#z0%bSj4#ZzVQaZ#8bb8>4mB z&?QnQo~>5EITW9vFB3f$C#7Rn3zQydI|H+m$xX1LH`D-tp+Z|0I%IJ+WC8N{|*Qwu`@>pZl&D{Zv2R%PZ{a7cJZWZMT;ZPBDo2aPuEdm(p&UkJ6K$0}3&Y zV{M_jGMGvq%1mPy=)YF+;pJmH9+Y=|L2JXEtKts0`h~mjoRb2!=9)HvYc}Lv@_6^M zRx7ePY5?a|a*$XRWeuA8x0HA3|2dnP>g*GyCFgIuX1-hZW%ml}Nso^QRR5 zUHLKd<$m*t>S=LY_tae5(cpBu%x%(pH!GeV5#ti69;{spdo+0RA}edcJ^oW-dCANt zrYFL-cGKzo`{ASL$!}kLL9R7(dS_lf&CXIWns{k)L8DUiKIC9ju|##xfK#wGwVUrqtk2Qd z_T!38KegDO3!wfLXrN3Gu)ikL7_G~*R=nmyoJDB7co{UyzZxRO*^kAY0o9u8L(!hx zHsF?fV}Bd*ckYcR_2ji{H)vP2#VY8C<>5VR#ab5YT)KPg)_x{14B`5PK`+M_0b2Zkc z_U4+bYB399E_o3&qCFJ$H^mFEqt2{-G?aCAMoX3Kr+MsQWt)`@X1`u7XXy^hP7j~L z@poTUT0YUW*Tq5x5pd5dUmeb;2L^-zty&*JL!i@z`T-<4<5?_r;w zIS+BD)s=dqqR-s4qWVC3yR{cue5OqoUD%Cg4mlL;5ATacj|4%T1Xi+Z;#`jN%6+Du z**|d<5~Me355Nu}4<)3=^PP;)jFr}_`i%3cIq#r;rwlt2!`zTrqOovxW)0UYksTg# zg1I8=8ein7d(xU;G^eZ|A=1tv-p;kFXT{uVT5-3UHj-%W)ULLFaA=*BN41`}wy!PS z6JOu@B{QN%%|xHQkVvagtcYTFW^!l1#UH}d1j&t@b8eJ22{0i594M$k3tX(%oNQ}{Wf!KMiZRoqHNo(mAQg;C7R6^L1}kdLqYRh+Un%j?3T-XYj-j*ORQI)p6Srg zd-t#Rhy|B=R=CHCZv*v>J#Gjc<&9WzyS#)+-YiDn#a1r8)vgxQdt|Stj)oS+KrSuOZ` z0iO3$Qz4>vwmeknRB)&AV@AS`lQcCE7Ta}HhP!n=gQEvO_7nm;OKAA zU* z6v)--s?k;%P5Btgi>RE0uaA&w`pOC7YOz(ipR(`mI#^G2npbO&llsg)H#@L4=kHCC z0BvkIIFsk~UG(x(ZzdO6SCi;W<{mp?&U$k_%kJn8v9B>j`zh2$p=q5ypai*_qk3l> z{%@{jJ$$+@oN2p)OOsog#kEa^8#9OfK6J}XR9P&bpMyat=8MhCy9PkuQ}f8YFw9^HnZ~W6-h>J zGtS?`e52Z9W3-6giSoZD)Uf{643a25x!q=XeNMe>jFQ_sVE6yzB3Vk8D{SSk^H7}7 zuFkZ^Lcc!QV7o5@QDjot z%)E!W(GKc~)U}Jn#o<2BjVP#TT5gH@mDmHQ_-a4^6;(`DD48;qJ6pzna65Xv9(#HdWz>F|(Me7t84N9;h%gvOl;icQDdTfQYqj9h^!B8gI!wyB zFKepA{T_20<~q!4sCRz>Gi5GBej6P(GoofTyCf3*rdU)_e5OQ*T$2TPAC8j~&1}Dc zj7a6wg}HB*y?pAO_u-TpHI#~W@LIKEbf;pHssYrf_N>||pv}~BN=0p^*3vAzpfOtWI5N_SnvlOmY_uEXjAL!(VB)noJK~X*d3qT&SANN(nQ$nUHF=#2(x{c4^=Z z^SkPR4S28e-xUc=e9FE8Yf80y`kZq3_neQ`*G!jDykxh6_ro1ljA%<6XQHU1s(sUk zl^=|FnQQ!!Pi(~PPBjy!nKRTH+1+^!G|T+D^GinLOmyjK#76DgKA-59UEAsBPtSI; zh@vU`YBH0+h?KQCMi?VMbw_&jP<9m2;|$%5ccybWXlf7qIcrLc>^Ql}obmzqdIuHH z@4(`}Yi*787$32&qz^V?{RU|>XRhC!{`q;hauL<{g^KEk_)cLE3)Pg66ATFeS>2nw2HMo_hv)9_sCuVdEBAlHNG-9$={+q zu~DsAFiv3yh&&k2NX=n}M?EXw=!x3xr$6SsdLb3rda}{}*4hR`8*^A5Y0>Uz@r`t4 zZ!RXI*POh4VLCFX2blRl>v|N0hp zf{Jn|?WwUd=MoAh>^FTK z+Eavd75D1YtBXFhrohe_^{I2PQo(2>JQ-WFAVx{$W|4}%K=KA>{uI5XN5rU{(+bReq-({; z)?X&8ASO-cNH^Y@xLrrCN8ON&h%w^@xDx$oCH-scjM9|~a1qqgtf|>tt9|cbt(`4e zcT>zXV{5QPQF_(pa>dSx>Df`834PL8+-xLTy`#UT4)U;1!wwATs{Tm@u(~I?56{V1 zlNEHC2&m>3`=+wa=ve%m@3;apO6fyLrp=hFNZ3`if=L^cDLD4kWJhYL<*usPSyg22 z|BQ54J1g%=J$)Rpx_RXE)o5poFNt6mWYy+FrtF?e@1^;WPvLv(udU6jc+}b^b0Ue2 zS>dGoIJ3z-gQz5QG&Y*vemki$Gs#GldNoz;dL5p%JHKvFj$S+xc^apQZ(+xoK5|RW z(D;HK6XJ?m6Jd|Gu~74O`to)cTYE8)d!uv{i+;4OF@4%fbS5`O|JRB}wW0`8UsK%b zI(W=sH7asd;?k`cKReZp`iibjCn#)=DjiMoZtZ(AZ)Kj!|JmDQj!M6!Jv<~I%RNfJ zQ9pIAhyH{bSpTBd4v68jjLNsVrE-F?aQD=emhyt@-@Sa7+R|8p=&VJt`KeX2#zOpL zg@su|KXQr&)!T z!FWb1;#1kfRAV`Mc=k8+;Ovdo>s{N08%*kSCwm$^67v(IS`lYWoZh{$JrR^Nu;b16 zesaFjR|@N|St%i>OJnbs;908xVyDb!D;d&O9bq_~|F=fcY_(mcM!L*}SikI~ zJ1hStLCt5e$|9z_po(Zz+jBY8x`5-H68lJ9{RLprR5Sd2evmWIQp4~XHpjd|P?Y=H zsS~RIZiLk9vEnt{;MuOsRcl@1DJ%Wa0dj+}UXS$UnPE2Sp=K~^Bqt^hXD-CdkzGW8 zp!xJrtt0dbr*TGav9j@;++{d>4Vy{SLMD?~>u+j?eo@YJY0h&6=;+y5->!ytm5oT6 z54VQdX!H8Wy_NUYGF$bVNs2}lM0Dw>%M5t^W%Ci%*jYp9VVvE1yyO@%(ZPBg^9$N0 zYw5&+wHDC`t=Kn*9;E(C?o+F%4{PQ+zN}Sv$#nQ!-Oi^@8;U zA9MSFbJO=U0;$FlpE>JCAM{QBj?Abp#qoOZsaeTnwA$fsb+Z+$&bhU^S;XaMxx7gMggSizWB4+4P5o}&IIfnRjRy8rn+R}d7SbuZMT2Je5616kep4jIW{I*-b>Ki-2tglzj)-D#p+>V+}3?q7Rda&_3 zt2W|kTS2CTy<508GOyh0VR(~|tt2pqXf#d#B0V_fo3n~dpp^b*Dv157Y4PT*RFm(_ zC#CX239*7YGwao*!JkIE#2HpI)I%!94@w1~b3E#fkLjGWg5C*p;!rV^)YB7@c2x`6 zNtbRLeWnrGB#n2L`4eLw3r!i1+CEb zdzC14o|xGFUS-)FQ{s!(0jCn!-%h|a3Ko4{XIq=0HKt$Fgd?so$0`0ye!~tAt#o#7sZmuwH?cu;Dc0;X#WzZ9Pz|9b zFp?2%A*We)sUK=hy=$+ur$aqGlp+jw2?@T)PCWKY8s)NQ(#nd>3#u?GW5=Y`j8+S1 z`}N}WG^~QiR2<{9=0TNtYgBImP2EU6gx=@K;&bY8))=01HAdrw)M;b-opQ3dukf;1 zwMHae38T7t4#|gP=B@FwpSX7GTwM+09_Gku1}L>?*?mD9uP0bDX7!OcM{J`mG}j?g zv{x&BP4ZDb4$2S@B@%CSu+I3q9+h5&(*TT58>P1LO%$QkvOCg9vzk$qXNPG}UgBh~jcepARJ}kE zTlUg$z2bXX7m%!{UcH{DQ9V64BVCdGRDisRc6qDeNERk@>1Uvaqr3HgoJx=$5YNUa za-tiVOc4LqxSjd3b{y@?eP+zaL((GIOXG7&wx6KfE7xX9)E~-o)6%`5%@J>koRYP% z24_mqSFgbr*;UlIM5;7Wq4_|`j7NTqh(&{D24*@Mq;}?bB%h;3(3U2%WK6<(A`e%$ zuCMEwoM7YJoaA|oO=pIe7}=PN(HZea?RiOsRrMS*|4YA7eH6dL{;EWZQfrlbuYQU& z(=&=kD9x+~i3W@gw5CHetUWdA;ZI^j!L!!jXj8?z$t$V*t-G&KGy4Q%Nal zTO>rJVZE<>YF=4{Y@ER!71!E%Bp%LPe7qcwmHSK>v;Hd6PDRg|EN-sejIntr^G{+y z5s2tfPByB$K;dLL&~>OCZqaYnHtIz=Nz;nLOfre*q<0+;TVG1=Nqr!Xq(;{mh2EjH zcUrV?cxpXdskw^1(r$UEud|A-Xlg8L)lYnV{k!<4MwN^@IlD#9O9aHe1yPe-r$+6Z z0&Dd1Vf0_}NLnAWNcx}7%81VVFOT#)FU5N|3*zir^`?QzrYGuR}$$FojWPX zUg}uY=t*sakzMVFh$?w;J-c2F_*wsb>w>B5TGKjs)E)1VPtrDsq|D8mmrqxlT_Jj= zdhq(CRzFD5ME32CseNZkU3|D?0qZ3B>=|cYRBAGm7g-u#%vR-OZ?VpIOz^C-V|#s(o}s9nkWApXzJg*D1rI=?jd$vC4tY^8tt z)PhabPPIZn&18j+m^Amn#CjciAbK2XC$qWoslJEa>NIqSdQPmA>#_UJxU2rANIJDV zMy1R|nkf_6)*d?%Zah%=NWQ9BX1Oe$XwE9vGmSXk*i*PSdF14l%r1$gw7SV*Mr-O%m~)Ay zH0zRS@lsgpootI8vL5N<(E`bf%8b#jV5TO}&RRStz#65H7p)R-8q$mC0HesEmfiD@ z71|kR7Gs+8v<^5CuRW=h(T(li*aKk z;i8mOyVs5uF`!yN1n6!d#w3i~nfa_z9~%*!=KRxIH*96FSLTr&$X)&Fa~REw?h-Ml zf=I6Z9laZGqhgxWp9RNNooakWET_bz4^?8fYe`6H{rjU$>t$i1GqmEYR!^mWM<(%m?io{!uRD-$Ye zXU*W4X>h)Xl}uvN)aMvsR8Lt&=$r(pXC1IUynK?`u<5L|J~N%3sSB-kU=D~R`Ec=| zy%q6Ls|66-o1yW%DvHjYd0AgkpZe4K8#P!(%h_v6lxK~2j`SrfxgP3x<8Z0QP(>)iv{N!~CuYRmC%r9M+4 z(;4y|v~Xh7+pN`#tlmVdxK+QRC@V_zK;0G5IvjJz-r~{QnQldnd!kwoY&`1Wf>u+5 zmsz&Sff*Ugo?oH3weHpb6qRJ6keF0Dd5!v2HOrI9dNp&ZZjkHsSn?=ZLquxFn^E>~ zcW^`Wopls`syyXyGY8=}Ggo$2q@Ffego=RuL^V@p@=5F332C2+-3f_t>BDEHpcVDW zg=Ctjw7d@r*{f{VROYW=i_fmt9t3I~u=~D9$FaQLPDE)ajBI;8gV||JUlz92R>pJ}a!bgPlr*j2N>O0!(psLx}6trW8MN==|%5O+%nKP{h{ z>ovalb)kmcnxZU^c%9-id#bgH|@nA=SGG4$!aWGP%ZdqMB{0 z*v?1BZ{lAkUldFzHS{%uv{R?3HqA^N|Lb!#I*{oYMrPH$$tdZs>aqJ*onuuaME)&z z$!Ec>`JD+o@?YH%S=#$rV_Z4v+=z-tjU6Y49WSoYqS|4p_6YLOqUy_KCkgFprtM}D zmwldAVAww)o{z8L6&k(CwC(p;N2><3k*TxSn-?>QICGyK8Yie5dCn|hQM_{zYCVLU zEAIG<2`Q--H8Q5AOvQ^;7~($VsqPe{BxS!Zs~$30G`dlHkeHpB533>6dv?| z5yT@$i7h>hfqICML^k3c5vND?2dFs9Yl+qk-iubsyqy~LP?xBQ)Jgu1$149B_ewn3 zsVG+c=loJ5X7x=nVNR4(*0Y*5dgof32lZL!9o2f0OyM(IX!ck%>KWEL0i$^0<9DEE z?XVFQTb1`XhxKI62vid^dY@J=IvK@kGCirRgSFVP1fp3Z*g>y)XKDw1&-n4`J7b+9 zD>bSTSaXlb85;*mBwPDtbUpRv$(N-9-8Bq~RWsHVaz)dp<224>Ld*>WdnQJx|Dm^$ z$?;B=@^I#X*+lo)vA1rkVv{y)h;d0#^9QM&78$3{)6DCST#1}&-IbX_Ga^nT$$s5- z0gMH0S`BJ7oAtM;47E>DtzZ<&3Ac5R8ZlHOKN|}P-Hh~`eUN8Tp&wn9$Yi>T#8#p! zQN0mK5xq9UoAfYp1QbEiy2&)07b zwoBce6~N-Cd~ai2j~Q2yO!fpcFK8B9WD*ZFvrw#K(eq3$#CyHa^n)nzMvRP*$yHX~ z1k0s@+H;Gylb-n0f2Cu~3`lm+6IW!4ONU}r%%h3sk{vR#FMrj3cdKbVXR($)p10i< z$=%A`a=KA0^<(llsUa~I795p`ed1$w11LSYBCjX@o|P}odQmT!Ax(FGwWW!nn12X@ z&e@uaP=~3x#QIf}C&QdN4E3>EDEKD#!G;wH#eVypYUyN??B$CM4-NH5T-WwcEY!0` zP`jKMhqSBSWb~-ru<5taTi3SMnPg_$az5shL{z~}Vr4O@vBJ zM<%8v@2bqi0w^!`_g7?ZcSZIsaE7ONA~nCsB$z$aZ!(jZY^ywx%5{-_Iu*57HUE{Y zjq6=G)tpVTHezS%hWzb1drR#_ops|{*QlmB5G_{Xa#@8`4r?Vvg0VSj!{qE!aj5jV zHp+WE_wYq#d4@l{0(nJ`%bZnKCv|r6jP~yDK7M8|Y4IBC%9R4`WU_iXO z6@KtuX-`H``7wiNXMCL*W0iwl$?2eSUa)aHvq;Gnjzd?fFRj9{wmuVqoeZ2jym{mJ z@9C~Js%J$>CXPBSDLtTKkW{ru!&+11m0B4bIwhY}@o1ua$s%hp)cgAGM)r(ffC6e=TRLftwyTGX zHrdUo#t*8m_$mBWr(~%&QJ{izg@7 zh`sfi^pwQ>}mpXTI%1(DOifeXBon!xmsMJ`6R=}%G1*_DQ ziqZZ?N!9OKfJ97FMLLaFSa&PcjbMlbve$I3)N2i8q*a~RDE;JZspyf)4)JLGFV`Wv zTBmnhU92x;oI#XsEt6iM2uvR_NX+|MIcEeWd1I|;I9pp9JW{QAV#(o5?WmDtW69$E z_`2FBqg`56v74w(O`Rx{_)I+@{t!8(9#qt!9?0C0H<9T~);B9>hAtD+&4GwZY7MKg z8}mc%+nhYSQe>TyRhVXd#ER-nwPre3wEz0@*1F_A2tY09qrZ29#q*C=R2LYHFbkrk z39>g^ZqzK?AWHG7l!eSDk#mikC2H1Kx9XG=S6xdgKPy?8j3iR_aP`ElS)aN_`7J6+ ze%c6-H9zVVdC-_x{M?E^#GzX7U{O1@lFRf}qEInMa9g_QGWD!RJmbk{okbprB3i$o zoiT>1HSw=RKcbgJBgQ@w1vLsLR&B?T+OO;^5^bs#(axHAGjd@qlNwZCGm=$lsBD>g zoyIMal&-|7{GI5xl^IHoGNPZRR;c|`WENommRfl89jO?I%!!z4#MHCar>T_% z@o-{KeV^#Ss6_tnyZozOtB-v~>iW)Fqu_6`Wj@(Qk@NqL(L_$E#8QVE0aHTE7>GW# zJn}%z7+Ot{N|kg)m^F+oQdZJG<-BV%O2(m5F_vi=?wDt0#Hif_EWftjd+V_3zJKPF z#ZB?$v*(70K@=T)YJ?{erY({3iFYcKM%AJh)rrx4YP%q;#H2;~sU1~Y8DCL*XhXbt zEouVqJ>sGIS9n@fxeRp5-{xTDceRPUugV=^3qT$CO=!Fh(;BYNj7(w4jw6) zIZ}4-HRr6Y57qW$0)pA4_tA}J3^V1}*{~udXAM0>-bn1};a|a01{FCcmnl9@y+X}RrqbH1q}I!Lcb$kW zEpkTj+l~Gv!f%w*v&;F#2Gr`CU~79+#B?GKwPuis2t<^k-ptf}EtHm2OJp=xI)|=$ zaH%dxEscQ9zIiPCHyu#~=N3Kt+22lF^o@5DKL)Y0Wtrd+; zk%Igv@lw_^%sKkIBGyS6sB#Ax6=rxz5C8|B1k2hE%tx@dK z1oUQlKJADXmpY9g{qI`-c_9M{?2}zbllYLI_qyTTQ<2Q zQB_cunWe`4Xk#I)*;Cfcm%A3Oj&>r*%4)>ah)24r6i5dnSh-mZ<73fh^sC;poZ!2Z zL2usYaq9kRo;DOs-MJ{#S=XrswxY|v9(hvxBu8p#%!OD%ZOqA=k+7)^`Y6$35uCYC zH9{=5);d)o;mBxEt&fz|l33;C-$CeFr`*)E>RoY2cr1uW)Fg+P1u@oM1IU@Um#b zDILmv>T*-*FTFF>E!i!pt!B4ws3QHu^Q3Ogch@N%wMNH114OcV)#^EOVCpyZnK!e) z`kDDl3s?P4y%o`LycYePtd(@B+D|KGCMh=CN_FK=U2LvIE{ZQBvlooU-;vZ`3_()GG1fGn+Rt@LGSB zUM2HP@j@FbMXhc#r)*T*>|X8o)+0%6XewRxU5r*pMWYpJ*;oltRkHU=n$*#E4U&oO ziw;wY)gGCbk$bs3=%IE&<+nOif50O?O#A`;1GQm%ji6mWHSxK|XsO!RBls7<=*|>L!oaky!4UO{%Ef)vxi-WNE~y(Vuc| z?6UnT`tMqkL_!jCG|uCU3L{>LH>TfABq}GwcZ=^_k?7xJdv*sH+p8V6UXS=A^;lko z(a&0WCg#v%(~GOs0m*=+(!#h&BAJ;TRjUNjnH&ig9e+>`E4@WZsYy2Gq@GMRShSov z?)0KoPe~mY4VQdmkc#$3wDSM`d9NuMba_*o6|ALikjiDBlY6!6{;z6-_z}J<{3Ozd zAL|Nf@3ea9;Pld#>mPE!9-;#1>K$2-p+)nEwQZpBli9C8|dS&9M>dEV2nCmiE zq*YJ9XE0Riam*Gw^+tcw`W$;}oNjDgj-F>~BGM0pJR3C0?VvBp0H^RIZf`tLjCg#_FujsU0He;dKg=@eiqJ9kAJV zJCsDB;wf{CMnY1{7-XNR$C+&zO_X{T^=ddJ$SC}lnuJEJ>7!VLTuJ^@5m)USlLsqu z_j4PORE<)q5uy|Hr_;gUBfT*3LiLy8^+of$zUoT3SMIOt>Z)dCm2q{J=)eexd|X$_ zb5p+>sgI3{7bq9`o!Ve)qLhBUzlwl@f%NCJ+0kF&zf^oAieyYtPL!hBZGDUM3I`nr z;dnKP(yAGf6-pgnuy#BVzoYu_VyvL5pfAtHn;u$X4O&WVQH>spwVn4I%j0{E2n3&c zmg=Br&DhUWI4k2$c**pr>`Wq}vcoFbLLcjUB)6XH+Wm=k?k|6N%YK2W^kK zL7SEOMWYkJ1&LFnlE`Zg63R;Wx1NZr61w=v_k}j8h7)gQWn%Zl3|d8RxpH6c`%)ROKPvT!D8*tX=OK1Cvygjr zPh$m(HdMNaJ&f^K+msr;aHD*vYs|^Q?I126da}P)GEmXQpW2y0+sGG z?N2bDlHj>(H^fm&lXyURQO8##6^}!!>KaFj`kY$OIH=F0{>JNwX3JE7cxLgZq*s2F z5oJI+y8=1n4S6)R19D69H?BcmE$1plu0Xtn*wdg4Jqi6%eN{QleOBZE<-pjxm`n#xTF7g6&5iVWSV3R2@HHN0&8HD^ zV{a_c&>?(iQuD4wYb{cs=I2$s_F-Q?qojH5BeHvFY&a} z5DAdWD{o6x*GO+w?Sk-vI@$X%?isqC@zgMp?`B*Wpcur~u8~MoRQahIzni?%K z?izd3il=`o@nmgAP=2h4$XrQNx8#}B9*C0tPow`5uSTBJR+#lr+eyBKjvHcNIgc`oe_emF{Hh!;qQwvo64so+M)3|;xMC1C4E{uO#h2uJ_1(NepZh}O; zBG1romw!dc$rVaTqb8{v3I@m|W8)@9r{vrEjCDE5>6y_pYCoO#S_6^w&_=jUiB-l^ zh;5NCFi{NoBYvoIr``y{N^YsHNop9yrAmW%A(%jv5UkUPjg*vV5VeQaIq|^c zUzHp)A+<*>b<{?=%qG@&SDh)R3|aG=UbN)l)jP&IEnfMkDAMqcjP|l6%LKxs?H6n zSMLVHCsvim@kvuPW1L4UrR2-exfvaiE0s_A)O)|tRf>jB^fP$Lm8z9iW>)g8!EqJO ziRP;IuHPA7IN1+%cI3WhVI%j(OM^X>ckya^LJ~zv%t+m%yfjC8i4UPo63May$b4&){XRA$cx&RvnYItXfDtBzn}c z1zWntKBE@1$1{8-ccd@Q&y#|Fek8Jbg|Wzqb|!w5Ot9Q3pZcACQ>B}62G^@1V{2N1 z){+;AJuwbwoKVauMYTBb>D4si)4%6K@=lSM_9xnVh`XU4Oubt={Gl=I{((UT}gno5{`ZTVm+}$@}X+7a6ob*u^92!t??FDiJ60@MM|-=T8nhr zX-U+H6_F}Q zi7_d?@#BM>)CYRIH5#POk7ke$8}(24BGIDU$43sI1x;yns&)VbmsTn9Ok^&e}z_p)zTe zQ1ZE{zzZ&ocdR{1tTu==kM#^PQy@De)oez*W-W$3q!urpXAqJ|Nf}W;_&Ya!E#=0X zmyhz-|No?TzP{t{hnUOHPc9&{9D>lJcl0`>x9c1H8XS@Av35ooX+)s<8XlQ4WOZ*Q z_r{M;Z>@g3J?WyT;8Lqo)T@c<>TkI2dZXz_&oxKmYw5L}=0=Q!rb|F;sfJB9DLU4u zU85~9dY4I5(kDKE*X`lw$2KHq6f5D1hOhKH;tgt}&$j|ORQ23Y4qrW zP2#hQNsQr(+r?e#i1?abg<4VBmx@wVE{x}H9K|DY5gCYwnRrZkfMVU0naKL zF*I^mt&O#;W(civ({uAsBh~&8V?6Rfctg|@D#q$-(PAaE4DzGeGFmFM`#;A1E=iIc z=)&`;doF_7|E9DFUPwmLR8PHg=*aAz`50-5037}ghXaVn>h1|o9<%FcH|31hZn~Yd z`O|MgvyD&tg>z@;b|pQPazg9>vS<%tuPo2@5uH|q_6e0f6Gh!E3@&i z`CGlWb96Cx!|wOSY&1LDQ#z6KtBvBdeJ3r8E^Se))%nKyptetItv!C{De+MzwC`#j zZ&n*!ansv-n56Pbd$gWO@r~848Dlrlj@?t7ewT8;Rj~%$nE!9T`w21P8JHbtyEi)t zP~}Yx%?_i_;wz!a)^kX)^}bhyU7$z>Gz(uQ+wH@=YZvv^59PNWi6NE>}?K;{e)9$C|LYw8S&^r-1!`UCiXDe;K+v;IKi;Ehth*6p_o6?3Z=opnUm% z{S110`OXuI##Ue&_vzO%jbJlH)W`wGQAY4#t-Y{3Va04j8`-Vd?nYa7w`O|ws4TNb zD+XG*%Qod2HqezuW!U~w^>R#mjO7utzmr&5g5Tbivu9BKO3kjLe*>kzO=XqmqSi`# zqW)sZn76-FyH@+mW_PPo|JJEru=+IevWzGBopm}r8@crNG{;yrcbfa2%=z{Nt=*D- zt8AZgg<0QMY2|4@NaDt?_ZCLpD2n~YQR!2Jh=$HvO0;x;1%l##YmiNV0opz;8?mfvVGCU)YvBau%)>HPLFxp4%?`xe& z`dce|s^(DZsJ&Fbh5z4v;QRQkqSZl6v@*1Z6%~y`e_>~|TKDbgc|s18KiDkhJ@fP3 zZ+pul;AJ>H{YG#3cK%*Oi;=FLzUjBZI@RKem#iehJO_2sv_orW-*Xddbx#f#q5Vdr zUxT)Gw+MsRfNBu z+i!hanR*V{Iioqh-(1P){X&svi(w~Dt(~0OCGBtM_`b+D19~HBC6Gs)sd_ro2~?(Z zI%&?x8|6>&)BDMOmEeiXVn`$vHG4*DmRhZOe$V3NGNWV%*67(IludeDV$JD!7a#S0 zxW5zeZ_M;JHvZ10_etKodspIpl9|-=kv-wEpR&{U?=$v9v>eI?@#=StMQU%A`?o9q zKR+{m&%kAX-Y5I5o~NXqk+%DkXM6L3Q!7I=tF@u$de*hgCb?-DtDGysJCC<}Z+Gr2 zu4YB9-mamTF22LOXLUXOiOcAt;_Y@yWp?@5sw`fef;#7SZr+L|lkfDqw~Fmr8c}(m z+}hvRdB0@slo#b#nPAzVsOt?#t7s9m)2|s{te8zZ$*%C_WO(Gz6@CgYhBp$rk+C4-3iaS>MM_l5SfZKpt79r{@S;Hm8R2pS+sS?S+93b zvO{|-IgDK(-M1Uea^q$d@w91k$C_BivYMG&o`G#|;P$249rwJiGqLk%qg{Vsz_*k^~6->WY;VbyW8J;O4v@rdLoxQkM=f4&a=O7hfgCG$cS>G zzOrF!Q~OA3pViNdZY(yR+s&BGM%`XlEVH6rf34!+P6T1}cD~XnwP=XaoCm{cCy2b0 ze~fnTIhxPqJ0~2or!$ceH}3rc&}!o&-|}KT%`uT63d$=*VS6$@lQlin^(zPG3oDMd z@_ys!eWBlzcuwEH-nuvNp51#7-@oVN$*BC<=|pUZ3A=KU;HloSfjH=?1-n^i#bBQ- zwN<0{NXEb4ZuT2Q=lE9Des5_1&0c2{IP3qrOwj13l)H^zPwRa#z z=W>vC6q<@(M6?O@DlCpoiR z4(OSCr^EI-?QP5J&dJW>eCt~o@3R%O{dDKeo!xt@(GH6|y9KAro*wSrendo+hsv$8 zWKWFEk7h_R4OKwZ8P4S=xMzjsD0=E`)8Nr^x;`v=a2qfbNi=$C+=CU z6N{(gW(}|R3*DmIx@IqJO|S=W64)7Q9_*|wTkx;*fgaY>Rw z_kyjh#cJ`{dAYU7^KWx-`M|h&PgrF3J5DQ)orTqMr>J%vt)B9vXffA&&b+;M|005D zUSaWG>6dtWob^~T?PnDIu3EA8)zZDU!lARJa zdyMSh?UZDMeLyao$Rj3k1@OW#kar3+HaTJ$8Go4`raB}^wFuP zXm2hfiF2bbAMSlpYp2zu-?KQSINkNO)>$uzRi|~ezX;)7Z<2Z&mRH-yw==anVVzR~ zAF%6Y6wAEbQEsnlCN+!1db5TfoMB~!c8Tq%8?p9=#kLIDXCqt0o7L@Cx}Q$T zp#e>3(24`n8~C&|lbgFnh&8PV<*9TMqpc(&%4|!s&MxL*AALJ>^t?#K#YyDZdHMEC zvC-?yB`X!zb>Rc^Dn%6~U`ze2yzIXHX0@nC_ytVhdzK{ISv%PXcPc_Ab`LNSS z>y(k$+$_??nq1MfooiX6y`{2j-_FxqkQwBLGMZe`KCE@QRYG>`-Cujme$ikz)?3&; z6Y*5U6Hh+uZ|ST~y-R6->S+isCR6?_LyOB!aLxRFW#E0Gr=FfP^)}uUy`JjXnf5#8 z{;uCsl6kjZ=h=aIBGRtEUj>(2=Pwb}PG9VnW1BZ-ky)*5kKQi5J)pYyE{EpRu)W>! z?5J2&nyZuY6S)6Gt`(!KUbKf%9E;y(5BWv%bm@syIjj>xXHIKcBWn(Jb}XmwiCAx9 zFvteE0i1Q)=o=qlHn`W)P3|QH(PoOBDeM1A?s0VQ0rW4K|b_cw|#Q!r_-i2p6JG1Y z+j`mVHJ`OZHG1-Pesk~4JCDjItvf*)?LFga*R}J83}d9a*hkkQzP-7b+_NV8dgnDC z`E^;dd!FRwPFy_LJYr#cj&=f~&acF?qBY6Cf!$N6o|UewHyc@H)P0Iu^IT?W6q;{Y zP*j?$M(hwCJ82;nU}8 zb}Y29W~OcbcKn{+$okGMd}g2Q%+oI!dRo&v#D3-L*Er=-??(1l@qRJp?7pn&*{T0k zV>7J3P;TXKcioem%ZtlLjqctn?d*NeDV#St!I)9{`=~d=;_#lwi?aM%#x**|uNk*2 zTBdEb{e;s$^X&E$@D>5?%~tp3VsAq39j2_;?`(S);)Ey5h`iSKt?{0S?H4RP7qfwIO-#ptcMucpwDO-r9WsBtqcC_lB_1h0@4s54o zcWTb{?&EglW=g*4Ues=lvGeIH-~PAfHGXZeeA)A-PM4nDl{q{2%M>^?yJ#(|_`kwr zvND(%T(&CHdv<5uJ0W#13-qjUI6PdolQm>AbHV!6*|WLPnNv?0GKuP!H}!=vY;iA} z7&rBFwQBVwWKRvPvt>%2YuzhHn9)0P`gaZXiw6E_KV=u{+~GGjc1-;^B+d_MT_R!$oJY*K7nSZd(P+#(vj0xL7t9e5~B9K4rvmVtH99 zI>p*Pz1`+-aVQ!=#A>@xJw>H4+Fwo4%zSAzGVUU!^{8jaG9#~w%d+kEA3b#vb@rR$ z&Pd=i>ziGy*jiAAYd2>sjp&}4rCsYpdk$l5R-$jOhhuqtv!jSD@t?`+(RX<3LT+k?`obLZw`8t_pul)ucYeLOKR zuf}zI4`b~V);omO4kK(%C>!tIlJq3x%I%wrn`TWhC>QS;MZTNoM0VQpl-*VHpm|}Z zA|B+R&i3V7k`X29hb@aJ*gePFE4Gxn~CkFnwR>0|5lpaC|XsT;c*1> zi4qR_o8LX52C*md?eF-FqS?`oT-h$P=XpEj_bgd_IM>3Dt~qowsOKW0F#mb}v46!w zhU^#NPMDt3J8SNl{oc>__ci|du>aXxZm?=~O4;eNY{}O=-Kxyn?E%bWmNvV0Z%5cU zJqwi9kv-+h`bB$tspY!PHH-c-VyiOPyj7i_@Z~Ao<*&ck=zcf;Gp}5We)`N5B8n?x{%oP@dd7Mp+ggk=@?j>Q>yF1EQ-KZ2#2W zJuh~C7dPckv#fnp`*EdxWpORf?1O8uX>`ipJ5k6=aOz3u#3XWK2uO;$^a_$pV} zL*^Ti=vi#ftNguO&-MB{|DHYdcMYE8^~9CPU@4C14+7R&jWwT-WF@7%o=bZb>@ z>%;lBeUN;t)~c5UVEt`nbY_a$_LXYQ9cAlBJ5;%vUwPLYSZ_X>r^xG*4#t}1n=3}z zShBCVD<)e_ow2N{t*Oft*3z=5mDMkQ#i>u&yjC8?uai8Vv6`*z!Ha>O)EIf=-hEk8 zOPutt3$b*&%Is-g6{Y{n|2>;FJKVEkf2G*FTkB?h^|os$6BIvYW@j%MplqZjsRldyd6sYiD^Nk5Av`S2IkBgR)cn&JN8?k+dlAJkG3<|5{P{%XztU zzrg8VL+M|T>WtKGv?qN3`wuTWtYkFy?6lXgg7&<(^|D{g+e7Vd+B?sAb_?dYPJd6b zU!1h_!b28oh2>+XJpSd$b}OwCG7MkPruigBnkUA4vFjZ}dN-@=M4N-{mC&Tdfo7-hj4qF!y~rb@F+m+S6RQNzRhbi`>rcer@Fpx_6;_|JnPJdSwACJ9{q|1kX^s+v!%vSd29sIgP zV=G5l#;;8~qg(IoiaMWcm*j3#<)`k-pq=TBalDp4_xxr10D5>k-cyF{2C^j^^!S+Z z@Ic%9XFYr3velABeB~-ij7E6~wvXQo^Op~vBvdw{?Tmxe!;j&cC$eU%O3xZD$BSaC z=*F;pRL^jXu+v>@VIPSv%9SV6(BB zp1sGXSGy$hw%Oijv)XKJ7VttDEO@hA-`2!-TFR_$JUfvs{+coQj~|P*MMk6FYTB8N z9a-ktO}p^_-;YmPDa?*e9CD!S>FK9@DLYp4ez|{lz=_k}g7n_l>BPxXJXpwzqm1nI^*UQ zk<|RUs2r>q4c<&G^YCESY~``95$EmB%p^P5@Hui1Jwzh6;1;rH+I^>+yU5-uNZ zU)7$poOCmZFa5H*8Kg9a#04waUglBCKyuXO0(D+61|sibbSu-=BhkMs(LPeEIkNqe z`O*v*RryD>%a(XA%FCGK7OU-~dt%c0sGYU3Z}i)7Z3Jn}h8vAVGEF*5@Q_u?O!myE zT}}HM^ITTwzFFV?w!drXU2$0-2Rw1;Oo-OnBEuME{N=k=m`?0lTg-qm<<=JW@}#@j z(TTlzV#V?82^XJlr_dT8dW-11)|2KHfH@o|FmVb}YzmLZLK%uaS`1|WQxk>cr&AHS?(kTFi+CS{s}Do%p0}Y z+exZ9PvdqTGMb$f@A0H>r5PpGdETezDAv%e7(HES%@4;6G&+st#;%#w-!+PLA5zF6 zx4RivOe|*5C!_uS=`m?Z>R$~ka^<4-id%W}L)pJ)GQHvQB&@fw{T9PqGNbOqZq4c3 z?*Dab<>@@PHANOBt>MZA>P8y=4Rmk}D zHlQ~KVtY2LwU2CG?^yzGqiU`+cbbLm*XB?d6-(e)T*0k3U z3w^fdXusj~y7P$$YR$~zegQ!{aVuvvnmrNO+xJfL{Vh;u(*7E#Q~dsopq}rd^c+Pb zv}ZNbmQRgM-f#5sSkEb&#Uj7`N%Olgp>ty(k620DH&~bROQ)@JNPCFwEqTe=aeMN- z)9<_cb)p%Ftxs?wj#E zvbA)xtv;>xdyP%AOKka)fq!o)Y1s*=m8v$qwabsZ;`2Ta=8@)h^QTqCxRn<@fw3Fj z`LkzTJ>vxzA?>cryJm{r2n)(tY0dAvym6A78dKInyqs1JH5$bN>&sUt`qDHHE9dPnwbf+2{8nym)--cuh`#nsWsde{tw8NH z_^1riTGZaHb-rkj(ae}6(Kzpkvf{q1-tM%0lainOu4FU6Jlko^-emJ>5!-BGE5C?^ zi^85%mit)Zdpi)>wjD_rtlqx8HG%egW&|3gr0w|`OR}fkm8htljmq-9n*Y= z@+@6kZOnMd2$AaQe=yT)T!SnQ-(9J>-VE}uu{V=Jk|#D?%^ot@)UVMN2S$JU^zvS( zjLxBU7n?(!O=|V*2L9rJN1KbhX|*vbBo?b|--<39wN^Cl#@c+bV)}@M&UMasL6xxE zN4IMg6^jgO1N(O(+5BREQIdc8^L7qqTJtSm!-n4n#aU1*yg{?EsM#zPOUE&Xr~Ud+`r$t*`LpM=FI!`ZKqxQ$_M4bIMoId*GhP`cG_6%8@tVd zaLY=I2|jf3U9+R;Y@KgM-aKiQ=7apPlVxYjd#cr$+>Wz%B;F+V9;x4Fwd-m{!pTV3 zjpE;~y7joY?mU&9ymH<$<2!w{Q*EVP256t^ihxFbv$+_- z!&M&8N55!W$(2UE5zjkfzR}u_fPai<=T&3c9;7GBW{wQMl+{O)YDJp)^@9Uxu~3#-^xerWyFzaBi|mSnN*aVMZA$e^9-Gf12r*> z5(jl-P%Jk-;-Z+X#MyoEEDN6^}%T^3A?gv*1oa)W~AFi;ivA?Xt!tB z8+o~uW{qrn>8+x-ui6YDuZ-EK@S<8?$a}_ik!hCO7j2)_8D3jvaBuyWG2{ZR&88a( z{yi@j3!-e1kdJ!q=-GIF%r|iIqguWzR!3j%E4IuP_{)fDB)JCX3%s^_FUsg}{5O91 zujV;iUCW!D8mtlzN1AXfuTt=GNwudoAT^a+(5NRV)T_Fs!_QS%5S`;mYhlB#r#)3pjR^vpLSH{f2*D`QS>cV(Vi8HBgj$4l~*_-X{sW4E7REw5#Nd9PEW zy0yhq54+BueLAE2OZm?GU@&?nV%2F)YW-;}_;G6+{nF4Vi%;w6)^BU){APD)kJ|gh zc22?i1&)(lPp8zZ$g)HqzH83qCAloA#bR-+UL5huyrtH)7~{iEb|SiLiqgeTH-kcPV;*?)+pHBn2&c<7x_@einGAe(YRb^Fm zyV>(s>umY?R@>qz%h*{2CBM;C@>98N@sn)eBf@?2^!CX-4x@HhVeqtXd4QiczGbrJRyHc*WuT}a?U=+*e^cw@BH@qmj8J(ZjHFI2x37Wy0N;9 zdj8{?KCPYYuf%b&yr=v{cy=F8HQtfC>;PEQ$JjQ;w7qC83iK=nwq7*X@j$!S&-cqZ z`I#NF$J1G(kfrzj=u_Q+BCr21dPTic_l!4PmBRwOUHO#l+_6_8dQ< z@@uPhnTh4?i;A;m6VK!=Wv4qey|R;dEQ@&#iGxUtPkb*kEQ0bXe`XOqiwKsnA*=I+ zzvG8HYW~3?h*)KzUh}Dvb(7mjR{DRHF2BguIEiL*jZfq7|N0q?a%E$^F>McZC!_M} z_R~f7az$%dqmZsel{hY*dCJ<;&WF$VupH3utc*mXV&Rm{?DPHdHAr>E^T@*C(MsSwVzAsBeZ?jzNn&-uEd(dWman8H#4CGyYE*mKM zqn%W_>)PR6=j*lX4yuZg76UE z`^Ym!z{qS|f{Xu+U|!2MWvff}&vW^xj8FtBd6r*}BACC~iwZ8p)a9M)V> z_t~%9%7mB8MA>xyW&i6jo_Ec|YW-qsvmd%vSoC?S1vC!$zm2KVRo({+>PON7nO`8IyPHmX{r?Dcdi# zZ*m?D((_$??Xjb6KPEpEF^w48`H3%^IV)-yNEiM*0W z@={*OpZw#~6OW!8cgCMrJq;;~wu=*wsz8;yFEG^f$DB%ztlc2_f-->WHC=O-g! zW><=JUl7@2`I#>lfn;2Jxp$D|G<=qKJbmrtD8uw=)HZhgeTuoWy@xTq-i`0hpX{Mq zc|Y&7JWudTQOJkO?P6WDmLoiW62m>;=U1(In+;_Ie2isTW@{=R-?ceQr)A0Juhu-S z=98!SOD4Iz8s+*~C|5@%Be$-UFY+O8@Y808c_Y{Kgs?0#kD_?T8{c*w$rar_^AgEr zSdqo<;xrGG9h&n+<2+rfwYAvXTq$z478Eg!;CW;h32&=Xdh~?*_yRAH}CL! z{%%g14aKXn`SAnzjSi<75F=UdvQ$=v&F;l}aU}-gNg8hDHl=zqeVO-S#%#_*N<6{| z1C`o*tQB8Un~U06H!Ir%wE`^$+?|Aq0WB*{YmKs2YmmPRUhbU-MZxU4c$fbh%Xb#p zC(BA^P_wET)67C6KFV_0$3i~k-Tdh;CnwW9=xS#1C>RgW(C*VkNB>V~_5AbWrk|@E zD<6`xtVotmBVL*0)1^F@_08Yr+uBx}tv9VUt?qlvAy@X4 z-YK*5si*h;f?ftS`ju*}Q{7pWztf6rTJdE$U0%BVP}xjRdx5fm3{ZS8vz7mNyq#>j z+qlARbkx8)qi)pdGlG|MU>VJ3MI)M?i#htY&e#jEs~J&k>xWgu8k7I_muEcHyAR&0 z-l=`1HzNHFRcHR4?aeTr<<}ym|1F^LyxGAYEXo_|^J#CDE~EAosg7Majy=ko6Od5r|hZ<~`nNMd3Fs zImg~geIMRz&h$*tO!3+7OTA~enD%SD3X*+Ul?9FPR>N79MJ#CDw1SD%q9PCE zF@7;ZNn%GMN_RT5C`*qcsGz}*P)10S^mS={x+OD1S{-+H6uFW)jydZkfsjI#gc&PHBN?A@2Q z?EUs`zsd6sWP2z3sJ#iXw^}Z}C@M!ZuZn?-0+E?L?3&ibd86%#WmXgmY{~ZS#RZ7f zbr~#-`3X+gEAykdv)PE&%qkl-OSbRa420V`uTkAegssh#W{;B9*4X@!XIi;=OIw*= zolf|%PwTmuU2YZG&CvWOM$%B(hik})Gz;4Ik)z)oeHM|} z3@g$v+s%KQXWh$9?NN;!i~U+gPH9fI8q|ZP(cbQ;XZ+E1PDe7+X~G;dcG4JG`5(z4v&t2Va#d+?#Vdaj>Bn%@+0)PrWHfyW8oro#)P< zIQ+Q&=i&kSF3vB5H;0P%&6&-dvF6VApkt@v)VahSJ@2AtGj$3;!lv8+oJZ6&(=+bSyZL}05=*@RBf z7uVXwbs1mQ`4#zF{3GPKodxNh4MSjOUN`$lYW}F*DnqYkaHa9|G4G9QYfkvhb$!hv zoYc~G5-3Y=-$vQ2UVQHtCOd8RJhx|g)`#|}c3nHYm=(8QE%z;_qFB6?`@~ZoC@S(` zW0yvam#A!=1U>(=f~VM(eXQdfU$(#V#Vl!tfLdklY~RxmklwZr*L>kY-d#<7^Ch1) za>~o-trp5WVutLz0oI2PJBRS$e(%7;^DaC&qZOCmHm|xjYWayTTXl<`m2mXA_^;Ve z%q{NzkKYIs3E{-CcC#ugjl@PFT3No?P$p?K_^2Ii{whNAWKmdLuH5}Bp?&!%omx%G zqckdmmXqj7I{fKu#&3p$y2OgH3wNVOj(RgkNsFvfn|0M`lh=*5Q7}TA1MQq|d^a16 zXY*ydEc2{hQL=Tx2y1P1B9rE9p=E!K&yM`c$MQM>!4QK`qZ_+yb+ORB6;{`$H7 zTy-uZ<#@l8RYBJ8qt_pH+;$}Nesc%J41^IERQN{Em$WLrH^L&SmC!gF zd0sUe$_UEl$-GeREvul=_F24kb|Wb()#SY5pQSE#=c7DORHfZ7>2Hy~c$1K>Wi#W^ z3^rP7=onZMNWY4ltwHMGQ?~TT2 zZ4SNT2|G;vjcK^xMauN#%X#`N+Dy4o+fK9DAPYKM^p>caxw4bUyxShEn8?rV$68s+ zo~;)oC4bSM|BQBVKHr)Vvs$bdwbj)1=H23)#a7w;%$s=!#Mq$eNqbz$^Wl}1qOHiM z#e7!;Zk+RE`A(TVN;AE-=<(r^B7afYz4;^Znh|Qnq>|TAn_a~^d^H?NTI~G*f3BT) z$}HM>v%DyZ25B~-QS)uL{o0(YjRj76v#XHcIjR-{0K| zsy)u>nRbJNH8OOpFAmBq3Ww)6T6E*NE|}Fe7xYaRwq}1LpWhoveWd1(I4VIJ)yAl? zC!^jl8a>cy(mj1>Z}e`wHkNv5gf5+C0k5>`vZ&{Q!N!v>ikG9A zVYO^$|E+h6%i=bRvu3h4$JVAxBbw}K!1~5U@5UsXldl&%Pl&4NKDu~m;ed*5wcV@9 z7cinnBU6C1BhTOYtk~6$HoADjcg?pjijh2|e-X3M?u&@YHs_R?U?UxKVz~ytb%JIeRB+cWv_|Oh4 zNolsec=BYj+acv&bZT@;$6@7ptN**MwVOH%$~lXW#x6bm-sR$nJsX{T12P-4W*!0! z#~oI7?}VZ?Z)^-gF}C1zRa4kf;!t~2c0+LM_F7dvvVGEVrZ>4FWQIkon6*E zx!oCD*m<)|V;+@7(o2i{R=NF7Y}#kJ5+7dMhweTiu9#j<2p-R7M6nh``K#8C9$7X> zk#)W4E-I?gbP;j!p|2jbyj7Wh)pw;?@x}W1O+uJ)Q72>TPMY#t9`f4&a+94UjbL<* zXZ^D?{8=-Pl2qKFX~inuI7tsB;lX$ji6c2~!*YnH}2A20K6yvU|WW%GpR zwii*`=x>f}{JYmH9`H!>=}eF5Lw>$ty?UCbD;@NrbiIArdb4e1+}Po&#NjZ}g%w5- z6Q9OMe=D&Xt={>PH(Rmu{i5czY1SlL>-FdK%fE{X7>xk@EYBy#+a*$rhD$A;)qjEi z6ywDE`9JxML(v4@$X2E!XjIk%&l9w)HCo16KT0c?C{PQhm>7Ndi1=3ErS%C+~8LR{pB3UQI-!%--%`&5So9 z^%)6QyzYGX3;YM}v^I>!MK7&(;mWHZ?n=?HOH^f#9#?m8b_AK`(_Vz-{kY?YVzUL+ z=Zs08(ZeJ)31N-7A9{Mb|}v7X0j1!ns!Sh*UKBi|*P< z!oihSZ^XdTDxYh4B9q3AIWDsyY!tJEt|X#QZ{>OOr}1-zO>es9_ps#y&|vLrU+dV3 z8f~9ehvDP}^c(%K(BV5gJiz*>cQwZ9japFFgmboLP(pozo`*)ty;oh18FFxnn#_PD^wpjss`15+c$H5owX<7bjvvGQ%&w$hL~y^2kCz;CAL6;UATsZQEOOzU7HMb>g= z+|yn?DxB0v20QC`r1`Cf2a9uh(&98x$Pl-1El#9}Nte=7GF6URjV2rb`&K=AWL{&|q^p|HYB~)8yq%o@+5P zEf#<1=A~x;T4@vh?bM7|GMg8h73CnUtFe1FiKe$bY82qn<%i{Fde1{RX~FIef)b}u zXSsf`?rPDE!{E`2PFGmJ)!&wO6h;0dnFT((!^(#A$|BU-%Cu5fE6TR7HIM3_=kjja zH0vs-A^JR&9=w$N_~^&WJq@DFOKC8F;jDhpc0VAqZ}Xm?XuS-)ESYxV^he;&m1Ot) z(JT_3t>Gf7-H}?WyFAxNRJUXDPPmnS*dXE-eIN9$qF9+N^JN|YU)<-LJOzvP(|OR^ z`i9}zM)}Y>(Hr0(Jw?BDq&24BtF+qqJ%hLK{SF0n^u3*5>CL@g4!1VtTjRVZKq(WS*eWt2=r8Mz3mY>9aA*zgcEn{BkSbv-d_}l=1?et~e*PJPfic>1l+9 zwW#w>@wV11f`fipDmkc&Ydm+2Ll)!e)W9QA(L`(WfThzpt=2AMpkige43QJe=8Ijm zWs1dd5zXrP6;>zvB3-L26HdFXaOP*@Q>4=|?`EA=QO4@(U^EX`Ee~YdD8j-QCs_LP z3|{&ni+>Vv@@bTLXd~|~dW!V%Kix+u+FY~`Yjy4HR%*HEb0g5G=gGwZ z{p2xTd;dM@e2drU-LqGj7x|-FwkJ^uirPqgjz&B#uI7*94ui$NCj8X+p^NTX>C#G- zH}hQHK=Nhs+q zV|Uu(Fdpf$F$rE7g{;A&Xm~@-dc)V7^?6ANlMnS+gWP>4k(@lJWJ?jA7TVMMg@Dyo zvU+#EDoaj6J^_b0E}OSmUqt0m7FC;Peo230E_!wyY|jo~eTx4dMn9Zfm1QFJX-T(w zt7S_R_2AKUdKJ6;wb*MMV9>d;s9$bqF8_A@QjNmh)7xk^muPU5EYoB3!rSa2tMOEu z?yx%y!Im!)L9UYZ}Wh_4o#^9f(LimXYa z^Dp=pKkrNioXx*RI-STsGk?*;tj^Zc>nw+nSM_Flt2-GkxGr3il2+3#dxp`jgFWCr zt?9~Y>v7*aEB}SREZWS5g)@7vg9#Vlq+*!0$#&eQzt5(zMPy;-f!@+=;FZM8_B9V!m4!IzIaA;tB)i>M)|!KhFdMNylo9)GXCg{AE>jyf#*-T*d*;FnEj(@Z6^X z&Y+~iXmspUH&gWG#jtn_)$(O=JKwUly%xw1=?~xeZ?Qd!qk%Vz=21G%clu$_@$T<< z4>z7SUXA4Zm*$PrM!T8S$i_XNG)hJ?8*qk|rE-@u7i-l*ri8}8;sBXJghCjsv`7&UmogKY%B-*1v> zK<*+PFP6^yCE8i4xqegLDcr(0F9rr<^95*B4ngwCeRW0rEYZjp^|D)j7 z>6y)Nlc{&3IjpQ`bz&{aN_?tsWsEx*x#5UUU-)Xvn!M@*YqDwnOZZdJA`WO#!qc0M z>WkehPM3JF7VpYwa~{i%$xpY#npGs;hvfX*d9qp16Z$fr-mNrcpGL;`E;nAJWclL5 z-IXouEWW|!t7=!~(Y(s9t+%k}b&z2hrLDu^@DDkYL?gbSMVbczb){ReqlM15dbsNa zbKyQ%kdt}-1oIt!k{VxFEZ` z(!W)6{UoKk`KGtXs2@+6VLRMFt+CRM zOO`kxETZ!{Y(7Wlieh%gC1^9Ru^J}sAiS{Aj2k}TYPTy=YlKPDo77k5>hr;@KsTSI z2Mx17UpJD#0T@9AY7=(YmMYlYMzHxHj2gmHzFNMv zCgiF7)U%R)+tA22qtyGv15bX8m(tZJ*2;5A8U|ZAiOtNT`{vge;H-yT^JYFQBJ{h~ zpD##t#j|^!263HMwP~kDyY6T8tj}x1(W>6;H%s#4d=;noHg9m&rdxTw-o?a?CCo`W z-9|fK=L4;Mf60EWXjiXI(s-CC%nam)@Q!VBQLby5U_u`;x*6B8;yikQr#!} z^vV|cx#CEN)#Ciy`70ycX2)oZ>%5X}mGOZcrihvB_B@j{C_B;8J{=}QPujHdjAs$e ze)9%ZI(DBg$q}7+y52McE(cQSb(KFacle%8B^+rB8g^TcUBo1z=M97khWy*Z;V`C(O`oM1`d*=l7C z^sQ4kUSIr!H8MwuJDu3OQA0Vt-Ie1AD=3%wcRkuzZr^KaBhq)$1MF7$>u_(T5|7yV51kj7NGb@<9j5Pxx$f)HZL% zg-&sYPxnQ9`s)o=dst{TQ`Gg2S2d7$qfv8Lx>tv>c2Icq1@F@DlNLu8ckRgwADm`E zyGNz02J>oKsM@_QHqeJv4F*V88tEUO?#eKgU5kjcGK#}WN3Hn91GHMu@!4Ik`in)Y zpr170;m#{BoxUP1jT+OFqh0)<>cU?X-E6C-y!aY^+MpyqXd2{)L4#${{E@urzY(2w zv#NFyaG8{-$#-R6(Wacd`f8K(i~LiZR|eS~PI%R}8)V6>Ot)muTc=5LaHHow`NI{wKl^#vPG zzN$V*`m(UhwHO(%bcUUMvjL}9K8sx^6-~bRt^8=!h}D8A-d&$IYQ=lrg)`p6))%$D zv*K5YAHF_im+F;`XSSdLtvp_8VUwGdou{Y6;buja!PU->TJNPNiSMjQ_H4oj@3^$r zT0LI#_`ID)@s116>0Nn!IItFfa@DdVnEv>IvqEc6#CN^*5G!_2t{_L1k~2 z+@mK6bh1f=lTUvLel^fyQM|~SeL;@jY?-{q51)G6XrgXMuR!=B{0>M5&CoBF36Xmm-awegvMPfn6ibm4;|E$8J)0*imr8bfsT zL^Vy|s&}QwTGum9dgjYl@?omMzHhb7V>-B|?O_fEe_YfKTTd-;*ZPX2wb$of#HmHC zranuPR<9l$!<;nsG?(}Ha9$ph_`<;^Wmav4ZTxt@Pva5wWW_%# zkJDMwj7=x?dOmd|KU-iF&1=tI?eN0oJAH5ebOFsK<*3qbt!y@c3f0Ev>SNT-R%bmW!N|pjJ<{>QP?baH7|Ha?bunxhPIL z{IK1trEe{?T`+5@b>@&8rhcZB0K?BGeO zK1toPqaK`t4yPP2%p{$jpPDq6@GJH`GYi&T6!qLF`%&-dQk-fquuqWCI45@KhoUJyU88i z=>bbi&wXksU-;GOR4YBkB^$KJPRm-A^@BGD!W+h@^`&_l>CZmxu2~jj@Oq=d`XVdfHE3?fGWh&+_%) zIiF9&j z7gzWEQ2XjdR5aemf z3Gtg1_rx-fO*-1Nng-KocDeO!~57V*t<(+kXn zVl}d+Pk4*K@wxu^E1MH>nl97h(@wbAfloLfo^aF`j2FPy!h6<6uQ%<}cb05qe~Eu_ zstxyTB_T-l^3ds`FG zc~ax8oE*I4KCh(JFl)sj&g+TGNl&-&1wVbmRo;vk#`T?^YN!tq_B>|1qC1}z&Aq)& z@~liIZsELqgHDH&2j2Is!Her8Xpd`sbmYq}uu-IQGUiKnks217{5UH2ATa-7`tS#- zXZ^nf{}lN#xyN}Uy||ixXpS&UR-Gk!E>2$f?491}mzlSg^VcuxKNRyb{8oqKbG2lq*<^Kp zrJcO_tls1u_v2g7_@>?VYwr`3mrvAa_Tk~|Kb?+ZT<4+2<0R*)O3?a;6`v%XhsJBV zL={y1Vk0@}fTx-{xOmW-)=3B1WtO<&GF~rUbu(wuzb$yN@iJ3h?4o$SgdODMCO=x1 zC22Nl9qy##vkS(n9qjoc{L|?A{zgN46!S|pa>j9W?Qn;^o-)rtqg=21?7N;|+AgiQ zlkjPA{exaB4YOLWYo6@h2*eFtUjNi4_hO%dDXhVb(nFDN7B-8bkiW}gQ}rEt>!D3hnHo?SzmhvwaJZp z5_zO7=BdoLFt+%wene-M)hYVJ}R8b*I?5L$8iX% zmDKgs8tm0F?)8j1*epucyawmEB`N8ZVMoiBYM>~!P)}aawcKeQ&GCSB*W|cw^{f6; zd`hY{IY)k0+F=i4UV?oqR;@{UIp8yRT83K= zte$P^WTo5WpFQIg84$y;}_{y(~F}~-JQg)&E)Y~=776;dgh0X?D}xY^7x?}1kUhy zLw#|Rw)KKV7p*lXY~@y^=U&nhezHhq2t6o@uCj+bFg=#PPK%-a$@IqjlJ$Pr~54(?$hb;&i~Qrajo@zAJ;OhR+d(aGMm9c zPr|s4r=Hc_XZB*Oq1y&6ceYK=MyV=$WGWyxpSAwN}Qh9&&l{ z*4sf~!#U}zP1m@uoMmYr&ZMJED&J0iz3WMyKJakBRzDsf$3HGB-Rt}0J9v1w!YcMB zA<3WIVFnL>`n~Y#%Jt4uS#h@Ht2iB%_FZd%v7vVQ&$jMwoQ^78PmkLF1tmFItDQgI zvHi4#ygsOKc>Nu0@U_klAX@49sr!o^&y}Q*W2UteOY^)Jzj58YzQY@QHfq;9xq8B4 zz1n$a+1kCnoA>LBV_YU3kAo*EDe%TAowUAjQ4Cg1>l>0ECuv?iERZysUU9iHJ+n8Q zbO|yoCV>X^qZ@RPtQ(!w8p!J=8uY{{lYyNO;JqRAG za-1fUO)qSA!Cueqaa$|P)|!m*T^r5IU;SXybJ*@_6knw)U8;@WZ^ut@&n(v;w@C^= z9?Q1DvZpe+H#)CI`i50qd$@Y86(7>n?i%D`==5KtgoCEqwS(Om`f!r{%4(j#NEi3U zYWmayf3JF#lm6+?>u2rh7QFUwy2pL6KV=0$**A~8c(dzp^k(5%v60aCY8I|?HdX&@ zP1m>#!+k4Zuq@0ce@Ypbw3~!^a2}hC@xnQr*?v80ab4eGPouDF;SK+2hBX=M`S&M9 zQ7HeW6?i<j&~4L2R|!zGA%eDrG73*$3?f!sA-2N`AFnQq*` zYd#FN);J!YlbS}_xK95`7zKX3vo>9aGu_vAuQ$jrwT=TS*UL$LntbvIcXp&_tu)$b z#Q(G$MLs*t#&7b|^z;ey+QYx+36rs&&-*98^j)U>lk%`9@o>ItpSbYZOt0HbeMZPM zJ5Gb_KJUd_c^s})FIl5|@1XizFMMM)*kQo?=2<)3c;4noh?f$h2Dj?s0yx^}Nu@E}GUi9i}4=pM62z7{qDZszssfGw3(>mv_2e zS#21+8AL3_@6YjAJd%?RCvmm;VEk~KtiySqWwZ6{8&3D&*ME2$|KSgFbk|l(1GT}^ z@6~K|_xzey)Q>*?+QV0??!H=hVNGW8YpwtD{@M2@e!kkIVt`+Q~(o_tq2ld1-zPZ?eYwXr|LJUhT53o>A6Hhu(2+EasIP^5!YjdVrYG%sDE6;{U!OQ7zfR0qg?%D<8<7IKU-g_=>yhvl<2yS*JQ!f_vdr@ z)8bD~Xhw&6;}8sF{c7`v`U|gJIS$|b({H%&K5?f>DmmA0IF@-;m%hWf0u{8&_}q*gO5_gK?S0+9;-Z+(!kj=dN1gcAA`~VRR}S z^zgprp01|@X)i_62RYf_o}6yy&Bes@ACDxg)x9zvaR6C+9BNO$yfc{AxU5!t)ZNEZ z-+k#y?n^Pw8;AO%`NXXCayyxEeEI5|hKEO+lbCMT{-%$|sKZmn_ZvIghK=_y+)qkU zho^6N@!r#hDAqUp?gx$jWSz}_)AH3Kod$`6mKv`0tUoR5pDy986~=pVKg;06=`@e7 z_DKPq^cM;Gka%SOZcmEr=cND)p zIcet;ob%&&?q1KggwI~Bea92Ou%`9P$%suR%+E{D_;pu?AJ6b_Hm?^>crVAvdSTP< z#eXT|^g@z+@~izC@+%eY_15}?>3#Fy%YAx`A6?;p;vDq)CiC??ygzC4!^*=TW8YuG ze`w=4{_FYX8^3zfN?Gk_4)SLYjYtSqjAT1Z4~OYE2qm^vnZ^?9_=STQcw5sngqSO^7Ki5IS;?y&(^_SJBAbY z@fyZxCfj|m*S81#_S5y8*T?yvKS>6MfuoIi)-9^T4-R!J`OX{pB8K*rU)pi${AfwPAioV_2g(Jp9t>rB?46 zKdtdNjnobo&TC!u<1ozKPm6Gdx%>6w{C$nf?pYC5u;cUXGgzbB{iXb~9z=Ou4;KG= zsvp;&>pPnB6kPo;CcMF`>y7JC>WND2t%tq7(`MSagI*8Z;p4TQ@IdZbA4pu1r~RoN z$bVns^%=3#WW9KN>s()D)JJt%>mNKW%DY|(S@Vf~SqJ>HZ?wbF9;|!bc&T0wg<}SO za(=mtU$EKr31v-^{)B^!3f?f&>G11YIgK}(?t=|yckuXp^L)AMd%vjTInGhX^}8PB zpfBHV{NdobdLBN^(Z>~h`j0Bg(MLI~@fnx(4!-u=kJH-@2jrkvJGs@~p6kQoZ>?eL zTmO8a{TU6X$*apZ*2^o3jozdkbhY(Nc5*>at8mx8p26#R%TJt*Y#1bbTQ@CskMrTt zZfhVeCo8?D{d&+JUajLDbuxxo>%|!sSiA-~%8kZy%V8YVi(P$K^Fp|T4*%0@gMQvm z-*{d7#lu<8-+K@Grv|u8OZC~Q{&_W4I2@Pb40o;Fj~--D5~|Ps!@JsWKqfCPqdnXg zXVA3$w5`7Bu~uByIz2&>Jo({#mVavVC*+g<&;HX=AM9{m`9aJA*QX9zqpbcX#zC$I z_p=4k_3v@;tw9|4`=xp7>E37upH%nNCwbO?!!IJPXSI3fcw8-cBeQ3w6`^xSx)BcloQ96w#=hgH$RC{q(-~BWy zN|L7cxV$*?$#;LFG@4qYn+4aq9(T}T41QAn)bks6H0fVNz1qe9g&f6OBPnT<`L@H7pn})>@0! zwYwiB>eqj#Rop&#o@{jQ_4hgRDJT4saP*&228a14-_g&n*T$vRzw^sl68?@e+4WXV z!uq1lwr~CZo9oL#9p}lI#7}9UL9Sfx`cu~NJ4vwn=zTHbc$m{A$an9jG_8YdoG0n! z3GXmRH5=AWv$RX{OB282GwIV&JB~-YzG#ipIHqNiVZE4d{d9van)Aw-GB25mecCcCbLZ)k&*K>3))oh)F@RhLIGps(G?&DJHm2-GJ#{A{Ybcc1AH$v&~Ni#1`kLAn5S`Ek5aeC`CIQ4vMmX^c8V|Z_E zykERvv+U%4^1x+%^LUh>CC>2r=8@5TV%8d-*62>VdGJr%Ab;~5K8uLszOgw@@X~B@ zKY12ghac{6-0AsZz1qx%sKWfFjmK<^;-sjJ3ry`<@xtT!C*)7~Cr+(T*!d*tw}i>s zK6<`sr{k#d&AdElcUI5V|A|k0(T$>dtxs6oul#%OK|f_@RUg0&}gpzvlhlA-Tf2mljCP=@p*P!?N5H67Sul-+@ponXR^SD z_j=?ceAYG-M&JFU^ZnVF)am`+S$BBRr{}O<38&!;Re!P7-?skAh&RZt7gK$h>2qXvR{fnCG#i@WtgW8ev7X0K5obBB4xdw@(*)d z{=84>w20IAz4&438O`gJ(Z8^Zx|h$@kI%urCI7wsN%`%E^%f~iY{o* zm&gB;HmzT7K`N6Q1sva3#*3>y{9%pnXv4Wu%@t2&wb5y<{?^cR%|9q_{IY#G)9Sc= zN`V*TEPUGseKE(K27l)lRIl1Nrs=5v>|M|2eY`%Q=NtWNxu0JKML7?>*so^s7}k1T z4zK(dp3Gzn=aq8(laV&rHHuIAK}Y+_i_0+FH#^iv@nQ}3a+#&)t=Wu=EB#*H)dv~3 z@FwFdufO)6ywWfDSumbya5fx#wcQWrYWlAKaSik0Yrd}^zHhBhntB!`!}=S}bo|x|?tF5Z9o9Fm zR;%y+zhMP?wf`IcsMcD~D`kCPtGmBAqjUe;72T*mG3JwRe#07k_iwoNIDg-- z=WkSl?Vf~hIqSW9Qa}vQr&eKHIgD_@zj>T=HN8i3eS?YcIDm{-o?f2#lgHs5 zN40UqX`=;p@WT&&z27)y(7`4Vef8-$oN0R!hItT_v-hBC-`qZX=j|k)&ZGO51TVdm zNxb&)4i;BXT1-ZihpQHTwW}w6?a>c^UOXxB`IH2E*C^KBeG=ZgdhO$X^nT1?lR!_hAgacK6wG&_D4$agt5T;7YC0xqnjN{kMpG@s7jz zpyGk`yzuHLWwr17OM~A@yq>@FBK5e1qXZj#n*B2VwSO4JMey~GN_)I_4=0Rdy^tt^ zT={Ae^rd|3Fa4`URDX-5NjWa7b!YkKHNLogYc||^CiUoxbM32*Cah0CeU8HMIowa` zZ;Z9WI?cgf*l80yyI28hbaA6$ke36jpmz^nspp&1xW=V&6n|?Ce>gb4w9{)I9FNI9 zx@yPo8$-L^@z{N~9fx|3m!4*np1*O5$2i8Ra$NMyHa#bG7~xmy9sK0w@7XafpHfFP zKJLTNH&52TUKmGt_%AFdNV<*2{r!G&{x#$`4~BJi9bUDQ_1Tl$SHI8x|87NHISIR? zeCu(Vy#JZ+(H{T7b|;~6e<82r)9^4R8*R|fzUjF8@OS?k{LKgB_I*#UH%`-keBV16 z$%#w#pw$n1qds}p^Y8rO9G|~Y#^t|y^!gD;#BYwyEZGUqAY<+UPR#?1n zwe@~-NYah;;wD|z8!gH>e)9Mm=O|w*2O0L-yHE1^-IE4mSR|kH)mS==mwt4|A&Sur zchEn1{0WI>6yvZvJzUcg-lXk*5LA=!cN^9l&9#z%<7&I(`FWjO@Z&Lf`hLRK_pNI) zOYdP!+MvfN&W9V#MO!*gC%uC#cE@Yd);>(q=kd39v47ITOgf6wL(l(oO@sKYHHowI zq#nP+iAp*8VU2$6_m2J>e?B|BF#eAFCv|kw?6~0jC-$eWzNz59F!i;^EBjuLuZQfv z%dAKF`aI6#f0Bmvi~j4c!wKu!ErdduO8>*{0STP=5ZDctG<7;{`ju;Nf&gm z;e6lqPlvbfg&75S%?Ah7?0>e=;$%$5zuNAH^YT>x z>{(oVL%%U+&8Hrt{)BE+r?1}h8U*(spEb~fA2#XNLi2h3*>_yRJdD-4uLbMhWW-CE zcK^m3ukXI3{|OE9!l%jZQJz-At$(dy9o}@9Ct;ptpVWVH!EYLT-}MYOxp4@(a(8k` zu8iU&o=zt*oWecR!9t zb@HyIfn=H_3kt%|DgCcsYkWii#h8*Vc#0iBMC2FutCN> zov+ltzUo;zy2GsY4S(tJiPv}3O#&V?U3>RWDc{h^1iyN<@%pCufFb-#O z{=B~ut1I8{Xa>_3mtoiYH%Y+`>(h>xYw`xEh5Bd@HtRnnezwNxrJr|REk65)@i)mh zzA}!>H;>8w+x_Km{hyvZ_$2<9tjlFr8{Ie^{UF`XPBw&f)l>J4vL?r&e&>iUy2 z>{|cNN?hYS>fJx-C;bz5INzECpVr?rgO555k_*>&T8#7VqlxEQClB@Yy<_!>d#$k3 z>7`AN@vr&T6%A(F>lH`_iojKJU}1#B!V24K6oNyH6QKzU<_%s-UuOI>>o$g_TbggEKyF?+F@gB_p=7oD?5~7CdV0_ W^|gWxjPfw6U{2CW_$pWfn^f_ literal 0 HcmV?d00001 diff --git a/bot/BotData.py b/bot/BotData.py deleted file mode 100644 index 43877f7..0000000 --- a/bot/BotData.py +++ /dev/null @@ -1,156 +0,0 @@ -import os -from datetime import datetime -import sqlite3 as sq -import json - -prop_table_info = [ - ("users", "users", ["userid"]), - ("guilds", "guilds", ["guildid"]), -] - - -class BotData: - def __init__(self, app="", data_file="data.db", version=0): - to_create = not os.path.exists(data_file) - - # Connect to database - self.conn = sq.connect(data_file, timeout=20) - - # Handle version checking - now = datetime.timestamp(datetime.utcnow()) - cursor = self.conn.cursor() - version_columns = "version INTEGER NOT NULL, time INTEGER NOT NULL" - if to_create: - # Create version table - cursor.execute('CREATE TABLE VersionHistory ({})'.format(version_columns)) - - # Insert current version into table - cursor.execute('INSERT INTO VersionHistory VALUES ({}, {})'.format(version, now)) - self.conn.commit() - else: - # Check if table exists - cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='VersionHistory'") - version_exists = cursor.fetchone() - if not version_exists: - # Create version table - cursor.execute('CREATE TABLE VersionHistory ({})'.format(version_columns)) - - # Insert version 0 into table - cursor.execute('INSERT INTO VersionHistory VALUES (0, {})'.format(now)) - self.conn.commit() - - # Get last entry in version table, compare against desired version - cursor.execute("SELECT * FROM VersionHistory ORDER BY rowid DESC LIMIT 1") - current_version, _ = cursor.fetchone() - - if current_version != version: - # Complain - raise Exception( - ("Database version is {}, required version is {}. " - "Please migrate database.").format(current_version, version) - ) - - # Load property tables - for name, table_name, keys in prop_table_info: - manipulator = _propTableManipulator(table_name, keys, self.conn, app) - self.__setattr__(name, manipulator) - - def close(self): - self.conn.commit() - self.conn.close() - - -class _propTableManipulator: - def __init__(self, table, keys, conn, app): - self.table = table - self.keys = keys - self.conn = conn - self.app = app - - self.ensure_tables() - self.propmap = self.get_propmap() - - def ensure_tables(self): - cursor = self.conn.cursor() - keys = "{},".format(", ".join("{} INTEGER NOT NULL".format(key) for key in self.keys)) if self.keys else "" - key_list = "{},".format(", ".join(self.keys)) if self.keys else "" - columns = "{} property TEXT NOT NULL, value TEXT, PRIMARY KEY ({} property)".format(keys, key_list) - cursor.execute('CREATE TABLE IF NOT EXISTS {} ({})'.format(self.table, columns)) - cursor.execute('CREATE TABLE IF NOT EXISTS {}_props (property TEXT NOT NULL,\ - shared BOOLEAN NOT NULL,\ - PRIMARY KEY (property))'.format(self.table)) - self.conn.commit() - - def get_propmap(self): - cursor = self.conn.cursor() - cursor.execute('SELECT * from {}_props'.format(self.table)) - propmap = {} - for prop in cursor.fetchall(): - propmap[prop[0]] = prop[1] - return propmap - - def map_prop(self, prop): - return "{}_{}".format(self.app, prop) if (prop in self.propmap and not self.propmap[prop] and self.app) else prop - - def ensure_exists(self, *props, shared=True): - for prop in props: - if prop in self.propmap: - if self.propmap[prop] != shared: - cursor = self.conn.cursor() - cursor.execute('UPDATE {}_props SET shared = ? WHERE property = ?'.format(self.table), (shared, prop)) - self.propmap[prop] = shared - self.conn.commit() - else: - cursor = self.conn.cursor() - cursor.execute('INSERT INTO {}_props VALUES (?, ?)'.format(self.table), (prop, shared)) - self.propmap = self.get_propmap() - self.conn.commit() - - def get(self, *args, default=None): - if len(args) != len(self.keys) + 1: - raise Exception("Improper number of keys passed to get.") - prop = self.map_prop(args[-1]) - criteria = " AND ".join("{} = ?" for key in args) - - cursor = self.conn.cursor() - cursor.execute('SELECT value from {} where {}'.format(self.table, criteria).format(*self.keys, 'property'), tuple([*args[:-1], prop])) - value = cursor.fetchone() - return json.loads(value[0]) if (value and value[0]) else default - - def set(self, *args): - if len(args) != len(self.keys) + 2: - raise Exception("Improper number of keys passed to set.") - prop = self.map_prop(args[-2]) - value = json.dumps(args[-1]) - criteria = " AND ".join("{} = ?" for key in args[:-1]) - values = ", ".join("?" for key in args) - - cursor = self.conn.cursor() - cursor.execute('SELECT EXISTS(SELECT 1 from {} where {})'.format(self.table, criteria).format(*self.keys, 'property'), tuple([*args[:-2], prop])) - exists = cursor.fetchone() - - if not exists[0]: - cursor.execute('INSERT INTO {} VALUES ({})'.format(self.table, values), tuple([*args[:-2], prop, value])) - else: - cursor.execute('UPDATE {} SET value = ? WHERE {}'.format(self.table, criteria).format(*self.keys, 'property'), tuple([value, *args[:-2], prop])) - self.conn.commit() - - def find(self, prop, value, read=False): - if len(self.keys) > 1: - raise Exception("This method cannot currently be used when there are multiple keys") - prop = self.map_prop(prop) - if read: - value = json.dumps(value) - - cursor = self.conn.cursor() - cursor.execute('SELECT {} FROM {} WHERE property = ? AND value = ?'.format(self.keys[0], self.table), (prop, value)) - return [value[0] for value in cursor.fetchall()] - - def find_not_empty(self, prop): - if len(self.keys) > 1: - raise Exception("This method cannot currently be used when there are multiple keys") - prop = self.map_prop(prop) - - cursor = self.conn.cursor() - cursor.execute('SELECT {} FROM {} WHERE property = ? AND value IS NOT NULL AND value != \'\''.format(self.keys[0], self.table), (prop,)) - return [value[0] for value in cursor.fetchall()] diff --git a/bot/Timer/Timer.py b/bot/Timer/Timer.py deleted file mode 100644 index a3dce9e..0000000 --- a/bot/Timer/Timer.py +++ /dev/null @@ -1,686 +0,0 @@ -import asyncio -import datetime -import logging -import traceback -import discord -from enum import Enum - -from logger import log - - -class Timer(object): - clock_period = 600 - max_warning = 1 - - def __init__(self, name, role, channel, clock_channel=None, stages=None): - self.channel = channel - self.clock_channel = clock_channel - self.role = role - self.name = name - self._truename = name - - self.start_time = None # Session start time - self.current_stage_start = None # Time at which the current stage started - self.remaining = None # Amount of time until the next stage starts - self.state = TimerState.STOPPED # Current state of the timer - - self.stages = stages # List of stages in this timer - self.current_stage = 0 # Index of current stage - - self.subscribed = {} # Dict of subbed members, userid maps to (user, lastupdate, timesubbed) - - self.timer_messages = [] # List of sent message ids that this timer owns, e.g. for reaction handling - - self.last_clockupdate = 0 - - if stages: - self.setup(stages) - - def __contains__(self, userid): - """ - Containment interface acts as list of subscribers. - """ - return userid in self.subscribed - - def setup(self, stages): - """ - Setup the timer with a list of TimerStages. - """ - self.stop() - - self.stages = stages - self.current_stage = 0 - - now = self.now() - self.start_time = now - - self.remaining = stages[0].duration - self.current_stage_start = now - - # Return self for method chaining - return self - - async def update_clock_channel(self, force=False): - """ - Try to update the name of the status channel with the current status - """ - # Quit if there's no status channel set - if self.clock_channel is None: - return - - # Quit if we aren't due for a clock update yet - if not force and self.now() - self.last_clockupdate < self.clock_period: - return - - # Get the name and time strings - stage_name = self.stages[self.current_stage].name - - # Update the channel name, or quit silently if something goes wrong. - self.last_clockupdate = self.now() - try: - await self.clock_channel.edit(name="{} - {}".format(self.name, stage_name)) - self.last_clockupdate = self.now() - except Exception: - pass - - def pretty_remaining(self, show_seconds=False): - """ - Return a formatted version of the time remaining until the next stage. - """ - return self.parse_dur(self.remaining, show_seconds=show_seconds) - - def pretty_pinstatus(self): - """ - Return a formatted status string for use in the pinned status message. - """ - subbed_names = [m.member.name for m in self.subscribed.values()] - subbed_str = "```{}```".format(", ".join(subbed_names)) if subbed_names else "*No members*" - - if self.state in [TimerState.RUNNING, TimerState.PAUSED]: - # Collect the component strings and data - current_stage_name = self.stages[self.current_stage].name - remaining = self.pretty_remaining() - - # Create a list of lines for the stage string - longest_stage_len = max(len(stage.name) for stage in self.stages) - stage_format = "`{{prefix}}{{name:>{}}}:` {{dur}} min {{current}}".format(longest_stage_len) - - stage_str_lines = [ - stage_format.format( - prefix="->" if i == self.current_stage else "​ ", - name=stage.name, - dur=stage.duration, - current="(**{}**)".format(remaining) if i == self.current_stage else "" - ) for i, stage in enumerate(self.stages) - ] - # Create the stage string itself - stage_str = "\n".join(stage_str_lines) - - # Create the final formatted status string - status_str = ("**{name}**: {current_stage_name} {paused}\n" - "{stage_str}\n" - "{subbed_str}").format(name=self.name, - role=self.role.mention, - paused=" ***Paused***" if self.state == TimerState.PAUSED else "", - current_stage_name=current_stage_name, - stage_str=stage_str, - subbed_str=subbed_str) - elif self.state == TimerState.STOPPED: - status_str = "**{}**: *Timer not running.*\n{}".format(self.name, subbed_str) - return status_str - - def pretty_summary(self): - """ - Return a short summary status message. - """ - if self.stages: - stage_str = "/".join(("**{}**".format(stage.duration) if i == self.current_stage else str(stage.duration)) - for i, stage in enumerate(self.stages)) - else: - stage_str = "*Not set up.*" - - if self.state == TimerState.RUNNING: - status_str = "Stage `{}`, `{}` remaining\n".format(self.stages[self.current_stage].name, - self.pretty_remaining()) - elif self.state == TimerState.PAUSED: - status_str = "*Timer is paused.*\n" - elif self.state == TimerState.STOPPED: - status_str = "" - - if self.subscribed: - member_str = "Members: " + ", ".join(s.member.mention for s in self.subscribed.values()) - else: - member_str = "*No members.*" - - return "{} ({}): {}\n{}{}".format( - self.role.mention, - self.name, - stage_str, - status_str, - member_str - ) - - def oneline_summary(self): - """ - Return a one line summary status message - """ - if self.state == TimerState.RUNNING: - status = "Running" - elif self.state == TimerState.PAUSED: - status = "Paused" - elif self.state == TimerState.STOPPED: - status = "Stopped" - - if self.stages: - stage_str = "/".join(str(stage.duration) for i, stage in enumerate(self.stages)) - else: - stage_str = "not set up" - - return "{name} ({status} with {members} members, {setup}.)".format( - name=self.name, - status=status, - members=len(self.subscribed) if self.subscribed else 'no', - setup=stage_str - ) - - async def change_stage(self, stage_index, notify=True, inactivity_check=True, report_old=True): - """ - Advance the timer to the new stage. - """ - stage_index = stage_index % len(self.stages) - current_stage = self.stages[self.current_stage] - new_stage = self.stages[stage_index] - - self.current_stage = stage_index - self.current_stage_start = self.now() - self.remaining = self.stages[stage_index].duration * 60 - - # Update clocked times for all the subbed users and handle inactivity - needs_warning = [] - unsubs = [] - for subber in self.subscribed.values(): - subber.touch() - if inactivity_check: - if subber.warnings >= self.max_warning: - subber.warnings += 1 - unsubs.append(subber) - elif (self.now() - subber.last_seen) > current_stage.duration * 60: - subber.warnings += 1 - if subber.warnings >= self.max_warning: - needs_warning.append(subber) - - # Handle not having any subscribers - empty = (len(self.subscribed) == 0) - - # Handle notifications - if notify: - old_stage_str = "**{}** finished! ".format(current_stage.name) if report_old else "" - if needs_warning: - warning_str = ("{} you will be unsubscribed on the next stage " - "if you do not reply or react to this message.\n").format( - ", ".join(subber.member.mention for subber in needs_warning) - ) - else: - warning_str = "" - if unsubs: - unsub_str = "{} you have been unsubscribed due to inactivity!\n".format( - ", ".join(subber.member.mention for subber in unsubs) - ) - else: - unsub_str = "" - - main_line = "{}Starting **{}** ({} minutes). {}".format( - old_stage_str, - new_stage.name, - new_stage.duration, - new_stage.message - ) - - if not empty: - out_msg = await self.channel.send( - ("{}\n{}\n" - "Please reply or react to this message to register your existence.\n{}{}").format( - self.role.mention, - main_line, - warning_str, - unsub_str - ) - ) - try: - await out_msg.add_reaction("✅") - except Exception: - pass - - # Add the stage message to the owned message list - self.timer_messages.append(out_msg.id) - self.timer_messages = self.timer_messages[-5:] # Truncate - else: - """ - await self.channel.send( - ("{}\n " - "{}No subscribers, stopping group timer.").format( - self.role.mention, - old_stage_str - ) - ) - self.stop() - """ - pass - - # Notify the subscribers as desired - for subber in self.subscribed.values(): - try: - out_msg = None - if subber in unsubs and subber.notify >= NotifyLevel.FINAL: - await subber.member.send( - "You have been unsubscribed from group **{}** in {} due to inactivity!".format( - self.name, - self.channel.mention - ) - ) - elif subber in needs_warning and subber.notify >= NotifyLevel.WARNING: - out_msg = await subber.member.send( - ("**Warning** from group **{}** in {}!\n" - "Please respond or react to a timer message " - "to avoid being unsubscribed on the next stage.\n{}").format( - self.name, - self.channel.mention, - main_line - ) - ) - elif subber.notify >= NotifyLevel.ALL: - out_msg = await subber.member.send( - "Status update for group **{}** in {}!\n{}".format(self.name, - self.channel.mention, - main_line) - ) - except discord.Forbidden: - pass - except discord.HTTPException: - pass - - for subber in unsubs: - await subber.unsub() - - async def start(self): - """ - Start or restart the timer. - """ - await self.change_stage(0, report_old=False) - self.state = TimerState.RUNNING - for subber in self.subscribed.values(): - subber.touch() - subber.active = True - - asyncio.ensure_future(self.runloop()) - - def stop(self): - """ - Stop the timer, and ensure the subscriber clocked times are updated. - """ - for subber in self.subscribed.values(): - subber.touch() - subber.active = False - - self.state = TimerState.STOPPED - - async def runloop(self): - while self.state == TimerState.RUNNING: - self.remaining = int(60*self.stages[self.current_stage].duration - (self.now() - self.current_stage_start)) - if self.remaining <= 0: - try: - await self.change_stage(self.current_stage + 1) - asyncio.ensure_future(self.update_clock_channel(force=True)) - except Exception: - full_traceback = traceback.format_exc() - log("Exception encountered while changing stage.\n{}".format(full_traceback), - context="TIMER_RUNLOOP", - level=logging.ERROR) - - # Disable clock update since the channel update ratelimit is too slow - # asyncio.ensure_future(self.update_clock_channel()) - await asyncio.sleep(1) - - @staticmethod - def now(): - """ - Helper to get the current UTC timestamp as an integer. - """ - return int(datetime.datetime.timestamp(datetime.datetime.utcnow())) - - @staticmethod - def parse_dur(diff, show_seconds=False): - """ - Parse a duration given in seconds to a time string. - """ - diff = max(diff, 0) - if show_seconds: - diff = int(60 * round(diff / 60)) - hours = diff // 3600 - minutes = (diff % 3600) // 60 - return "{:02d}:{:02d}".format(hours, minutes) - else: - hours = diff // 3600 - minutes = (diff % 3600) // 60 - seconds = diff % 60 - return "{:02d}:{:02d}:{:02d}".format(hours, minutes, seconds) - - def serialise(self): - """ - Serialise current timer status to a dictionary. - Does not serialise subscribers or fixed attributes such as channels. - """ - return { - 'roleid': self.role.id, - 'name': self.name, - 'start_time': self.start_time, - 'current_stage_start': self.current_stage_start, - 'remaining': self.remaining, - 'state': self.state.value, - 'stages': [stage.serialise() for stage in self.stages] if self.stages else None, - 'current_stage': self.current_stage, - 'messages': self.timer_messages, - } - - def update_from_data(self, data): - """ - Restore timer status from the provided status dict, as produced by `serialise`. - """ - self.name = data['name'] - self.start_time = data['start_time'] - self.current_stage_start = data['current_stage_start'] - self.remaining = data['remaining'] - self.state = TimerState(data['state']) - self.stages = [ - TimerStage.deserialise(stage_data) for stage_data in data['stages'] - ] if data['stages'] else None - self.current_stage = data.get('current_stage', 0) - self.timer_messages = data.get('messages', []) - - asyncio.ensure_future(self.runloop()) - return self - - -class TimerState(Enum): - """ - Enum representing the current running state of the timer. - STOPPED: The timer either hasn't been set up, or has been stopped externally. - RUNNING: The timer is running normally. - PAUSED: The timer has been paused by a user. - """ - STOPPED = 1 - RUNNING = 2 - PAUSED = 3 - - -class TimerStage(object): - """ - Small data class to encapsualate a "stage" of a timer. - - Parameters - ---------- - name: str - The human readable name of the stage. - duration: int - The number of minutes the stage lasts for. - message: str - An optional message to send when starting this stage. - focus: bool - Whether `focus` mode is set for this stage. - modifiers: Dict(str, bool) - An unspecified collection of stage modifiers, stored for external use. - """ - __slots__ = ('name', 'message', 'duration', 'focus', 'modifiers') - - def __init__(self, name, duration, message="", focus=False, **modifiers): - self.name = name - self.duration = duration - self.message = message - - self.focus = focus - - self.modifiers = modifiers - - def serialise(self): - """ - Serialise stage to a serialisable dictionary. - """ - return { - 'name': self.name, - 'duration': self.duration, - 'message': self.message, - 'focus': self.focus, - 'modifiers': self.modifiers - } - - @classmethod - def deserialise(cls, data_dict): - """ - Deserialise stage from a dictionary formatted like the output of `serialise. - """ - return cls( - data_dict['name'], - data_dict['duration'], - message=data_dict['message'], - focus=data_dict['focus'], - **data_dict['modifiers'] - ) - - -class TimerChannel(object): - """ - A data class representing a guild channel bound to (potentially) several timers. - - Parameters - ---------- - channel: discord.Channel - The bound discord guild channel - timers: List(Timer) - The timers bound to the channel - msg: discord.Message - A valid and current discord Message in the channel. - Holds the updating timer status messages. - """ - __slots__ = ('channel', 'timers', 'msg', 'old_desc') - - def __init__(self, channel): - self.channel = channel - - self.timers = [] - self.msg = None - - self.old_desc = "" - - async def update(self): - """ - Create or update the channel status message. - """ - messages = [timer.pretty_pinstatus() for timer in self.timers] - if messages: - desc = "\n\n".join(messages) - - # Don't resend the same message - if desc == self.old_desc: - return - self.old_desc = desc - - embed = discord.Embed( - title="Pomodoro Timer Status", - description=desc, - timestamp=datetime.datetime.utcnow() - ) - if self.msg is not None: - try: - await self.msg.edit(embed=embed) - except discord.NotFound: - self.msg = None - except discord.Forbidden: - pass - except Exception: - pass - - """ - if all(timer.state == TimerState.STOPPED for timer in self.timers): - # Unpin and unset message - try: - await self.msg.unpin() - except Exception: - pass - - self.msg = None - """ - elif any(timer.state != TimerState.STOPPED for timer in self.timers): - # Attempt to generate a new message - try: - # Send a new message - self.msg = await self.channel.send(embed=embed) - - # Pin the message - try: - await self.msg.pin() - except Exception: - pass - except discord.Forbidden: - try: - await self.channel.send( - "I require permission to send embeds in this channel! " - "Stopping all timers." - ) - except Exception: - # There's no point trying to handle this if we can't send anything, just quietly unload - pass - - for timer in self.timers: - timer.stop() - except discord.NotFound: - # The channel doesn't even exist anymore! Stop all timers so we don't try to post anymore. - # TODO: Handle garbage collection, cautiously because this might be an outage - for timer in self.timers: - timer.stop() - - -class NotifyLevel(Enum): - """ - Enum representing a subscriber's notification level. - NONE: Never send direct messages. - FINAL: Send a direct message when kicking for inactivity. - WARNING: Send direct messages for unsubscription warnings. - ALL: Send direct messages for all stage updates. - """ - NONE = 1 - FINAL = 2 - WARNING = 3 - ALL = 4 - - def __ge__(self, other): - if self.__class__ is other.__class__: - return self.value >= other.value - return NotImplemented - - def __gt__(self, other): - if self.__class__ is other.__class__: - return self.value > other.value - return NotImplemented - - def __le__(self, other): - if self.__class__ is other.__class__: - return self.value <= other.value - return NotImplemented - - def __lt__(self, other): - if self.__class__ is other.__class__: - return self.value < other.value - return NotImplemented - - -class TimerSubscriber(object): - __slots__ = ( - 'member', - 'timer', - 'interface', - 'notify', - 'client', - 'id', - 'time_joined', - 'last_updated', - 'clocked_time', - 'active', - 'last_seen', - 'warnings' - ) - - def __init__(self, member, timer, interface, notify=NotifyLevel.WARNING): - self.member = member - self.timer = timer - self.interface = interface - self.notify = notify - - self.client = interface.client - self.id = member.id - - now = Timer.now() - self.time_joined = now - - self.last_updated = now - self.clocked_time = 0 - self.active = (timer.state == TimerState.RUNNING) - - self.last_seen = now - self.warnings = 0 - - async def unsub(self): - return await self.interface.unsub(self.member.guild.id, self.id) - - def bump(self): - self.last_seen = Timer.now() - self.warnings = 0 - - def touch(self): - """ - Update the clocked time based on the active status. - """ - now = Timer.now() - self.clocked_time += (now - self.last_updated) if self.active else 0 - self.last_updated = now - - def session_data(self): - """ - Return session data in a format compatible with the registry. - """ - self.touch() - - return ( - self.id, - self.member.guild.id, - self.timer.role.id, - self.time_joined, - self.clocked_time - ) - - def serialise(self): - return { - 'id': self.id, - 'guildid': self.member.guild.id, - 'roleid': self.timer.role.id, - 'notify': self.notify.value, - 'time_joined': self.time_joined, - 'last_updated': self.last_updated, - 'clocked_time': self.clocked_time, - 'active': self.active, - 'last_seen': self.last_seen, - 'warnings': self.warnings - } - - @classmethod - def deserialise(cls, member, timer, interface, data): - self = cls(member, timer, interface) - - self.time_joined = data['time_joined'] - self.last_updated = data['last_updated'] - self.clocked_time = data['clocked_time'] - self.active = data['active'] - self.notify = NotifyLevel(data['notify']) - self.last_seen = data['last_seen'] - self.warnings = data['warnings'] - - return self diff --git a/bot/Timer/__init__.py b/bot/Timer/__init__.py index 939d6d6..5d9d178 100644 --- a/bot/Timer/__init__.py +++ b/bot/Timer/__init__.py @@ -1,2 +1,217 @@ -from .interface import TimerInterface -from .Timer import Timer, TimerChannel, TimerSubscriber, TimerState, TimerStage, NotifyLevel +import os +import shutil +import logging +import asyncio +import pickle + +import discord +from cmdClient import Module +from cmdClient.Context import Context + +from meta import log +from data import tables + +from .lib import TimerState, NotifyLevel, InvalidPattern # noqa +from .core import Timer, TimerSubscriber, TimerChannel, Pattern # noqa +from . import activity_events +from . import timer_reactions +from . import voice_events +from . import guild_events + + +class TimerInterface(Module): + name = "TimerInterface" + save_dir = "data/timerstatus/" + save_fn = "timerstatus.pickle" + + def __init__(self, **kwargs): + super().__init__(**kwargs) + + self.guild_channels = {} # guildid -> {channelid -> TimerChannel} + + self.init_task(self.core_init) + self.launch_task(self.core_launch) + + def core_init(self, _client): + _client.interface = self + Context.timers = module + _client.add_after_event('message', activity_events.message_tracker) + _client.add_after_event('raw_reaction_add', activity_events.reaction_tracker) + + _client.add_after_event('raw_reaction_add', timer_reactions.joinleave_tracker) + _client.add_after_event('voice_state_update', voice_events.vc_update_handler) + + _client.add_after_event('guild_join', guild_events.on_guild_join) + _client.add_after_event('guild_remove', guild_events.on_guild_remove) + + async def core_launch(self, _client): + await self.load_timers() + self.restore_from_save() + asyncio.create_task(self._runloop()) + asyncio.create_task(self._saveloop()) + + async def _runloop(self): + while True: + channel_keys = [ + (guildid, channelid) + for guildid, channels in self.guild_channels.items() + for channelid, channel in channels.items() + if any(timer.state == TimerState.RUNNING for timer in channel.timers) + ] + channel_count = len(channel_keys) + if channel_count == 0: + await asyncio.sleep(30) + continue + + delay = max(0.1, 30/channel_count) + + channels = ( + self.guild_channels[gid][cid] + for gid, cid in channel_keys + if gid in self.guild_channels and cid in self.guild_channels[gid] + ) + for channel in channels: + try: + await channel.update_pin() + except Exception: + log("Exception encountered updating channel pin for {!r}".format(channel.channel), + context="TIMER_RUNLOOP", + level=logging.ERROR, + add_exc_info=True) + await asyncio.sleep(delay) + + async def _saveloop(self): + while True: + await asyncio.sleep(60) + self.update_save() + + def update_save(self, reason=None): + # TODO: Move save file location to config? For e.g. sharding + log("Writing session savefile.", context="TIMER_SAVE", level=logging.DEBUG) + save_data = { + guildid: [tchannel.serialise() for tchannel in tchannels.values()] + for guildid, tchannels in self.guild_channels.items() + } + path = os.path.join(self.save_dir, self.save_fn) + # Rotate + if os.path.exists(path): + os.rename(path, path + '.old') + + with open(path, 'wb') as f: + pickle.dump(save_data, f, pickle.HIGHEST_PROTOCOL) + + if reason: + shutil.copy2(path, path + '.' + reason) + + def restore_from_save(self): + log("------------------------Beginning session restore.", context="TIMER_RESTORE") + path = os.path.join(self.save_dir, self.save_fn) + if os.path.exists(path): + with open(path, 'rb') as f: + save_data = pickle.load(f) + + for guildid, data_channels in save_data.items(): + log("Restoring Guild (gid:{}).".format(guildid), context='TIMER_RESTORE') + tchannels = self.guild_channels.get(guildid, None) + if tchannels: + [tchannels[data['channelid']].restore_from(data) + for data in data_channels if data['channelid'] in tchannels] + log("------------------------Session restore complete.", context="TIMER_RESTORE") + + async def load_timers(self): + # Populate the pattern cache with the latest patterns + tables.patterns.fetch_rows_where(_extra="INNER JOIN 'current_timer_patterns' USING (patternid)") + + # Build and load all the timers, preserving the existing ones + timer_rows = tables.timers.fetch_rows_where() + timers = [Timer(row) for row in timer_rows] + timers = [timer for timer in timers if timer.load()] + + # Create the TimerChannels + guild_channels = {} + for timer in timers: + channels = guild_channels.get(timer.channel.guild.id, None) + if channels is None: + channels = guild_channels[timer.channel.guild.id] = {} + channel = channels.get(timer.channel.id, None) + if channel is None: + channel = channels[timer.channel.id] = TimerChannel(timer.channel) + channel.timers.append(timer) + + self.guild_channels = guild_channels + + def create_timer(self, role, channel, name, **kwargs): + guild = role.guild + new_timer = Timer.create(role.id, guild.id, name, channel.id, **kwargs) + if not new_timer.load(): + return None + + tchannels = self.guild_channels.get(guild.id, None) + if tchannels is None: + tchannels = self.guild_channels[guild.id] = {} + tchannel = tchannels.get(channel.id, None) + if tchannel is None: + tchannel = tchannels[channel.id] = TimerChannel(channel) + tchannel.timers.append(new_timer) + asyncio.create_task(tchannel.update_pin(force=True)) + + return new_timer + + async def obliterate_timer(self, timer): + # Remove the timer from its channel + channel = self.guild_channels[timer.channel.guild.id][timer.channel.id] + channel.timers.remove(timer) + if not channel.timers: + self.guild_channels[timer.channel.guild.id].pop(timer.channel.id) + + # Destroy the timer, unsubscribing members and deleting it from data + await timer.destroy() + + # Refresh the pinned message + await channel.update_pin(force=True) + + def fetch_timer(self, roleid): + row = tables.timers.fetch(roleid) + if row: + channels = self.guild_channels.get(row.guildid, None) + if channels: + channel = channels.get(row.channelid, None) + if channel: + return next((timer for timer in channel.timers if timer.roleid == roleid), None) + + def get_timers_in(self, guildid, channelid=None): + timers = [] + channels = self.guild_channels.get(guildid, None) + if channels is not None: + if channelid is None: + timers = [timer for channel in channels.values() for timer in channel.timers] + elif channelid in channels: + timers = channels[channelid].timers + + return timers + + def get_subscriber(self, userid, guildid): + return next( + (timer.subscribers[userid] + for channel in self.guild_channels.get(guildid, {}).values() + for timer in channel.timers + if userid in timer.subscribers), + None + ) + + async def on_exception(self, ctx, exception): + if isinstance(exception, InvalidPattern): + await ctx.reply( + embed=discord.Embed( + description=( + "{}\n\n" + "See `{}help patterns` for more information about timer patterns." + ).format(exception.msg, ctx.best_prefix), + colour=discord.Colour.red() + ) + ) + else: + await super().on_exception(ctx, exception) + + +module = TimerInterface() diff --git a/bot/Timer/activity_events.py b/bot/Timer/activity_events.py new file mode 100644 index 0000000..6173a94 --- /dev/null +++ b/bot/Timer/activity_events.py @@ -0,0 +1,14 @@ +async def message_tracker(client, message): + if message.guild: + sub = client.interface.get_subscriber(message.author.id, message.guild.id) + if sub and sub.timer.channel == message.channel: + sub.touch() + if not sub.member: + sub.set_member(message.author) + + +async def reaction_tracker(client, payload): + if payload.guild_id: + sub = client.interface.get_subscriber(payload.user_id, payload.guild_id) + if sub and sub.timer.channel.id == payload.channel_id: + sub.touch() diff --git a/bot/Timer/core.py b/bot/Timer/core.py new file mode 100644 index 0000000..45768f8 --- /dev/null +++ b/bot/Timer/core.py @@ -0,0 +1,1181 @@ +import json +import asyncio +import logging +import datetime +from collections import namedtuple + +import cachetools +import discord + +from meta import client, log +from data import tables +from settings import TimerSettings, GuildSettings + +from .voice_notify import play_alert +from .lib import join_emoji, leave_emoji, now, parse_dur, best_prefix, TimerState, NotifyLevel, InvalidPattern + + +# NamedTuple represeting a pattern stage +Stage = namedtuple('Stage', ('name', 'duration', 'message', 'focus')) + + +class Pattern: + _slots = ('row', 'stages') + + _cache = cachetools.LFUCache(1000) + _table = tables.patterns + + default_work_stage = "Work" + default_work_message = "Good luck!" + default_break_stage = "Break" + default_break_message = "Have a rest!" + + def __init__(self, row, stages=None): + self.row = row + self.stages = stages or [Stage(*stage) for stage in json.loads(row.stage_str)] + self._cache[(self.__class__, self.row.patternid)] = self + + def __iter__(self): + return iter(self.stages) + + def __str__(self): + return self.display() + + def display(self, brief=None, truncate=None): + brief = brief if brief is not None else self.row.short_repr + if brief: + if truncate and len(self.stages) > truncate: + return "/".join(str(stage.duration) for stage in self.stages[:truncate]) + '/...' + else: + return "/".join(str(stage.duration) for stage in self.stages) + else: + return ";\n".join( + "{0.name}, {0.duration}{1}, {0.message}".format(stage, '*' * stage.focus) + for stage in self.stages + ) + + @classmethod + def from_userstr(cls, string, timerid=None, userid=None, guildid=None): + """ + Parse a user-provided string into a `Pattern`, if possible. + Raises `InvalidPattern` for parsing errors. + Where possible, an existing `Pattern` will be returned, + otherwise a new `Pattern` will be created. + + Accepts kwargs to describe the parsing context. + """ + if not string: + raise InvalidPattern("No pattern provided!") + + # Parsing step + pattern = None + stages = None + if ';' in string or ',' in string: + # Long form + # Accepts stages as 'name, length' or 'name, length, message' + short_repr = False + stage_blocks = string.strip(';').split(';') + stages = [] + for block in stage_blocks: + # Extract stage components + parts = block.split(',', maxsplit=2) + if len(parts) == 1: + raise InvalidPattern( + "`{}` is not of the form `name, length` or `name, length, message`.".format(block) + ) + elif len(parts) == 2: + name, dur = parts + message = None + else: + name, dur, message = parts + + # Parse duration + dur = dur.strip() + focus = dur.startswith('*') or dur.endswith('*') + if focus: + dur = dur.strip('* ') + + if not dur.isdigit(): + raise InvalidPattern( + "`{}` in `{}` couldn't be parsed as a duration.".format(dur, block.strip()) + ) + + # Build and add stage + stages.append(Stage(name.strip(), int(dur), (message or '').strip(), focus)) + elif '/' in string: + # Short form + # Only accepts numerical stages + short_repr = True + stage_blocks = string.strip('/').split('/') + stages = [] + + is_work = True # Whether the current stage is a work or break stage + default_focus = '*' not in string # Whether to use default focus flags + for block in stage_blocks: + # Parse duration + dur = block.strip() + focus = dur.startswith('*') or dur.endswith('*') + if focus: + dur = dur.strip('* ') + + if not dur.isdigit(): + raise InvalidPattern( + "`{}` couldn't be parsed as a duration.".format(dur) + ) + + # Build and add stage + if is_work: + stages.append(Stage( + cls.default_work_stage, + int(dur), + cls.default_work_message, + focus=True if default_focus else focus + )) + else: + stages.append(Stage( + cls.default_break_stage, + int(dur), + cls.default_break_message, + focus=False if default_focus else focus + )) + + is_work = not is_work + else: + # Attempt to find a matching preset + if userid: + row = tables.user_presets.select_one_where(userid=userid, preset_name=string) + if row: + pattern = cls.get(row['patternid']) + + if not pattern and guildid: + row = tables.guild_presets.select_one_where(guildid=guildid, preset_name=string) + if row: + pattern = cls.get(row['patternid']) + + if not pattern: + raise InvalidPattern("Patterns must have more than one stage!") + + if stages: + # Create the stage string + stage_str = json.dumps(stages) + + # Fetch or create the pattern row + row = cls._table.fetch_or_create( + short_repr=short_repr, + stage_str=stage_str + ) + + # Initialise and return the pattern + if row.patternid in cls._cache: + pattern = cls._cache[row.patternid] + else: + pattern = cls(row, stages=stages) + + return pattern + + @classmethod + @cachetools.cached(_cache) + def get(cls, patternid): + return cls(cls._table.fetch(patternid)) + + +class Timer: + __slots__ = ( + 'data', + 'settings', + 'state', + 'current_pattern', + 'stage_index', + 'stage_start', + '_loop_wait_task', + 'subscribers', + 'message_ids', + 'guild', + 'role', + 'channel', + 'voice_channel', + 'last_voice_update' + ) + + _table = tables.timers + + max_warnings = 1 + + def __init__(self, data): + self.data = data + self.settings = TimerSettings(data.roleid, timer=self) + + self.state: TimerState = TimerState.UNSET # State of the timer + self.current_pattern: Pattern = None # Current pattern set up + self.stage_index: int = None # Index of the current stage in the pattern + self.stage_start: int = None # Timestamp of the start of the stage + + self._loop_wait_task = None # Task used to trigger runloop read + + self.subscribers = {} # TimerSubscribers in the timer + self.message_ids = [] # Notification messages owned by the timer + + self.last_voice_update = 0 # Timestamp of last vc update + + # Discord objects, intialised in `Timer.load()` + self.guild: discord.Guild = None + self.role: discord.Role = None + self.channel: discord.TextChannel = None + self.voice_channel: discord.VoiceChannel = None + + def __getattr__(self, key): + # TODO: Dangerous due to potential property attribute errors + if key in self.data.table.columns: + return getattr(self.data, key) + else: + raise AttributeError(key) + + def __contains__(self, userid): + return userid in self.subscribers + + @property + def default_pattern(self) -> Pattern: + return Pattern.get(self.data.patternid) + + @property + def current_stage(self): + return self.current_pattern.stages[self.stage_index] + + @property + def remaining(self): + """ + The remaining time (in seconds) in the current stage. + """ + return int(60*self.current_stage.duration - (now() - self.stage_start)) + + @property + def pretty_remaining(self): + return parse_dur( + self.remaining, + show_seconds=True + ) if self.state == TimerState.RUNNING else '*Not Running*' + + @property + def pinstatus(self): + """ + Return a formatted status string for use in the pinned status message. + """ + return self.status_string() + + @property + def voice_channel_name(self): + return self.settings.vc_name.value.replace( + "{stage_name}", self.current_stage.name + ).replace( + "{remaining}", parse_dur( + int(60*self.current_stage.duration - (now() - self.stage_start)), + show_seconds=False + ) + ).replace( + "{name}", self.data.name + ).replace( + "{stage_dur}", parse_dur(self.current_stage.duration * 60, show_seconds=False) + ).replace( + "{sub_count}", str(len(self.subscribers)) + ).replace( + "{pattern}", (self.current_pattern or self.default_pattern).display(brief=True, truncate=6) + ) + + @property + def oneline_summary(self): + """ + Return a one line summary status message + """ + if self.state == TimerState.RUNNING: + status = "Running" + elif self.state == TimerState.PAUSED: + status = "Paused" + elif self.state in (TimerState.STOPPED, TimerState.UNSET): + status = "Stopped" + + return "{name} ({status} with {members} members, {setup}.)".format( + name=self.data.name, + status=status, + members=len(self.subscribers) if self.subscribers else 'no', + setup=(self.current_pattern or self.default_pattern).display(brief=True) + ) + + @property + def pretty_summary(self): + pattern = self.current_pattern or self.default_pattern + stage_str = "/".join( + "{1}{0}{1}".format(stage.duration, (i == self.stage_index) * '**') + for i, stage in enumerate(pattern.stages) + ) + + if self.state == TimerState.RUNNING: + status_str = "Stage `{}`, `{}` remaining\n".format(self.current_stage.name, self.pretty_remaining) + elif self.state == TimerState.PAUSED: + status_str = "*Timer is paused.*\n" + else: + status_str = '' + + if self.subscribers: + member_str = "Members: " + ", ".join("<@{}>".format(uid) for uid in self.subscribers) + else: + member_str = "*No members.*" + + return "{}{}: {}\n{}{}".format( + self.role.mention, + "({})".format(self.data.name) if self.data.name != self.role.name else '', + stage_str, + status_str, + member_str + ) + + def status_string(self, show_seconds=False): + subbed_names = [m.name for m in self.subscribers.values()] + subbed_str = "```{}```".format(", ".join(subbed_names)) if subbed_names else "*No members*" + + if self.state in (TimerState.RUNNING, TimerState.PAUSED, TimerState.STOPPED): + running = self.state in (TimerState.RUNNING, TimerState.PAUSED) + + # Collect the component strings and data + pretty_remaining = parse_dur( + int(60*self.current_stage.duration - (now() - self.stage_start)), + show_seconds=show_seconds + ) if running else '' + + # Create the stage string + longest_stage_len = max(len(stage.name) for stage in self.current_pattern.stages) + stage_format = "`{{prefix}}{{name:>{}}}:` {{dur}} min {{current}}".format(longest_stage_len) + + stage_str = '\n'.join( + stage_format.format( + prefix="->" if running and i == self.stage_index else "​ ", + name=stage.name, + dur=stage.duration, + current="(**{}**)".format(pretty_remaining) if running and i == self.stage_index else '' + ) for i, stage in enumerate(self.current_pattern.stages) + ) + + # Create the final formatted status string + status_str = ("**{name}**: {stage} {paused}\n" + "{stage_str}\n" + "{subbed_str}").format(name=self.data.name, + paused=" ***Paused***" if self.state == TimerState.PAUSED else "", + stage=self.current_stage.name if running else "*Timer not running.*", + stage_str=stage_str, + subbed_str=subbed_str) + else: + status_str = "**{}**: *Timer not set up.*\n{}".format(self.data.name, subbed_str) + + return status_str + + @classmethod + def create(cls, roleid, guildid, name, channelid, **kwargs): + log("Creating Timer with (roleid={!r}, guildid={!r}, name={!r}, channelid={!r})".format(roleid, + guildid, + name, + channelid), + context="rid:{}".format(roleid)) + + # Remove any existing timers under the same roleid + cls._table.delete_where(roleid=roleid) + + # Create new timer + data = cls._table.create_row(roleid=roleid, + guildid=guildid, + name=name, + channelid=channelid, + **kwargs) + + # Instantiate and return + return cls(data) + + async def destroy(self): + log("Destroying Timer with data {!r}".format(self.data), context="rid:{}".format(self.data.roleid)) + + # Stop the timer and unsubscribe all members + self.stop() + for subid in list(self.subscribers.keys()): + await self.unsubscribe(subid) + + # Remove the timer from data + self._table.delete_where(roleid=self.data.roleid) + + def load(self): + """ + Load discord objects from data. + + Returns + ------- + `True` if the timer successfully loaded. + `False` if the guild, channel, or role no longer exist. + """ + data = self.data + + self.guild = client.get_guild(data.guildid) + if not self.guild: + log("Timer gone, guild (gid: {}) no longer exists.".format(data.guildid), + "tid:{}".format(data.roleid)) + return False + + self.role = self.guild.get_role(data.roleid) + if not self.role: + log("Timer gone, role no longer exists.", + "tid:{}".format(data.roleid)) + return False + + self.channel = self.guild.get_channel(data.channelid) + if not self.channel: + log("Timer gone, channel (cid: {}) no longer exists.".format(data.channelid), + "tid:{}".format(data.roleid)) + return False + + if data.voice_channelid: + self.voice_channel = self.guild.get_channel(data.voice_channelid) + + return True + + async def post(self, *args, **kwargs): + """ + Safely send a message to the timer channel. + If an error occurs, in most cases ignore it. + As such, is not guaranteed to yield a `discord.Message`. + """ + # TODO: Reconsider if we want some form of cleanup here + try: + return await self.channel.send(*args, **kwargs) + except discord.Forbidden: + # We are not allowed to send to the timer channel + # Stop the timer + await self.stop() + except discord.HTTPException: + # An unknown discord error occured + # Silently continue + pass + + async def setup(self, pattern=None, actor=None): + """ + Setup the timer with the given timer pattern. + If no pattern is given, uses the default pattern. + """ + pattern = pattern or self.default_pattern + + log("Setting up timer with pattern {!r}.".format(pattern.row), context="rid:{}".format(self.data.roleid)) + + # Ensure timer is stopped + self.stop() + + # Update runtime data for new pattern + self.current_pattern = pattern + self.stage_index = 0 + + tables.timer_pattern_history.insert( + timerid=self.data.roleid, + patternid=pattern.row.patternid, + modified_by=actor + ) + + async def start(self): + """ + Start the timer with the current pattern, or the default pattern. + """ + log("Starting timer.", context="rid:{}".format(self.data.roleid)) + if not self.current_pattern: + await self.setup() + + await self.change_stage(0, inactivity_check=False, finished_old=False) + self.state = TimerState.RUNNING + for subber in self.subscribers.values(): + subber.new_session() + + asyncio.create_task(self.runloop()) + + def stop(self): + """ + Stop the timer. + """ + if not self.state == TimerState.STOPPED: + log("Stopping timer.", context="rid:{}".format(self.data.roleid)) + # Trigger session save on all subscribers + for subber in self.subscribers.values(): + subber.close_session() + + # Change status to stopped + self.state = TimerState.STOPPED + + # Cancel loop wait task + if self._loop_wait_task and not self._loop_wait_task.done(): + self._loop_wait_task.cancel() + + def shift(self, amount=None): + """ + Shift the running timer forwards or backwards by the provided amount. + If `amount` is not given, aligns the start of the session to the nearest (UTC) hour. + + `amount` is the amount (in seconds) the stage start is shifted *forwards*. + This effectively adds `amount` to the stage duration, since it will change `amount` seconds later. + """ + if amount is None: + # Get the difference to the nearest hour + started = datetime.datetime.utcfromtimestamp(self.stage_start) + amount = started.minute * 60 + started.second + if amount > 1800: + amount = 3600 - amount + else: + amount = -1 * amount + + # Find the target stage and new stage start + remaining_amount = -1 * amount + i = self.stage_index + is_first = True + while True: + stage = self.current_pattern.stages[i] + stage_remaining = self.remaining if is_first else stage.duration * 60 + if remaining_amount >= stage_remaining: + is_first = False + remaining_amount -= stage_remaining + i = (i + 1) % len(self.current_pattern.stages) + else: + break + target_stage = i + if is_first: + new_stage_start = self.stage_start - remaining_amount + shifts = [(self.stage_index, -remaining_amount)] + else: + new_stage_start = now() - remaining_amount + shifts = [ + (self.stage_index, now() - self.stage_start), + (target_stage, -1 * remaining_amount) + ] + + # Apply shifts + for subber in self.subscribers.values(): + for shift in shifts: + subber.stage_shift(*shift) + + # Update timer + self.stage_index = target_stage + self.stage_start = new_stage_start + + # Cancel loop wait task to rerun runloop + if self._loop_wait_task and not self._loop_wait_task.done(): + self._loop_wait_task.cancel() + + async def change_stage(self, stage_index, post=True, inactivity_check=True, finished_old=True): + """ + Change the timer stage to the given index in the current pattern. + + Parameters + ---------- + stage_index: int + Index to move to in the current pattern. + Will be modded by the lenth of the pattern. + post: bool + Whether to post a stage change message in the linked text channel. + """ + log( + "Changing stage from {} to {}. (post={}, inactivity_check={}, finished_old={})".format( + self.stage_index, + stage_index, + post, + inactivity_check, + finished_old + ), context="rid:{}".format(self.data.roleid), level=logging.DEBUG + ) + + # If the stage change is triggered by finishing a stage, adjust current time to match + if finished_old: + _now = self.stage_start + self.current_stage.duration * 60 + if not -3600 < _now - now() < 3600: + # Don't voice notify if there is a significant real time difference + post = False + else: + _now = now() + + # Update stage info and save the current and new stages + old_stage = self.current_stage + old_index = self.stage_index + + self.stage_index = stage_index % len(self.current_pattern.stages) + self.stage_start = _now + new_stage = self.current_stage + + # Update the voice channel + asyncio.create_task(self.update_voice()) + + if len(self.subscribers) == 0: + # Skip notification and subscriber checks + # Handle empty reset, if enabled + if self.settings.auto_reset.value: + await self.setup() + return + + # Update subscriber sessions + if finished_old: + for sub in self.subscribers.values(): + sub.stage_finished(old_index) + + # Track subscriber inactivity + needs_warning = [] + unsubs = [] + if inactivity_check: + for sub in self.subscribers.values(): + if sub.warnings >= self.max_warnings: + sub.warnings += 1 + unsubs.append(sub) + elif (_now - sub.last_seen) > old_stage.duration * 60: + sub.warnings += 1 + if sub.warnings >= self.max_warnings: + needs_warning.append(sub) + + # Build message components + old_stage_str = "**{}** finished! ".format(old_stage.name) if finished_old else "" + warning_str = ( + "{} you will be unsubscribed on the next stage if you do not respond or react to this message.\n".format( + ', '.join('<@{}>'.format(sub.userid) for sub in needs_warning) + ) + ) if needs_warning else "" + unsub_str = ( + "{} you have been unsubscribed due to inactivity!\n".format( + ', '.join('<@{}>'.format(sub.userid) for sub in unsubs) + ) + ) if unsubs else "" + main_line = "{}Starting **{}** ({} minutes). {}".format( + old_stage_str, + new_stage.name, + new_stage.duration, + new_stage.message + ) + please_line = ( + "Please respond or react to this message to avoid being unsubscribed.\n" + ) if not self.settings.compact.value else "" + + # Post stage change message, if required + if post: + make_unmentionable = False + can_manage = self.guild.me.guild_permissions.manage_roles and self.guild.me.top_role > self.role + # Make role mentionable + if not self.role.mentionable and can_manage: + try: + await self.role.edit(mentionable=True, reason="Notifying for stage change.") + make_unmentionable = True + except discord.HTTPException: + pass + + # Send the message + out_msg = await self.post( + "{} {}\n{}{}{}".format( + self.role.mention, + main_line, + please_line, + warning_str, + unsub_str + ) + ) + if out_msg: + # Mark the message as being tracked + self.message_ids.append(out_msg.id) + self.message_ids = self.message_ids[-5:] # Truncate + + # Add the check reaction + try: + await out_msg.add_reaction(join_emoji) + await out_msg.add_reaction(leave_emoji) + except discord.HTTPException: + pass + + if make_unmentionable: + try: + await self.role.edit(mentionable=False, reason="Notifying finished.") + except discord.HTTPException: + pass + + # Do the voice alert, if required + if self.settings.voice_alert.value and self.voice_channel and finished_old and post: + asyncio.create_task(play_alert(self.voice_channel)) + + # Notify and unsubscribe as required + for sub in list(self.subscribers.values()): + try: + to_send = None + if sub in unsubs: + sub = await self.unsubscribe(sub.userid) + if sub.notify_level >= NotifyLevel.FINAL: + to_send = ( + "You have been unsubscribed from the group **{}** in {} due to inactivity!\n" + "You were subscribed for **{}**." + ).format(self.data.name, self.channel.mention, sub.pretty_clocked) + elif sub in needs_warning and sub.notify_level >= NotifyLevel.WARNING: + to_send = ( + "**Warning** from group **{}** in {}!\n" + "Please respond or react to a timer message " + "to avoid being unsubscribed on the next stage.\n{}".format( + self.data.name, + self.channel.mention, + main_line + ) + ) + elif sub.notify_level >= NotifyLevel.ALL: + to_send = "Status update for group **{}** in {}!\n{}".format(self.data.name, + self.channel.mention, + main_line) + + if to_send is not None: + await sub.send(to_send) + except discord.HTTPException: + pass + + async def subscribe(self, member, post=False): + """ + Subscribe a new member to the timer. + This may raise `discord.HTTPException`. + """ + log("Subscribing {!r}.".format(member), context="rid:{}".format(self.data.roleid)) + studyrole = GuildSettings(member.guild.id).studyrole.value + if studyrole: + await member.add_roles(self.role, studyrole, reason="Applying study group role and global studyrole.") + else: + await member.add_roles(self.role, reason="Applying study group role.") + subscriber = TimerSubscriber(self, member.id, member=member) + if self.state == TimerState.RUNNING: + subscriber.new_session() + + self.subscribers[member.id] = subscriber + + if post: + # Send a welcome message + welcome = "Welcome to **{}**, {}!".format(self.data.name, member.mention) + welcome += ' ' if self.settings.compact.value else '\n' + + if self.state == TimerState.RUNNING: + welcome += "Currently on stage **{}** with **{}** remaining. {}".format( + self.current_stage.name, + self.pretty_remaining, + self.current_stage.message + ) + elif self.state in (TimerState.STOPPED, TimerState.UNSET): + welcome += ( + "The group timer is not running. Start it with `{0}start` " + "(or `{0}start ` to use a different timer pattern)." + ).format(best_prefix(member.guild.id)) + await self.post(welcome) + return subscriber + + async def unsubscribe(self, userid, post=False): + """ + Unsubscribe a member from the timer. + Raises `ValueError` if the user isn't subscribed. + Returns the old subscriber for session reporting. + """ + log("Unsubscribing (uid:{}).".format(userid), context="rid:{}".format(self.data.roleid)) + + if userid not in self.subscribers: + raise ValueError("Attempted to unsubscribe a non-existent user!") + subscriber = self.subscribers.pop(userid) + subscriber.close_session() + + studyrole = GuildSettings(self.guild.id).studyrole.value + try: + # Use a manual request to avoid requiring the member object + await client.http.remove_role(self.guild.id, userid, self.role.id, reason="Removing study group role.") + if studyrole: + await client.http.remove_role(self.guild.id, userid, studyrole.id, reason="Removing global studyrole.") + except discord.HTTPException: + pass + + if post: + await self.post( + "Goodbye <@{}>! You were subscribed for **{}**.".format( + userid, subscriber.pretty_clocked + ) + ) + + return subscriber + + async def update_voice(self): + """ + Update the name of the associated voice channel. + """ + if not self.voice_channel or self.voice_channel not in self.guild.channels: + # Return if there is no associated voice channel + return + if self.state != TimerState.RUNNING: + # Don't update if we aren't running + return + if now() - self.last_voice_update < 10 * 60: + # Return if the last update was less than 10 minutes ago (discord ratelimiting) + return + + name = self.voice_channel_name + + if name == self.voice_channel.name: + # Don't update if there are no changes + return + + log("Updating vc name to {}.".format(name), + context="rid:{}".format(self.data.roleid), + level=logging.DEBUG) + try: + self.last_voice_update = now() + await self.voice_channel.edit(name=name) + self.last_voice_update = now() + except discord.HTTPException: + # Nothing we can do + pass + + async def runloop(self): + """ + Central runloop. + Handles firing stage-changes and voice channel updates. + """ + while self.state == TimerState.RUNNING: + remaining = self.remaining + if remaining <= 0: + try: + await self.change_stage(self.stage_index + 1) + except Exception: + log("Exception encountered while changing stage.", + context="rid:{}".format(self.role.id), + level=logging.ERROR, + add_exc_info=True) + elif remaining > 600: + await self.update_voice() + + self._loop_wait_task = asyncio.create_task(asyncio.sleep(min(600, remaining))) + try: + await self._loop_wait_task + except asyncio.CancelledError: + pass + + def serialise(self): + return { + 'roleid': self.data.roleid, + 'state': self.state.value, + 'patternid': self.current_pattern.row.patternid if self.current_pattern else None, + 'stage_index': self.stage_index, + 'stage_start': self.stage_start, + 'message_ids': self.message_ids, + 'subscribers': [subber.serialise() for subber in self.subscribers.values()], + 'last_voice_update': self.last_voice_update + } + + def restore_from(self, data): + log("Restoring Timer (rid:{}).".format(data['roleid']), context='TIMER_RESTORE') + self.stage_index = data['stage_index'] + self.stage_start = data['stage_start'] + self.state = TimerState(data['state']) + self.current_pattern = Pattern.get(data['patternid'] if data['patternid'] is not None else self.patternid) + self.message_ids = data['message_ids'] + self.last_voice_update = data['last_voice_update'] + + self.subscribers = {} + for sub_data in data['subscribers']: + subber = TimerSubscriber(self, sub_data['userid'], name=sub_data['name']) + subber.restore_from(sub_data) + self.subscribers[sub_data['userid']] = subber + + asyncio.create_task(self.runloop()) + + +class TimerChannel: + """ + Represents a discord text channel holding one or more timers. + + Manages the pinned update message. + """ + __slots__ = ( + 'channel', + 'timers', + 'pinned_msg', + 'pinned_msg_id', + 'previous_desc', + 'failure_count' + ) + + def __init__(self, channel): + self.channel: discord.TextChannel = channel + + self.timers = [] + self.pinned_msg = None + self.pinned_msg_id = None + + self.previous_desc = '' + + self.failure_count = 0 + + async def update_pin(self, force=False): + if not force and self.failure_count > 5: + return + + if self.channel not in self.channel.guild.channels: + return + + if self.pinned_msg is None and self.pinned_msg_id is not None: + try: + self.pinned_msg = await self.channel.fetch_message(self.pinned_msg_id) + except discord.HTTPException: + self.pinned_msg_id = None + + desc = '\n\n'.join(timer.pinstatus for timer in self.timers) + if desc and desc != self.previous_desc: + self.previous_desc = desc + + # Build embed + embed = discord.Embed( + title="Pomodoro Timer Status", + description=desc, + timestamp=datetime.datetime.utcnow() + ) + embed.set_footer(text="Last Updated") + + if self.pinned_msg is not None: + try: + await self.pinned_msg.edit(embed=embed) + except discord.NotFound: + self.msg = None + except discord.HTTPException: + # An obscure permission error or discord dying? + self.failure_count += 1 + return + elif force or all(timer.state != TimerState.STOPPED for timer in self.timers): + # Attempt to generate a new pinned message + try: + self.pinned_msg = await self.channel.send(embed=embed) + except discord.Forbidden: + # We can't send embeds, or maybe any messages? + # First stop the timers, then try to report the error + self.failure_count = 100 + for timer in self.timers: + timer.stop() + + perms = self.channel.permissions_for(self.channel.guild.me) + if perms.send_messges and not perms.embed_links: + try: + await self.channel.send( + "I require the `embed links` permission in this channel! Timers stopped." + ) + except discord.HTTPException: + # Nothing we can do... + pass + return + + # Now attempt to pin the message + try: + await self.pinned_msg.pin() + except discord.Forbidden: + await self.channel.send( + "I don't have the `manage messages` permission required to pin the channel status message! " + "Please pin the message manually." + ) + except discord.HTTPException: + pass + + def serialise(self): + return { + 'channelid': self.channel.id, + 'pinned_msg_id': self.pinned_msg.id if self.pinned_msg else None, + 'timers': [timer.serialise() for timer in self.timers] + } + + def restore_from(self, data): + log("Restoring Timer Channel (cid:{}).".format(data['channelid']), context='TIMER_RESTORE') + self.pinned_msg_id = data['pinned_msg_id'] + + timers = {timer.data.roleid: timer for timer in self.timers} + for timer_data in data['timers']: + timer = timers.get(timer_data['roleid'], None) + if timer is not None: + timer.restore_from(timer_data) + + +class TimerSubscriber: + """ + Represents a member subscribed to a timer. + """ + __slots__ = ( + 'timer', + 'userid', + '_name', + 'member', + '_fetch_task', + 'subscribed_at', + 'last_seen', + 'warnings', + 'clocked_time', + 'session_started', + 'session', + ) + + def __init__(self, timer: Timer, userid, member=None, name=None): + self.timer = timer # Timer the member is subscribed to + self.userid = userid # Discord userid + self.member = member # Discord member object, if provided + + self._name = name # Backup name used when there is no member object + self._fetch_task = None # Potential asyncio.Task for fetching the member object + + self.last_seen = now() # Last seen, for activity tracking + self.warnings = 0 # Current number of warnings + self.clocked_time = 0 # Total clocked session time in this subscription (in seconds) + + self.session_started = None + self.session = None + + if self.member and self.member.name != self.user_data.name: + self.user_data.name = self.member.name + + @property + def name(self): + """ + Name of the member. + May be retrieved from `_name` if the member doesn't exist yet. + """ + return self.member.display_name if self.member else self._name or 'Unknown' + + @property + def user_data(self): + return tables.users.fetch_or_create(self.userid) + + @property + def notify_level(self): + raw = self.user_data.notify_level + return NotifyLevel(raw) if raw is not None else NotifyLevel.WARNING + + @property + def pretty_clocked(self): + return parse_dur(self.clocked_time, True) + + @property + def unsaved_time(self): + """ + Clocked time not yet saved in a session. + """ + return (now() - self.session_started) if self.session else 0 + + def touch(self): + """ + Update `last_seen`, and reset warning count. + """ + self.last_seen = now() + self.warnings = 0 + + async def _fetch_member(self): + try: + self.member = await self.timer.guild.fetch_member(self.userid) + if self.member.name != self.user_data.name: + self.user_data.name = self.member.name + except discord.HTTPException: + pass + + async def send(self, *args, **kwargs): + if self.member: + await self.member.send(*args, **kwargs) + else: + if self._fetch_task is None: + self._fetch_task = asyncio.create_task(self._fetch_member()) + if not self._fetch_task.done(): + try: + await self._fetch_task + except asyncio.CancelledError: + pass + await self.send(*args, **kwargs) + + def set_member(self, member): + """ + Set the member for this subscriber, if unset. + """ + if not self.member and member.id == self.userid: + self.member = member + if self.member.name != self.user_data.name: + self.user_data.name = self.member.name + + if self._fetch_task: + if self._fetch_task.done(): + self._fetch_task = None + else: + self._fetch_task.cancel() + + def new_session(self): + """ + Start a new session for this subscriber. + Requires the timer to be setup. + Typically called after subscription or timer start. + """ + # Close any existing session + self.close_session() + + # Initialise the new session + self.session_started = now() + self.session = [(0, 0) for stage in self.timer.current_pattern] + + # Apply the initial join shift + if self.timer.state == TimerState.RUNNING: + shift = self.timer.stage_start - self.session_started + self.session[self.timer.stage_index] = (0, shift) + + def close_session(self): + """ + Save and close the current session, if any. + This may occur upon unsubscribing or stopping/pausing the timer. + """ + if self.session: + _now = now() + + # Final shift + if self.timer.state == TimerState.RUNNING: + shift = _now - self.timer.stage_start + count, current_shift = self.session[self.timer.stage_index] + self.session[self.timer.stage_index] = (count, current_shift + shift) + + # Save session + duration = _now - self.session_started + focused_duration = sum( + t[0] * stage.duration * 60 + t[1] + for t, stage in zip(self.session, self.timer.current_pattern) + if stage.focus + ) + # Don't save if the session was under a minute + if duration > 60: + tables.sessions.insert( + guildid=self.timer.guild.id, + userid=self.userid, + roleid=self.timer.role.id, + start_time=self.session_started, + duration=duration, + focused_duration=focused_duration, + patternid=self.timer.current_pattern.row.patternid, + stages=json.dumps(self.session) + ) + + # Update clocked time + self.clocked_time += duration + + # Reset session state + self.session_started = None + self.session = None + + def stage_finished(self, stageid): + """ + Finish a stage, adding it to the running session + """ + count, shift = self.session[stageid] + self.session[stageid] = count + 1, shift + + def stage_shift(self, stageid, diff): + """ + Shift a stage (i.e. move the stage start forwards by `shift`, temporarily increasing the stage length). + """ + count, shift = self.session[stageid] + self.session[stageid] = count, shift + diff + + def serialise(self): + return { + 'userid': self.userid, + 'timerid': self.timer.role.id, + 'session_started': self.session_started, + 'session': self.session, + 'name': self.name + } + + def restore_from(self, data): + log("Restoring Subscriber (uid:{}).".format(data['userid']), context='TIMER_RESTORE') + self.session_started = data['session_started'] + self.session = data['session'] diff --git a/bot/Timer/guild_events.py b/bot/Timer/guild_events.py new file mode 100644 index 0000000..5ccc02c --- /dev/null +++ b/bot/Timer/guild_events.py @@ -0,0 +1,52 @@ +from data import tables + +from .core import Timer, TimerChannel + + +async def on_guild_join(client, guild): + """ + (Re)-load the guild timers when we join a guild. + """ + count = 0 + timer_rows = tables.timers.fetch_rows_where(guildid=guild.id) + if timer_rows: + timers = [Timer(row) for row in timer_rows] + timers = [timer for timer in timers if timer.load()] + + channels = client.interface.guild_channels[guild.id] = {} + for timer in timers: + channel = channels.get(timer.channel.id, None) + if channel is None: + channel = channels[timer.channel.id] = TimerChannel(timer.channel) + channel.timers.append(timer) + count += 1 + + client.log( + "Joined new guild \"{}\" (gid: {}) and loaded {} pre-existing timers.".format( + guild.name, + guild.id, + count + ) + ) + + +async def on_guild_remove(client, guild): + """ + Unsubscribe and unload the guild timers when we leave a guild. + """ + count = 0 + channels = client.interface.guild_channels.pop(guild.id, {}) + for channelid, tchannel in channels.items(): + for timer in tchannel.timers: + count += 1 + timer.stop() + for subber in timer.subscribers.values(): + subber.close_session() + + client.log( + "Left guild \"{}\" (gid: {}) and cleaned up {} timers.".format( + guild.name, + guild.id, + count + ) + ) diff --git a/bot/Timer/interface.py b/bot/Timer/interface.py deleted file mode 100644 index ba00790..0000000 --- a/bot/Timer/interface.py +++ /dev/null @@ -1,436 +0,0 @@ -import os -import traceback -import logging -import json -import asyncio - -import discord - -from cmdClient import Context - -from logger import log - -from .trackers import message_tracker, reaction_tracker -from .Timer import Timer, TimerChannel, TimerSubscriber, TimerStage, NotifyLevel, TimerState -from .registry import TimerRegistry -from .voice import sub_on_vcjoin - - -class TimerInterface(object): - save_interval = 120 - save_fp = "data/timerstatus.json" - - def __init__(self, client, db_filename): - self.client = client - self.registry = TimerRegistry(db_filename) - - self.guild_channels = {} - self.channels = {} - self.subscribers = {} - - self.last_save = 0 - - self.ready = False - - self.setup_client() - - def setup_client(self): - client = self.client - - # Bind the interface - client.interface = self - - # Ensure required config entry exists - client.config.guilds.ensure_exists("timers") - - # Load timers from database - client.add_after_event("ready", self.launch) - - # Track user activity in timer channels - client.add_after_event("message", message_tracker) - client.add_after_event("raw_reaction_add", reaction_tracker) - client.add_after_event("raw_reaction_add", self.reaction_sub) - - # Voice event handlers - client.add_after_event("voice_state_update", sub_on_vcjoin) - - async def launch(self, client): - if self.ready: - return - - self.load_timers() - await self.restore_save() - - self.ready = True - asyncio.ensure_future(self.updateloop()) - - async def updateloop(self): - while True: - channels = self.channels.values() - delay = max((0.1, 60/len(channels))) - - for tchan in channels: - asyncio.ensure_future(tchan.update()) - await asyncio.sleep(delay) - - if Timer.now() - self.last_save > self.save_interval: - self.update_save() - - def load_timers(self): - client = self.client - - # Get the guilds with timers - guilds = client.config.guilds.find_not_empty("timers") - - for guildid in guilds: - # List of TimerChannels in the guild - channels = [] - - # Fetch the actual guild, if possible - guild = client.get_guild(guildid) - if guild is None: - continue - - # Get the corresponding timers - raw_timers = client.config.guilds.get(guildid, "timers") - for name, roleid, channelid, clock_channelid in raw_timers: - # Get the objects corresponding to the ids - role = guild.get_role(roleid) - channel = guild.get_channel(channelid) - clock_channel = guild.get_channel(clock_channelid) if clock_channelid != 0 else None - - if role is None or channel is None: - # This timer doesn't exist - # TODO: Handle garbage collection - continue - - # Create the new timer - new_timer = Timer(name, role, channel, clock_channel) - - # Get the timer channel, or create it - tchan = self.channels.get(channelid, None) - if tchan is None: - tchan = TimerChannel(channel) - channels.append(tchan) - self.channels[channelid] = tchan - - # Bind the timer to the channel - tchan.timers.append(new_timer) - - # Assign the channels to the guild - self.guild_channels[guildid] = channels - - async def restore_save(self): - # Open save file if it exists - if not os.path.exists(self.save_fp): - return - - with open(self.save_fp) as f: - try: - savedata = json.load(f) - except Exception: - log("Caught the following exception loading the temporary savefile\n{}".format(traceback.format_exc()), - context="TIMER_RESTORE", - level=logging.ERROR) - os.rename(self.save_fp, self.save_fp + '_CORRUPTED') - return - - if savedata: - # Create a roleid: timer map - timers = {timer.role.id: timer for channel in self.channels.values() for timer in channel.timers} - - for timer in savedata['timers']: - if timer['roleid'] in timers: - timers[timer['roleid']].update_from_data(timer) - log("Restored timer {} (roleid {}) from save.".format(timer['name'], timer['roleid']), - context="TIMER_RESTORE") - - for sub_data in savedata['subscribers']: - if sub_data['roleid'] in timers: - timer = timers[sub_data['roleid']] - - guild = self.client.get_guild(sub_data['guildid']) - if guild is None: - continue - - try: - member = await guild.fetch_member(sub_data['id']) - except discord.Forbidden: - continue - except discord.NotFound: - continue - - if member is None: - continue - - subber = TimerSubscriber.deserialise(member, timer, self, sub_data) - self.subscribers[(member.guild.id, member.id)] = subber - timer.subscribed[member.id] = subber - - log("Restored subscriber {} (id {}) in timer {} (roleid {}) from save.".format(member.name, - member.id, - timer.name, - timer.role.id), - context="TIMER_RESTORE") - - for tchan_data in savedata['timer_channels']: - if tchan_data['id'] in self.channels: - tchan = self.channels[tchan_data['id']] - try: - tchan.msg = await tchan.channel.fetch_message(tchan_data['msgid']) - except discord.NotFound: - continue - except discord.Forbidden: - continue - - def update_save(self, save_name="autosave"): - # Generate save dict - timers = [timer for channel in self.channels.values() for timer in channel.timers] - timer_data = [timer.serialise() for timer in timers if timer.stages] - sub_data = [subber.serialise() for subber in self.subscribers.values()] - tchan_data = [{'id': tchan.channel.id, 'msgid': tchan.msg.id} for tchan in self.channels.values() if tchan.msg] - - data = { - 'timers': timer_data, - 'subscribers': sub_data, - 'timer_channels': tchan_data - } - data_str = json.dumps(data) - - # Backup the save file - if os.path.exists(self.save_fp): - os.replace(self.save_fp, "{}.{}.old".format(self.save_fp, save_name)) - - with open(self.save_fp, 'w') as f: - f.write(data_str) - - self.last_save = Timer.now() - - async def reaction_sub(self, client, payload): - """ - Subscribe a user to a timer if press the subscribe reaction. - """ - # Return if the emoji isn't the right one - if str(payload.emoji) != "✅": - return - - # Quit if the reaction is in DM or we can't see the guild - if payload.guild_id is None: - return - guild = client.get_guild(payload.guild_id) - if guild is None: - return - - # Return if the member is already subscribed - if (payload.guild_id, payload.user_id) in self.subscribers: - return - - # Quit if the user is the client - if payload.user_id == client.user.id: - return - - # Get the timers in the current channel - tchan = self.channels.get(payload.channel_id, None) - if tchan is None: - return - timers = tchan.timers - - # Get the timer who owns the message, if any - timer = next((timer for timer in timers if payload.message_id in timer.timer_messages), None) - if timer is None: - return - - # Get the reacting user - user = guild.get_member(payload.user_id) - if user is None: - log(("Recieved subscribe reaction from (uid: {}) " - "in (cid: {}) but could not find the user!").format(payload.user_id, payload.channel_id), - context="TIMER_INTERFACE", - level=logging.ERROR) - return - - # Finally, subscribe the user to the timer - ctx = Context(client, channel=timer.channel, guild=timer.channel.guild, author=user) - log("Reaction-subscribing user {} (uid: {}) to timer {} (rid: {})".format(user.name, - user.id, - timer.name, - timer.role.id), - context="TIMER_INTERFACE") - await self.sub(ctx, user, timer) - - # Send a welcome message - welcome = "Welcome to **{}**, {}!\n".format(timer.name, user.mention) - if timer.stages and timer.state == TimerState.RUNNING: - welcome += "Currently on stage **{}** with **{}** remaining. {}".format( - timer.stages[timer.current_stage].name, - timer.pretty_remaining(), - timer.stages[timer.current_stage].message - ) - elif timer.stages: - welcome += "Group timer is set up but not running." - - await ctx.ch.send(welcome) - - def create_timer(self, group_name, group_role, bound_channel, clock_channel=None): - """ - Create a new timer, attach it to a timer channel, and save it to storage. - """ - guild = group_role.guild - - # Create the new timer - new_timer = Timer(group_name, group_role, bound_channel, clock_channel) - - # Bind the timer to a timer channel, creating if required - tchan = self.channels.get(bound_channel.id, None) - if tchan is None: - # Create the timer channel - tchan = TimerChannel(bound_channel) - self.channels[bound_channel.id] = tchan - - # Add the timer channel to the guild list, creating if required - guild_channels = self.guild_channels.get(guild.id, None) - if guild_channels is None: - guild_channels = [] - self.guild_channels[guild.id] = guild_channels - guild_channels.append(tchan) - tchan.timers.append(new_timer) - - # Store the new timer in guild config - timers = self.client.config.guilds.get(guild.id, "timers") or [] - timers.append( - (group_name, - group_role.id, - bound_channel.id, - clock_channel.id if clock_channel else 0) - ) - self.client.config.guilds.set(guild.id, "timers", timers) - - return new_timer - - def destroy_timer(self, timer): - # Unsubscribe all members - for sub in timer.subscribed.values(): - asyncio.ensure_future(sub.unsub()) - - # Stop the timer - timer.stop() - - # Remove the timer from its channel - tchan = self.channels.get(timer.channel.id, None) - if tchan is not None: - tchan.timers.remove(timer) - # Cleanup if the channel has no remaining timers - if len(tchan.timers) == 0: - self.channels.pop(timer.channel.id) - - # Update the guild timer config - guild = timer.channel.guild - timers = self.client.config.guilds.get(guild.id, "timers") or [] - tup = next(tup for tup in timers if tup[0] == timer._truename and tup[1] == timer.role.id) - timers.remove(tup) - self.client.config.guilds.set(guild.id, "timers", timers) - - def get_timer_for(self, guildid, userid): - """ - Retrieve timer for the given member, or None. - """ - if (guildid, userid) in self.subscribers: - return self.subscribers[(guildid, userid)].timer - else: - return None - - def get_subs_for(self, userid): - """ - Retrieve all TimerSubscribers for the given userid. - """ - return [value for (key, value) in self.subscribers.items() if key[1] == userid] - - def get_channel_timers(self, channelid): - if channelid in self.channels: - return self.channels[channelid].timers - else: - return None - - def get_guild_timers(self, guildid): - if guildid in self.guild_channels: - return [timer for tchan in self.guild_channels[guildid] for timer in tchan.timers] - - async def wait_until_ready(self): - while not self.ready: - await asyncio.sleep(1) - - def bump_user(self, guildid, channelid, userid): - # Return if we are in a DM context - if guildid == 0: - return - - # Grab the subscriber if it exists - subber = self.subscribers.get((guildid, userid), None) - - # Bump the subscriber - if subber is not None and channelid == subber.timer.channel.id: - subber.bump() - - async def sub(self, ctx, member, timer): - log("Subscribing user {} (uid: {}) to timer {} (rid: {})".format(member.name, - member.id, - timer.name, - timer.role.id), - context="TIMER_INTERFACE") - - # Ensure that the user is not subscribed elsewhere - await self.unsub(member.guild.id, member.id) - - # Get the notify level - notify = ctx.client.config.users.get(member.id, "notify_level") - notify = NotifyLevel(notify) if notify is not None else NotifyLevel.WARNING - - # Create the subscriber - subber = TimerSubscriber(member, timer, self, notify=notify) - - # Attempt to add the sub role - try: - await member.add_roles(timer.role) - except discord.Forbidden: - await ctx.error_reply("Insufficient permissions to add the group role `{}`.".format(timer.role.name)) - except discord.NotFound: - await ctx.error_reply("Group role `{}` doesn't exist! This group is broken.".format(timer.role.id)) - - timer.subscribed[member.id] = subber - self.subscribers[(member.guild.id, member.id)] = subber - - async def unsub(self, guildid, userid): - """ - Unsubscribe a member from a timer, if they are subscribed. - Otherwise, do nothing. - Return the session data for ease of access. - """ - subber = self.subscribers.get((guildid, userid), None) - if subber is not None: - session = subber.session_data() - subber.active = False - - self.subscribers.pop((guildid, userid)) - subber.timer.subscribed.pop(userid) - - try: - await subber.member.remove_roles(subber.timer.role) - except Exception: - pass - - self.registry.new_session(*session) - return session - - @staticmethod - def parse_setupstr(setupstr): - stringy_stages = [stage.strip() for stage in setupstr.strip(';').split(';')] - - stages = [] - for stringy_stage in stringy_stages: - parts = [part.strip() for part in stringy_stage.split(",", maxsplit=2)] - - if len(parts) < 2 or not parts[1].isdigit(): - return None - stages.append(TimerStage(parts[0], int(parts[1]), message=parts[2] if len(parts) > 2 else "")) - - return stages diff --git a/bot/Timer/lib.py b/bot/Timer/lib.py new file mode 100644 index 0000000..c9e1212 --- /dev/null +++ b/bot/Timer/lib.py @@ -0,0 +1,75 @@ +from enum import IntEnum + +from cmdClient.lib import SafeCancellation + +from meta import client +from data import tables +from utils.lib import timestamp_utcnow as now # noqa + + +join_emoji = '✅' +leave_emoji = '❌' + + +def parse_dur(diff, show_seconds=False): + """ + Parse a duration given in seconds to a time string. + """ + diff = max(diff, 0) + if show_seconds: + hours = diff // 3600 + minutes = (diff % 3600) // 60 + seconds = diff % 60 + return "{:02d}:{:02d}:{:02d}".format(hours, minutes, seconds) + else: + diff = int(60 * round(diff / 60)) + hours = diff // 3600 + minutes = (diff % 3600) // 60 + return "{:02d}:{:02d}".format(hours, minutes) + + +def best_prefix(guildid): + if not guildid: + return client.prefix + else: + return tables.guilds.fetch_or_create(guildid).prefix or client.prefix + + +class TimerState(IntEnum): + """ + Enum representing the current running state of the timer. + + Values + ------ + UNSET: The timer isn't set up. + STOPPED: The timer is stopped. + RUNNING: The timer is running. + PAUSED: The timer has been paused. + """ + + UNSET = 0 + STOPPED = 1 + RUNNING = 2 + PAUSED = 3 + + +class NotifyLevel(IntEnum): + """ + Enum representing a subscriber's notification level. + NONE: Never send direct messages. + FINAL: Send a direct message when kicking for inactivity. + WARNING: Send direct messages for unsubscription warnings. + ALL: Send direct messages for all stage updates. + """ + NONE = 1 + FINAL = 2 + WARNING = 3 + ALL = 4 + + +class InvalidPattern(SafeCancellation): + """ + Exception raised when an invalid pattern format is encountered. + Stores user-readable information about the pattern error. + """ + pass diff --git a/bot/Timer/registry.py b/bot/Timer/registry.py deleted file mode 100644 index 70d79ea..0000000 --- a/bot/Timer/registry.py +++ /dev/null @@ -1,57 +0,0 @@ -import sqlite3 as sq - - -class TimerRegistry(object): - session_keys = ( - 'userid', - 'guildid', - 'roleid', - 'starttime', - 'duration' - ) - - def __init__(self, db_file): - self.conn = sq.connect(db_file, timeout=20) - self.conn.row_factory = sq.Row - - self.ensure_table() - - def ensure_table(self): - """ - Ensure the session table exists, otherwise create it. - """ - cursor = self.conn.cursor() - columns = ("userid INTEGER NOT NULL, " - "guildid INTEGER NOT NULL, " - "roleid INTEGER NOT NULL, " - "starttime INTEGER NOT NULL, " - "duration INTEGER NOT NULL") - - cursor.execute("CREATE TABLE IF NOT EXISTS sessions ({})".format(columns)) - self.conn.commit() - - def close(self): - self.conn.commit() - self.conn.close() - - def get_sessions_where(self, **kwargs): - keys = [(key, kwargs[key]) for key in kwargs if key in self.session_keys] - - if keys: - keystr = "WHERE " + " AND ".join("{} = ?".format(key) for key, val in keys) - else: - keystr = "" - - cursor = self.conn.cursor() - cursor.execute('SELECT * FROM sessions {}'.format(keystr), tuple(value for key, value in keys)) - return cursor.fetchall() - - def new_session(self, *args): - if len(args) != len(self.session_keys): - raise ValueError("Improper number of session keys passed for storage.") - - cursor = self.conn.cursor() - value_str = ", ".join('?' for key in args) - - cursor.execute('INSERT INTO sessions VALUES ({})'.format(value_str), tuple(args)) - self.conn.commit() diff --git a/bot/Timer/timer_reactions.py b/bot/Timer/timer_reactions.py new file mode 100644 index 0000000..8b74341 --- /dev/null +++ b/bot/Timer/timer_reactions.py @@ -0,0 +1,28 @@ +from meta import log + +from .core import join_emoji, leave_emoji + + +async def joinleave_tracker(client, payload): + if payload.user_id == client.user.id: + return + + if payload.guild_id and (payload.emoji.name in (join_emoji, leave_emoji)): + timers = client.interface.get_timers_in(payload.guild_id, payload.channel_id) + timer = next((timer for timer in timers if payload.message_id in timer.message_ids), None) + if timer: + userid = payload.user_id + guild = timer.channel.guild + if payload.emoji.name == join_emoji and not client.interface.get_subscriber(userid, guild.id): + member = guild.get_member(userid) or await guild.fetch_member(userid) + + # Subscribe member + log("Reaction subscribing {}(uid:{}) in {}(gid:{}) to {}(rid:{})".format( + member, userid, guild, payload.guild_id, timer.data.name, timer.data.roleid + ), context="TIMER_REACTIONS") + await timer.subscribe(member, post=True) + elif payload.emoji.name == leave_emoji and payload.user_id in timer: + log("Reaction unsubscribing (uid:{}) in {}(gid:{}) from {}(rid:{})".format( + userid, guild, payload.guild_id, timer.data.name, timer.data.roleid + ), context="TIMER_REACTIONS") + await timer.unsubscribe(payload.user_id, post=True) diff --git a/bot/Timer/trackers.py b/bot/Timer/trackers.py deleted file mode 100644 index 0bee405..0000000 --- a/bot/Timer/trackers.py +++ /dev/null @@ -1,8 +0,0 @@ -async def message_tracker(client, message): - client.interface.bump_user(message.guild or 0, message.channel.id, message.author.id) - - -async def reaction_tracker(client, payload): - client.interface.bump_user(payload.guild_id or 0, - payload.channel_id, - payload.user_id) diff --git a/bot/Timer/voice.py b/bot/Timer/voice.py deleted file mode 100644 index 23a4283..0000000 --- a/bot/Timer/voice.py +++ /dev/null @@ -1,57 +0,0 @@ -from cmdClient import Context - -from logger import log - -from .Timer import TimerState - - -async def sub_on_vcjoin(client, member, before, after): - """ - When a member joins a study group voice channel, automatically subscribe them to the study group. - """ - if before.channel is None and after.channel is not None: - # Join voice channel event - - # Quit if the member is a bot - if member.bot: - return - - # Quit if the member is already subscribed - if (member.guild.id, member.id) in client.interface.subscribers: - return - - guild_timers = client.interface.get_guild_timers(member.guild.id) - - # Quit if there are no groups in this guild - if not guild_timers: - return - - # Get the collection of clocks in the guild - guild_clocks = {timer.clock_channel.id: timer for timer in guild_timers if timer.clock_channel is not None} - - # Quit if the voice channel is not a clock channel, otherwise get the related timer - timer = guild_clocks.get(after.channel.id, None) - if timer is None: - return - - # Finally, subscribe the member to the timer - ctx = Context(client, channel=timer.channel, guild=timer.channel.guild, author=member) - log("Reaction-subscribing user {} (uid: {}) to timer {} (rid: {})".format(member.name, - member.id, - timer.name, - timer.role.id), - context="CLOCK_AUTOSUB") - await client.interface.sub(ctx, member, timer) - - # Send a welcome message - welcome = "Welcome to **{}**, {}!\n".format(timer.name, member.mention) - if timer.stages and timer.state == TimerState.RUNNING: - welcome += "Currently on stage **{}** with **{}** remaining. {}".format( - timer.stages[timer.current_stage].name, - timer.pretty_remaining(), - timer.stages[timer.current_stage].message - ) - elif timer.stages: - welcome += "Group timer is set up but not running." - - await ctx.ch.send(welcome) diff --git a/bot/Timer/voice_events.py b/bot/Timer/voice_events.py new file mode 100644 index 0000000..4e8689d --- /dev/null +++ b/bot/Timer/voice_events.py @@ -0,0 +1,45 @@ +import asyncio + +from meta import log + + +async def vc_update_handler(client, member, before, after): + if before.channel != after.channel: + if member.bot: + return + + voice_channels = { + timer.voice_channel.id: timer + for timer in client.interface.get_timers_in(member.guild.id) + if timer.voice_channel + } + left = voice_channels.get(before.channel.id, None) if before.channel else None + joined = voice_channels.get(after.channel.id, None) if after.channel else None + + leave = (left and member.id in left and left.data.track_voice_join is not False) + join = ( + joined and + joined.data.track_voice_leave is not False and + (leave or not client.interface.get_subscriber(member.id, member.guild.id)) + ) + existing_sub = None + if leave: + # TODO: Improve hysterisis, with locks maybe? + # TODO: Maybe add a personal ignore_voice setting + # Briefly wait to handle connection issues + if not after.channel: + await asyncio.sleep(5) + if member.voice and member.voice.channel: + return + log("Voice unsubscribing {}(uid:{}) in {}(gid:{}) from {}(rid:{})".format( + member, member.id, member.guild, member.guild.id, left.data.name, left.data.roleid + ), context="TIMER_REACTIONS") + existing_sub = await left.unsubscribe(member.id, post=not join) + + if join: + log("Voice subscribing {}(uid:{}) in {}(gid:{}) to {}(rid:{})".format( + member, member.id, member.guild, member.guild.id, joined.data.name, joined.data.roleid + ), context="TIMER_REACTIONS") + new_sub = await joined.subscribe(member, post=True) + if existing_sub: + new_sub.clocked_time = existing_sub.clocked_time diff --git a/bot/Timer/voice_notify.py b/bot/Timer/voice_notify.py new file mode 100644 index 0000000..e513e54 --- /dev/null +++ b/bot/Timer/voice_notify.py @@ -0,0 +1,37 @@ +import asyncio +import discord + + +voice_alert_path = "assets/sounds/slow-spring-board.wav" + +guild_locks = {} + + +async def play_alert(channel: discord.VoiceChannel): + if not channel.members: + # Don't notify an empty channel + return + + lock = guild_locks.get(channel.guild.id, None) + if not lock: + lock = guild_locks[channel.guild.id] = asyncio.Lock() + + async with lock: + vc = channel.guild.voice_client + if not vc: + vc = await channel.connect() + elif vc.channel != channel: + await vc.move_to(channel) + + audio_stream = open(voice_alert_path, 'rb') + try: + vc.play(discord.PCMAudio(audio_stream), after=lambda e: audio_stream.close()) + except discord.HTTPException: + pass + + count = 0 + while vc.is_playing() and count < 10: + await asyncio.sleep(0.5) + count += 1 + + await vc.disconnect() diff --git a/bot/commands/config.py b/bot/commands/config.py deleted file mode 100644 index 18157a8..0000000 --- a/bot/commands/config.py +++ /dev/null @@ -1,254 +0,0 @@ -import discord - -from cmdClient import cmd -from cmdClient.lib import ResponseTimedOut, UserCancelled -from cmdClient.checks import in_guild - -from wards import timer_admin, timer_ready -from utils import seekers, ctx_addons, timer_utils # noqa -# from Timer import create_timer - - -@cmd("newgroup", - group="Configuration", - desc="Create a new timer group.") -@in_guild() -@timer_ready() -@timer_admin() -async def cmd_addgrp(ctx): - """ - Usage``: - newgroup - newgroup - newgroup , , , - Description: - Creates a new group with the specified properties. - With no arguments or just `name` given, prompts for the remaining information. - Parameters:: - name: The name of the group to create. - role: The role given to people who join the group. - channel: The text channel which can access this group. - clock channel: The voice channel displaying the status of the group timer. - Related: - group, groups, delgroup - Examples``: - newgroup Espresso - newgroup Espresso, Study Group 1, #study-channel, #espresso-vc - """ - args = ctx.arg_str.split(",") - args = [arg.strip() for arg in args] - - if len(args) == 4: - name, role_str, channel_str, clockchannel_str = args - - # Find the specified objects - try: - role = await ctx.find_role(role_str.strip(), interactive=True) - channel = await ctx.find_channel(channel_str.strip(), interactive=True) - clockchannel = await ctx.find_channel(clockchannel_str.strip(), interactive=True) - except UserCancelled: - raise UserCancelled("User cancelled selection, no group was created.") from None - except ResponseTimedOut: - raise ResponseTimedOut("Selection timed out, no group was created.") from None - - # Create the timer - timer = ctx.client.interface.create_timer(name, role, channel, clockchannel) - elif len(args) >= 1 and args[0]: - timer = await newgroup_interactive(ctx, name=args[0]) - else: - timer = await newgroup_interactive(ctx) - - await ctx.reply("Group **{}** has been created and bound to channel {}.".format(timer.name, timer.channel.mention)) - - -async def newgroup_interactive(ctx, name=None, role=None, channel=None, clock_channel=None): - """ - Interactively create a new study group. - Takes keyword arguments to use any pre-existing data. - """ - try: - if name is None: - name = await ctx.input("Please enter a friendly name for the new study group:") - while role is None: - role_str = await ctx.input( - "Please enter the study group role.\n" - "This role is given to people who join the group, " - "and is used for notifications.\n" - "I must have permission to mention this role and give it to members. " - "Note that it must be below my highest role in the role list.\n" - "(Accepted input: Role name or partial name, role id, role mention, or `c` to cancel.)" - ) - if role_str.lower() == 'c': - raise UserCancelled - - role = await ctx.find_role(role_str.strip(), interactive=True) - - while channel is None: - channel_str = await ctx.input( - "Please enter the text channel to bind the group to.\n" - "The group will only be accessible from commands in this channel, " - "and the channel will host the pinned status message for this group.\n" - "I must have the `MANAGE_MESSAGES` permission in this channel to pin the status message.\n" - "(Accepted input: Channel name or partial name, channel id, channel mention, or `c` to cancel.)" - ) - if channel_str.lower() == 'c': - raise UserCancelled - - channel = await ctx.find_channel( - channel_str.strip(), - interactive=True, - chan_type=discord.ChannelType.text - ) - - while clock_channel is None: - clock_channel_str = await ctx.input( - "Please enter the group voice channel, or `s` to continue without an associated voice channel.\n" - "The name of this channel will be updated with the current stage and time remaining, " - "and members who join the channel will automatically be subscribed to the study group.\n" - "I must have the `MANAGE_CHANNEL` permission in this channel to update the name.\n" - "(Accepted input: Channel name or partial name, channel id, channel mention, " - "or `s` to skip or `c` to cancel.)" - ) - if clock_channel_str.lower() == 's': - break - - if clock_channel_str.lower() == 'c': - raise UserCancelled - - clock_channel = await ctx.find_channel( - clock_channel_str.strip(), - interactive=True, - chan_type=discord.ChannelType.voice - ) - except UserCancelled: - raise UserCancelled( - "User cancelled during group creation! " - "No group was created." - ) from None - except ResponseTimedOut: - raise ResponseTimedOut( - "Timed out waiting for a response during group creation! " - "No group was created." - ) from None - - # We now have all the data we need - return ctx.client.interface.create_timer(name, role, channel, clock_channel) - - -@cmd("delgroup", - group="Configuration", - desc="Remove a timer group.") -@in_guild() -@timer_ready() -@timer_admin() -async def cmd_delgrp(ctx): - """ - Usage``: - delgroup - Description: - Deletes the given group from the collection of timer groups in the current guild. - If `name` is not given or matches multiple groups, will prompt for group selection. - Parameters:: - name: The name of the group to delete. - Related: - group, groups, newgroup - Examples``: - delgroup Espresso - """ - try: - timer = await ctx.get_timers_matching(ctx.arg_str, channel_only=False) - except ResponseTimedOut: - raise ResponseTimedOut("Group selection timed out. No groups were deleted.") from None - except UserCancelled: - raise UserCancelled("User cancelled group selection. No groups were deleted.") from None - - if timer is None: - return await ctx.error_reply("No matching timers found!") - - # Delete the timer - ctx.client.interface.destroy_timer(timer) - - # Notify the user - await ctx.reply("The group `{}` has been removed!".format(timer.name)) - - -@cmd("adminrole", - group="Configuration", - desc="View or configure the timer admin role") -@in_guild() -async def cmd_adminrole(ctx): - """ - Usage``: - adminrole - adminrole - Description: - View the timer admin role (in the first usage), or set it to the provided role (in the second usage). - The timer admin role allows creation and deletion of group timers, - as well as modification of the guild registry and forcing timer operations. - - *Setting the timer admin role requires the guild permission `manage_guild`.* - Parameters:: - role: The name, partial name, or id of the new timer admin role. - """ - if ctx.arg_str: - if not ctx.author.guild_permissions.manage_guild: - return await ctx.error_reply("You need the `manage_guild` permission to change the timer admin role.") - - try: - role = await ctx.find_role(ctx.arg_str, interactive=True) - except UserCancelled: - raise UserCancelled("User cancelled role selection. Timer admin role unchanged.") from None - except ResponseTimedOut: - raise ResponseTimedOut("Role selection timed out. Timer admin role unchanged.") from None - - ctx.client.config.guilds.set(ctx.guild.id, "timeradmin", role.id) - - await ctx.embedreply("Timer admin role set to {}.".format(role.mention), color=discord.Colour.green()) - else: - roleid = ctx.client.config.guilds.get(ctx.guild.id, "timeradmin") - if roleid is None: - return await ctx.embedreply("No timer admin role set for this guild.") - role = ctx.guild.get_role(roleid) - if role is None: - await ctx.embedreply("Timer admin role set to a nonexistent role `{}`.".format(roleid)) - else: - await ctx.embedreply("Timer admin role is {}.".format(role.mention)) - - -@cmd("globalgroups", - group="Configuration", - desc="Configure whether groups are accessible away from their channel.") -@in_guild() -async def cmd_globalgroups(ctx): - """ - Usage``: - globalgroups [off | on] - Description: - Configure whether groups may only be joined from their associated channel. - This can, for instance, allow members to join a group before getting access to - the group study channel. - **Setting this option required the timer admin role, see `adminrole`.** - Options:: - on: Groups may be joined from any channel. - off: Groups may only be joined from the channel they are bound to. (**Default**) - Related: - newgroup, join, adminrole - """ - if ctx.arg_str: - if not (await timer_admin.run(ctx) or ctx.author.guild_permissions.manage_guild): - return await ctx.error_reply("You need the timeradmin role to configure this setting!") - - if ctx.arg_str.lower() == 'off': - ctx.client.config.guilds.set(ctx.guild.id, 'globalgroups', False) - await ctx.reply("Groups may now only be joined from their associated channel.") - elif ctx.arg_str.lower() == 'on': - ctx.client.config.guilds.set(ctx.guild.id, 'globalgroups', True) - await ctx.reply("Groups may now be joined from any guild channel.") - else: - await ctx.error_reply("Unrecognised option `{}`. See `help globalgroups` for usage.".format(ctx.arg_tr)) - else: - setting = ctx.client.config.guilds.get(ctx.guild.id, 'globalgroups') - if setting: - await ctx.reply("Groups may be joined from any guild channel.") - else: - await ctx.reply("Groups may only be joined from their associated channel.") diff --git a/bot/commands/existential_cmds.py b/bot/commands/existential_cmds.py new file mode 100644 index 0000000..10c5905 --- /dev/null +++ b/bot/commands/existential_cmds.py @@ -0,0 +1,255 @@ +import asyncio +import logging +import discord + +from cmdClient import cmd +from cmdClient.lib import ResponseTimedOut +from cmdClient.Context import Context + +from Timer import Pattern +from utils import ctx_addons, seekers, interactive # noqa +from wards import timer_admin, timer_ready + + +@cmd('newgroup', + group="Group Admin", + aliases=('newtimer', 'create', 'creategroup'), + short_help="Create a new study group.", + flags=('role==', 'channel==', 'voice==', 'pattern==')) +@timer_admin() +@timer_ready() +async def cmd_newgroup(ctx: Context, flags): + """ + Usage:`` + {prefix}newgroup [flags] + Description: + Create a new study group (also called a timer) in the guild. + Flags:: + role: Role to give to members in the study group. + channel: Text channel where timer messages are posted. + voice: Voice channel associated with the group. + pattern: Default timer pattern used when resetting the group timer. + Examples``: + {prefix}newgroup AwesomeStudyGroup + {prefix}newgroup ExtraAwesomeStudyGroup --channel #studychannel --pattern 50/10 + """ + timers = ctx.timers.get_timers_in(ctx.guild.id) + + # Parse the group name + name = ctx.args + if name: + if len(name) > 30: + return await ctx.error_reply("The group name must be under 30 characters!") + else: + while not name or len(name) > 30: + try: + if not name: + name = await ctx.input("Please enter a name for the new study group:") + else: + name = await ctx.input("The group name must be under 30 characters! Please try again:") + except ResponseTimedOut: + raise ResponseTimedOut("Session timed out! No group created.") + + if any(name.lower() == timer.data.name.lower() for timer in timers): + return await ctx.error_reply("There is already a group with this name!") + name_line = "**Creating new study group `{}`.**".format(name) + + # Parse flags + role = None + if flags['role']: + role = await ctx.find_role(flags['role'], interactive=True) + if not role: + return + + channel = None + if flags['channel']: + channel = await ctx.find_channel(flags['channel'], interactive=True, chan_type=discord.ChannelType.text) + if not channel: + return + + voice = None + if flags['voice']: + voice = await ctx.find_channel(flags['voice'], interactive=True, chan_type=discord.ChannelType.voice) + if not voice: + return + + pattern = None + if flags['pattern']: + pattern = Pattern.from_userstr(flags['pattern']) + + # Extract parameters and report lines + me = ctx.guild.me + + role_line = "" + role_error_line = "" + role_created = False + guild_perms = me.guild_permissions + if not guild_perms.manage_roles: + role_error_line = "Lacking `MANAGE ROLES` guild permission." + elif role is not None: + role_line = "Using provided group role {}.".format(role.mention) + if role >= me.top_role: + role_error_line = "Provided role {} is higher than my top role.".format(role.mention) + elif any(role.id == timer.data.roleid for timer in timers): + role_error_line = "Provided role {} is already associated to a group!".format(role.mention) + else: + # Attempt to find existing role + role = next((role for role in ctx.guild.roles if role.name.lower() == name.lower()), None) + if role: + if role >= me.top_role: + role_line = "Found existing role {}, but it is higher than my top role. ".format(role.mention) + role = None + else: + role_line = "Using existing group role {}.".format(role.mention) + + if not role: + # Create a new role + role = await ctx.guild.create_role(name=name) + role_created = True + await asyncio.sleep(0.1) # Ensure the caches are populated + role_line += "Created the study group role {}.".format(role.mention) + role_line += " This role will automatically be given to members when they join the group." + + channel = channel or ctx.ch + channel_error_line = '' + chan_perms = channel.permissions_for(me) + if not chan_perms.read_messages: + channel_error_line = "Cannot read messages in {}.".format(channel.mention) + elif not chan_perms.send_messages: + channel_error_line = "Cannot send messages in {}.".format(channel.mention) + elif not chan_perms.read_message_history: + channel_error_line = "Cannot read message history in {}.".format(channel.mention) + elif not chan_perms.embed_links: + channel_error_line = "Cannot send embeds in {}.".format(channel.mention) + elif not chan_perms.manage_messages: + channel_error_line = "Cannot manage messages in {}.".format(channel.mention) + + voice_line = "" + voice_error_line = "" + if voice is None: + voice_line = ( + "To associate a voice channel (for voice alerts or to auto-join members) " + "use `{}tconfig \"{}\" voice `." + ).format(ctx.best_prefix, name) + else: + other = next( + (timer for timer in ctx.timers.get_timers_in(ctx.guild.id) if timer.voice_channelid == voice.id), + None + ) + + voice_line = ( + "Group bound to provided voice channel {}.".format(voice.mention) + ) + voice_perms = voice.permissions_for(me) + if other is not None: + voice_error_line = "{} is already bound to the group **{}**.".format( + voice.mention, + other.name + ) + elif not voice_perms.connect: + voice_error_line = "Cannot connect to voice channel." + elif not voice_perms.speak: + voice_error_line = "Cannot speak in voice channel." + elif not voice_perms.view_channel: + voice_error_line = "Cannot see voice channel." + + pattern_line = ( + "The default timer pattern (applied when the timer is reset, e.g. by `{0}reset`) is `{1}`. " + ).format( + ctx.best_prefix, + (pattern if pattern is not None else Pattern.get(0)).display(brief=True), + ) + + lines = [name_line, role_line, pattern_line, voice_line] + errors = (role_error_line, channel_error_line, voice_error_line) + if any(errors): + # A permission error occured, report and exit + error_lines = '\n'.join( + '`{}`: {} {}'.format(cat, '❌' if error else '✅', '*{}*'.format(error) if error else '') + for cat, error in zip(('Group role', 'Text channel', 'Voice channel'), errors) + ) + + embed = discord.Embed( + title="Status", + description=error_lines, + colour=discord.Colour.red() + ) + lines.append("**Couldn't create the new group due to a permission or parameter error.**") + await ctx.reply( + content='\n'.join(lines), + embed=embed + ) + if role_created: + await role.delete() + else: + # Create the new group and report + timer = ctx.timers.create_timer( + role, channel, name, + voice_channelid=voice.id if voice else None, + patternid=pattern.row.patternid if pattern else 0 + ) + if not timer: + # This shouldn't happen, due to the permission check + ctx.client.log( + "Failed to create timer!", + context='mid:{}'.format(ctx.msg.id), + level=logging.ERROR + ) + lines.append("**An unknown error occured, please try again later.**") + return await ctx.reply('\n'.join(lines)) + # TODO: Initial usage tips + # Info about cloning? + lines[0] = "**Created the study group {} in {}.**".format( + '`{}`'.format(name) if name != role.name else role.mention, + channel.mention + ) + lines[1] = "The role {} will be automatically given to members when they join the group.".format(role.mention) + lines.append("*For more advanced configuration options see `{}tconfig \"{}\"`.*".format(ctx.best_prefix, name)) + tips = ( + "• Join the new group using `{prefix}join` in {channel}{voice_msg}.\n" + "• Then start the group timer with `{prefix}start`.\n" + "• To change the pattern of work/break times instead use `{prefix}start `.\n" + " (E.g. `{prefix}start 50/10` for `50` minutes work and `10` minutes break.)\n\n" + "For more information, see `{prefix}help` for the command list and introductory guides, " + "and use `{prefix}help cmd` to get detailed help with a particular command." + ).format( + prefix=ctx.best_prefix, + channel=channel.mention, + voice_msg=", or by joining the {} voice channel.".format(voice.mention) if voice else '' + ) + await ctx.reply( + '\n'.join(lines), + embed=discord.Embed(title="Usage Tips", description=tips), + allowed_mentions=discord.AllowedMentions.none() + ) + + +@cmd('delgroup', + group="Group Admin", + aliases=('rmgroup',), + short_help="Delete a study group.") +@timer_admin() +@timer_ready() +async def cmd_delgroup(ctx): + """ + Usage:`` + {prefix}delgroup + Description: + Delete a guild study group. + Examples``: + {prefix}delgroup {ctx.example_group_name} + """ + groups = ctx.timers.get_timers_in(ctx.guild.id) + group = next((group for group in groups if group.data.name.lower() == ctx.args.lower()), None) + + if group is None: + await ctx.error_reply("No group found with the name `{}`.".format(ctx.args)) + else: + if await ctx.ask("Are you sure you want to delete the group `{}`?".format(group.data.name)): + await ctx.timers.obliterate_timer(group) + await ctx.reply("Deleted the group `{}`.".format(group.data.name)) + if await ctx.ask("Do you also want to delete the group discord role **{}**?".format(group.role.name)): + try: + await group.role.delete() + except discord.HTTPException: + await ctx.reply("Failed to delete the associated role!") diff --git a/bot/commands/guides.py b/bot/commands/guides.py new file mode 100644 index 0000000..3311334 --- /dev/null +++ b/bot/commands/guides.py @@ -0,0 +1,76 @@ +import discord + +from cmdClient import cmd + + +def guide(name, **kwargs): + def wrapped(func): + # Create command + command = cmd(name, group="Guides", **kwargs)(func) + command.smart_help = func + return command + return wrapped + + +@guide("patterns", + short_help="How to change the timer work/break patterns.") +async def guide_patterns(ctx): + pattern_gif = discord.File('assets/guide-gifs/pattern-guide.gif') + embed = discord.Embed( + title="Guide to changing your timer pattern", + description=""" + A *timer pattern* is the sequence of stages the timer follows,\ + for example *50 minutes Work* followed by *10 minutes Break*. + Each timer's pattern is easily customisable, and patterns may be saved for simpler timer setup. + + The pattern is usually given as the stage durations separated by `/`.\ + For example, `50/10` represents 50 minutes work followed by 10 minutes break.\ + See the extended format below for finer control over the pattern stages. + + To modify a timer's pattern, use the `start` or `setup` commands.\ + For example, use `{prefix}start 50/10` to start your timer with a `50/10` pattern.\ + `setup` will stop the timer and change the pattern, while `start` will also restart the timer. + + Patterns always repeat forever, so in the above example, \ + after the break is finished the 50 minute work stage will start again. + + *See the gif below and `,phelp start` for more pattern usage examples.* + """.format(prefix=ctx.best_prefix) + ).add_field( + name="Extended Format", + value=""" + The *stage names* and *stage messages* of a pattern may be customised using the *extended pattern format*. + Stages are separated by `;` instead of `/`, and each stage has the form `name, duration, message`, \ + with the `message` being optional.\ + A `*` may be added after the duration to mark a stage as a "work" stage (visible in the study time summaries). + For example a custom `50/10` pattern could be given as \ + ```Study🔥, 50*, Good luck!; Break🌝, 10, Have a rest.``` + """, + inline=False + ).add_field( + name="Saving patterns", + value=""" + Patterns may also be *saved* and given names using the `savepattern` command. \ + Simply type `{prefix}savepattern pattern` (replacing `pattern` with the desired pattern), \ + and enter the name when prompted. \ + The saved pattern name may then be used wherever a pattern is required, \ + including in the `start` and `setup` commands. + """.format(prefix=ctx.best_prefix), + inline=False + ).set_image( + url='attachment://pattern-guide.gif' + ) + + await ctx.reply(embed=embed, file=pattern_gif) + + +@guide("settingup", + short_help="Setting up {ctx.client.user.name} in your server.") +async def guide_setting_up(ctx): + await ctx.reply("Setup guide coming soon!") + + +@guide("gettingstarted", + short_help="Getting started with using {ctx.client.user.name}.") +async def guide_getting_started(ctx): + await ctx.reply("Getting started guide coming soon!") diff --git a/bot/commands/guild_config.py b/bot/commands/guild_config.py new file mode 100644 index 0000000..5b1c6af --- /dev/null +++ b/bot/commands/guild_config.py @@ -0,0 +1,168 @@ +from cmdClient.checks import in_guild + +from settings import GuildSettings + +from Timer import module + + +@module.cmd( + "globalgroups", + group="Server Configuration", + short_help=("Whether groups may be joined outside their channel. " + "(`{ctx.guild_settings.globalgroups.formatted}`)") +) +@in_guild() +async def cmd_globalgroups(ctx): + """ + Usage``: + {prefix}globalgroups + {prefix}globalgroups on | off + Setting Description: + {ctx.guild_settings.settings.globalgroups.long_desc} + """ + await GuildSettings.settings.globalgroups.command(ctx, ctx.guild.id) + + +@module.cmd( + "prefix", + group="Server Configuration", + short_help=("The server command prefix. " + "(Currently `{ctx.guild_settings.prefix.formatted}`)") +) +@in_guild() +async def cmd_prefix(ctx): + """ + Usage``: + {prefix}prefix + {prefix}prefix + Setting Description: + {ctx.guild_settings.settings.prefix.long_desc} + """ + await GuildSettings.settings.prefix.command(ctx, ctx.guild.id) + + +@module.cmd( + "timeradmin", + group="Server Configuration", + short_help=("The role required for timer admin actions. " + "({ctx.guild_settings.timer_admin_role.formatted})") +) +@in_guild() +async def cmd_timeradmin(ctx): + """ + Usage``: + {prefix}timeradmin + {prefix}timeradmin + Setting Description: + {ctx.guild_settings.settings.timer_admin_role.long_desc} + Accepted Values: + Roles maybe given as their name, id, or partial name. + + *Modifying the `timeradmin` role requires the `administrator` server permission.* + """ + await GuildSettings.settings.timer_admin_role.command(ctx, ctx.guild.id) + + +@module.cmd( + "timezone", + group="Server Configuration", + short_help=("The server leaderboard timezone. " + "({ctx.guild_settings.timezone.formatted})") +) +@in_guild() +async def cmd_timezone(ctx): + """ + Usage``: + {prefix}timezone + {prefix}timezone + Setting Description: + {ctx.guild_settings.settings.timezone.long_desc} + Accepted Values: + Timezone names must be from the "TZ Database Name" column of \ + [this list](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones). + For example, `Europe/London`, `Australia/Melbourne`, or `America/New_York`. + """ + await GuildSettings.settings.timezone.command(ctx, ctx.guild.id) + + +@module.cmd( + "studyrole", + group="Server Configuration", + short_help=("The global study role. " + "({ctx.guild_settings.studyrole.formatted})") +) +@in_guild() +async def cmd_studyrole(ctx): + """ + Usage``: + {prefix}studyrole + {prefix}studyrole + Setting Description: + {ctx.guild_settings.settings.studyrole.long_desc} + Accepted Values: + Roles maybe given as their name, id, or partial name. + """ + await GuildSettings.settings.studyrole.command(ctx, ctx.guild.id) + + +""" +@module.cmd("config", + group="Server Configuration", + short_help="View and modify server configuration.") +@in_guild() +async def cmd_config(ctx): + # Cache and map some info for faster access + setting_displaynames = {setting.display_name.lower(): setting for setting in GuildSettings.settings.values()} + + if not ctx.args or ctx.args.lower() == 'help': + # Display the current configuration, with either values or descriptions + props = { + setting.display_name: setting.get(ctx.guild.id).formatted if not ctx.args else setting.desc + for setting in GuildSettings.settings.values() + } + table = prop_tabulate(*zip(*props.items())) + embed = discord.Embed( + description="{table}\n\nUse `{prefix}config ` to view more information.".format( + prefix=ctx.best_prefix, + table=table + ), + title="Server settings" + ) + await ctx.reply(embed=embed) + else: + # Some args were given + parts = ctx.args.split(maxsplit=1) + + name = parts[0] + setting = setting_displaynames.get(name.lower(), None) + if setting is None: + return await ctx.error_reply( + "Server setting `{}` doesn't exist! Use `{}config` to see all server settings".format( + name, ctx.best_prefix + ) + ) + + if len(parts) == 1: + # config + # View config embed for provided setting + await ctx.reply(embed=setting.get(ctx.guild.id).embed) + else: + # config + # Check the write ward + if not await setting.write_ward.run(ctx): + await ctx.error_reply(setting.msg) + + # Attempt to set config setting + try: + (await setting.parse(ctx.guild.id, ctx, parts[1])).write() + except UserInputError as e: + await ctx.reply(embed=discord.Embed( + description="{} {}".format('❌', e.msg), + Colour=discord.Colour.red() + )) + else: + await ctx.reply(embed=discord.Embed( + description="{} Setting updated!".format('✅'), + Colour=discord.Colour.green() + )) +""" diff --git a/bot/commands/help.py b/bot/commands/help.py index 1e47b91..d09091b 100644 --- a/bot/commands/help.py +++ b/bot/commands/help.py @@ -4,9 +4,53 @@ from utils.lib import prop_tabulate from utils import interactive # noqa +from utils.timer_utils import is_timer_admin # Set the command groups to appear in the help +group_hints = { + 'Guides': "*Short general usage guides for different aspects of PomoBot.*", + 'Timer Usage': "*View and join the server groups.*", + 'Timer Control': "*Setup and control the group timers. May need timer admin permissions!*", + 'Personal Settings': "*Control how I interact with you.*", + 'Registry': "*Server leaderboard and personal study statistics.*", + 'Registry Admin': "*View and modify server session data.*", + 'Saved Patterns': "*Name custom timer patterns for faster setup.*", + 'Group Admin': "*Create, delete, and configure study groups.*", + 'Server Configuration': "*Control how I behave in your server.*", + 'Meta': "*Information about me!*" +} +standard_group_order = ( + ('Timer Usage', 'Timer Control', 'Personal Settings'), + ('Registry', 'Saved Patterns'), + ('Meta', 'Guides'), +) +admin_group_order = ( + ('Group Admin', 'Server Configuration', 'Meta', 'Guides'), + ('Timer Usage', 'Timer Control', 'Personal Settings'), + ('Registry', 'Registry Admin', 'Saved Patterns'), +) + +# Help embed format +title = "PomoBot Usage Manual and Command List" +header = """ +Flexible study group system with Pomodoro-style timers! +Supports multiple groups and custom timer patterns. +Join the [support server](https://discord.gg/MnMrQDe) \ +or make an issue on the [repository](https://github.com/Intery/PomoBot) if you have any \ +questions or issues. + +For more detailed information about each command use `{ctx.best_prefix}help `. +(For example, see `{ctx.best_prefix}help newgroup` and `{ctx.best_prefix}help start`.) +""" + +# Possible tips +tips = { + 'no_groups': "Get started by creating your first group with `{ctx.best_prefix}newgroup`!", + 'non_admin': "Use `{ctx.best_prefix}groups` to see the groups, and `{ctx.best_prefix}join` to join a group!", + 'admin': "Tweak timer behaviour with `{ctx.best_prefix}tconfig`." +} + help_groups = [ ("Timer", "*View and interact with the guild group timers.*"), ("Registry", "*Timer leaderboard and session history.*"), @@ -26,17 +70,19 @@ @cmd("help", - desc="Display information about commands.") + group="Meta", + short_help="Usage manual and command list.") async def cmd_help(ctx): """ - Usage: - help [cmdname] + Usage``: + {prefix}help [cmdname] Description: When used with no arguments, displays a list of commands with brief descriptions. Otherwise, shows documentation for the provided command. Examples: - help - help help + {prefix}help + {prefix}help join + {prefix}help newgroup """ if ctx.arg_str: # Attempt to fetch the command @@ -44,9 +90,13 @@ async def cmd_help(ctx): if command is None: return await ctx.error_reply( ("Command `{}` not found!\n" - "Use the `help` command without arguments to see a list of commands.").format(ctx.arg_str) + "Write `{}help` to see a list of commands.").format(ctx.args, ctx.best_prefix) ) + smart_help = getattr(command, 'smart_help', None) + if smart_help is not None: + return await smart_help(ctx) + help_fields = command.long_help.copy() help_map = {field_name: i for i, (field_name, _) in enumerate(help_fields)} @@ -79,16 +129,15 @@ async def cmd_help(ctx): # Handle the related field names = [cmd_name.strip() for cmd_name in help_fields[pos][1].split(',')] names.sort(key=len) - values = [getattr(ctx.client.cmd_names.get(cmd_name, None), 'desc', "") for cmd_name in names] + values = [ + (getattr(ctx.client.cmd_names.get(cmd_name, None), 'short_help', '') or '').format(ctx=ctx) + for cmd_name in names + ] help_fields[pos] = ( name, prop_tabulate(names, values) ) - usage_index = help_map.get("Usage", None) - if usage_index is not None: - help_fields[usage_index] = ("Usage", "`{}`".format('`\n`'.join(help_fields[usage_index][1].splitlines()))) - aliases = getattr(command, 'aliases', []) alias_str = "(Aliases `{}`.)".format("`, `".join(aliases)) if aliases else "" @@ -98,7 +147,16 @@ async def cmd_help(ctx): colour=discord.Colour(0x9b59b6) ) for fieldname, fieldvalue in help_fields: - embed.add_field(name=fieldname, value=fieldvalue, inline=False) + embed.add_field( + name=fieldname, + value=fieldvalue.format(ctx=ctx, prefix=ctx.best_prefix), + inline=False + ) + embed.add_field( + name="Still need help?", + value="Join our [support server](https://discord.gg/MnMrQDe)!" + ) + # TODO: Link to online docs embed.set_footer(text="[optional] and denote optional and required arguments, respectively.") @@ -115,7 +173,7 @@ async def cmd_help(ctx): cmd_groups[group] = cmd_group # Add the command name and description to the group - cmd_group.append((command.name, getattr(command, 'desc', ""))) + cmd_group.append((command.name, getattr(command, 'short_help', ''))) # Turn the command groups into strings stringy_cmd_groups = {} @@ -124,27 +182,33 @@ async def cmd_help(ctx): stringy_cmd_groups[group_name] = prop_tabulate(*zip(*cmd_group)) # Now put everything into a bunch of embeds - help_embeds = [] - active_fields = [] - for group_name, group_desc in help_groups: - group_str = stringy_cmd_groups.get(group_name, None) - if group_str is None: - continue - - active_fields.append((group_name, group_desc + '\n' + group_str)) - - if group_name == help_groups[-1][0] or sum([len(field.splitlines()) for _, field in active_fields]) > 10: - # Roll a new embed - embed = discord.Embed(description=help_str, colour=discord.Colour(0x9b59b6), title=help_title) - - # Add the active fields - for name, field in active_fields: - embed.add_field(name=name, value=field, inline=False) + if ctx.guild and await is_timer_admin(ctx.author): + group_order = admin_group_order + tip = tips['admin'] + else: + group_order = standard_group_order + tip = tips['non_admin'] - help_embeds.append(embed) + if ctx.guild and not ctx.timers.get_timers_in(ctx.guild.id): + tip = tips['no_groups'] - # Clear the active fields - active_fields = [] + help_embeds = [] + for page_groups in group_order: + embed = discord.Embed( + description=header.format(ctx=ctx), + colour=discord.Colour(0x9b59b6), + title=title + ) + for group in page_groups: + group_hint = group_hints.get(group, '').format(ctx=ctx) + group_str = stringy_cmd_groups.get(group, None) + if group_str: + embed.add_field( + name=group, + value="{}\n{}".format(group_hint, group_str).format(ctx=ctx), + inline=False + ) + help_embeds.append(embed) # Add the page numbers for i, embed in enumerate(help_embeds): @@ -152,6 +216,6 @@ async def cmd_help(ctx): # Send the embeds if help_embeds: - await ctx.pager(help_embeds) + await ctx.pager(help_embeds, content="**Tip:** {}".format(tip.format(ctx=ctx))) else: await ctx.reply(embed=discord.Embed(description=help_str, colour=discord.Colour(0x9b59b6))) diff --git a/bot/commands/meta.py b/bot/commands/meta.py new file mode 100644 index 0000000..afeb083 --- /dev/null +++ b/bot/commands/meta.py @@ -0,0 +1,87 @@ +import discord +from cmdClient import cmd + +from data import tables +from utils.lib import prop_tabulate + + +@cmd("about", + group="Meta", + short_help="Display some general information about me.") +async def cmd_about(ctx): + """ + Usage``: + {prefix}about + Description: + Replies with some general information about me. + """ + # Gather usage statistics + guild_row = tables.guilds.select_one_where(select_columns=('COUNT()',)) + guild_count = guild_row[0] if guild_row else 0 + + timer_row = tables.timers.select_one_where(select_columns=('COUNT()',)) + timer_count = timer_row[0] if timer_row else 0 + + session_row = tables.sessions.select_one_where(select_columns=('COUNT()', 'SUM(duration)')) + session_count = session_row[0] if session_row else 0 + session_time = session_row[1] // 3600 if session_row else 0 + + stats = { + 'Guilds': str(guild_count), + 'Timers': str(timer_count), + 'Recorded': "`{}` hours over `{}` sessions".format(session_time, session_count) + } + stats_str = prop_tabulate(*zip(*stats.items())) + + # Define links + links = { + 'Support server': "https://discord.gg/MnMrQDe", + 'Invite me!': ("https://discordapp.com/oauth2/authorize" + "?client_id=674238793431384067&scope=bot&permissions=271608912"), + 'Github page': "https://github.com/Intery/PomoBot" + } + link_str = ', '.join("[{}]({})".format(name, link) for name, link in links.items()) + + # Create embed + desc = ( + "Flexible study or work group timer using a customisable Pomodoro system.\n" + "Supports multiple groups and different timer setups.\n" + "{stats}\n\n" + "{links}" + ).format(stats=stats_str, links=link_str) + embed = discord.Embed( + description=desc, + colour=discord.Colour(0x9b59b6), + title='About Me' + ) + + # Finally send! + await ctx.reply(embed=embed) + + +@cmd("support", + group="Meta", + short_help="Sends my support server invite link.") +async def cmd_support(ctx): + """ + Usage``: + {prefix}support + Description: + Replies with the support server link. + """ + await ctx.reply("Chat with our friendly support team here: https://discord.gg/MnMrQDe") + + +@cmd("invite", + group="Meta", + short_help="Invite me.") +async def cmd_invite(ctx): + """ + Usage``: + {prefix}invite + Description: + Replies with the bot invite link. + """ + await ctx.reply("Invite PomoBot to your server with this link: {}".format( + "" + )) diff --git a/bot/commands/presets.py b/bot/commands/presets.py index 960515a..e3f7e2e 100644 --- a/bot/commands/presets.py +++ b/bot/commands/presets.py @@ -1,154 +1,294 @@ -from cmdClient import cmd +import datetime +import discord from cmdClient.checks import in_guild -from Timer import TimerInterface +from Timer import Pattern, module -from wards import timer_admin +from data import tables from utils import timer_utils, interactive, ctx_addons # noqa -from utils.lib import paginate_list +from utils.lib import prop_tabulate, paginate_list +from utils.timer_utils import is_timer_admin -def get_presets(ctx): +def _fetch_presets(ctx): """ - Get the valid setup string presets in the current context. + Fetch the current valid presets in this context. + Returns a list of the form (preset_type, preset_row). + Accounts for user pattern name overrides. """ - presets = {} - if ctx.guild: - presets.update(ctx.client.config.guilds.get(ctx.guild.id, "timer_presets") or {}) # Guild presets - presets.update(ctx.client.config.users.get(ctx.author.id, "timer_presets") or {}) # Personal presets + user_rows = tables.user_presets.select_where(userid=ctx.author.id) + guild_rows = tables.guild_presets.select_where(guildid=ctx.guild.id) - return presets + presets = {} + presets.update( + {row['preset_name'].lower(): (0, row) for row in guild_rows} + ) + presets.update( + {row['preset_name'].lower(): (1, row) for row in user_rows} + ) + return list(reversed(list(presets.values()))) -def preset_summary(setupstr): +def _format_preset(preset_type, preset_row): """ - Return a summary string of stage durations for the given setup string. + Format the available patterns into a pretty-viewable list. + Returns a list of tuples `(pattern_str, preset_type, pattern)`. """ - # First compile the preset - stages = TimerInterface.parse_setupstr(setupstr) - return "/".join(str(stage.duration) for stage in stages) + pattern = Pattern.get(preset_row['patternid']) + return "{} ({}, {})".format( + preset_row['preset_name'], + 'Personal' if preset_type == 1 else 'Server', + pattern.display(brief=True) + ) -@cmd("preset", - group="Timer", - desc="Create, view, and remove personal or guild setup string presets.", - aliases=["addpreset", "presets", "rmpreset"]) -async def cmd_preset(ctx): +@module.cmd("savepattern", + group="Saved Patterns", + short_help="Name a given pattern.") +@in_guild() +async def cmd_savepattern(ctx): """ Usage``: - presets - preset [presetname] - addpreset [presetname] - rmpreset + {prefix}savepattern Description: - Create, view, and remove personal or guild setup string presets. - See the `setup` command documentation for more information about setup string format. - - Note that the `Timer Admin` role is required to create or remove guild presets. - Forms:: - preset: Display information about the specified preset. - presets: List available personal and guild presets. - addpreset: Create a new preset. Prompts for name if not provided. - rmpreset: Remove the specified preset. - Related: - setup + See `{prefix}help patterns` for more information about timer patterns. + Examples``: + {prefix}savepattern 50/10 + {prefix}savepattern Work, 50, Good luck!; Break, 10, Have a rest. """ - presets = get_presets(ctx) - preset_list = list(presets.items()) - pretty_presets = [ - "{}\t ({})".format(name, preset_summary(preset)) - for name, preset in preset_list - ] - - if ctx.alias.lower() == "presets": - # Handle having no presets - if not pretty_presets: - return await ctx.embedreply("No presets available! Start creating presets with `addpreset`") - - # Format and return the list - pages = paginate_list(pretty_presets, title="Available Timer Presets") - return await ctx.pager(pages) - elif ctx.alias.lower() == "preset": - # Prompt for the preset if not given - if not ctx.arg_str: - preset = preset_list[await ctx.selector("Please select a preset.", pretty_presets)] - elif ctx.arg_str not in presets: - return await ctx.error_reply("Unrecognised preset `{}`.\n" - "Use `presets` to view the available presets.".format(ctx.arg_str)) + if ctx.args: + pattern = Pattern.from_userstr(ctx.args) + else: + # Get the current timer pattern, if applicable + sub = ctx.timers.get_subscriber(ctx.author.id, ctx.guild.id) + if sub is not None: + pattern = sub.timer.current_pattern or sub.timer.default_pattern else: - preset = (ctx.arg_str, presets[ctx.arg_str]) - - # Build preset info - preset_info = "Preset `{}` with stages `{}`.\n```{}```".format(preset[0], preset_summary(preset[1]), preset[1]) - - # Output info - await ctx.reply(preset_info) - elif ctx.alias.lower() == "addpreset": - # Start by prompting for a name if none was given - name = ctx.arg_str or await ctx.input("Please enter a name for the new timer preset.") - - # Ragequit on names with bad characters - if "," in name or ";" in name: - return await ctx.error_reply("Preset names must not contain `,` or `;`.") - - # Prompt for the setup string - stages = None - while stages is None: - setupstr = await ctx.input( - "Please enter the timer preset setup string." + # Request pattern + pattern = Pattern.from_userstr(await ctx.input( + "Please enter the timer pattern you want to save.\n" + "**Tip**: See `{}help patterns` for more information " + "about creating or using timer patterns".format(ctx.best_prefix) + )) + + # Confirm and request name + name = await ctx.input( + "Please enter a name for this pattern." + "```{}```".format(pattern.display()) + ) + if not name: + return + + # Ask for preset type + pattern_type = 0 + if await is_timer_admin(ctx.author): + options = ( + "User Pattern (the saved pattern is available to you across all servers).", + "Server Pattern (the saved pattern is available to everyone in this server)." + ) + pattern_type = await ctx.selector("Would you like to create a User or Server pattern?", options) + + # Save preset + if pattern_type == 0: + # User preset + tables.user_presets.insert(userid=ctx.author.id, preset_name=name, patternid=pattern.row.patternid) + await ctx.reply( + "Saved the new user pattern `{name}`. " + "Apply it by joining any study group and writing `{prefix}start {name}`.".format( + prefix=ctx.best_prefix, + name=name ) - # Handle cancellation - if setupstr == "c": - return await ctx.embedreply("Preset creation cancelled by user.") - - # Parse setup string to ensure validity - stages = TimerInterface.parse_setupstr(setupstr) - if stages is None: - await ctx.error_reply("Setup string not understood.") - - # Prompt for whether to add a guild or personal preset - preset_type = 1 # 0 is Guild preset and 1 is personal preset - if await in_guild.run(ctx) and await timer_admin.run(ctx): - preset_type = await ctx.selector( - "What type of preset would you like to create?", - ["Guild preset (available to everyone in the guild)", - "Personal preset (only available to yourself)"] + ) + else: + # Guild preset + tables.guild_presets.insert( + guildid=ctx.guild.id, + preset_name=name, + created_by=ctx.author.id, + patternid=pattern.row.patternid + ) + await ctx.reply( + "Saved the new guild pattern `{name}`. " + "Any member may now apply it by joining a study group and writing `{prefix}start {name}`.".format( + prefix=ctx.best_prefix, + name=name ) + ) + + +@module.cmd("delpattern", + group="Saved Patterns", + short_help="Delete a saved pattern by name.", + aliases=('rmpattern',)) +@in_guild() +async def cmd_delpattern(ctx): + """ + Usage``: + {prefix}delpattern + Description: + Delete the given saved pattern. + """ + is_admin = await is_timer_admin(ctx.author) + is_user_preset = True + + if not ctx.args: + # Prompt for a saved pattern to remove + presets = _fetch_presets(ctx) + if not presets: + return await ctx.reply( + "No saved patterns exist yet! " + "See `{}help savepattern` for information about saving a pattern.".format( + ctx.best_prefix + ) + ) + if not is_admin: + presets = [preset for preset in presets if preset[0] == 1] + + ids = [row['patternid'] for _, row in presets] + tables.patterns.fetch_rows_where(patternid=ids) + + pretty_presets = [_format_preset(t, row) for t, row in presets] + result = await ctx.selector( + "Please select a saved pattern to remove.", + pretty_presets + ) + is_user_preset, row = presets[result] + else: + row = tables.user_presets.select_one_where(userid=ctx.author.id, preset_name=ctx.args) + if not row: + is_user_preset = False + row = tables.guild_presets.select_one_where(guildid=ctx.guild.id, preset_name=ctx.args) + if not row: + return await ctx.error_reply( + "No saved pattern found called `{}`.".format(ctx.args) + ) + + if not is_user_preset: + if is_admin: + tables.guild_presets.delete_where(guildid=ctx.guild.id, preset_name=ctx.args) + await ctx.reply("Removed saved server pattern `{}`.".format(row['preset_name'])) else: - # Non-admins don't get an option - preset_type = 1 - - # Create the preset - if preset_type == 0: - guild_presets = ctx.client.config.guilds.get(ctx.guild.id, "timer_presets") or {} - if name in guild_presets and not await ctx.ask("Preset `{}` already exists, overwrite?".format(name)): - return - guild_presets[name] = setupstr - ctx.client.config.guilds.set(ctx.guild.id, "timer_presets", guild_presets) - await ctx.embedreply("Guild preset `{}` created.".format(name)) - elif preset_type == 1: - personal_presets = ctx.client.config.users.get(ctx.author.id, "timer_presets") or {} - if name in personal_presets and not await ctx.ask("Preset `{}` already exists, overwrite?".format(name)): - return - personal_presets[name] = setupstr - ctx.client.config.users.set(ctx.author.id, "timer_presets", personal_presets) - await ctx.embedreply("Personal preset `{}` created.".format(name)) - elif ctx.alias.lower() == "rmpreset": - # Handle trying to remove nonexistent preset - if not ctx.arg_str: - return await ctx.error_reply("Please provide a preset to remove.") - if ctx.arg_str not in presets: - return await ctx.error_reply("Unrecognised preset `{}`.".format(ctx.arg_str)) - - personal_presets = ctx.client.config.users.get(ctx.author.id, "timer_presets") or {} - if ctx.arg_str in personal_presets: - personal_presets.pop(ctx.arg_str) - ctx.client.config.users.set(ctx.author.id, "timer_presets", personal_presets) - else: - if not await timer_admin.run(ctx): - return await ctx.error_reply("You need to be a timer admin to remove guild presets.") - guild_presets = ctx.client.config.guilds.get(ctx.guild.id, "timer_presets") or {} - guild_presets.pop(ctx.arg_str) - ctx.client.config.guilds.set(ctx.guild.id, "timer_presets", guild_presets) + await ctx.error_reply("You need timer admin permissions to remove a saved server pattern!") + else: + tables.user_presets.delete_where(userid=ctx.author.id, preset_name=ctx.args) + await ctx.reply("Removed saved personal pattern `{}`.".format(row['preset_name'])) + + +@module.cmd("savedpatterns", + group="Saved Patterns", + short_help="View the accessible saved patterns.", + aliases=('presets', 'patterns', 'showpatterns')) +@in_guild() +async def cmd_savedpatterns(ctx): + """ + Usage``: + {prefix}savedpatterns + Description: + List the personal and server-wide saved patterns accessible for custom timer setup. + """ + presets = _fetch_presets(ctx) + if not presets: + return await ctx.reply( + "No saved patterns exist yet! See `{}help savepattern` for information about saving a pattern.".format( + ctx.best_prefix + ) + ) + + ids = [row['patternid'] for _, row in presets] + tables.patterns.fetch_rows_where(patternid=ids) + + pretty_presets = [_format_preset(t, row) for t, row in presets] + + await ctx.pager( + paginate_list(pretty_presets, title="Saved Patterns") + ) + + +@module.cmd("showpattern", + group="Saved Patterns", + short_help="View details about a saved pattern.") +@in_guild() +async def cmd_showpattern(ctx): + """ + Usage``: + {prefix}showpattern + Description: + Show details about the provided saved pattern. + """ + is_user_preset = True + if not ctx.args: + # Prompt for a saved pattern to display + presets = _fetch_presets(ctx) + if not presets: + return await ctx.reply( + "No saved patterns exist yet! See `{}help savepattern` for information about saving a pattern.".format( + ctx.best_prefix + ) + ) + + ids = [row['patternid'] for _, row in presets] + tables.patterns.fetch_rows_where(patternid=ids) + + pretty_presets = [_format_preset(t, row) for t, row in presets] + result = await ctx.selector( + "Please select a saved pattern to view.", + pretty_presets + ) + is_user_preset, row = presets[result] + else: + row = tables.user_presets.select_one_where(userid=ctx.author.id, preset_name=ctx.args) + if not row: + is_user_preset = False + row = tables.guild_presets.select_one_where(guildid=ctx.guild.id, preset_name=ctx.args) + + if not row: + return await ctx.error_reply( + "No saved pattern found called `{}`.".format(ctx.args) + ) + + # Extract pattern information + pid = row['patternid'] + pattern = Pattern.get(pid) + + if is_user_preset: + session_data = tables.sessions.select_one_where( + select_columns=('SUM(duration)', ), + patternid=pid, + userid=ctx.author.id + ) + setup_data = tables.timer_pattern_history.select_one_where( + select_columns=('COUNT()', ), + patternid=pid, + modified_by=ctx.author.id + ) + else: + session_data = tables.sessions.select_one_where( + select_columns=('SUM(duration)', ), + patternid=pid, + guildid=ctx.guild.id + ) + setup_data = tables.timer_pattern_history.select_one_where( + select_columns=('COUNT()', ), + patternid=pid, + timerid=[timer.role.id for timer in ctx.timers.get_timers_in(ctx.guild.id)] + ) + total_dur = session_data[0] or 0 + times_used = setup_data[0] or 0 - await ctx.embedreply("Preset `{}` has been deleted.".format(ctx.arg_str)) + table = prop_tabulate( + ('Created by', 'Used', 'Used for'), + ("<@{}>".format(ctx.author.id if is_user_preset else row['created_by']) if row['created_by'] else "Unknown", + "{} times".format(times_used), + "{:.1f} hours (total session duration)".format(total_dur / 3600)) + ) + embed = discord.Embed( + title="{} Pattern `{}`".format('User' if is_user_preset else 'Guild', row['preset_name']), + description=table, + timestamp=datetime.datetime.utcfromtimestamp(row['created_at']) + ).set_footer( + text='Created At' + ).add_field( + name='Pattern', + value="```{}```".format(pattern.display()) + ) + await ctx.reply(embed=embed) diff --git a/bot/commands/registry.py b/bot/commands/registry.py index 1b1b731..f49ac81 100644 --- a/bot/commands/registry.py +++ b/bot/commands/registry.py @@ -1,233 +1,530 @@ import datetime as dt -import discord +import json -from cmdClient import cmd -from cmdClient import checks +import pytz +import discord +from cmdClient.checks import in_guild -from utils import interactive # noqa +from Timer import module, Pattern +from Timer.lib import parse_dur -from Timer import Timer +from data import tables +from data.queries import get_session_user_totals +from utils.lib import paginate_list, timestamp_utcnow, prop_tabulate +from wards import timer_admin, has_timers +from settings import UserSettings -@cmd("history", - group="Registry", - desc="Display a list of past sessions in the current guild.", - aliases=['hist']) -@checks.in_guild() -async def cmd_hist(ctx): +@module.cmd("leaderboard", + group="Registry", + short_help="Server study leaderboards over a given time period.", + aliases=('lb',)) +@has_timers() +async def cmd_leaderboard(ctx): """ Usage``: - history + {prefix}lb [day | week | month | year] Description: - Display a list of your past timer sessions in the current guild. - All times are given in UTC. + Display the server study board in the given timeframe (or all time). + + The timeframe is determined using the *guild timezone* (see `{prefix}timezone`). + Examples``: + {prefix}lb + {prefix}lb week + {prefix}lb year """ - # Get the past sessions for this user - sessions = ctx.client.interface.registry.get_sessions_where(userid=ctx.author.id, guildid=ctx.guild.id) + # Extract the target timeframe + title = None + period_start = None + spec = ctx.args.lower() + timezone = ctx.guild_settings.timezone.value + day_start = dt.datetime.now(tz=timezone).replace(hour=0, minute=0, second=0, microsecond=0) + if not spec or spec == 'all': + period_start = None + title = "All-Time Leaderboard" + elif spec == 'day': + period_start = day_start + title = "Daily Leaderboard" + elif spec == 'week': + period_start = day_start - dt.timedelta(days=day_start.weekday()) + title = "Weekly Leaderboard" + elif spec == 'month': + period_start = day_start.replace(day=1) + title = "{} Leaderboard".format(period_start.strftime('%B')) + elif spec == 'year': + period_start = day_start.replace(month=1, day=1) + title = "{} Leaderboard".format(period_start.year) + else: + return await ctx.error_reply( + "Unrecognised timeframe `{}`.\n" + "**Usage:**`{}leaderboard day | month | week | year`".format(ctx.args, ctx.best_prefix) + ) + start_ts = int(period_start.astimezone(pytz.utc).timestamp() if period_start else 0) + + # lb data from saved sessions + lb_rows = get_session_user_totals(start_ts, guildid=ctx.guild.id) + + # Currently running sessions + subscribers = { + sub.userid: sub + for timer in ctx.timers.get_timers_in(ctx.guild.id) for sub in timer.subscribers.values() + if sub.session + } + + # Calculate names and totals + names = {} + user_totals = {} + for row in lb_rows: + names[row['userid']] = row['name'] or str(row['userid']) + user_totals[row['userid']] = row['total'] + + max_unsaved = int(timestamp_utcnow() - start_ts) + for uid, sub in subscribers.items(): + if sub.member: + names[uid] = sub.member.name + elif uid not in names: + names[uid] = sub.name + + user_totals[uid] = user_totals.get(uid, 0) + min((sub.unsaved_time, max_unsaved)) + + if not user_totals: + return await ctx.reply( + "No session data to show! " + "Join a running timer to start recording data." + ) + + # Sort based on total duration + sorted_totals = sorted( + [(uid, names[uid], user_totals[uid]) for uid in user_totals], + key=lambda tup: tup[2], + reverse=True + ) - # Get the current timer if it exists - timer = ctx.client.interface.get_timer_for(ctx.guild.id, ctx.author.id) + # Format and find index of author + lb_strings = [] + author_index = None + max_name_len = min((30, max(len(name) for name in names.values()))) + for i, (uid, name, total) in enumerate(sorted_totals): + if author_index is None and uid == ctx.author.id: + author_index = i + lb_strings.append( + "{:<{}}\t{:<9}".format( + name, + max_name_len, + parse_dur(total, show_seconds=True) + ) + ) - # Quit if we don't have anything - if not sessions and not timer: - return await ctx.reply("You have not completed any timer sessions!") + page_len = 20 + pages = paginate_list(lb_strings, block_length=page_len, title=title) + start_page = author_index // page_len if author_index is not None else 0 - # Get today's date and timestamp - today = dt.datetime.utcnow().date() - today = dt.datetime(today.year, today.month, today.day) - today_ts = dt.datetime.timestamp(today) + await ctx.pager( + pages, + start_at=start_page + ) - # Build a sorted list of the author's sessions - session_table = sorted( - [(sesh['starttime'], sesh['duration']) for sesh in sessions], - key=lambda tup: tup[0], - reverse=True + +_history_pattern = """\ +```md +{day} ({tz}) (Page {page}/{page_count}) + +Period | Duration | Focused | Pattern +--------------------------------------------- +{sessions} ++-----------------------------------+ +{total} +``` +""" +_history_session_pattern = ( + "{start} - {end} | {duration} | {focused} | {pattern}" +) +_history_total_pattern = ( + "{start} - {end} | {duration} | {focused}" +) + + +@module.cmd("history", + group="Registry", + short_help="Show your personal study session history.", + aliases=('hist',)) +@in_guild() +async def cmd_history(ctx): + """ + Usage``: + {prefix}history + Description: + Display your day by day study session history. + + The times are determined using your personal timezone (see `{prefix}mytimezone`). + """ + timezone = ctx.author_settings.timezone.value + + # Get the saved session rows, ordered by newest first + rows = tables.session_patterns.select_where( + _extra="ORDER BY start_time DESC", + guildid=ctx.guild.id, + userid=ctx.author.id ) - # Add the current session if it exists - if timer: - sesh_data = timer.subscribed[ctx.author.id].session_data() - session_table.insert(0, (sesh_data[3], sesh_data[4])) - - # Build the map (date_string, [session strings]) - day_sessions = [] - - current_offset = 0 - current_sessions = [] - current_total = 0 - for start, dur in session_table: - # Get current offset and corresponding session list - date_offset = (today_ts - start) // (60 * 60 * 24) + 1 - - # If we have a new offset, generate and store the old day's data - if date_offset > current_offset: - if current_sessions: - day_str = (today - dt.timedelta(current_offset)).strftime("%A, %d %b %Y") - dur_str = "{:<13} {}".format("Total:", _parse_duration(current_total)) - day_sessions.append((day_str, current_sessions, dur_str)) - - current_offset = date_offset - current_sessions = [] - current_total = 0 - - # Generate the session string - sesh_str = "{} - {} -- {}".format( - dt.datetime.fromtimestamp(start).strftime("%H:%M"), - dt.datetime.fromtimestamp(start + dur).strftime("%H:%M"), - _parse_duration(dur) - ) - current_sessions.append(sesh_str) - - current_total += dur - - # Add the last day - # TODO: Is there a nicer recipe for this? - if current_sessions: - day_str = (today - dt.timedelta(current_offset)).strftime("%A, %d %b %Y") - dur_str = "{:<13} {}".format("Total:", _parse_duration(current_total)) - day_sessions.append((day_str, current_sessions, dur_str)) - - # Make the pages + if not rows: + return await ctx.reply( + "You have no recorded sessions! Join a running timer to start recording study time!" + ) + + # Bin these into days + day_rows = {} + for row in rows: + # Get the row day + start_day = ( + dt.datetime + .utcfromtimestamp(row['start_time']) + .replace(tzinfo=pytz.utc) + .astimezone(timezone) + .strftime("%A, %d/%b/%Y") + ) + if start_day not in day_rows: + day_rows[start_day] = [row] + else: + day_rows[start_day].append(row) + + # Create the pages + # TODO: If there are too many sessions in a day (~30), this may cause overflow issues pages = [] - num = len(day_sessions) - for i, (day_str, sessions, total_str) in enumerate(day_sessions): - page_str = " ({}/{})".format(i+1, num) if num > 1 else "" - header = day_str + page_str - - page = ( - "All times are in UTC! The current time in UTC is {now}.\n" - "```md\n" - "{header}\n" - "{header_rule}\n" - "{session_list}\n" - "{total_rule}\n" - "{total_str}" - "```" - ).format( - now=dt.datetime.utcnow().strftime("**%H:%M** on **%d %b %Y**"), - header=header, - header_rule='=' * len(header), - session_list='\n'.join(sessions), - total_rule='+' + (len(total_str) - 2) * '-' + '+', - total_str=total_str - ) - pages.append(page) - - # Finally, run the pager + page_count = len(day_rows) + for i, (day, rows) in enumerate(day_rows.items()): + # Sort day sessions in time ascending order + rows.reverse() + + # Build session lines + row_lines = [] + for row in rows: + start = ( + dt.datetime + .utcfromtimestamp(row['start_time']) + .replace(tzinfo=pytz.utc) + .astimezone(timezone) + ) + end = start + dt.timedelta(seconds=row['duration']) + pattern = '/'.join(str(stage[1]) for stage in json.loads(row['stage_str'])) if row['stage_str'] else '' + row_lines.append( + _history_session_pattern.format( + start=start.strftime("%H:%M"), + end=end.strftime("%H:%M"), + duration=parse_dur(row['duration'] or 0, show_seconds=True), + focused=parse_dur(row['focused_duration'] or 0, show_seconds=True), + pattern=pattern + ) + ) + sessions = '\n'.join(row_lines) + + # Build total info + start = ( + dt.datetime + .utcfromtimestamp(rows[0]['start_time']) + .replace(tzinfo=pytz.utc) + .astimezone(timezone) + .strftime("%H:%M") + ) + end = ( + dt.datetime + .utcfromtimestamp(rows[-1]['start_time'] + rows[-1]['duration']) + .replace(tzinfo=pytz.utc) + .astimezone(timezone) + .strftime("%H:%M") + ) + duration = sum(row['duration'] for row in rows) + focused = sum(row['focused_duration'] or 0 for row in rows) + total_str = _history_total_pattern.format( + start=start, + end=end, + duration=parse_dur(duration, show_seconds=True), + focused=parse_dur(focused, show_seconds=True) + ) + + # Add to page list + pages.append( + _history_pattern.format( + day=day, + tz=timezone, + page=i+1, + page_count=page_count, + sessions=sessions, + total=total_str + ) + ) await ctx.pager(pages) -def _parse_duration(dur): - dur = int(dur) - hours = dur // 3600 - minutes = (dur % 3600) // 60 - seconds = dur % 60 +def utctimestamp(aware_dt): + return int(aware_dt.astimezone(pytz.utc).timestamp()) + - return "{:02d}:{:02d}:{:02d}".format(hours, minutes, seconds) +def _get_user_time_since(guildid, userid, period_start): + start_ts = int(utctimestamp(period_start) if period_start else 0) + rows = get_session_user_totals(start_ts, guildid=guildid, userid=userid) + return rows[0]['total'] if rows else 0 -@cmd("leaderboard", - group="Registry", - desc="Display total member group time in the last day/week/month or all-time.", - aliases=['lb']) -@checks.in_guild() -async def cmd_lb(ctx): +@module.cmd("stats", + group="Registry", + short_help="View a table of personal study statistics.", + aliases=('profile',)) +@has_timers() +async def cmd_stats(ctx): """ Usage``: - lb [day | week | month] + {prefix}stats + {prefix}stats Description: - Display the total timer time of each guild member, within the specified period. - The periods are rolling, i.e. `day` means the last 24h. - Without a period specified, the all-time totals will be shown. - Parameters:: - day: Show totals of sessions within the last 24 hours - week: Show totals of sessions within the last 7 days - month: Show totals of sessions within the last 31 days + View summary study statistics for yourself or the mentioned user. """ - out_msg = await ctx.reply("Generating leaderboard, please wait.") - - # Get the past sessions for this guild - sessions = ctx.client.interface.registry.get_sessions_where(guildid=ctx.guild.id) - - if not sessions: - return await ctx.reply("This guild has no past group sessions! Please check back soon.") - - # Current utc timestamp - now = Timer.now() - - # Determine maximum time separation allowed for sessions - region = ctx.arg_str.lower().strip() - if not region or region == 'all': - max_dist = now - head = "All-time leaderboard" - elif region == 'day': - max_dist = 60 * 60 * 24 - head = "Daily leaderboard" - elif region == 'week': - max_dist = 60 * 60 * 24 * 7 - head = "Weekly leaderboard" - elif region == 'month': - max_dist = 60 * 60 * 24 * 31 - head = "Monthly leaderboard" + target = None + if ctx.args: + maybe_id = ctx.args.strip('') + if not maybe_id.isdigit(): + return await ctx.error_reply( + "**Usage:** `{}stats [mention]`\n" + "Couldn't parse `{}` as a user mention or id!".format(ctx.best_prefix, ctx.args) + ) + targetid = int(maybe_id) + target = ctx.guild.get_member(targetid) else: - return await ctx.error_reply("Unknown region specification `{}`.".format(ctx.arg_str)) - - # Tally total session times - total_dict = {} - for session in sessions: - if now - session['starttime'] > max_dist: - continue - - if session['userid'] not in total_dict: - total_dict[session['userid']] = 0 - total_dict[session['userid']] += session['duration'] - - for guildid, userid in ctx.client.interface.subscribers: - if guildid == ctx.guild.id: - sub_data = ctx.client.interface.subscribers[(guildid, userid)].session_data() - if userid not in total_dict: - total_dict[userid] = 0 - total_dict[userid] += sub_data[4] - - # Reshape and sort the totals - totals = sorted(list(total_dict.items()), key=lambda tup: tup[1], reverse=True) - - # Build the string pairs - total_strs = [] - for userid, total in totals: - # Find the user - user = ctx.client.get_user(userid) - if user is None: - try: - user = await ctx.client.fetch_user(userid) - user_str = user.name - except discord.NotFound: - user_str = str(userid) + target = ctx.author + targetid = ctx.author.id + + timezone = UserSettings(targetid).timezone.value + day_start = dt.datetime.now(tz=timezone).replace(hour=0, minute=0, second=0, microsecond=0) + + sub = ctx.timers.get_subscriber(targetid, ctx.guild.id) + if not target and sub and sub.member: + target = sub.member + unsaved = sub.unsaved_time if sub else 0 + + # Total session count and duration + summary_row = tables.sessions.select_one_where( + select_columns=('COUNT() AS count', 'SUM(duration) AS total'), + userid=targetid, + guildid=ctx.guild.id + ) + if not summary_row['count']: + if target == ctx.author: + return await ctx.embed_reply( + "You have no recorded sessions! Join a running timer to start recording study time!" + ) else: - user_str = user.name + return await ctx.embed_reply( + "<@{}> has no recorded sessions!".format(targetid) + ) + session_count = summary_row['count'] + total_duration = summary_row['total'] + + # Favourites + pattern_rows = tables.session_patterns.select_where( + select_columns=('SUM(duration) AS total', 'patternid'), + _extra="GROUP BY patternid ORDER BY total DESC LIMIT 5", + userid=targetid, + guildid=ctx.guild.id + ) + print([dict(row) for row in pattern_rows]) + pattern_pairs = [ + (Pattern.get(row['patternid']).display(brief=True, truncate=6) if row['patternid'] is not None else "Unknown", + row['total']) + for row in pattern_rows + ] + max_len = max(len(p) for p, _ in pattern_pairs) + pattern_block = "```{}```".format( + '\n'.join( + "{:<{}} - {} ({}%)".format( + pattern, + max_len, + parse_dur(total), + (total * 100) // total_duration + ) + for pattern, total in pattern_pairs + ) + ) - total_strs.append((user_str, _parse_duration(total))) + timer_rows = tables.session_patterns.select_where( + select_columns=('SUM(duration) AS total', 'roleid'), + _extra="GROUP BY roleid ORDER BY total DESC LIMIT 5", + userid=targetid, + guildid=ctx.guild.id + ) + timer_pairs = [] + for row in timer_rows: + timer_row = tables.timers.fetch(row['roleid']) + if timer_row: + name = timer_row.name + else: + name = str(row['roleid']) + timer_pairs.append((name, row['total'])) + max_len = max(len(t) for t, _ in timer_pairs) + timer_block = "```{}```".format( + '\n'.join( + "{:^{}} - {} ({}%)".format( + timer_name, + max_len, + parse_dur(total), + (total * 100) // total_duration + ) + for timer_name, total in timer_pairs + ) + ) - # Build pages in groups of 20 - blocks = [total_strs[i:i+20] for i in range(0, len(total_strs), 20)] - max_block_lens = [len(max(list(zip(*block))[0], key=len)) for block in blocks] - page_blocks = [["{0[0]:^{max_len}} {0[1]:>10}".format(pair, max_len=max_block_lens[i]) for pair in block] - for i, block in enumerate(blocks)] + # Calculate streak and first session + streak = 0 + day_window = (day_start, day_start + dt.timedelta(days=1)) + ts_window = (utctimestamp(day_window[0]), utctimestamp(day_window[1])) - num = len(page_blocks) - pages = [] - for i, block in enumerate(page_blocks): - header = head + " (Page {}/{})".format(i+1, num) if num > 1 else head - header_rule = "=" * len(header) - page = "```md\n{}\n{}\n{}```".format( - header, - header_rule, - "\n".join(block) + session_rows = tables.sessions.select_where( + select_columns=('start_time', 'start_time + duration AS end_time'), + _extra="ORDER BY start_time DESC", + guildid=ctx.guild.id, + userid=targetid + ) + first_session_ts = session_rows[-1]['start_time'] + session_periods = ((row['start_time'], row['end_time']) for row in session_rows) + + # Account for the current day + start_time, end_time = next(session_periods, (0, 0)) + if sub or end_time > ts_window[0]: + streak += 1 + day_window = (day_window[0] - dt.timedelta(days=1), day_window[0]) + ts_window = (utctimestamp(day_window[0]), ts_window[0]) + + for start, end in session_periods: + if end < ts_window[0]: + break + elif start < ts_window[1]: + streak += 1 + day_window = (day_window[0] - dt.timedelta(days=1), day_window[0]) + ts_window = (utctimestamp(day_window[0]), ts_window[0]) + + # Binned time totals + time_totals = {} + total_fields = { + 'Today': day_start, + 'This Week': day_start - dt.timedelta(days=day_start.weekday()), + 'This Month': day_start.replace(day=1), + 'This Year': day_start.replace(month=1, day=1), + 'All Time': None + } + for name, start in total_fields.items(): + time_totals[name] = parse_dur( + _get_user_time_since(ctx.guild.id, targetid, start) + unsaved, + show_seconds=False + ) + subtotal_table = prop_tabulate(*zip(*time_totals.items())) + + # Format stats into the final embed + desc = ( + "**{}** sessions completed, with a total of **{}** hours." + ).format(session_count, total_duration // 3600) + if sub: + desc += "\nCurrently studying in **{}** (in {}) for **{}**!".format( + sub.timer.name, + sub.timer.channel.mention, + parse_dur(sub.clocked_time + unsaved, show_seconds=True) ) - pages.append(page) - await out_msg.delete() - if not pages: - return await ctx.reply("No entries exist in the given range!") + embed = ( + discord.Embed( + title="Study Statistics", + description=desc, + timestamp=dt.datetime.utcfromtimestamp(first_session_ts) + ) + .set_footer(text="Studying Since") + .add_field( + name="Subtotals", + value=subtotal_table, + inline=True + ) + .add_field( + name="Streak", + value="**{}** days!".format(streak), + inline=True + ) + .add_field( + name="Favourite Patterns", + value=pattern_block, + inline=False + ) + .add_field( + name="Favourite Groups", + value=timer_block, + inline=True + ) + ) + if not target: + try: + target = await ctx.guild.fetch_member(targetid) + except discord.HTTPException: + pass + if target: + embed.set_author(name=target.name, icon_url=target.avatar_url) + else: + row = tables.users.fetch(targetid) + name = row.name if row else str(target.id) + embed.set_author(name=name) + await ctx.reply(embed=embed) + - await ctx.pager(pages, locked=False) +@module.cmd("clearregistry", + group="Registry Admin", + short_help="Remove all session history in this server.") +@timer_admin() +async def cmd_clearregistry(ctx): + """ + Usage``: + {prefix}clearregistry + Description: + Remove **all** session history in the server. + This will reset the server leaderboard, along with all personal statistics (including `stats` and `hist`). + ***This cannot be undone.*** + + *This command requires timer admin permissions.* + """ + prompt = ( + "Are you sure you want to delete **all** session history in this server? " + "This will reset the leaderboard and all member history. " + "**This cannot be undone**." + ) + if not await ctx.ask(prompt): + return + tables.sessions.delete_where(guildid=ctx.guild.id) + await ctx.reply("All session data has been deleted.") + + +""" +@module.cmd("forgetuser", + group="Registry Admin", + short_help="Remove all session history for a given member.") +@in_guild() +async def cmd_forgetuser(ctx): + ... + + +@module.cmd("delsession", + group="Registry Admin", + short_help="Remove a selected session from the registry.") +@in_guild() +async def cmd_delsession(ctx): + ... + + +@module.cmd("showsessions", + group="Registry Admin", + short_help="Show recent study sessions, with optional filtering.") +@in_guild() +async def cmd_showsessions(ctx): + ... + + +@module.cmd("showtimerhistory", + group="Registry Admin", + short_help="Show the pattern log for a given timer.") +@in_guild() +async def cmd_showtimerhistory(ctx): + ... +""" diff --git a/bot/commands/timer.py b/bot/commands/timer.py index 1584cc1..0d8e87d 100644 --- a/bot/commands/timer.py +++ b/bot/commands/timer.py @@ -1,89 +1,116 @@ -# import datetime -import discord -from cmdClient import cmd -from cmdClient.checks import in_guild +import datetime +import asyncio -from Timer import TimerState, NotifyLevel +import discord -from utils import timer_utils, interactive, ctx_addons # noqa +from meta import client +from data import tables -from wards import timer_ready +from Timer import TimerState, Pattern, module -from presets import get_presets +from utils import timer_utils, interactive, ctx_addons # noqa +from utils.live_messages import live_edit +from utils.timer_utils import is_timer_admin +from wards import has_timers -@cmd("join", - group="Timer", - desc="Join a group bound to the current channel.", - aliases=['sub']) -@in_guild() -@timer_ready() +@module.cmd("join", + group="Timer Usage", + short_help="Join a study group.", + aliases=['sub']) +@has_timers() async def cmd_join(ctx): """ Usage``: - join - join + {prefix}join + {prefix}join Description: - Join a group in the current channel or guild. - If there are multiple matching groups, or no group is provided, - will show the group selector. + Join a study group, and subscribe to the group timer notifications. + When used with no arguments, displays a selection prompt with the available groups. + + The `group` may be given as a group name or partial name. \ + See `{prefix}groups` for the list of groups in this server. Related: leave, status, groups, globalgroups Examples``: - join espresso + {prefix}join {ctx.example_group_name} """ # Get the timer they want to join - globalgroups = ctx.client.config.guilds.get(ctx.guild.id, 'globalgroups') - timer = await ctx.get_timers_matching(ctx.arg_str, channel_only=(not globalgroups), info=True) + globalgroups = ctx.guild_settings.globalgroups.value + timer = await ctx.get_timers_matching(ctx.args, channel_only=(not globalgroups), info=True) if timer is None: - return await ctx.error_reply( - ("No matching groups in this {}.\n" - "Use the `groups` command to see the groups in this guild!").format( - 'guild' if globalgroups else 'channel' - ) - ) + if not ctx.timers.get_timers_in(ctx.guild.id): + await ctx.error_reply( + "There are no study groups to join!\n" + "Create a new study group with `{prefix}newgroup ` " + "(e.g. `{prefix}newgroup Pomodoro`).".format(prefix=ctx.best_prefix) + ) + elif not globalgroups and not ctx.timers.get_timers_in(ctx.guild.id, ctx.ch.id): + await ctx.error_reply( + "No study groups in this channel!\n" + "Use `{prefix}groups` to see all server groups.".format(prefix=ctx.best_prefix) + ) + else: + await ctx.error_reply( + ("No matching groups in this {}.\n" + "Use `{}groups` to see the server study groups!").format( + 'server' if globalgroups else 'channel', + ctx.best_prefix + ) + ) + return # Query if the author is already in a group - current_timer = ctx.client.interface.get_timer_for(ctx.guild.id, ctx.author.id) - if current_timer is not None: - if current_timer == timer: - return await ctx.error_reply("You are already in this group!\n" - "Use `status` to see the current timer status.") - - chan_info = " in {}".format(current_timer.channel.mention) if current_timer.channel != ctx.ch else "" - result = await ctx.ask("You are already in the group `{}`{}.\nAre you sure you want to switch?".format( - current_timer.name, - chan_info - )) - if not result: - return - - await current_timer.subscribed[ctx.author.id].unsub() + sub = ctx.timers.get_subscriber(ctx.author.id, ctx.guild.id) + if sub is not None: + if sub.timer == timer: + return await ctx.error_reply( + "You are already in this study group!\n" + "Use `{prefix}status` to see the current timer status.".format(prefix=ctx.best_prefix) + ) + else: + result = await ctx.ask( + "You are already in the group **{}**{}. " + "Are you sure you want to switch groups?".format( + sub.timer.name, + " in {}".format(sub.timer.channel.mention) if ctx.ch != sub.timer.channel else "" + ) + ) + if not result: + return + # TODO: Vulnerable to interactive race-states + await sub.timer.unsubscribe(ctx.author.id) # Subscribe the member - await ctx.client.interface.sub(ctx, ctx.author, timer) + new_sub = await timer.subscribe(ctx.author) + if sub: + new_sub.clocked_time = sub.clocked_time - # Specify channel info if they are joining from a different channel + # Check if member is joining from a different channel this_channel = (timer.channel == ctx.ch) - chan_info = " in {}".format(timer.channel.mention) if not this_channel else "" - # Reply with the join message - message = "You have joined the group **{}**{}!".format(timer.name, chan_info) + # Build and send the join message + message = "You have {} **{}**{}!".format( + 'switched to' if sub is not None else 'joined', + timer.name, + " in {}".format(timer.channel.mention) if not this_channel else "" + ) if ctx.author.bot: message += " Good luck, colleague!" if timer.state == TimerState.RUNNING: - message += "\nCurrently on stage **{}** with **{}** remaining. {}".format( - timer.stages[timer.current_stage].name, - timer.pretty_remaining(), - timer.stages[timer.current_stage].message + message += " Currently on stage **{}** with **{}** remaining. {}".format( + timer.current_stage.name, + timer.pretty_remaining, + timer.current_stage.message ) - elif timer.stages: - message += "\nGroup timer is set up but not running. Use `start` to start the timer!" else: - message += "\nSet up the timer with `set`!" + message += ( + "\nTimer is not running! Start it with `{prefix}start` " + "(or `{prefix}start [pattern]` to use a custom timer pattern)." + ).format(prefix=ctx.best_prefix) - await ctx.reply(message) + await ctx.reply(message, reference=ctx.msg) # Poke a welcome message to the timer channel if we are somewhere else if not this_channel: @@ -93,439 +120,592 @@ async def cmd_join(ctx): )) -@cmd("leave", - group="Timer", - desc="Leave your current group.", - aliases=['unsub']) -@in_guild() -@timer_ready() +@module.cmd("leave", + group="Timer Usage", + short_help="Leave your current group.", + aliases=['unsub']) +@has_timers() async def cmd_unsub(ctx): """ Usage``: - leave + {prefix}leave Description: - Leave your current group, and unsubscribe from the group timer. + Leave your study group. Related: join, status, groups """ - timer = ctx.client.interface.get_timer_for(ctx.guild.id, ctx.author.id) - if timer is None: + sub = ctx.timers.get_subscriber(ctx.author.id, ctx.guild.id) + if sub is None: + await ctx.error_reply( + "You are not in a study group! Join one with `{prefix}join`.".format(prefix=ctx.best_prefix) + ) + else: + await sub.timer.unsubscribe(ctx.author.id) + await ctx.reply( + "You left **{}**! You were subscribed for **{}**.".format( + sub.timer.name, + sub.pretty_clocked + ), + reference=ctx.msg + ) + + +@module.cmd("setup", + group="Timer Control", + short_help="Stop and change your group timer pattern.", + aliases=['set']) +@has_timers() +async def cmd_set(ctx): + """ + Usage``: + {prefix}setup + {prefix}setup + Description: + Sets your group timer pattern (i.e. the pattern of work/break periods). + + See `{prefix}help patterns` and the examples below for more information about the pattern format. \ + A saved pattern name (see `{prefix}help presets`) may also be used in place of the associated pattern. + + *If the `admin_locked` timer option is set, this command requires timer admin permissions.* + Related: + join, start, reset, savepattern + Examples: + `{prefix}setup 50/10` (`50` minutes work followed by `10` minutes break.) + `{prefix}setup 25/5/25/5/25/10` (A standard Pomodoro pattern of work and breaks.) + `{prefix}setup Study, 50; Rest, 10` (Another `50/10` pattern, now with custom stage names.) + """ + sub = ctx.timers.get_subscriber(ctx.author.id, ctx.guild.id) + if sub is None: return await ctx.error_reply( - "You need to join a group before you can leave one!" + "You are not in a study group! Join one with `{prefix}join`.".format(prefix=ctx.best_prefix) ) - session = await ctx.client.interface.unsub(ctx.guild.id, ctx.author.id) - clocked = session[-1] + if sub.timer.settings.admin_locked and not await is_timer_admin(ctx.author): + return await ctx.error_reply("This timer may only be setup by timer admins.") - dur = int(clocked) - hours = dur // 3600 - minutes = (dur % 3600) // 60 - seconds = dur % 60 + if not ctx.args: + return await ctx.error_reply( + "Please provide a timer pattern! See `{}help setup` for usage".format(ctx.best_prefix) + ) - dur_str = "{:02d}:{:02d}:{:02d}".format(hours, minutes, seconds) + if sub.timer.state == TimerState.RUNNING: + if not await ctx.ask("Are you sure you want to **stop and reset** your study group timer?"): + return - await ctx.reply("You have been unsubscribed from **{}**! You were subscribed for **{}**.".format( - timer.name, - dur_str - )) + pattern = Pattern.from_userstr(ctx.args, timerid=sub.timer.roleid, userid=ctx.author.id, guildid=ctx.guild.id) + await sub.timer.setup(pattern, ctx.author.id) + + content = "**{}** set up! Use `{}start` to start when ready.".format(sub.timer.name, ctx.best_prefix) + asyncio.create_task( + live_edit( + None, + _status_msg, + 'status', + timer=sub.timer, + ctx=ctx, + content=content, + reference=ctx.msg + ) + ) -@cmd("set", - group="Timer", - desc="Setup the stages of a group timer.", - aliases=['setup', 'reset']) -@in_guild() -@timer_ready() -async def cmd_set(ctx): +@module.cmd("reset", + group="Timer Control", + short_help="Reset the timer pattern to the default.") +@has_timers() +async def cmd_reset(ctx): """ Usage``: - set - set - set + {prefix}reset Description: - Setup the stages of the timer you are subscribed to. - When used with no parameters, uses the following default setup string: - ``` - Study, 25, Good luck!; Break, 5, Have a rest.; - Study, 25, Good luck!; Break, 5, Have a rest.; - Study, 25, Good luck!; Long Break, 10, Have a rest. - ``` - Stages are separated by semicolons, - and are of the format `stage name, stage duration, stage message`. - The `stage message` is optional. - - See the `presets` command for more information on using setup presets. + Stop your group timer, and reset the timer pattern to the timer default. + (To change the default pattern, see `{prefix}tconfig default_pattern`.) + + *If the `admin_locked` timer option is set, this command requires timer admin permissions.* Related: - join, start, presets + tconfig, setup, stop """ - # Get the timer we are acting on - timer = ctx.client.interface.get_timer_for(ctx.guild.id, ctx.author.id) - if timer is None: - tchan = ctx.client.interface.channels.get(ctx.ch.id, None) - if tchan is None or not tchan.timers: - await ctx.error_reply("There are no timers in this channel!") - else: - await ctx.error_reply("Please join a group first!") - return + sub = ctx.timers.get_subscriber(ctx.author.id, ctx.guild.id) + if sub is None: + return await ctx.error_reply( + "You are not in a study group! Join one with `{prefix}join`.".format(prefix=ctx.best_prefix) + ) - # If the timer is running, prompt for confirmation - if timer.state == TimerState.RUNNING: - if ctx.arg_str: - if not await ctx.ask("The timer is running! Are you sure you want to reset it?"): - return - else: - if not await ctx.ask("The timer is running! Are you sure you want to reset it? " - "This will reset the stage sequence to the default!"): - return + if sub.timer.settings.admin_locked and not await is_timer_admin(ctx.author): + return await ctx.error_reply("This timer may only be reset by timer admins.") - if not ctx.arg_str: - # Use the default setup string - # TODO: Customise defaults for different timers - setupstr = ( - "Study, 25, Good luck!; Break, 5, Have a rest.;" - "Study, 25, Good luck!; Break, 5, Have a rest.;" - "Study, 25, Good luck!; Long Break, 10, Have a rest." - ) - stages = ctx.client.interface.parse_setupstr(setupstr) - else: - # Parse the provided setup string - if "," in ctx.arg_str: - # Parse as a standard setup string - stages = ctx.client.interface.parse_setupstr(ctx.arg_str) - if stages is None: - return await ctx.error_reply("Didn't understand setup string!") - else: - # Parse as a preset - presets = get_presets(ctx) - if ctx.arg_str in presets: - stages = ctx.client.interface.parse_setupstr(presets[ctx.arg_str]) - else: - return await ctx.error_reply( - ("Didn't recognise the timer preset `{}`.\n" - "Use the `presets` command to view available presets.").format(ctx.arg_str) - ) + if sub.timer.state == TimerState.RUNNING: + if not await ctx.ask("Are you sure you want to **stop and reset** your study group timer?"): + return - timer.setup(stages) - await ctx.reply("Timer pattern set up! Start when ready.") + await sub.timer.setup(sub.timer.default_pattern, ctx.author.id) + + content = "**{}** has been reset! Use `{}start` to start when ready.".format(sub.timer.name, ctx.best_prefix) + asyncio.create_task( + live_edit( + None, + _status_msg, + 'status', + timer=sub.timer, + ctx=ctx, + content=content, + reference=ctx.msg + ) + ) -@cmd("start", - group="Timer", - desc="Start your timer.", - aliases=["restart"]) -@in_guild() -@timer_ready() +@module.cmd("start", + group="Timer Control", + short_help="Start your group timer (and optionally change the pattern).", + aliases=["restart"]) +@has_timers() async def cmd_start(ctx): """ Usage``: - start - start + {prefix}start + {prefix}start + {prefix}start + {prefix}restart Description: - Start the timer you are subscribed to. - Can be used with a setup string to set up and start the timer in one go. + Start or restart your group timer. + + To modify the timer pattern (i.e. the pattern of work/break stages), \ + provide a timer pattern or a saved pattern name. \ + See `{prefix}help patterns` and the examples below for more information about the pattern format. + + *If the `admin_locked` timer option is set, this command requires timer admin permissions, unless\ + the timer is already stopped.* + Related: + stop, setup, tconfig, savepattern + Examples: + `{prefix}start` (Start/restart the timer with the current pattern.) + `{prefix}start 50/10` (`50` minutes work followed by `10` minutes break.) + `{prefix}start 25/5/25/5/25/10` (A standard Pomodoro pattern of work and breaks.) + `{prefix}start Study, 50; Rest, 10` (Another `50/10` pattern, now with custom stage names.) """ - timer = ctx.client.interface.get_timer_for(ctx.guild.id, ctx.author.id) - if timer is None: - tchan = ctx.client.interface.channels.get(ctx.ch.id, None) - if tchan is None or not tchan.timers: - await ctx.error_reply("There are no timers in this channel!") - else: - await ctx.error_reply("Please join a group first!") - return - if timer.state == TimerState.RUNNING: - if await ctx.ask("Are you sure you want to restart your study group timer?"): - timer.stop() + sub = ctx.timers.get_subscriber(ctx.author.id, ctx.guild.id) + if sub is None: + return await ctx.error_reply( + "You are not in a study group! Join one with `{prefix}join`.".format(prefix=ctx.best_prefix) + ) + if sub.timer.state == TimerState.RUNNING: + if sub.timer.settings.admin_locked and not await is_timer_admin(ctx.author): + return await ctx.error_reply("This timer may only be restarted by timer admins.") + + if await ctx.ask("Are you sure you want to **restart** your study group timer?"): + sub.timer.stop() else: return - if ctx.arg_str: - stages = ctx.client.interface.parse_setupstr(ctx.arg_str) + timer = sub.timer - if stages is None: - return await ctx.error_reply("Didn't understand setup string!") + if ctx.args: + pattern = Pattern.from_userstr(ctx.args, timerid=sub.timer.roleid, userid=ctx.author.id, guildid=ctx.guild.id) + await timer.setup(pattern, ctx.author.id) - timer.setup(stages) - - if not timer.stages: - return await ctx.error_reply("Please set up the timer first!") + this_channel = (ctx.ch == timer.channel) + content = "Started **{}** in {}!".format( + timer.name, + timer.channel.mention + ) if not this_channel else '' await timer.start() - - if timer.channel != ctx.ch: - await ctx.reply("Timer has been started in {}".format(timer.channel.mention)) + if ctx.args: + asyncio.create_task( + live_edit( + None, + _status_msg, + 'status', + timer=timer, + ctx=ctx, + content=content, + reference=ctx.msg + ) + ) + elif not this_channel: + await ctx.reply(content, reference=ctx.msg) -@cmd("stop", - group="Timer", - desc="Stop your timer.") -@in_guild() -@timer_ready() +@module.cmd("stop", + group="Timer Control", + short_help="Stop your group timer.") +@has_timers() async def cmd_stop(ctx): """ Usage``: - stop + {prefix}stop Description: - Stop the timer you are subscribed to. + Stop your study group timer. + + *If the `admin_locked` timer option is set, this command requires timer admin permissions.* + Related: + start, reset, tconfig """ - timer = ctx.client.interface.get_timer_for(ctx.guild.id, ctx.author.id) - if timer is None: - tchan = ctx.client.interface.channels.get(ctx.ch.id, None) - if tchan is None or not tchan.timers: - await ctx.error_reply("There are no timers in this channel!") - else: - await ctx.error_reply("Please join a group first!") - return - if timer.state == TimerState.STOPPED: - return await ctx.error_reply("Can't stop something that's not moving!") + sub = ctx.timers.get_subscriber(ctx.author.id, ctx.guild.id) + if sub is None: + return await ctx.error_reply( + "You are not in a study group! Join one with `{prefix}join`.".format(prefix=ctx.best_prefix) + ) - if len(timer.subscribed) > 1: + if sub.timer.settings.admin_locked and not await is_timer_admin(ctx.author): + return await ctx.error_reply("This timer may only be stopped by timer admins.") + + if sub.timer.state != TimerState.RUNNING: + # TODO: Might want an extra clause when we have Pause states + return await ctx.error_reply( + "Can't stop something that's not moving! (Your group timer is already stopped.)" + ) + + if len(sub.timer.subscribers) > 1: if not await ctx.ask("There are other people in your study group! " "Are you sure you want to stop the study group timer?"): return - timer.stop() - await ctx.reply("Your timer has been stopped.") + sub.timer.stop() + await ctx.reply("Your group timer has been stopped.") -@cmd("groups", - group="Timer", - desc="View the guild's groups.", - aliases=["timers"]) -@in_guild() -@timer_ready() +async def _group_msg(msg, ctx=None): + """ + Group message live-editor. + """ + sections = [] + for tchan in client.interface.guild_channels.get(ctx.guild.id, {}).values(): + if len(tchan.timers) > 0: + sections.append("{}\n\n{}".format( + tchan.channel.mention, + "\n\n".join(timer.pretty_summary for timer in tchan.timers) + )) + + embed = discord.Embed( + description="\n\n\n".join(sections) or "No timers in this guild!", + colour=discord.Colour(0x9b59b6), + title="Study groups", + timestamp=datetime.datetime.utcnow() + ).set_footer(text="Last Updated") + + if msg: + try: + await msg.edit(embed=embed) + return msg + except discord.HTTPException: + pass + else: + return await ctx.reply(embed=embed) + + +@module.cmd("groups", + group="Timer Usage", + short_help="List the server study groups.", + aliases=["timers"]) +@has_timers() async def cmd_groups(ctx): + """ + Usage``: + {prefix}groups + Description: + List all the study groups in this server. + Related: + join, newgroup, delgroup + """ # Handle there being no timers - if not ctx.client.interface.get_guild_timers(ctx.guild.id): - return await ctx.error_reply("There are no groups set up in this guild!") - - if "live_grouptokens" not in ctx.client.objects: - ctx.client.objects["live_grouptokens"] = {} - ctx.client.objects["live_grouptokens"][ctx.ch.id] = ctx.msg.id - - async def _groups(): - # Check if we have a new token - if ctx.client.objects["live_grouptokens"].get(ctx.ch.id, 0) != ctx.msg.id: - return None - - # Build the embed description - sections = [] - for tchan in ctx.client.interface.guild_channels[ctx.guild.id]: - if len(tchan.timers) > 0: - sections.append("{}\n\n{}".format( - tchan.channel.mention, - "\n\n".join(timer.pretty_summary() for timer in tchan.timers) - )) - - embed = discord.Embed( - description="\n\n\n".join(sections) or "No timers in this guild!", - colour=discord.Colour(0x9b59b6), - title="Group timers in this guild" + timers = ctx.timers.get_timers_in(ctx.guild.id) + if not timers: + return await ctx.error_reply( + "This server doesn't have any study groups yet!\n" + "Create one with `{prefix}newgroup ` " + "(e.g. `{prefix}newgroup Pomodoro`).".format(prefix=ctx.best_prefix) ) - return {'embed': embed} - await ctx.live_reply(_groups) + asyncio.create_task(live_edit( + None, + _group_msg, + 'groups', + ctx=ctx + )) + +async def _status_msg(msg, timer, ctx, content='', reference=None): + embed = discord.Embed( + description=timer.status_string(show_seconds=True), + colour=discord.Colour(0x9b59b6), + timestamp=datetime.datetime.utcnow() + ).set_footer(text="Last Updated") -@cmd("status", - group="Timer", - desc="View detailed information about a group.", - aliases=["group", "timer"]) -@in_guild() -@timer_ready() -async def cmd_group(ctx): + if msg: + try: + await msg.edit(content=content, embed=embed) + return msg + except discord.HTTPException: + pass + else: + return await ctx.reply(content=content, embed=embed, reference=reference) + + +@module.cmd("status", + group="Timer Usage", + short_help="Show the status of a group.") +@has_timers() +async def cmd_status(ctx): """ Usage``: - status [group] + {prefix}status + {prefix}status Description: - Display detailed information about the current group or the specified group. + Display the status of the provided group (or your current/selected group if none was given). + + The `group` may be given as a group name or partial name. \ + See `{prefix}groups` for the list of groups in this server. + Related: + groups, start, stop, setup """ - if ctx.arg_str: - timer = await ctx.get_timers_matching(ctx.arg_str, channel_only=False) + # Get target group + if ctx.args: + timer = await ctx.get_timers_matching(ctx.args, channel_only=False) if timer is None: - return await ctx.error_reply("No groups matching `{}`!".format(ctx.arg_str)) + return await ctx.error_reply("No groups found matching `{}`!".format(ctx.args)) else: - timer = ctx.client.interface.get_timer_for(ctx.guild.id, ctx.author.id) - if timer is None: - timer = await ctx.get_timers_matching("", channel_only=False) + sub = ctx.timers.get_subscriber(ctx.author.id, ctx.guild.id) + if sub: + timer = sub.timer + else: + timer = await ctx.get_timers_matching('', channel_only=False) if timer is None: - return await ctx.error_reply("No groups are set up in this guild.") - - if "live_statustokens" not in ctx.client.objects: - ctx.client.objects["live_statustokens"] = {} - ctx.client.objects["live_statustokens"][ctx.ch.id] = ctx.msg.id - - async def _status(): - # Check if we have a new token - if ctx.client.objects["live_statustokens"].get(ctx.ch.id, 0) != ctx.msg.id: - return None + return await ctx.error_reply( + "This server doesn't have any study groups yet!\n" + "Create one with `{prefix}newgroup ` " + "(e.g. `{prefix}newgroup Pomodoro`).".format(prefix=ctx.best_prefix) + ) - embed = discord.Embed( - description=timer.pretty_pinstatus(), - colour=discord.Colour(0x9b59b6) + asyncio.create_task( + live_edit( + None, + _status_msg, + 'status', + ctx=ctx, + timer=timer ) - return {'embed': embed} + ) - await ctx.live_reply(_status) - -@cmd("notify", - group="Timer", - desc="Configure your personal notification level.", - aliases=["dm"]) -async def cmd_notify(ctx): +@module.cmd("shift", + group="Timer Control", + short_help="Add or remove time from the current stage.") +@has_timers() +async def cmd_shift(ctx): """ Usage``: - notify - notify + {prefix}shift + {prefix}shift Description: - View or set your notification level. - The possible levels are described below. - Notification levels:: - all: Receive all stage changes and status updates via DM. - warnings: Only receive a DM for inactivity warnings (default). - kick: Only receive a DM after being kicked for inactivity. - none: Never get sent any status updates via DM. + Adds or removes time from the current stage. + When `amount` is *positive*, adds time to the stage, and removes time when `amount` is *negative*. + `amount` must be given in minutes, with no units (see examples below). + If `amount` is not given, instead aligns the start of the stage to the nearest hour. + + *If the `admin_locked` timer option is set, this command requires timer admin permissions.* Examples``: - notify warnings + {prefix}shift +10 + {prefix}shift -10 """ - if not ctx.arg_str: - # Read the current level and report - level = ctx.client.config.users.get(ctx.author.id, "notify_level") or None - level = NotifyLevel(level) if level is not None else NotifyLevel.WARNING - - if level == NotifyLevel.ALL: - await ctx.reply("Your notification level is `ALL`.\n" - "You will be notified of all group status changes by direct message.") - elif level == NotifyLevel.WARNING: - await ctx.reply("Your notification level is `WARNING`.\n" - "You will receive a direct message when you are about to be kicked for inactivity.") - elif level == NotifyLevel.FINAL: - await ctx.reply("Your notification level is `KICK`.\n" - "You will only be messaged when you are kicked for inactivity.") - elif level == NotifyLevel.NONE: - await ctx.reply("Your notification level is `NONE`.\n" - "You will never be direct messaged about group status updates.") - else: - content = ctx.arg_str.lower() - - newlevel = None - message = None - if content in ["all", "everything"]: - newlevel = NotifyLevel.ALL - message = ("Your notification level has been set to `ALL`\n" - "You will be notified of all group status changes by direct message.") - elif content in ["warnings", "warning"]: - newlevel = NotifyLevel.WARNING - message = ("Your notification level has been set to `WARNING`.\n" - "You will receive a direct message when you are about to be kicked for inactivity.") - elif content in ["final", "kick"]: - newlevel = NotifyLevel.FINAL - message = ("Your notification level has been set to `KICK`.\n" - "You will only be messaged when you are kicked for inactivity.") - elif content in ["none", "dnd"]: - newlevel = NotifyLevel.NONE - message = ("Your notification level has been set to `NONE`.\n" - "You will never be direct messaged about group status updates.") - else: - await ctx.error_reply( - "I don't understand notification level `{}`! See `help notify` for valid levels.".format(ctx.arg_str) - ) - if newlevel is not None: - # Update the db entry - ctx.client.config.users.set(ctx.author.id, "notify_level", newlevel.value) + sub = ctx.timers.get_subscriber(ctx.author.id, ctx.guild.id) + if sub is None: + return await ctx.error_reply( + "You are not in a study group!" + ) - # Update any existing timers - for subber in ctx.client.interface.get_subs_for(ctx.author.id): - subber.notify = NotifyLevel(newlevel) + if sub.timer.settings.admin_locked and not await is_timer_admin(ctx.author): + return await ctx.error_reply("This timer may only be shifted by timer admins.") - # Send the update message - await ctx.reply(message) + if sub.timer.state != TimerState.RUNNING: + return await ctx.error_reply( + "You can only shift a group timer while it is running!" + ) + if len(sub.timer.subscribers) > 1: + if not await ctx.ask("There are other people in your study group! " + "Are you sure you want to shift the study group timer?"): + return -@cmd("rename", - group="Timer", - desc="Rename your group.") -@in_guild() -@timer_ready() -async def cmd_rename(ctx): + if not ctx.args: + quantity = None + elif ctx.args.strip('+-').isdigit(): + quantity = (-1 if ctx.args.startswith('-') else 1) * int(ctx.args.strip('+-')) + else: + return await ctx.error_reply( + "Could not parse `{}` as a shift amount!".format(ctx.args) + ) + + sub.timer.shift(quantity * 60 if quantity is not None else None) + asyncio.create_task( + live_edit( + None, + _status_msg, + 'status', + timer=sub.timer, + ctx=ctx, + content="Timer shifted!", + reference=ctx.msg + ) + ) + + +@module.cmd("skip", + group="Timer Control", + short_help="Skip the current stage.") +@has_timers() +async def cmd_skip(ctx): """ Usage``: - rename + {prefix}skip + {prefix}skip Description: - Set the name of your current group to `groupname`. - Arguments:: - groupname: The new name for your group, less than `20` characters long. - Related: - join, status, groups + Skip the current timer stage, or the number of stages given. + Examples``: + {prefix}skip 1 """ - timer = ctx.client.interface.get_timer_for(ctx.guild.id, ctx.author.id) - if timer is None: + sub = ctx.timers.get_subscriber(ctx.author.id, ctx.guild.id) + if sub is None: return await ctx.error_reply( - "You need to join a group first!" + "You are not in a study group!" ) - if not (0 < len(ctx.arg_str) < 20): + timer = sub.timer + + if timer.settings.admin_locked and not await is_timer_admin(ctx.author): + return await ctx.error_reply("This timer may only be skipped by timer admins.") + + if timer.state != TimerState.RUNNING: return await ctx.error_reply( - "Please supply a new group name under `20` characters long!\n" - "**Usage:** `rename `" + "You can only skip stages of a group timer while it is running!" ) - timer.name = ctx.arg_str - await ctx.embedreply("Your group has been renamed to **{}**.".format(ctx.arg_str)) + if len(timer.subscribers) > 1: + if not await ctx.ask("There are other people in your study group! " + "Are you sure you want to skip forwards?"): + return + + # Collect the number of stages to skip + count = 1 + pattern_len = len(timer.current_pattern.stages) + if ctx.args: + if not ctx.args.isdigit(): + return await ctx.error_reply( + "**Usage:** `{prefix}skip [number].\n" + "Couldn't parse the number of stages to skip.".format(prefix=ctx.best_prefix) + ) + if len(ctx.args) > 10 or int(ctx.args) > pattern_len: + return await ctx.error_reply( + "Maximum number of skippable stages is `{}`.".format(pattern_len) + ) + count = int(ctx.args) + if count == 0: + return await ctx.error_reply( + "Skipping no stages.. done?" + ) -@cmd("syncwith", - group="Timer", - desc="Sync the start of your group timer with another group") -@in_guild() -@timer_ready() -async def cmd_syncwith(ctx): + # Calculate the shift time + shift_by = timer.remaining + sum( + timer.current_pattern.stages[(timer.stage_index + i + 1) % pattern_len].duration * 60 + for i in range(count - 1) + ) - 1 + + timer.shift(-1 * shift_by) + content = "**{}** stages skipped!".format(count) if count > 1 else "Stage skipped!" + await asyncio.sleep(1) + asyncio.create_task( + live_edit( + None, + _status_msg, + 'status', + timer=sub.timer, + ctx=ctx, + content=content, + reference=ctx.msg + ) + ) + + +@module.cmd("syncwith", + group="Timer Control", + short_help="Sync the timer with another group.", + flags=('end',)) +@has_timers() +async def cmd_syncwith(ctx, flags): """ Usage``: - syncwith + {prefix}syncwith [--end] Description: - Align the start of your group timer with the other group. - This will possibly change your stage without notification. - Arguments:: - group: The name of the group to sync with. - Related: - join, status, groups, set + Synchronise your current timer with the timer of the provided group. + This is usually done by *moving* the start of your current stage to the start of the target group's stage. + If the `-end` flag is added, instead moves the *end* of your current stage to match the end of the target stage. + + *If the `admin_locked` timer option is set, this command requires timer admin permissions.* + Examples``: + {prefix}syncwith {ctx.example_group_name} + {prefix}syncwith {ctx.example_group_name} --end """ - # Check an argumnet was given - if not ctx.arg_str: - return await ctx.error_reply("No group name provided!\n**Usage:** `syncwith `.") - - # Check the author is in a group - current_timer = ctx.client.interface.get_timer_for(ctx.guild.id, ctx.author.id) - if current_timer is None: - return await ctx.error_reply("You can only sync a group you are a member of!") - - # Get the target timer to sync with - sync_timer = await ctx.get_timers_matching(ctx.arg_str, channel_only=False) - if sync_timer is None: - return await ctx.error_reply("No groups matching `{}`!".format(ctx.arg_str)) - - # Check both timers are set up - if not sync_timer.stages or not current_timer.stages: - return await ctx.error_reply("Both the current and target timer must be set up first!") - - # Calculate the total duration from the start of the timer - target_duration = sum(stage.duration for i, stage in enumerate(sync_timer.stages) if i < sync_timer.current_stage) - target_duration *= 60 - target_duration += sync_timer.now() - sync_timer.current_stage_start - - # Calculate the target stage in the current timer - i = -1 - elapsed = 0 - while elapsed < target_duration: - i = (i + 1) % len(current_timer.stages) - elapsed += current_timer.stages[i].duration * 60 - - # Calculate new stage start - new_stage_start = sync_timer.now() - (current_timer.stages[i].duration * 60 - (elapsed - target_duration)) - - # Change the stage and adjust the time - await current_timer.change_stage(i, notify=False, inactivity_check=False, report_old=False) - current_timer.current_stage_start = new_stage_start - current_timer.remaining = elapsed - target_duration - - # Notify the user - await ctx.embedreply(current_timer.pretty_pinstatus(), title="Timers synced!") + sub = ctx.timers.get_subscriber(ctx.author.id, ctx.guild.id) + if sub is None: + return await ctx.error_reply( + "You are not in a study group!" + ) + timer = sub.timer + + if timer.settings.admin_locked and not await is_timer_admin(ctx.author): + return await ctx.error_reply("This timer may only be synced by a timer admin.") + + if timer.state != TimerState.RUNNING: + return await ctx.error_reply( + "Timers may only be synced while they are running!" + ) + + if ctx.args: + target = await ctx.get_timers_matching(ctx.args, channel_only=False) + if target is None and ctx.args.isdigit(): + # Last-ditch check, accept roleids from foreign guilds + roleid = int(ctx.args) + timer_row = tables.timers.fetch(roleid) + if timer_row is not None and timer_row.guildid in ctx.timers.guild_channels: + target = next( + (t for t in ctx.timers.guild_channels[timer_row.guildid][timer_row.channelid].timers + if t.roleid == roleid), + None + ) + + if target is None: + return await ctx.error_reply("No target groups found matching `{}`!".format(ctx.args)) + else: + return await ctx.error_reply( + "**Usage:** `{}syncwith [--end]`\n" + "No target group provided!".format(ctx.best_prefix) + ) + + if len(timer.subscribers) > 1: + if not await ctx.ask("There are other people in your study group! " + "Are you sure you want to sync it with **{}**?".format(target.name)): + return + + if target.state != TimerState.RUNNING: + return await ctx.error_reply( + "Target timer isn't running! Use `{}restart` if you want to restart your timer.".format(ctx.best_prefix) + ) + + # Perform the actual sync + diff = target.stage_start - timer.stage_start + if flags['end']: + diff += (target.current_stage.duration - timer.current_stage.duration) * 60 + + timer.shift(diff) + + content = "Timer synced with **{}**!".format(target.name) + asyncio.create_task( + live_edit( + None, + _status_msg, + 'status', + timer=sub.timer, + ctx=ctx, + content=content, + reference=ctx.msg + ) + ) diff --git a/bot/commands/timer_admin.py b/bot/commands/timer_admin.py new file mode 100644 index 0000000..b61fc10 --- /dev/null +++ b/bot/commands/timer_admin.py @@ -0,0 +1,50 @@ +from utils import seekers, ctx_addons # noqa +from wards import has_timers, timer_admin + +from Timer import module + + +@module.cmd("forcekick", + group="Timer Control", + short_help="Kick a member from a study group.", + aliases=('kick',)) +@has_timers() +@timer_admin() +async def cmd_forcekick(ctx): + """ + Usage``: + {prefix}forcekick + Description: + Forcefully unsubscribe a group member. + + *Requires timer admin permissions.* + Examples``: + {prefix}forcekick {ctx.author.name} + """ + subscribers = [ + sub for timer in ctx.timers.get_timers_in(ctx.guild.id) for sub in timer.subscribers.values() + ] + members = [ + sub.member for sub in subscribers if sub.member + ] + if len(members) != len(subscribers): + # There are some subscribers without a member! First get them + for sub in subscribers: + if not sub.member: + await sub._fetch_member() + members = [ + sub.member for sub in subscribers if sub.member + ] + + member = await ctx.find_member(ctx.args, collection=members, silent=True) + if not member: + return await ctx.error_reply("No subscriber found matching `{}`!".format(ctx.args)) + + sub = ctx.timers.get_subscriber(member.id, member.guild.id) + if not sub: + return await ctx.error_reply("This member is no longer subscribed!") + + await sub.timer.unsubscribe(sub.userid, post=True) + + if ctx.ch != sub.timer.channel: + await ctx.embed_reply("{} was unsubscribed.".format(member.mention)) diff --git a/bot/commands/timer_config.py b/bot/commands/timer_config.py new file mode 100644 index 0000000..173f18c --- /dev/null +++ b/bot/commands/timer_config.py @@ -0,0 +1,154 @@ +import discord + +from utils.lib import prop_tabulate +from settings import TimerSettings, UserInputError +from wards import has_timers + +from Timer import module + + +@module.cmd("timerconfig", + group="Group Admin", + short_help="Advanced timer configuration.", + aliases=("tconfig", "groupconfig")) +@has_timers() +async def cmd_tconfig(ctx): + """ + Usage: + `{prefix}tconfig help` (*See short descriptions of all the timer settings.*) + `{prefix}tconfig [timer name]` (*See the current settings for the given timer.*) + `{prefix}tconfig [timer name] ` (*See details about the given setting.*) + `{prefix}tconfig [timer name] ` (*Modify a setting in the given timer.*) + Description: + View or set advanced timer settings. + + The `timer name` argument is optional and you will be prompted to select a timer if it is not provided. \ + However, **if the timer name contains a space it must be given in quotes**. + Partial timer names are also supported. + + *Modifying timer settings requires at least timer admin permissions.* + Examples``: + {prefix}tconfig help + {prefix}tconfig "{ctx.example_group_name}" + {prefix}tconfig "{ctx.example_group_name}" default_pattern + {prefix}tconfig "{ctx.example_group_name}" default_pattern 50/10 + """ + # Cache and map setting info + timers = ctx.timers.get_timers_in(ctx.guild.id) + timer_names = (timer.name.lower() for timer in timers) + setting_displaynames = {setting.display_name.lower(): setting for setting in TimerSettings.settings.values()} + args = ctx.args + + cats = {} # Timer setting categories + for setting in TimerSettings.settings.values(): + if setting.category not in cats: + cats[setting.category] = {} + cats[setting.category][setting.display_name] = setting + + # Parse + timer = None + setting = None + value = None + if args.lower() == 'help': + # No parsing to do + # Signified by empty timer value + pass + elif args: + splits = args[1:].split('"', maxsplit=1) if args.startswith('"') else args.split(maxsplit=1) + maybe_name = splits[0] + if maybe_name.lower() in setting_displaynames and maybe_name.lower() not in timer_names: + # Assume the provided name is a setting name + setting = setting_displaynames[maybe_name.lower()] + value = splits[1] if len(splits) > 1 else None + + # Retrieve the timer from context, or prompt + sub = ctx.timers.get_subscriber(ctx.author.id, ctx.guild.id) + if sub is not None: + timer = sub.timer + elif len(timers) == 1: + timer = timers[0] + else: + timer = await ctx.get_timers_matching( + '', channel_only=False, info=True, + header="Please select a group to configure." + ) + else: + timer = await ctx.get_timers_matching(maybe_name, channel_only=False, info=True) + if not timer: + return await ctx.error_reply("No groups found matching `{}`.".format(maybe_name)) + if len(splits) > 1: + remaining_splits = splits[1].split(maxsplit=1) + setting = setting_displaynames.get(remaining_splits[0].lower(), None) + if setting is None: + return await ctx.error_reply( + "`{}`is not a timer setting!\n" + "Use `{}tconfig \"{}\"` to see the available settings.".format( + remaining_splits[1], ctx.best_prefix, timer.name + ) + ) + if len(remaining_splits) > 1: + value = remaining_splits[1] + else: + # Retrieve the timer from context, or prompt + sub = ctx.timers.get_subscriber(ctx.author.id, ctx.guild.id) + if sub is not None: + timer = sub.timer + elif len(timers) == 1: + timer = timers[0] + else: + timer = await ctx.get_timers_matching( + '', channel_only=False, info=True, + header="Please select a group to view." + ) + + # Handle different modes + if timer is None or setting is None: + # Display timer configuration or descriptions + fields = ( + (cat, prop_tabulate(*zip(*( + (setting.display_name, setting.get(timer.roleid).formatted if timer is not None else setting.desc) + for name, setting in cat_settings.items() + )))) + for cat, cat_settings in cats.items() + ) + if timer: + embed = discord.Embed( + title="Timer configuration for `{}`".format(timer.name), + description=( + "**Tip:** See `{0}help tconfig` for command usage and examples, " + "and `{0}tconfig help` to see short descriptions of each setting.".format(ctx.best_prefix) + ) + ) + else: + embed = discord.Embed( + title="Timer configuration options", + description=( + "**Tip:** See `{}help tconfig` for command usage and examples.".format(ctx.best_prefix) + ) + ) + embed.set_footer( + text="Use \"{}tconfig timer setting [value]\" to see or modify a setting.".format( + ctx.best_prefix + ) + ) + for i, (name, value) in enumerate(fields): + embed.add_field(name=name, value=value, inline=(bool(timer) and bool((i + 1) % 3))) + await ctx.reply(embed=embed) + elif value is None: + # Display setting information for the given timer and value + await ctx.reply(embed=setting.get(timer.roleid).embed) + else: + # Check the write ward + if not await setting.write_ward.run(ctx): + return await ctx.error_reply(setting.msg) + + # Write the setting value + try: + (await setting.parse(timer.roleid, ctx, value)).write() + except UserInputError as e: + await ctx.reply(embed=discord.Embed( + description="{} {}".format('❌', e.msg), + color=discord.Colour.red() + )) + else: + await ctx.reply(embed=discord.Embed(description="{} Setting updated!".format('✅'))) diff --git a/bot/commands/user_config.py b/bot/commands/user_config.py new file mode 100644 index 0000000..101f0db --- /dev/null +++ b/bot/commands/user_config.py @@ -0,0 +1,44 @@ +from settings import UserSettings + +from Timer import module + + +@module.cmd( + "mytimezone", + group="Personal Settings", + short_help=("Timezone for displaying session data. " + "(Currently {ctx.author_settings.timezone.formatted})"), + aliases=('mytz',) +) +async def cmd_mytimezone(ctx): + """ + Usage``: + {prefix}mytimezone + {prefix}mytimezone + Setting Description: + {ctx.author_settings.settings.timezone.long_desc} + Accepted Values: + Timezone names must be from the "TZ Database Name" column of \ + [this list](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones). + For example, `Europe/London`, `Australia/Melbourne`, or `America/New_York`. + """ + await UserSettings.settings.timezone.command(ctx, ctx.author.id) + + +@module.cmd( + "notify", + group="Personal Settings", + short_help=("DM notification level. " + "(Currently {ctx.author_settings.notify_level.formatted})") +) +async def cmd_notify(ctx): + """ + Usage``: + {prefix}notify + {prefix}notify + Setting Description: + {ctx.author_settings.settings.notify_level.long_desc} + Accepted Values: + {ctx.author_settings.settings.notify_level.accepted_table} + """ + await UserSettings.notify_level.command(ctx, ctx.author.id) diff --git a/bot/data/__init__.py b/bot/data/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bot/data/data.py b/bot/data/data.py new file mode 100644 index 0000000..f29b9e7 --- /dev/null +++ b/bot/data/data.py @@ -0,0 +1,481 @@ +import os +import logging +import contextlib +from datetime import datetime +from itertools import chain +from enum import Enum + +import sqlite3 as sq +from cachetools import LRUCache +from meta import log + + +# Database constants +DB_PATH = 'data/data.db' +SCHEMA_PATH = 'data/schema.sql' +REQUIRED_VERSION = 1 + + +# Set up database connection +requires_init = not os.path.exists(DB_PATH) + +log("Establishing connection.", "DB_INIT", level=logging.DEBUG) +conn = sq.connect(DB_PATH, timeout=20, isolation_level=None) +conn.row_factory = sq.Row +conn.set_trace_callback(lambda message: log(message, context="DB_CONNECTOR", level=logging.DEBUG)) +sq.register_adapter(datetime, lambda dt: dt.timestamp()) + + +# Initialise the database if it was just created +if requires_init: + log("Running first-time setup.", "DB_INIT") + # Execute schema file + with conn: + with open(SCHEMA_PATH, 'r') as script: + conn.executescript(script.read()) + +# Check the version matches the required version +with conn: + log("Checking db version.", "DB_INIT") + cursor = conn.cursor() + + # Check if table exists + cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='VersionHistory'") + version_exists = cursor.fetchone() + if not version_exists: + # Create version table and insert version 0 + cursor.execute('CREATE TABLE VersionHistory (version INTEGER NOT NULL, time INTEGER NOT NULL)') + now = datetime.timestamp(datetime.utcnow()) + cursor.execute('INSERT INTO VersionHistory VALUES (0, {})'.format(datetime.timestamp(datetime.utcnow()))) + + # Get last entry in version table, compare against desired version + cursor.execute("SELECT * FROM VersionHistory ORDER BY rowid DESC LIMIT 1") + current_version, _, _ = cursor.fetchone() + + if current_version != REQUIRED_VERSION: + # Complain + raise Exception( + ("Database version is {}, required version is {}. " + "Please migrate database.").format(current_version, REQUIRED_VERSION) + ) + + cursor.close() + + +log("Established connection.", "DB_INIT") + + +# --------------- Data Interface Classes --------------- +class Table: + """ + Transparent interface to a single table structure in the database. + Contains standard methods to access the table. + Intended to be subclassed to provide more derivative access for specific tables. + """ + conn = conn + + def __init__(self, name): + self.name = name + + def select_where(self, *args, **kwargs): + with self.conn: + return select_where(self.name, *args, **kwargs) + + def select_one_where(self, *args, **kwargs): + with self.conn: + rows = self.select_where(*args, **kwargs) + return rows[0] if rows else None + + def update_where(self, *args, **kwargs): + with self.conn: + return update_where(self.name, *args, **kwargs) + + def delete_where(self, *args, **kwargs): + with self.conn: + return delete_where(self.name, *args, **kwargs) + + def insert(self, *args, **kwargs): + with self.conn: + return insert(self.name, *args, **kwargs) + + def insert_many(self, *args, **kwargs): + with self.conn: + return insert_many(self.name, *args, **kwargs) + + def upsert(self, *args, **kwargs): + with self.conn: + return upsert(self.name, *args, **kwargs) + + +class Row: + __slots__ = ('table', 'data', '_pending') + + conn = conn + + def __init__(self, table, data, *args, **kwargs): + super().__setattr__('table', table) + self.data = data + self._pending = None + + @property + def rowid(self): + return self.data[self.table.id_col] + + def __repr__(self): + return "Row[{}]({})".format( + self.table.name, + ', '.join("{}={!r}".format(field, getattr(self, field)) for field in self.table.columns) + ) + + def __getattr__(self, key): + if key in self.table.columns: + if self._pending and key in self._pending: + return self._pending[key] + else: + return self.data[key] + else: + raise AttributeError(key) + + def __setattr__(self, key, value): + if key in self.table.columns: + if self._pending is None: + self.update(**{key: value}) + else: + self._pending[key] = value + else: + super().__setattr__(key, value) + + @contextlib.contextmanager + def batch_update(self): + if self._pending: + raise ValueError("Nested batch updates for {}!".format(self.__class__.__name__)) + + self._pending = {} + try: + yield self._pending + finally: + self.update(**self._pending) + self._pending = None + + def _refresh(self): + row = self.table.select_one_where(**{self.table.id_col: self.rowid}) + if not row: + raise ValueError("Refreshing a {} which no longer exists!".format(type(self).__name__)) + self.data = row + + def update(self, **values): + rows = self.table.update_where(values, **{self.table.id_col: self.rowid}) + self.data = rows[0] + + @classmethod + def _select_where(cls, _extra=None, **conditions): + return select_where(cls._table, **conditions) + + @classmethod + def _insert(cls, **values): + return insert(cls._table, **values) + + @classmethod + def _update_where(cls, values, **conditions): + return update_where(cls._table, values, **conditions) + + +class RowTable(Table): + __slots__ = ( + 'name', + 'columns', + 'id_col', + 'row_cache' + ) + + conn = conn + + def __init__(self, name, columns, id_col, use_cache=True, cache=None, cache_size=1000): + self.name = name + self.columns = columns + self.id_col = id_col + self.row_cache = (cache or LRUCache(cache_size)) if use_cache else None + + # Extend original Table update methods to modify the cached rows + def update_where(self, *args, **kwargs): + data = super().update_where(*args, **kwargs) + if self.row_cache is not None: + for data_row in data: + cached_row = self.row_cache.get(data_row[self.id_col], None) + if cached_row is not None: + cached_row.data = data_row + return data + + def delete_where(self, *args, **kwargs): + data = super().delete_where(*args, **kwargs) + if self.row_cache is not None: + for data_row in data: + self.row_cache.pop(data_row[self.id_col], None) + return data + + def upsert(self, *args, **kwargs): + data = super().upsert(*args, **kwargs) + if self.row_cache is not None: + cached_row = self.row_cache.get(data[self.id_col], None) + if cached_row is not None: + cached_row.data = data + return data + + # New methods to fetch and create rows + def _make_rows(self, *data_rows): + """ + Create or retrieve Row objects for each provided data row. + If the rows already exist in cache, updates the cached row. + """ + if self.row_cache is not None: + rows = [] + for data_row in data_rows: + rowid = data_row[self.id_col] + + cached_row = self.row_cache.get(rowid, None) + if cached_row is not None: + cached_row.data = data_row + row = cached_row + else: + row = Row(self, data_row) + self.row_cache[rowid] = row + rows.append(row) + else: + rows = [Row(self, data_row) for data_row in data_rows] + return rows + + def create_row(self, *args, **kwargs): + data = self.insert(*args, **kwargs) + return self._make_rows(data)[0] + + def fetch_rows_where(self, *args, **kwargs): + # TODO: Handle list of rowids here? + data = self.select_where(*args, **kwargs) + return self._make_rows(*data) + + def fetch(self, rowid): + """ + Fetch the row with the given id, retrieving from cache where possible. + """ + row = self.row_cache.get(rowid, None) if self.row_cache is not None else None + if row is None: + rows = self.fetch_rows_where(**{self.id_col: rowid}) + row = rows[0] if rows else None + return row + + def fetch_or_create(self, rowid=None, **kwargs): + """ + Helper method to fetch a row with the given id or fields, or create it if it doesn't exist. + """ + if rowid is not None: + row = self.fetch(rowid) + else: + data = self.select_where(**kwargs) + row = self._make_rows(data[0])[0] if data else None + + if row is None: + creation_kwargs = kwargs + if rowid is not None: + creation_kwargs[self.id_col] = rowid + row = self.create_row(**creation_kwargs) + return row + + +# --------------- Query Builders --------------- +def select_where(table, select_columns=None, cursor=None, _extra='', **conditions): + """ + Select rows from the given table matching the conditions + """ + criteria, criteria_values = _format_conditions(conditions) + col_str = _format_selectkeys(select_columns) + + if conditions: + where_str = "WHERE {}".format(criteria) + else: + where_str = "" + + cursor = cursor or conn.cursor() + cursor.execute( + 'SELECT {} FROM {} {} {}'.format(col_str, table, where_str, _extra), + criteria_values + ) + return cursor.fetchall() + + +def update_where(table, valuedict, cursor=None, **conditions): + """ + Update rows in the given table matching the conditions + """ + key_str, key_values = _format_updatestr(valuedict) + criteria, criteria_values = _format_conditions(conditions) + + if conditions: + where_str = "WHERE {}".format(criteria) + else: + where_str = "" + + cursor = cursor or conn.cursor() + cursor.execute( + 'UPDATE {} SET {} {} RETURNING *'.format(table, key_str, where_str), + tuple((*key_values, *criteria_values)) + ) + conn.commit() + return cursor.fetchall() + + +def delete_where(table, cursor=None, **conditions): + """ + Delete rows in the given table matching the conditions + """ + criteria, criteria_values = _format_conditions(conditions) + + cursor = cursor or conn.cursor() + cursor.execute( + 'DELETE FROM {} WHERE {}'.format(table, criteria), + criteria_values + ) + conn.commit() + return cursor.fetchall() + + +def insert(table, cursor=None, allow_replace=False, **values): + """ + Insert the given values into the table + """ + keys, values = zip(*values.items()) + + key_str = _format_insertkeys(keys) + value_str, values = _format_insertvalues(values) + + action = 'REPLACE' if allow_replace else 'INSERT' + + # log(str(values)) + + cursor = cursor or conn.cursor() + cursor.execute( + '{} INTO {} {} VALUES {} RETURNING *'.format(action, table, key_str, value_str), + values + ) + conn.commit() + return cursor.fetchone() + + +def insert_many(table, *value_tuples, insert_keys=None, cursor=None): + """ + Insert all the given values into the table + """ + key_str = _format_insertkeys(insert_keys) + value_strs, value_tuples = zip(*(_format_insertvalues(value_tuple) for value_tuple in value_tuples)) + + value_str = ", ".join(value_strs) + values = tuple(chain(*value_tuples)) + + cursor = cursor or conn.cursor() + cursor.execute( + 'INSERT INTO {} {} VALUES {} RETURNING *'.format(table, key_str, value_str), + values + ) + conn.commit() + return cursor.fetchall() + + +def upsert(table, constraint, cursor=None, **values): + """ + Insert or on conflict update. + """ + valuedict = values + keys, values = zip(*values.items()) + + key_str = _format_insertkeys(keys) + value_str, values = _format_insertvalues(values) + update_key_str, update_key_values = _format_updatestr(valuedict) + + if not isinstance(constraint, str): + constraint = ", ".join(constraint) + + cursor = cursor or conn.cursor() + cursor.execute( + 'INSERT INTO {} {} VALUES {} ON CONFLICT({}) DO UPDATE SET {} RETURNING *'.format( + table, key_str, value_str, constraint, update_key_str + ), + tuple((*values, *update_key_values)) + ) + conn.commit() + return cursor.fetchone() + + +# --------------- Query Formatting Tools --------------- +# Replace char used by the connection for query formatting +_replace_char: str = '?' + + +class fieldConstants(Enum): + """ + A collection of database field constants to use for selection conditions. + """ + NULL = "IS NULL" + NOTNULL = "IS NOT NULL" + + +def _format_conditions(conditions): + """ + Formats a dictionary of conditions into a string suitable for 'WHERE' clauses. + Supports `IN` type conditionals. + """ + if not conditions: + return ("", tuple()) + + values = [] + conditional_strings = [] + for key, item in conditions.items(): + if isinstance(item, (list, tuple)): + conditional_strings.append("{} IN ({})".format(key, ", ".join([_replace_char] * len(item)))) + values.extend(item) + elif isinstance(item, fieldConstants): + conditional_strings.append("{} {}".format(key, item.value)) + else: + conditional_strings.append("{}={}".format(key, _replace_char)) + values.append(item) + + return (' AND '.join(conditional_strings), values) + + +def _format_selectkeys(keys): + """ + Formats a list of keys into a string suitable for `SELECT`. + """ + if not keys: + return "*" + else: + return ", ".join(keys) + + +def _format_insertkeys(keys): + """ + Formats a list of keys into a string suitable for `INSERT` + """ + if not keys: + return "" + else: + return "({})".format(", ".join(keys)) + + +def _format_insertvalues(values): + """ + Formats a list of values into a string suitable for `INSERT` + """ + value_str = "({})".format(", ".join(_replace_char for value in values)) + return (value_str, values) + + +def _format_updatestr(valuedict): + """ + Formats a dictionary of keys and values into a string suitable for 'SET' clauses. + """ + if not valuedict: + return ("", tuple()) + keys, values = zip(*valuedict.items()) + + set_str = ", ".join("{} = {}".format(key, _replace_char) for key in keys) + + return (set_str, values) diff --git a/bot/data/queries.py b/bot/data/queries.py new file mode 100644 index 0000000..1c27678 --- /dev/null +++ b/bot/data/queries.py @@ -0,0 +1,18 @@ +""" +Collection of stored data queries and procedures. +""" + +from . import tables +# from .data import _format_conditions + + +def get_session_user_totals(start_ts, **kwargs): + sum_column = ( + "SUM(IIF(start_time < {start_ts}, duration - ({start_ts} - start_time), duration)) AS total" + ).format(start_ts=start_ts) if start_ts else "SUM(duration) AS total" + + return tables.session_patterns.select_where( + select_columns=('userid', 'name', sum_column), + _extra='AND start_time + duration > {} GROUP BY userid'.format(start_ts), + **kwargs + ) diff --git a/bot/data/tables.py b/bot/data/tables.py new file mode 100644 index 0000000..99c3585 --- /dev/null +++ b/bot/data/tables.py @@ -0,0 +1,46 @@ +from .data import RowTable, Table + +from cachetools import LFUCache +from weakref import WeakValueDictionary + + +guilds = RowTable( + 'guilds', + ('guildid', 'timer_admin_roleid', 'show_tips', + 'autoclean', 'timezone', 'prefix', 'globalgroups', 'studyrole_roleid'), + 'guildid', + cache_size=2500 +) + +users = RowTable( + 'users', + ('userid', 'notify_level', 'timezone', 'name'), + 'userid', + cache_size=2000 +) + +patterns = RowTable( + 'patterns', + ('patternid', 'short_repr', 'stage_str', 'created_at'), + 'patternid', + cache=LFUCache(1000) +) + +timers = RowTable( + 'timers', + ('roleid', 'guildid', 'name', 'channelid', 'patternid', + 'voice_channelid', 'voice_alert', 'track_voice_join', 'track_voice_leave', + 'auto_reset', 'admin_locked', 'track_role', 'compact', + 'voice_channel_name', + ), + # 'default_work_name', 'default_work_message', + # 'default_break_name', 'default_break_message'), + 'roleid', + cache=WeakValueDictionary() +) +sessions = Table('sessions') +session_patterns = Table('session_patterns') +timer_pattern_history = Table('timer_pattern_history') + +user_presets = Table('user_presets') +guild_presets = Table('guild_presets') diff --git a/bot/dev-main.py b/bot/dev-main.py new file mode 100644 index 0000000..29361dd --- /dev/null +++ b/bot/dev-main.py @@ -0,0 +1,7 @@ +import logging +import meta + +meta.logger.logger.setLevel(logging.DEBUG) +logging.getLogger("discord").setLevel(logging.INFO) + +import main # noqa diff --git a/bot/main.py b/bot/main.py index 66488d3..4444b02 100644 --- a/bot/main.py +++ b/bot/main.py @@ -1,31 +1,34 @@ import os -from config import conf -from logger import log -from cmdClient.cmdClient import cmdClient +from data import tables, data # noqa +from meta import client, conf + +import Timer # noqa -from BotData import BotData -from Timer import TimerInterface # Get the real location __location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__))) -# Load required data from configs -masters = [int(master.strip()) for master in conf['masters'].split(",")] -config = BotData(app="pomo", data_file="data/config_data.db", version=0) - -# Initialise the client -client = cmdClient(prefix=conf['prefix'], owners=masters) -client.config = config -client.log = log - # Load the commands client.load_dir(os.path.join(__location__, 'commands')) +client.load_dir(os.path.join(__location__, 'plugins')) +# TODO: Recursive plugin loader # Initialise the timer -TimerInterface(client, conf['session_store']) +# TimerInterface(client, conf['session_store']) +client.initialise_modules() + + +@client.set_valid_prefixes +async def valid_prefixes(client, message): + return ( + (tables.guilds.fetch_or_create(message.guild.id).prefix if message.guild else None) or client.prefix, + '<@{}>'.format(client.user.id), + '<@!{}>'.format(client.user.id), + ) + # Log and execute! -log("Initial setup complete, logging in", context='SETUP') +client.log("Initial setup complete, logging in", context='SETUP') client.run(conf['TOKEN']) diff --git a/bot/meta/__init__.py b/bot/meta/__init__.py new file mode 100644 index 0000000..b38ada2 --- /dev/null +++ b/bot/meta/__init__.py @@ -0,0 +1,3 @@ +from .client import client +from .config import conf +from .logger import log diff --git a/bot/meta/client.py b/bot/meta/client.py new file mode 100644 index 0000000..3a5d334 --- /dev/null +++ b/bot/meta/client.py @@ -0,0 +1,10 @@ +from cmdClient.cmdClient import cmdClient + +from .config import conf +from .logger import log + + +# Initialise client +masters = [int(master.strip()) for master in conf['masters'].split(",")] +client = cmdClient(prefix=conf['prefix'], owners=masters) +client.log = log diff --git a/bot/config.py b/bot/meta/config.py similarity index 100% rename from bot/config.py rename to bot/meta/config.py diff --git a/bot/logger.py b/bot/meta/logger.py similarity index 65% rename from bot/logger.py rename to bot/meta/logger.py index 45937a2..129f850 100644 --- a/bot/logger.py +++ b/bot/meta/logger.py @@ -1,7 +1,8 @@ import sys +import traceback import logging -from config import conf +from .config import conf # Setup the logger @@ -17,6 +18,8 @@ logger.setLevel(logging.INFO) -def log(message, context="Global".center(18, '='), level=logging.INFO): +def log(message, context="Global".center(22, '='), level=logging.INFO, add_exc_info=False): + if add_exc_info: + message += '\n{}'.format(traceback.format_exc()) for line in message.split('\n'): - logger.log(level, '[{}] {}'.format(str(context).center(18, '='), line)) + logger.log(level, '[{}] {}'.format(str(context).center(22, '='), line)) diff --git a/bot/commands/exec.py b/bot/plugins/exec-cmds.py similarity index 100% rename from bot/commands/exec.py rename to bot/plugins/exec-cmds.py diff --git a/bot/settings/__init__.py b/bot/settings/__init__.py new file mode 100644 index 0000000..05956a5 --- /dev/null +++ b/bot/settings/__init__.py @@ -0,0 +1,5 @@ +from .base import Setting, ObjectSettings, UserInputError + +from .guild_settings import GuildSettings, GuildSetting +from .timer_settings import TimerSettings, TimerSetting +from .user_settings import UserSettings, UserSetting diff --git a/bot/settings/base.py b/bot/settings/base.py new file mode 100644 index 0000000..1851c91 --- /dev/null +++ b/bot/settings/base.py @@ -0,0 +1,302 @@ +import discord +from cmdClient.cmdClient import cmdClient, Context +from cmdClient.lib import SafeCancellation +from cmdClient.Check import Check + +from utils.lib import prop_tabulate, DotDict + +from meta import client +from data.data import Table, RowTable + + +class Setting: + """ + Abstract base class describing a stored configuration setting. + A setting consists of logic to load the setting from storage, + present it in a readable form, understand user entered values, + and write it again in storage. + Additionally, the setting has attributes attached describing + the setting in a user-friendly manner for display purposes. + """ + attr_name: str = None # Internal attribute name for the setting + _default: ... = None # Default data value for the setting.. this may be None if the setting overrides 'default'. + + write_ward: Check = None # Check that must be passed to write the setting. Not implemented internally. + + # Configuration interface descriptions + display_name: str = None # User readable name of the setting + desc: str = None # User readable brief description of the setting + long_desc: str = None # User readable long description of the setting + accepts: str = None # User readable description of the acceptable values + + def __init__(self, id, data: ..., **kwargs): + self.client: cmdClient = client + self.id = id + self._data = data + + # Configuration embeds + @property + def embed(self): + """ + Discord Embed showing an information summary about the setting. + """ + embed = discord.Embed( + title="Configuration options for `{}`".format(self.display_name), + ) + fields = ("Current value", "Default value", "Accepted input") + values = (self.formatted or "Not Set", + self._format_data(self.id, self.default) or "None", + self.accepts) + table = prop_tabulate(fields, values) + embed.description = "{}\n{}".format(self.long_desc.format(self=self, client=self.client), table) + return embed + + @property + def success_response(self): + """ + Response message sent when the setting has successfully been updated. + """ + return "Setting Updated!" + + # Instance generation + @classmethod + def get(cls, id: int, **kwargs): + """ + Return a setting instance initialised from the stored value. + """ + data = cls._reader(id, **kwargs) + return cls(id, data, **kwargs) + + @classmethod + async def parse(cls, id: int, ctx: Context, userstr: str, **kwargs): + """ + Return a setting instance initialised from a parsed user string. + """ + data = await cls._parse_userstr(ctx, id, userstr, **kwargs) + return cls(id, data, **kwargs) + + # Main interface + @property + def data(self): + """ + Retrieves the current internal setting data if it is set, otherwise the default data + """ + return self._data if self._data is not None else self.default + + @data.setter + def data(self, new_data): + """ + Sets the internal setting data and writes the changes. + """ + self._data = new_data + self.write() + + @property + def default(self): + """ + Retrieves the default value for this setting. + Settings should override this if the default depends on the object id. + """ + return self._default + + @property + def value(self): + """ + Discord-aware object or objects associated with the setting. + """ + return self._data_to_value(self.id, self.data) + + @value.setter + def value(self, new_value): + """ + Setter which reads the discord-aware object, converts it to data, and writes it. + """ + self._data = self._data_from_value(self.id, new_value) + self.write() + + @property + def formatted(self): + """ + User-readable form of the setting. + """ + return self._format_data(self.id, self.data) + + def write(self, **kwargs): + """ + Write value to the database. + For settings which override this, + ensure you handle deletion of values when internal data is None. + """ + self._writer(self.id, self._data, **kwargs) + + # Raw converters + @classmethod + def _data_from_value(cls, id: int, value, **kwargs): + """ + Convert a high-level setting value to internal data. + Must be overriden by the setting. + Be aware of None values, these should always pass through as None + to provide an unsetting interface. + """ + raise NotImplementedError + + @classmethod + def _data_to_value(cls, id: int, data: ..., **kwargs): + """ + Convert internal data to high-level setting value. + Must be overriden by the setting. + """ + raise NotImplementedError + + @classmethod + async def _parse_userstr(cls, ctx: Context, id: int, userstr: str, **kwargs): + """ + Parse user provided input into internal data. + Must be overriden by the setting if the setting is user-configurable. + """ + raise NotImplementedError + + @classmethod + def _format_data(cls, id: int, data: ..., **kwargs): + """ + Convert internal data into a formatted user-readable string. + Must be overriden by the setting if the setting is user-viewable. + """ + raise NotImplementedError + + # Database access classmethods + @classmethod + def _reader(cls, id: int, **kwargs): + """ + Read a setting from storage and return setting data or None. + Must be overriden by the setting. + """ + raise NotImplementedError + + @classmethod + def _writer(cls, id: int, data: ..., **kwargs): + """ + Write provided setting data to storage. + Must be overriden by the setting unless the `write` method is overidden. + If the data is None, the setting is empty and should be unset. + """ + raise NotImplementedError + + @classmethod + async def command(cls, ctx, id): + """ + Standardised command viewing/setting interface for the setting. + """ + if not ctx.args: + # View config embed for provided cls + await ctx.reply(embed=cls.get(id).embed) + else: + # Check the write ward + if not await cls.write_ward.run(ctx): + await ctx.error_reply(cls.msg) + else: + # Attempt to set config cls + try: + cls = await cls.parse(id, ctx, ctx.args) + except UserInputError as e: + await ctx.reply(embed=discord.Embed( + description="{} {}".format('❌', e.msg), + Colour=discord.Colour.red() + )) + else: + cls.write() + await ctx.reply(embed=discord.Embed( + description="{} {}".format('✅', cls.success_response), + Colour=discord.Colour.green() + )) + + +class ObjectSettings: + """ + Abstract class representing a linked collection of settings for a single object. + Initialised settings are provided as instance attributes in the form of properties. + """ + __slots__ = ('id', 'params') + + settings: DotDict = None + + def __init__(self, id, **kwargs): + self.id = id + self.params = tuple(kwargs.items()) + + @classmethod + def _setting_property(cls, setting): + def wrapped_setting(self): + return setting.get(self.id, **dict(self.params)) + return wrapped_setting + + @classmethod + def attach_setting(cls, setting: Setting): + name = setting.attr_name or setting.__name__ + setattr(cls, name, property(cls._setting_property(setting))) + cls.settings[name] = setting + + +class ColumnData: + """ + Mixin for settings stored in a single row and column of a Table. + Intended to be used with tables where the only primary key is the object id. + """ + # Table storing the desired data + _table_interface: Table = None + + # Name of the column storing the setting object id + _id_column: str = None + + # Name of the column with the desired data + _data_column: str = None + + # Whether to upsert or update for updates + _upsert: bool = True + + @classmethod + def _reader(cls, id: int, **kwargs): + """ + Read in the requested entry associated to the id. + Supports reading cached values from a `RowTable`. + """ + table = cls._table_interface + if isinstance(table, RowTable) and cls._id_column == table.id_col: + row = table.fetch(id) + return row.data[cls._data_column] if row else None + else: + params = { + "select_columns": (cls._data_column,), + cls._id_column: id + } + row = table.select_one_where(**params) + return row[cls._data_column] if row else None + + @classmethod + def _writer(cls, id: int, data: ..., **kwargs): + """ + Write the provided entry to the table, allowing replacements. + """ + table = cls._table_interface + params = { + cls._id_column: id + } + values = { + cls._data_column: data + } + + # Update data + if cls._upsert: + # Upsert data + table.upsert( + constraint=cls._id_column, + **params, + **values + ) + else: + # Update data + table.update_where(values, **params) + + +class UserInputError(SafeCancellation): + pass diff --git a/bot/settings/guild_settings.py b/bot/settings/guild_settings.py new file mode 100644 index 0000000..b097167 --- /dev/null +++ b/bot/settings/guild_settings.py @@ -0,0 +1,163 @@ +import datetime + +from data import tables +from wards import timer_admin, guild_admin +from meta import client +from utils.lib import DotDict + +from .base import Setting, ObjectSettings, ColumnData, UserInputError +from .setting_types import Role, Boolean, String, Timezone + + +class GuildSettings(ObjectSettings): + settings = DotDict() + + +class GuildSetting(ColumnData, Setting): + _table_interface = tables.guilds + _id_column = 'guildid' + + write_ward = timer_admin + + +@GuildSettings.attach_setting +class timer_admin_role(Role, GuildSetting): + attr_name = 'timer_admin_role' + _data_column = 'timer_admin_roleid' + + write_ward = guild_admin + + display_name = 'timer_admin_role' + desc = 'Role required to create and configure timers.' + long_desc = ( + "Role required to create and configure timers.\n" + "Having this role allows members to use most configuration commands " + "such as those under `Group Admin`, `Server Configuration`, " + "and `Registry Admin`.\n" + "Having the administrator server permission also allows use of these commands, " + "and some commands, such as `timeradmin` itself, require this permission instead.\n" + "(Required permissions for commands are listed in their `help` pages.)" + ) + + @property + def success_response(self): + return "The timer admin role is now {}.".format(self.formatted) + + +@GuildSettings.attach_setting +class show_tips(Boolean, GuildSetting): + attr_name = 'show_tips' + _data_column = 'show_tips' + + _default = True + + display_name = 'display_tips' + desc = 'Display usage tips for setting up and using the bot.' + long_desc = ( + "Display usage tips and hints on the output of various commands." + ) + + @property + def success_response(self): + return "Usage tips are now {}.".format("Enabled" if self.value else "Disabled") + + +@GuildSettings.attach_setting +class globalgroups(Boolean, GuildSetting): + attr_name = 'globalgroups' + _data_column = 'globalgroups' + + _default = False + + display_name = 'globalgroups' + desc = 'Whether timers may be joined from any channel.' + long_desc = ( + "By default, groups may only be joined from the text channel they are bound to. " + "This setting allows members to join study groups from any channel." + ) + + @property + def success_response(self): + if self.value: + return "Groups may now be joined from any channel." + else: + return "Groups may now only be joined from the text channel they are bound to." + + +@GuildSettings.attach_setting +class prefix(String, GuildSetting): + attr_name = 'prefix' + _data_column = 'prefix' + + _default = client.prefix + + write_ward = guild_admin + + display_name = 'prefix' + desc = 'The bot command prefix.' + long_desc = ( + "The command prefix required to run any command.\n" + "My mention will also always function as a prefix." + ) + + @property + def success_response(self): + return "The command prefix is now `{0}`. (E.g. `{0}help`.)".format(self.value) + + +@GuildSettings.attach_setting +class timezone(Timezone, GuildSetting): + attr_name = 'timezone' + _data_column = 'timezone' + + _default = 'UTC' + + write_ward = guild_admin + + display_name = 'timezone' + desc = 'The server leaderboard timezone.' + long_desc = ( + "The leaderboard timezone.\n" + "The current day/week/month/year displayed on the leaderboard will be calculated using this timezone." + ) + + @property + def success_response(self): + return ( + "The leaderboard timezone is now {}. " + "The current time is **{}**." + ).format(self.formatted, datetime.datetime.now(tz=self.value).strftime("%H:%M")) + + +@GuildSettings.attach_setting +class studyrole(Role, GuildSetting): + attr_name = 'studyrole' + _data_column = 'studyrole_roleid' + + write_ward = guild_admin + + display_name = 'studyrole' + desc = 'Common study role given to all timer members.' + long_desc = ( + "This role will be given to members when they join any group, " + "and removed when they leave the group, acting as a global study role.\n" + "The purpose is to facilitate easier simpler study permission management, " + "for example to control what channels studying members see." + ) + + @classmethod + async def _parse_userstr(cls, ctx, id: int, userstr: str, **kwargs): + roleid = await super()._parse_userstr(ctx, id, userstr, **kwargs) + if roleid: + role = ctx.guild.get_role(roleid) + # Check permissions + if role >= ctx.guild.me.top_role: + raise UserInputError("The study role must be lower than my top role!") + return roleid + + @property + def success_response(self): + if self.data: + return "The global study role is now {}.".format(self.value.mention) + else: + return "The global study role has been removed." diff --git a/bot/settings/setting_types.py b/bot/settings/setting_types.py new file mode 100644 index 0000000..43d65b2 --- /dev/null +++ b/bot/settings/setting_types.py @@ -0,0 +1,632 @@ +from enum import IntEnum +from typing import Any, Optional + +import pytz +import discord +from cmdClient.Context import Context +from cmdClient.lib import SafeCancellation + +from meta import client +import Timer + +from .base import UserInputError + + +class SettingType: + """ + Abstract class representing a setting type. + Intended to be used as a mixin for a Setting, + with the provided methods implementing converter methods for the setting. + """ + accepts: str = None # User readable description of the acceptable values + + # Raw converters + @classmethod + def _data_from_value(cls, id: int, value, **kwargs): + """ + Convert a high-level setting value to internal data. + """ + raise NotImplementedError + + @classmethod + def _data_to_value(cls, id: int, data: Any, **kwargs): + """ + Convert internal data to high-level setting value. + """ + raise NotImplementedError + + @classmethod + async def _parse_userstr(cls, ctx: Context, id: int, userstr: str, **kwargs): + """ + Parse user provided input into internal data. + """ + raise NotImplementedError + + @classmethod + def _format_data(cls, id: int, data: Any, **kwargs): + """ + Convert internal data into a formatted user-readable string. + """ + raise NotImplementedError + + +class Boolean(SettingType): + """ + Boolean type, supporting truthy and falsey user input. + Configurable to change truthy and falsey values, and the output map. + + Types: + data: Optional[bool] + The stored boolean value. + value: Optional[bool] + The stored boolean value. + """ + accepts = "Yes/No, On/Off, True/False, Enabled/Disabled" + + # Values that are accepted as truthy and falsey by the parser + _truthy = {"yes", "true", "on", "enable", "enabled"} + _falsey = {"no", "false", "off", "disable", "disabled"} + + # The user-friendly output strings to use for each value + _outputs = {True: "On", False: "Off", None: "Not Set"} + + @classmethod + def _data_from_value(cls, id: int, value: Optional[bool], **kwargs): + """ + Both data and value are of type Optional[bool]. + Directly return the provided value as data. + """ + return value + + @classmethod + def _data_to_value(cls, id: int, data: Optional[bool], **kwargs): + """ + Both data and value are of type Optional[bool]. + Directly return the internal data as the value. + """ + return data + + @classmethod + async def _parse_userstr(cls, ctx: Context, id: int, userstr: str, **kwargs): + """ + Looks up the provided string in the truthy and falsey tables. + """ + _userstr = userstr.lower() + if _userstr == "none": + return None + if _userstr in cls._truthy: + return True + elif _userstr in cls._falsey: + return False + else: + raise UserInputError("Unknown boolean type `{}`".format(userstr)) + + @classmethod + def _format_data(cls, id: int, data: bool, **kwargs): + """ + Pass the provided value through the outputs map. + """ + return cls._outputs[data] + + +class Integer(SettingType): + """ + Integer type. Storing any integer. + + Types: + data: Optional[int] + The stored integer value. + value: Optional[int] + The stored integer value. + """ + accepts = "An integer." + + # Set limits on the possible integers + _min = -4096 + _max = 4096 + + @classmethod + def _data_from_value(cls, id: int, value: Optional[bool], **kwargs): + """ + Both data and value are of type Optional[int]. + Directly return the provided value as data. + """ + return value + + @classmethod + def _data_to_value(cls, id: int, data: Optional[bool], **kwargs): + """ + Both data and value are of type Optional[int]. + Directly return the internal data as the value. + """ + return data + + @classmethod + async def _parse_userstr(cls, ctx: Context, id: int, userstr: str, **kwargs): + """ + Relies on integer casting to convert the user string + """ + if userstr.lower() == "none": + return None + + try: + num = int(userstr) + except Exception: + raise UserInputError("Couldn't parse provided integer.") from None + + if num > cls._max: + raise UserInputError("Provided integer was too large!") + elif num < cls._min: + raise UserInputError("Provided integer was too small!") + + return num + + @classmethod + def _format_data(cls, id: int, data: Optional[int], **kwargs): + """ + Return the string version of the data. + """ + if data is None: + return None + else: + return str(data) + + +class String(SettingType): + """ + String type, storing arbitrary text. + Configurable to limit text length and restrict input options. + + Types: + data: Optional[str] + The stored string. + value: Optional[str] + The stored string. + """ + accepts = "Any text" + + # Maximum length of string to accept + _maxlen: int = None + + # Set of input options to accept + _options: set = None + + # Whether to quote the string as code + _quote: bool = True + + @classmethod + def _data_from_value(cls, id: int, value: Optional[str], **kwargs): + """ + Return the provided value string as the data string. + """ + return value + + @classmethod + def _data_to_value(cls, id: int, data: Optional[str], **kwargs): + """ + Return the provided data string as the value string. + """ + return data + + @classmethod + async def _parse_userstr(cls, ctx: Context, id: int, userstr: str, **kwargs): + """ + Check that the user-entered string is of the correct length. + Accept "None" to unset. + """ + if userstr.lower() == "none": + # Unsetting case + return None + elif cls._maxlen is not None and len(userstr) > cls._maxlen: + raise UserInputError("Provided string was too long! Maximum length is `{}`".format(cls._maxlen)) + elif cls._options is not None and not userstr.lower() in cls._options: + raise UserInputError("Invalid option! Valid options are `{}`".format("`, `".join(cls._options))) + else: + return userstr + + @classmethod + def _format_data(cls, id: int, data: str, **kwargs): + """ + Wrap the string in backtics for formatting. + Handle the special case where the string is empty. + """ + if data: + return "`{}`".format(data) if cls._quote else str(data) + else: + return None + + +class Channel(SettingType): + """ + Channel type, storing a single `discord.Channel`. + + Types: + data: Optional[int] + The id of the stored Channel. + value: Optional[discord.abc.GuildChannel] + The stored Channel. + """ + accepts = "Channel mention/id/name, or 'None' to unset" + + # Type of channel, if any + _chan_type: discord.ChannelType = None + + @classmethod + def _data_from_value(cls, id: int, value: Optional[discord.abc.GuildChannel], **kwargs): + """ + Returns the channel id. + """ + return value.id if value is not None else None + + @classmethod + def _data_to_value(cls, id: int, data: Optional[int], **kwargs): + """ + Uses the client to look up the channel id. + Returns the Channel if found, otherwise None. + """ + # Always passthrough None + if data is None: + return None + + return client.get_channel(data) + + @classmethod + async def _parse_userstr(cls, ctx: Context, id: int, userstr: str, **kwargs): + """ + Pass to the channel seeker utility to find the requested channel. + Handle `0` and variants of `None` to unset. + """ + if userstr.lower() in ('0', 'none'): + return None + else: + channel = await ctx.find_channel(userstr, interactive=True, chan_type=cls._chan_type) + if channel is None: + raise SafeCancellation + else: + return channel.id + + @classmethod + def _format_data(cls, id: int, data: Optional[int], **kwargs): + """ + Retrieve an artificially created channel mention. + If the channel does not exist, this will show up as invalid-channel. + """ + if data is None: + return None + else: + return "<#{}>".format(data) + + +class Role(SettingType): + """ + Role type, storing a single `discord.Role`. + Configurably allows returning roles which don't exist or are not seen by the client + as `discord.Object`. + + Settings may override `get_guildid` if the setting object `id` is not the guildid. + + Types: + data: Optional[int] + The id of the stored Role. + value: Optional[Union[discord.Role, discord.Object]] + The stored Role, or, if the role wasn't found and `_strict` is not set, + a discord Object with the role id set. + """ + accepts = "Role mention/id/name, or 'None' to unset" + + # Whether to disallow returning roles which don't exist as `discord.Object`s + _strict = True + + @classmethod + def _data_from_value(cls, id: int, value: Optional[discord.Role], **kwargs): + """ + Returns the role id. + """ + return value.id if value is not None else None + + @classmethod + def _data_to_value(cls, id: int, data: Optional[int], **kwargs): + """ + Uses the client to look up the guild and role id. + Returns the role if found, otherwise returns a `discord.Object` with the id set, + depending on the `_strict` setting. + """ + # Always passthrough None + if data is None: + return None + + # Fetch guildid + guildid = cls._get_guildid(id, **kwargs) + + # Search for the role + role = None + guild = client.get_guild(guildid) + if guild is not None: + role = guild.get_role(data) + + if role is not None: + return role + elif not cls._strict: + return discord.Object(id=data) + else: + return None + + @classmethod + async def _parse_userstr(cls, ctx: Context, id: int, userstr: str, **kwargs): + """ + Pass to the role seeker utility to find the requested role. + Handle `0` and variants of `None` to unset. + """ + if userstr.lower() in ('0', 'none'): + return None + else: + role = await ctx.find_role(userstr, create=False, interactive=True) + if role is None: + raise SafeCancellation + else: + return role.id + + @classmethod + def _format_data(cls, id: int, data: Optional[int], **kwargs): + """ + Retrieve the role mention if found, otherwise the role id or None depending on `_strict`. + """ + role = cls._data_to_value(id, data, **kwargs) + if role is None: + return "Not Set" + elif isinstance(role, discord.Role): + return role.mention + else: + return "`{}`".format(role.id) + + @classmethod + def _get_guildid(cls, id: int, **kwargs): + """ + Fetch the current guildid. + Assumes that the guilid is either passed as a kwarg or is the object id. + Should be overriden in other cases. + """ + return kwargs.get('guildid', id) + + +class Emoji(SettingType): + """ + Emoji type. Stores both custom and unicode emojis. + """ + accepts = "Emoji, either built in or custom. Use 'None' to unset." + + @staticmethod + def _parse_emoji(emojistr): + """ + Converts a provided string into a PartialEmoji. + If the string is badly formatted, returns None. + """ + if ":" in emojistr: + emojistr = emojistr.strip('<>') + splits = emojistr.split(":") + if len(splits) == 3: + animated, name, id = splits + animated = bool(animated) + return discord.PartialEmoji(name, animated=animated, id=int(id)) + else: + # TODO: Check whether this is a valid emoji + return discord.PartialEmoji(emojistr) + + @classmethod + def _data_from_value(cls, id: int, value: Optional[discord.PartialEmoji], **kwargs): + """ + Both data and value are of type Optional[discord.PartialEmoji]. + Directly return the provided value as data. + """ + return value + + @classmethod + def _data_to_value(cls, id: int, data: Optional[discord.PartialEmoji], **kwargs): + """ + Both data and value are of type Optional[discord.PartialEmoji]. + Directly return the internal data as the value. + """ + return data + + @classmethod + async def _parse_userstr(cls, ctx: Context, id: int, userstr: str, **kwargs): + """ + Pass to the emoji string parser to get the emoji. + Handle `0` and variants of `None` to unset. + """ + if userstr.lower() in ('0', 'none'): + return None + else: + return cls._parse_emoji(userstr) + + @classmethod + def _format_data(cls, id: int, data: Optional[discord.PartialEmoji], **kwargs): + """ + Return a string form of the partial emoji, which generally displays the emoji. + """ + if data is None: + return None + else: + return str(data) + + +class PatternType(SettingType): + """ + Pattern type. Stores a valid Timer Pattern. + + Types: + data: Optional[int] + The stored patternid + value: Optional[Timer.Pattern] + The Timer.Pattern stored + """ + accepts = "A valid timer pattern." + + @classmethod + def _data_from_value(cls, id: int, value, **kwargs): + if value is not None: + return value.row.patternid + + @classmethod + def _data_to_value(cls, id: int, data: Optional[int], **kwargs): + return Timer.Pattern.get(data) if data is not None else None + + @classmethod + async def _parse_userstr(cls, ctx: Context, id: int, userstr: str, **kwargs): + if userstr.lower() == "none": + return None + + pattern = Timer.Pattern.from_userstr( + userstr, + userid=ctx.author.id, + guildid=ctx.guild.id if ctx.guild else None, + timerid=id + ) + + return pattern.row.patternid + + @classmethod + def _format_data(cls, id: int, data: Optional[int], brief=True, **kwargs): + """ + Return the string version of the data. + """ + if data is not None: + return cls._data_to_value(id, data, **kwargs).display(brief=brief) + + +class Timezone(SettingType): + """ + Timezone type, storing a valid timezone string. + + Types: + data: Optional[str] + The string representing the timezone in POSIX format. + value: Optional[timezone] + The pytz timezone. + """ + accepts = ( + "A timezone name from [this list](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) " + "(e.g. `Europe/London`)." + ) + + @classmethod + def _data_from_value(cls, id: int, value: Optional[str], **kwargs): + """ + Return the provided value string as the data string. + """ + if value is not None: + return str(value) + + @classmethod + def _data_to_value(cls, id: int, data: Optional[str], **kwargs): + """ + Return the provided data string as the value string. + """ + if data is not None: + return pytz.timezone(data) + + @classmethod + async def _parse_userstr(cls, ctx: Context, id: int, userstr: str, **kwargs): + """ + Check that the user-entered string is of the correct length. + Accept "None" to unset. + """ + if userstr.lower() == "none": + # Unsetting case + return None + try: + timezone = pytz.timezone(userstr) + except pytz.exceptions.UnknownTimeZoneError: + timezones = [tz for tz in pytz.all_timezones if userstr.lower() in tz.lower()] + if len(timezones) == 1: + timezone = timezones[0] + elif timezones: + result = await ctx.selector( + "Multiple matching timezones found, please select one.", + timezones + ) + timezone = timezones[result] + else: + raise UserInputError( + "Unknown timezone `{}`. " + "Please provide a TZ name from " + "[this list](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones)".format(userstr) + ) from None + + return str(timezone) + + @classmethod + def _format_data(cls, id: int, data: str, **kwargs): + """ + Wrap the string in backtics for formatting. + Handle the special case where the string is empty. + """ + if data: + return "`{}`".format(data) + else: + return 'Not Set' + + +class IntegerEnum(SettingType): + """ + Integer Enum type, accepting limited strings, storing an integer, and returning an IntEnum value + + Types: + data: Optional[int] + The stored integer. + value: Optional[Any] + The corresponding Enum member + """ + accepts = "A valid option." + + # Enum to use for mapping values + _enum: IntEnum = None + + # Custom map to format the value. If None, uses the enum names. + _output_map = None + + @classmethod + def _data_from_value(cls, id: int, value: ..., **kwargs): + """ + Return the value corresponding to the enum member + """ + if value is not None: + return value.value + + @classmethod + def _data_to_value(cls, id: int, data: ..., **kwargs): + """ + Return the enum member corresponding to the provided integer + """ + if data is not None: + return cls._enum(data) + + @classmethod + async def _parse_userstr(cls, ctx: Context, id: int, userstr: str, **kwargs): + """ + Find the corresponding enum member's value to the provided user input. + Accept "None" to unset. + """ + userstr = userstr.lower() + + options = {name.lower(): mem.value for name, mem in cls._enum.__members__.items()} + + if userstr == "none": + # Unsetting case + return None + elif userstr not in options: + raise UserInputError("Invalid option!") + else: + return options[userstr] + + @classmethod + def _format_data(cls, id: int, data: int, **kwargs): + """ + Format the data using either the `_enum` or the provided output map. + """ + if data is not None: + value = cls._enum(data) + if cls._output_map: + return cls._output_map[value] + else: + return "`{}`".format(value.name) diff --git a/bot/settings/timer_settings.py b/bot/settings/timer_settings.py new file mode 100644 index 0000000..1c542d1 --- /dev/null +++ b/bot/settings/timer_settings.py @@ -0,0 +1,315 @@ +from data import tables +from utils.lib import prop_tabulate +from wards import timer_admin +from utils.lib import DotDict + +from .base import Setting, ObjectSettings, ColumnData +from .setting_types import Boolean, String, Channel, PatternType, UserInputError + + +class TimerSettings(ObjectSettings): + settings = DotDict() + + +class TimerSetting(ColumnData, Setting): + _table_interface = tables.timers + _id_column = 'roleid' + _upsert = False + + write_ward = timer_admin + + category: str = 'Misc' + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._timer = kwargs.get('timer', None) + + @property + def timer(self): + if self._timer is None: + self._timer = self.client.interface.fetch_timer(self.id) + return self._timer + + @property + def embed(self): + embed = super().embed + embed.title = "Configuration options for `{}` in `{}`".format( + self.display_name, + self.timer.name + ) + return embed + + def write(self, **kwargs): + super().write(**kwargs) + self.timer.load() + + +@TimerSettings.attach_setting +class name(String, TimerSetting): + _data_column = 'name' + + category = 'Core' + display_name = 'name' + desc = "Name of the study group." + + long_desc = "Name of the group, shown in timer messages and used to join the timer." + + @classmethod + async def _parse_userstr(cls, ctx, id: int, userstr: str, **kwargs): + name = await super()._parse_userstr(ctx, id, userstr, **kwargs) + if name is None or not name: + raise UserInputError("Timer name cannot be none or empty!") + if len(name) > 30: + raise UserInputError("Timer name must be between `1` and `30` characters long!") + if name.lower() in (timer.name.lower() for timer in ctx.timers.get_timers_in(ctx.guild.id)): + raise UserInputError("Another timer already exists with this name!") + return name + + +@TimerSettings.attach_setting +class channel(Channel, TimerSetting): + _data_column = 'channelid' + + category = 'Core' + display_name = 'channel' + desc = "Text channel for timer subscription and messages." + long_desc = ( + "Text channel where the timer sends stage updates and other messages. " + "Unless the `globalgroups` server option is set, " + "the timer may also only be joined from this channel." + ) + + @classmethod + async def _parse_userstr(cls, ctx, id: int, userstr: str, **kwargs): + channelid = await super()._parse_userstr(ctx, id, userstr, **kwargs) + if channelid: + channel = ctx.guild.get_channel(channelid) + chan_perms = channel.permissions_for(ctx.guild.me) + if not chan_perms.read_messages: + raise UserInputError("Cannot read messages in {}.".format(channel.mention)) + elif not chan_perms.send_messages: + raise UserInputError("Cannot send messages in {}.".format(channel.mention)) + elif not chan_perms.read_message_history: + raise UserInputError("Cannot read message history in {}.".format(channel.mention)) + elif not chan_perms.embed_links: + raise UserInputError("Cannot send embeds in {}.".format(channel.mention)) + elif not chan_perms.manage_messages: + raise UserInputError("Cannot manage messages in {}.".format(channel.mention)) + return channelid + + +@TimerSettings.attach_setting +class default_pattern(PatternType, TimerSetting): + _data_column = 'patternid' + + category = 'Core' + display_name = 'default_pattern' + desc = "Default timer pattern to use when timer is reset." + + long_desc = ( + "The timer pattern applied when the timer is reset.\n" + "The timer may be reset either manually through the `reset` command, " + "or automatically if the `auto_reset` timer setting is on." + ) + + +@TimerSettings.attach_setting +class auto_reset(Boolean, TimerSetting): + _data_column = 'auto_reset' + + _default = False + + category = 'Core' + display_name = 'auto_reset' + desc = "Automatically reset when there are no members." + + long_desc = ( + "Automatically reset empty timers to their default pattern.\n" + "When set, the timer will automatically stop and reset itself to the default pattern " + "when it is empty. The reset occurs on the next stage change." + ) + + +@TimerSettings.attach_setting +class admin_locked(Boolean, TimerSetting): + _data_column = 'admin_locked' + + _default = False + + category = 'Core' + display_name = 'admin_locked' + desc = "Whether timer members are restricted from controlling the timer." + + long_desc = ( + "Whether timer admin permissions are required to control the timer.\n" + "When this is set, all **Timer Control** commands (such as `start`, `skip`, and `stop`) " + "require timer admin permissions. This essentially makes the timer 'static', " + "locked to a fixed pattern, and not modifiable by regular members.\n" + "There is one exception to the timer control rule, " + "in that members may start a timer that has been stopped (but not change its pattern). " + "This is to support use of the `auto_reset` setting." + ) + + +@TimerSettings.attach_setting +class voice_channel(Channel, TimerSetting): + _data_column = 'voice_channelid' + + category = 'Voice' + display_name = 'voice_channel' + desc = "Associated voice channel for alerts and auto-subscriptions." + + long_desc = ( + "Voice channel used for alerts and automatic subscriptions.\n" + "When set, this channel will be used for voice alerts when changing stage (see `voice_alerts`), " + "and automatic (un)subscriptions when members join or leave the channel " + "(see `track_vc_join` and `track_vc_leave`).\n" + "The name of the voice channel will also be updated to reflect the timer status (see `vc_name`).\n" + "To avoid ambiguitiy, each voice channel can be bound to at most one group." + ) + + @classmethod + async def _parse_userstr(cls, ctx, id: int, userstr: str, **kwargs): + channelid = await super()._parse_userstr(ctx, id, userstr, **kwargs) + if channelid: + channel = ctx.guild.get_channel(channelid) + # Check whether another timer exists with this voice channel + other = next( + (timer for timer in ctx.timers.get_timers_in(ctx.guild.id) if timer.voice_channelid == channel.id), + None + ) + if other is not None: + raise UserInputError("{} is already bound to the group **{}**".format(channel.mention, other.name)) + + # Check voice channel permissions + voice_perms = channel.permissions_for(ctx.guild.me) + if not voice_perms.connect: + raise UserInputError("Cannot connect to {}.".format(channel.mention)) + elif not voice_perms.speak: + raise UserInputError("Cannot speak in {}.".format(channel.mention)) + elif not voice_perms.view_channel: + raise UserInputError("Cannot see {}.".format(channel.mention)) + return channelid + + +@TimerSettings.attach_setting +class voice_alert(Boolean, TimerSetting): + _data_column = 'voice_alert' + + _default = True + + category = 'Voice' + display_name = 'voice_alerts' + desc = "Emit voice alerts on stage changes." + + long_desc = ( + "When set, the bot will join the voice channel and emit an audio alert upon each stage change." + ) + + +@TimerSettings.attach_setting +class track_voice_join(Boolean, TimerSetting): + _data_column = 'track_voice_join' + + _default = True + + category = 'Voice' + display_name = 'track_vc_join' + desc = "Automatically subscribe members joining the voice channel." + + long_desc = ( + "Whether to automatically subscribe members joining the voice channel." + ) + + +@TimerSettings.attach_setting +class track_voice_leave(Boolean, TimerSetting): + _data_column = 'track_voice_leave' + + _default = True + + category = 'Voice' + display_name = 'track_vc_leave' + desc = "Automatically unsubscribe members leaving the voice channel." + + long_desc = ( + "Whether to automatically unsubscribe members leaving the voice channel." + ) + + +@TimerSettings.attach_setting +class compact(Boolean, TimerSetting): + _data_column = 'compact' + + _default = False + + category = 'Format' + display_name = 'compact' + desc = "Use a more compact format for timer messages." + + long_desc = ( + "Whether to use a more compact format on timer messages, including " + "stage change messages and subscription/unsubscription messages. " + "Some information is lost, but this is generally safe to use on " + "servers where the members have experience with the timer." + ) + + +@TimerSettings.attach_setting +class vc_name(String, TimerSetting): + _data_column = 'voice_channel_name' + + _default = "{name} - {stage_name} ({remaining})" + + category = 'Format' + display_name = 'vc_name' + desc = "Updating name for the associated voice channel." + accepts = "A short text string, accepting the following substitutions." + + long_desc = ( + "When a voice channel is associated to the timer (see `voice_channel`), " + "the name of the voice channel will be updated to reflect the current status of the timer. " + "This setting controls the format of that name.\n" + "*Note that due to discord restrictions the name can update at most once per 10 minutes. " + "The remaining time property will thus generally be inaccurate.*" + ) + + @classmethod + async def _parse_userstr(cls, ctx, id: int, userstr: str, **kwargs): + name = await super()._parse_userstr(ctx, id, userstr, **kwargs) + + if not (2 < len(name) < 100): + raise UserInputError("Channel names must be between `2` and `100` characters long.") + + return name + + @property + def embed(self): + embed = super().embed + + fields = ("Current value", "Preview", "Default value", "Accepted input") + values = (self.formatted or "Not Set", + "`{}`".format(self.timer.voice_channel_name), + self._format_data(self.id, self.default) or "None", + self.accepts) + table = prop_tabulate(fields, values) + embed.description = "{}\n{}".format(self.long_desc, table) + + subs = { + '{name}': "Name of the study group.", + '{stage_name}': "Name of the current stage.", + '{stage_dur}': "Duration of the current stage.", + '{remaining}': "(Approximate) number of minutes left in the stage.", + '{sub_count}': "Number of members subscribed to the group.", + '{pattern}': "Short-form of the current timer pattern." + } + + embed.add_field( + name="Accepted substitutions.", + value=prop_tabulate(*zip(*subs.items())) + ) + return embed + + +# TODO: default_work_name, default_work_message, default_break_name, default_break_message +# Q: How do we update the default pattern with this info? Maybe we should use placeholders for the defaults instead? diff --git a/bot/settings/user_settings.py b/bot/settings/user_settings.py new file mode 100644 index 0000000..08a9122 --- /dev/null +++ b/bot/settings/user_settings.py @@ -0,0 +1,97 @@ +import datetime +import Timer + +from data import tables +from utils.lib import prop_tabulate, DotDict + +from .base import Setting, ObjectSettings, ColumnData, UserInputError +from .setting_types import Timezone, IntegerEnum + + +class UserSettings(ObjectSettings): + settings = DotDict() + + +class UserSetting(ColumnData, Setting): + _table_interface = tables.users + _id_column = 'userid' + + +@UserSettings.attach_setting +class timezone(Timezone, UserSetting): + attr_name = 'timezone' + _data_column = 'timezone' + + _default = 'UTC' + + display_name = 'timezone' + desc = "Timezone for displaying history and session data." + long_desc = ( + "Timezone used for displaying your historical sessions and study profile." + ) + + @property + def success_response(self): + return ( + "Your personal timezone is now {}. " + "This will apply to your session history and profile, but *not* to the server leaderboard.\n" + "Your current time is **{}**." + ).format(self.formatted, datetime.datetime.now(tz=self.value).strftime("%H:%M")) + + +@UserSettings.attach_setting +class notify_level(IntegerEnum, UserSetting): + attr_name = 'notify_level' + _data_column = 'notify_level' + + _enum = Timer.NotifyLevel + _default = _enum.WARNING + + display_name = 'notify_level' + desc = 'Control when you receive DM stage notifications.' + long_desc = ( + "Control when you receive notifications " + "via DM when a timer you are in changes stage (e.g. from `Work` to `Break`)." + ) + + accepts = "One of the following options." + accepted_dict = { + 'all': "Receive all stage changes and status updates via DM.", + 'warning': "Only receive a DM for inactivity warnings.", + 'final': "Only receive a DM after being kicked for inactivity.", + 'none': "Never receive status updates via DM." + } + accepted_table = prop_tabulate(*zip(*accepted_dict.items())) + + success_response = { + _enum.ALL: "You will receive all stage changes and status updates via DM.", + _enum.WARNING: "You will only receive a DM for inactivity warnings.", + _enum.FINAL: "You will only receive a DM after being kicked for inactivity.", + _enum.NONE: "You will never receive status updates via DM." + } + + @property + def embed(self): + embed = super().embed + embed.add_field( + name="Accepted Values", + value=self.accepted_table + ) + return embed + + @property + def success_response(self): + return ( + "Your notification level is now {}.\n{}" + ).format(self.formatted, self.success_response[self.value]) + + @classmethod + async def _parse_userstr(cls, ctx, id, userstr, **kwargs): + try: + value = await super()._parse_userstr(ctx, id, userstr, **kwargs) + except UserInputError: + raise UserInputError( + "Unrecognised notification level `{}`. " + "Please use one of the options below.\n{}".format(userstr, cls.accepted_table) + ) from None + return value diff --git a/bot/utils/ctx_addons.py b/bot/utils/ctx_addons.py index 37a27cb..b6fdae2 100644 --- a/bot/utils/ctx_addons.py +++ b/bot/utils/ctx_addons.py @@ -1,10 +1,13 @@ -import asyncio import discord from cmdClient import Context +from data import tables + +from settings import GuildSettings, UserSettings + @Context.util -async def embedreply(ctx, desc, colour=discord.Colour(0x9b59b6), **kwargs): +async def embed_reply(ctx, desc, colour=discord.Colour(0x9b59b6), **kwargs): """ Simple helper to embed replies. All arguments are passed to the embed constructor. @@ -15,59 +18,51 @@ async def embedreply(ctx, desc, colour=discord.Colour(0x9b59b6), **kwargs): @Context.util -async def live_reply(ctx, reply_func, update_interval=5, max_messages=20): +async def error_reply(ctx, error_str, **kwargs): """ - Acts as `ctx.reply`, but asynchronously updates the reply every `update_interval` seconds - with the value of `reply_func`, until the value is `None`. - - Parameters - ---------- - reply_func: coroutine - An async coroutine with no arguments. - Expected to return a dictionary of arguments suitable for `ctx.reply()` and `Message.edit()`. - update_interval: int - An integer number of seconds. - max_messages: int - Maximum number of messages in channel to keep the reply live for. - - Returns - ------- - The output message after the first reply. + Notify the user of a user level error. + Typically, this will occur in a red embed, posted in the command channel. """ - # Send the initial message - message = await ctx.reply(**(await reply_func())) + embed = discord.Embed( + colour=discord.Colour.red(), + description=error_str + ) + try: + message = await ctx.ch.send(embed=embed, reference=ctx.msg, **kwargs) + ctx.sent_messages.append(message) + return message + except discord.Forbidden: + message = await ctx.reply(error_str) + ctx.sent_messages.append(message) + return message - # Start the counter - future = asyncio.ensure_future(_message_counter(ctx.client, ctx.ch, max_messages)) - # Build the loop function - async def _reply_loop(): - while not future.done(): - await asyncio.sleep(update_interval) - args = await reply_func() - if args is not None: - await message.edit(**args) - else: - break +def context_property(func): + setattr(Context, func.__name__, property(func)) + return func - # Start the loop - asyncio.ensure_future(_reply_loop()) - # Return the original message - return message +@context_property +def best_prefix(ctx): + guild_prefix = tables.guilds.fetch_or_create(ctx.guild.id).prefix if ctx.guild else '' + return guild_prefix or ctx.client.prefix -async def _message_counter(client, channel, max_count): - """ - Helper for live_reply - """ - # Build check function - def _check(message): - return message.channel == channel - - # Loop until the message counter reaches maximum - count = 0 - while count < max_count: - await client.wait_for('message', check=_check) - count += 1 - return +@context_property +def example_group_name(ctx): + name = "AwesomeStudyGroup" + if ctx.guild: + groups = ctx.timers.get_timers_in(ctx.guild.id) + if groups: + name = groups[0].name + return name + + +@context_property +def guild_settings(ctx): + return GuildSettings(ctx.guild.id if ctx.guild else 0) + + +@context_property +def author_settings(ctx): + return UserSettings(ctx.author.id) diff --git a/bot/utils/interactive.py b/bot/utils/interactive.py index 496368d..d86c045 100644 --- a/bot/utils/interactive.py +++ b/bot/utils/interactive.py @@ -5,6 +5,62 @@ from .lib import paginate_list +# TODO: Interactive locks +cancel_emoji = '❌' +number_emojis = ( + '1️⃣', '2️⃣', '3️⃣', '4️⃣', '5️⃣', '6️⃣', '7️⃣', '8️⃣', '9️⃣' +) + + +async def discord_shield(coro): + try: + await coro + except discord.HTTPException: + pass + + +@Context.util +async def cancellable(ctx, msg, add_reaction=True, cancel_message=None, timeout=300): + """ + Add a cancellation reaction to the given message. + Pressing the reaction triggers cancellation of the original context, and a UserCancelled-style error response. + """ + # TODO: Not consistent with the exception driven flow, make a decision here? + # Add reaction + if add_reaction and cancel_emoji not in (str(r.emoji) for r in msg.reactions): + try: + await msg.add_reaction(cancel_emoji) + except discord.HTTPException: + return + + # Define cancellation function + async def _cancel(): + try: + await ctx.client.wait_for( + 'reaction_add', + timeout=timeout, + check=lambda r, u: (u == ctx.author + and r.message == msg + and str(r.emoji) == cancel_emoji) + ) + except asyncio.TimeoutError: + pass + else: + await ctx.client.active_command_response_cleaner(ctx) + if cancel_message: + await ctx.error_reply(cancel_message) + else: + try: + await ctx.msg.add_reaction(cancel_emoji) + except discord.HTTPException: + pass + [task.cancel() for task in ctx.tasks] + + # Launch cancellation task + task = asyncio.create_task(_cancel()) + ctx.tasks.append(task) + return task + @Context.util async def listen_for(ctx, allowed_input=None, timeout=120, lower=True, check=None): @@ -94,40 +150,72 @@ async def selector(ctx, header, select_from, timeout=120, max_len=20): raise ValueError("Selection list passed to `selector` cannot be empty.") # Generate the selector pages - footer = "Please type the number corresponding to your selection, or type `c` now to cancel." + footer = "Please reply with the number of your selection, or press {} to cancel.".format(cancel_emoji) list_pages = paginate_list(select_from, block_length=max_len) pages = ["\n".join([header, page, footer]) for page in list_pages] # Post the pages in a paged message - out_msg = await ctx.pager(pages) + out_msg = await ctx.pager(pages, add_cancel=True) + cancel_task = await ctx.cancellable(out_msg, add_reaction=False, timeout=None) - # Listen for valid input - valid_input = [str(i+1) for i in range(0, len(select_from))] + ['c', 'C'] - try: - result_msg = await ctx.listen_for(valid_input, timeout=timeout) - except ResponseTimedOut: - raise ResponseTimedOut("Selector timed out waiting for a response.") + if len(select_from) <= 5: + for i, _ in enumerate(select_from): + asyncio.create_task(discord_shield(out_msg.add_reaction(number_emojis[i]))) - # Try and delete the selector message and the user response. + # Build response tasks + valid_input = [str(i+1) for i in range(0, len(select_from))] + ['c', 'C'] + listen_task = asyncio.create_task(ctx.listen_for(valid_input, timeout=None)) + emoji_task = asyncio.create_task(ctx.client.wait_for( + 'reaction_add', + check=lambda r, u: (u == ctx.author + and r.message == out_msg + and str(r.emoji) in number_emojis) + )) + # Wait for the response tasks + done, pending = await asyncio.wait( + (listen_task, emoji_task), + timeout=timeout, + return_when=asyncio.FIRST_COMPLETED + ) + + # Cleanup try: await out_msg.delete() - await result_msg.delete() - except discord.NotFound: - pass - except discord.Forbidden: + except discord.HTTPException: pass - # Handle user cancellation - if result_msg.content in ['c', 'C']: - raise UserCancelled("User cancelled selection.") + # Handle different return cases + if listen_task in done: + emoji_task.cancel() - # The content must now be a valid index. Collect and return it. - index = int(result_msg.content) - 1 - return index + result_msg = listen_task.result() + try: + await result_msg.delete() + except discord.HTTPException: + pass + if result_msg.content.lower() == 'c': + raise UserCancelled("Selection cancelled!") + result = int(result_msg.content) - 1 + elif emoji_task in done: + listen_task.cancel() + + reaction, _ = emoji_task.result() + result = number_emojis.index(str(reaction.emoji)) + elif cancel_task in done: + # Manually cancelled case.. the current task should have been cancelled + # Raise UserCancelled in case the task wasn't cancelled for some reason + raise UserCancelled("Selection cancelled!") + elif not done: + # Timeout case + raise ResponseTimedOut("Selector timed out waiting for a response.") + + # Finally cancel the canceller and return the provided index + cancel_task.cancel() + return result @Context.util -async def pager(ctx, pages, locked=True, **kwargs): +async def pager(ctx, pages, locked=True, start_at=0, add_cancel=False, **kwargs): """ Shows the user each page from the provided list `pages` one at a time, providing reactions to page back and forth between pages. @@ -150,25 +238,28 @@ async def pager(ctx, pages, locked=True, **kwargs): raise ValueError("Pager cannot page with no pages!") # Post first page. Method depends on whether the page is an embed or not. - if isinstance(pages[0], discord.Embed): - out_msg = await ctx.reply(embed=pages[0]) + if isinstance(pages[start_at], discord.Embed): + out_msg = await ctx.reply(embed=pages[start_at], **kwargs) else: - out_msg = await ctx.reply(pages[0]) + out_msg = await ctx.reply(pages[start_at], **kwargs) # Run the paging loop if required if len(pages) > 1: - asyncio.ensure_future(_pager(ctx, out_msg, pages, locked)) + task = asyncio.create_task(_pager(ctx, out_msg, pages, locked, start_at, add_cancel, **kwargs)) + ctx.tasks.append(task) + elif add_cancel: + await out_msg.add_reaction(cancel_emoji) # Return the output message return out_msg -async def _pager(ctx, out_msg, pages, locked): +async def _pager(ctx, out_msg, pages, locked, start_at, add_cancel, **kwargs): """ Asynchronous initialiser and loop for the `pager` utility above. """ # Page number - page = 0 + page = start_at # Add reactions to the output message next_emoji = "▶" @@ -176,7 +267,9 @@ async def _pager(ctx, out_msg, pages, locked): try: await out_msg.add_reaction(prev_emoji) - await out_msg.add_reaction( next_emoji) + if add_cancel: + await out_msg.add_reaction(cancel_emoji) + await out_msg.add_reaction(next_emoji) except discord.Forbidden: # We don't have permission to add paging emojis # Die as gracefully as we can @@ -209,9 +302,9 @@ def check(reaction, user): # Edit the message with the new page active_page = pages[page] if isinstance(active_page, discord.Embed): - await out_msg.edit(embed=active_page) + await out_msg.edit(embed=active_page, **kwargs) else: - await out_msg.edit(content=active_page) + await out_msg.edit(content=active_page, **kwargs) # Clean up by removing the reactions try: @@ -225,6 +318,7 @@ def check(reaction, user): except discord.NotFound: pass + @Context.util async def input(ctx, msg="", timeout=120): """ diff --git a/bot/utils/lib.py b/bot/utils/lib.py index 99ec86c..ae308b4 100644 --- a/bot/utils/lib.py +++ b/bot/utils/lib.py @@ -1,4 +1,5 @@ import datetime +import pytz def prop_tabulate(prop_list, value_list): @@ -65,4 +66,13 @@ def timestamp_utcnow(): """ Return the current integer UTC timestamp. """ - return int(datetime.datetime.timestamp(datetime.datetime.utcnow())) + return int(datetime.datetime.now(tz=pytz.utc).timestamp()) + + +class DotDict(dict): + """ + Dict-type allowing dot access to keys. + """ + __getattr__ = dict.get + __setattr__ = dict.__setitem__ + __delattr__ = dict.__delitem__ diff --git a/bot/utils/live_messages.py b/bot/utils/live_messages.py new file mode 100644 index 0000000..4c54590 --- /dev/null +++ b/bot/utils/live_messages.py @@ -0,0 +1,33 @@ +import asyncio + +from meta import client + +current_live = {} # token -> task + + +async def live_edit(msg, update_func, label='global', update_interval=5, max_distance=20, **kwargs): + if not msg: + msg = await update_func(None, **kwargs) + if not msg: + return + + token = (msg.channel.id, label) + task = current_live.pop(token, None) + if task is not None: + task.cancel() + + task = current_live[token] = asyncio.create_task(_message_counter(msg, max_distance)) + while not task.done(): + await asyncio.sleep(update_interval) + if await update_func(msg, **kwargs) is None: + task.cancel() + + +async def _message_counter(msg, max_count): + count = 0 + while count < max_count: + try: + await client.wait_for('message', check=lambda m: m.channel == msg.channel) + except asyncio.CancelledError: + break + count += 1 diff --git a/bot/utils/seekers.py b/bot/utils/seekers.py index da2b7d9..f648059 100644 --- a/bot/utils/seekers.py +++ b/bot/utils/seekers.py @@ -1,10 +1,12 @@ +import discord + from cmdClient import Context -from cmdClient.lib import InvalidContext, UserCancelled, ResponseTimedOut +from cmdClient.lib import InvalidContext, UserCancelled, ResponseTimedOut, SafeCancellation from . import interactive @Context.util -async def find_role(ctx, userstr, interactive=False, collection=None): +async def find_role(ctx, userstr, create=False, interactive=False, collection=None, allow_notfound=True): """ Find a guild role given a partial matching string, allowing custom role collections and several behavioural switches. @@ -14,12 +16,18 @@ async def find_role(ctx, userstr, interactive=False, collection=None): userstr: str String obtained from a user, expected to partially match a role in the collection. The string will be tested against both the id and the name of the role. + create: bool + Whether to offer to create the role if it does not exist. + The bot will only offer to create the role if it has the `manage_channels` permission. interactive: bool Whether to offer the user a list of roles to choose from, or pick the first matching role. - collection: List(discord.Role) + collection: List[Union[discord.Role, discord.Object]] Collection of roles to search amongst. If none, uses the guild role list. + allow_notfound: bool + Whether to return `None` when there are no matches, instead of raising `SafeCancellation`. + Overriden by `create`, if it is set. Returns ------- @@ -34,6 +42,8 @@ async def find_role(ctx, userstr, interactive=False, collection=None): If the user cancels interactive role selection. cmdClient.lib.ResponseTimedOut: If the user fails to respond to interactive role selection within `60` seconds` + cmdClient.lib.SafeCancellation: + If `allow_notfound` is `False`, and the search returned no matches. """ # Handle invalid situations and input if not ctx.guild: @@ -43,10 +53,11 @@ async def find_role(ctx, userstr, interactive=False, collection=None): raise ValueError("User string passed to find_role was empty.") # Create the collection to search from args or guild roles - collection = collection if collection else ctx.guild.roles + collection = collection if collection is not None else ctx.guild.roles # If the unser input was a number or possible role mention, get it out - roleid = userstr.strip('<#@&!>') + userstr = userstr.strip() + roleid = userstr.strip('<#@&!> ') roleid = int(roleid) if roleid.isdigit() else None searchstr = userstr.lower() @@ -69,8 +80,10 @@ def check(role): else: # We have multiple matching roles! if interactive: - # Interactive prompt with the list of roles - role_names = [role.name for role in roles] + # Interactive prompt with the list of roles, handle `Object`s + role_names = [ + role.name if isinstance(role, discord.Role) else str(role.id) for role in roles + ] try: selected = await ctx.selector( @@ -88,8 +101,35 @@ def check(role): # Just select the first one role = roles[0] + # Handle non-existence of the role if role is None: - await ctx.error_reply("Couldn't find a role matching `{}`!".format(userstr)) + msgstr = "Couldn't find a role matching `{}`!".format(userstr) + if create: + # Inform the user + msg = await ctx.error_reply(msgstr) + if ctx.guild.me.guild_permissions.manage_roles: + # Offer to create it + resp = await ctx.ask("Would you like to create this role?", timeout=30) + if resp: + # They accepted, create the role + # Before creation, check if the role name is too long + if len(userstr) > 100: + await ctx.error_reply("Could not create a role with a name over 100 characters long!") + else: + role = await ctx.guild.create_role( + name=userstr, + reason="Interactive role creation for {} (uid:{})".format(ctx.author, ctx.author.id) + ) + await msg.delete() + await ctx.reply("You have created the role `{}`!".format(userstr)) + + # If we still don't have a role, cancel unless allow_notfound is set + if role is None and not allow_notfound: + raise SafeCancellation + elif not allow_notfound: + raise SafeCancellation(msgstr) + else: + await ctx.error_reply(msgstr) return role @@ -189,7 +229,7 @@ def check(chan): return chan @Context.util -async def find_member(ctx, userstr, interactive=False, collection=None): +async def find_member(ctx, userstr, interactive=False, collection=None, silent=False): """ Find a guild member given a partial matching string, allowing custom member collections. @@ -205,6 +245,8 @@ async def find_member(ctx, userstr, interactive=False, collection=None): collection: List(discord.Member) Collection of members to search amongst. If none, uses the full guild member list. + silent: bool + Whether to reply with an error when there are no matches. Returns ------- @@ -283,7 +325,7 @@ def check(member): # Just select the first one member = members[0] - if member is None: + if member is None and not silent: await ctx.error_reply("Couldn't find a member matching `{}`!".format(userstr)) return member diff --git a/bot/utils/timer_utils.py b/bot/utils/timer_utils.py index 3c03d89..faad4e6 100644 --- a/bot/utils/timer_utils.py +++ b/bot/utils/timer_utils.py @@ -1,9 +1,11 @@ from cmdClient import Context from cmdClient.lib import UserCancelled, ResponseTimedOut +from data import tables + @Context.util -async def get_timers_matching(ctx, name_str, channel_only=True, info=False): +async def get_timers_matching(ctx, name_str, channel_only=True, info=False, header=None): """ Interactively get a guild timer matching the given string. @@ -26,18 +28,26 @@ async def get_timers_matching(ctx, name_str, channel_only=True, info=False): Raised if the user fails to respond to the selector within `120` seconds. """ # Get the full timer list - if channel_only: - timers = ctx.client.interface.get_channel_timers(ctx.ch.id) - else: - timers = ctx.client.interface.get_guild_timers(ctx.guild.id) + all_timers = ctx.timers.get_timers_in(ctx.guild.id, ctx.ch.id if channel_only else None) # If there are no timers, quit early - if not timers: + if not all_timers: return None # Build a list of matching timers name_str = name_str.strip() - timers = [timer for timer in timers if name_str.lower() in timer.name.lower()] + timers = [ + timer for timer in all_timers + if (name_str.lower() in timer.data.name.lower() + or name_str.lower() in timer.role.name.lower()) + ] + + if not timers: + # Try matching on subscribers instead + timers = [ + timer for timer in all_timers + if any(name_str.lower() in sub.name.lower() for sub in timer.subscribers.values()) + ] if len(timers) == 0: return None @@ -45,15 +55,23 @@ async def get_timers_matching(ctx, name_str, channel_only=True, info=False): return timers[0] else: if info: - select_from = [timer.oneline_summary() for timer in timers] + select_from = [timer.oneline_summary for timer in timers] else: - select_from = [timer.name for timer in timers] + select_from = [timer.data.name for timer in timers] try: - selected = await ctx.selector("Multiple matching groups found, please select one.", select_from) + selected = await ctx.selector(header or "Multiple matching groups found, please select one.", select_from) except ResponseTimedOut: raise ResponseTimedOut("Group selection timed out.") from None except UserCancelled: raise UserCancelled("User cancelled group selection.") from None return timers[selected] + + +async def is_timer_admin(member): + result = member.guild_permissions.administrator + if not result: + tarid = tables.guilds.fetch_or_create(member.guild.id).timer_admin_roleid + result = tarid in (role.id for role in member.roles) + return result diff --git a/bot/wards.py b/bot/wards.py index 35e72dc..1e6bf5e 100644 --- a/bot/wards.py +++ b/bot/wards.py @@ -1,26 +1,41 @@ from cmdClient import check +from cmdClient.checks import in_guild + +from utils.timer_utils import is_timer_admin + + +@check( + name="TIMER_READY", + msg="I am restarting! Please try again in a moment." +) +async def timer_ready(ctx, *args, **kwargs): + return ctx.client.interface.ready @check( name="TIMER_ADMIN", - msg=("You need to have one of the following to use this command.\n" - "- The `manage_guild` permission in this guild.\n" - "- The timer admin role (refer to the `adminrole` command).") + msg=("You need to have one of the following to do this!\n" + "- The `administrator` server permission.\n" + "- The timer admin role (see the `timeradmin` command)."), + requires=[in_guild] ) async def timer_admin(ctx, *args, **kwargs): - if ctx.author.guild_permissions.manage_guild: - return True + return await is_timer_admin(ctx.author) - roleid = ctx.client.config.guilds.get(ctx.guild.id, "timeradmin") - if roleid is None: - return False - return roleid in [r.id for r in ctx.author.roles] +@check( + name="HAS_TIMERS", + msg="No study groups have been created! Create a new group with the `newgroup` command.", + requires=[in_guild, timer_ready] +) +async def has_timers(ctx, *args, **kwargs): + return bool(ctx.timers.get_timers_in(ctx.guild.id)) @check( - name="TIMER_READY", - msg="I am restarting! Please try again in a moment." + name="ADMIN", + msg=("You need to be a server admin to do this!"), + requires=[in_guild] ) -async def timer_ready(ctx, *args, **kwargs): - return ctx.client.interface.ready +async def guild_admin(ctx, *args, **kwargs): + return ctx.author.guild_permissions.administrator diff --git a/data/migration/v0-v1/README.md b/data/migration/v0-v1/README.md new file mode 100644 index 0000000..692ff5a --- /dev/null +++ b/data/migration/v0-v1/README.md @@ -0,0 +1,41 @@ +# Data migration from version 0 to version 1 + +## Summary +Version 1 represents a paradigm shift in how PomoBot's data is stored. +In particular, all user and guild properties are moving into appropriate tables, and the sessions database is merging with the properties database. +Timers now also have configuration properties, and Timer patterns are stored in the database, with presets referencing the central pattern table. +Several views are being added to simplify analysis of the data, both internally and via external programs or services. + + +## Instructions +Copy `sample-migration.conf` to `migration.conf` and edit the data paths as required. Then run `migration.py`. + +## Object migration notes +### Guilds +Originally stored in the `guilds` and `guild_props` tables, guild properties have been split into appropriate tables. +- `timeradmin` property -> `guilds.timer_admin_roleid` + - Straightforward transfer, integer type +- `globalgroups` property -> `guilds.globalgroups` + - Straightforward transfer, boolean type +- `timers` property -> `timers` table + - Originally a json-encoded list of timer data, with each timer encoded as a tuple `[name, roleid, channelid, clock_channelid]`. + - Now each timer is encoded in its own row in `timers`, with each tuple-field given its own column. + - Each new `timer` also holds considerably more data due to the new configuration. +- `timer_presets` property -> `guild_presets` table + - Originally a json-encoded dictionary of the form `presetname: setupstring`. + - Each preset is now given by a single row of `guild_presets`. + - Setupstrings are stored in `patterns` as their associated patterns, and referred to by `patternid`. + +### Users +Originally stored in the `users` and `user_props` tables, user properties have been split into appropriate tables. +- `notify_level` property -> `users.notify_level` + - Straightforward transfer, integer (enum data) type. +- `timer_presets` property -> `user_presets` table + - Originally a json-encoded dictionary of the form `presetname: setupstring`. + - Each preset is now given by a single row of `user_presets`. + - Setupstrings are stored in `patterns` as their associated patterns, and referred to by `patternid`. + +### Sessions +The `sessions` table has been moved from the separate `registry` database into the central database. +Overall the format is the same, with the exception of the `starttime` column being renamed to `start_time`. +The `sessions` table now also tracks the `focused_duration`, `patternid`, and `stages` information for each session. diff --git a/data/migration/v0-v1/lib.py b/data/migration/v0-v1/lib.py new file mode 100644 index 0000000..39a3c92 --- /dev/null +++ b/data/migration/v0-v1/lib.py @@ -0,0 +1,104 @@ +from collections import namedtuple +import datetime +import json +import pytz + + +class Base: + __slots__ = () + + def __init__(self, **kwargs): + for prop in self.__slots__: + setattr(self, prop, kwargs.get(prop, None)) + + +class Guild(Base): + __slots__ = ( + 'guildid', + 'timer_admin_roleid', + 'globalgroups', + 'presets', + ) + + +class Timer(Base): + __slots__ = ( + 'roleid', + 'guildid', + 'name', + 'channelid', + 'voice_channelid' + ) + + +Stage = namedtuple('Stage', ('name', 'duration', 'message', 'focus')) + + +class Pattern(Base): + __slots__ = ( + 'stages', + 'patternid', + 'stage_str' + ) + + pattern_cache = {} # pattern_str -> id + lastid = 0 + + @classmethod + def parse(cls, string): + """ + Parse a setup string into a pattern + """ + # Accepts stages as 'name, length' or 'name, length, message' + stage_blocks = string.strip(';').split(';') + stages = [] + for block in stage_blocks: + # Extract stage components + parts = block.split(',', maxsplit=2) + if len(parts) == 2: + name, dur = parts + message = None + else: + name, dur, message = parts + + # Parse duration + dur = dur.strip() + focus = dur.startswith('*') or dur.endswith('*') + if focus: + dur = dur.strip('* ') + + # Build and add stage + stages.append(Stage(name.strip(), int(dur), (message or '').strip(), focus)) + + stage_str = json.dumps(stages) + if stage_str in cls.pattern_cache: + pattern = cls.pattern_cache[stage_str] + else: + cls.lastid += 1 + pattern = cls(stages=stages, patternid=cls.lastid, stage_str=stage_str) + cls.pattern_cache[stage_str] = pattern + + return pattern + + +class Preset(Base): + __slots__ = ( + 'name', + 'patternid', + ) + + +class User(Base): + __slots__ = ( + 'userid', + 'notify_level', + 'presets' + ) + + +time_diff = ( + int(datetime.datetime.now(tz=pytz.utc).timestamp()) + - int(datetime.datetime.timestamp(datetime.datetime.utcnow())) +) +def adjust_timestamp(ts): + return ts + time_diff diff --git a/data/migration/v0-v1/migration.py b/data/migration/v0-v1/migration.py new file mode 100644 index 0000000..850ff18 --- /dev/null +++ b/data/migration/v0-v1/migration.py @@ -0,0 +1,311 @@ +import os +import json +import configparser as cfgp +import pickle + +import sqlite3 as sq + +import lib + + +CONFFILE = "migration.conf" +DATA_DIR = "../../" + +# Read config file +print("Reading configuration file...", end='') +if not os.path.isfile(CONFFILE): + raise Exception( + "Couldn't find migration configuration file '{}'. " + "Please copy 'sample-migration.conf' to 'migration.conf' and edit as required." + ) + +config = cfgp.ConfigParser() +config.read(CONFFILE) + +orig_settings_path = DATA_DIR + config['Original']['settings_db'] +orig_session_path = DATA_DIR + config['Original']['session_db'] +if config['Original']['savefile'].lower() != 'none': + orig_savefile_path = DATA_DIR + config['Original']['savefile'] +else: + orig_savefile_path = None + +target_database_path = DATA_DIR + config['Target']['database'] +if config['Target']['savefile'] .lower() != 'none': + target_savefile_path = DATA_DIR + config['Target']['savefile'] +else: + target_savefile_path = None + +if not os.path.isfile(orig_session_path): + raise Exception("Provided original sessions database not found.") +if not os.path.isfile(orig_settings_path): + raise Exception("Provided original settings database not found.") +if orig_savefile_path is not None and not os.path.isfile(orig_savefile_path): + raise Exception("Provided original savefile not found.") +if os.path.isfile(target_database_path): + raise Exception("Target database file already exists! Refusing to overwrite.") + +print("Done") + +# Open databases +print("Opening databases...", end='') +orig_session_conn = sq.connect(orig_session_path) +orig_settings_conn = sq.connect(orig_settings_path) +target_database_conn = sq.connect(target_database_path) +print("Done") + +# Initialise the new database +print("Initialising target database...", end='') +with target_database_conn as conn: + with open("v1_schema.sql", 'r') as script: + conn.executescript(script.read()) +print("Done") + + +# ---------------------------------------------------- +# Initial setup done, start migration +# ---------------------------------------------------- +guilds = {} +timers = {} +users = {} + + +# Migrate guild properties +# First read properties +count = 0 +with orig_settings_conn as conn: + cursor = conn.cursor() + rows = cursor.execute( + "SELECT * FROM guilds" + ) + for guildid, prop, value in cursor.fetchall(): + count += 1 + if guildid not in guilds: + guild = guilds[guildid] = lib.Guild(guildid=guildid, presets={}) + else: + guild = guilds[guildid] + + if prop == 'timeradmin': + guild.timer_admin_roleid = json.loads(value) + elif prop == 'globalgroups': + guild.globalgroups = json.loads(value) + elif prop == 'timers': + timer_list = json.loads(value) + if timer_list: + for name, roleid, channelid, vc_channelid in timer_list: + timer = lib.Timer( + roleid=roleid, + guildid=guildid, + name=name, + channelid=channelid, + voice_channelid=vc_channelid + ) + timers[roleid] = timer + elif prop == 'timer_presets': + presets = json.loads(value) + if presets: + for name, setupstr in presets.items(): + pattern = lib.Pattern.parse(setupstr) + while name.lower() in guild.presets: + name += '_' + guild.presets[name.lower()] = lib.Preset(name=name, patternid=pattern.patternid) +print("Read {} guild properties.".format(count)) + +# Insert the guild rows +with target_database_conn as conn: + cursor = conn.cursor() + cursor.executemany( + "INSERT INTO guilds (guildid, timer_admin_roleid, globalgroups) VALUES (?, ?, ?)", + ( + (guildid, guild.timer_admin_roleid, guild.globalgroups) + for guildid, guild in guilds.items() + ) + ) +print("Inserted {} guilds.".format(len(guilds))) + +# Insert the timer rows +with target_database_conn as conn: + cursor = conn.cursor() + cursor.executemany( + "INSERT INTO timers (roleid, guildid, name, channelid, voice_channelid) VALUES (?, ?, ?, ?, ?)", + ( + (timer.roleid, timer.guildid, timer.name, timer.channelid, timer.voice_channelid) + for timer in timers.values() + ) + ) +print("Inserted {} timers.".format(len(timers))) + + +# Read user properties +count = 0 +with orig_settings_conn as conn: + cursor = conn.cursor() + rows = cursor.execute( + "SELECT * FROM users" + ) + for userid, prop, value in cursor.fetchall(): + count += 1 + if userid not in users: + user = users[userid] = lib.User(userid=userid, presets={}) + else: + user = users[userid] + + if prop == 'notify_level': + user.notify_level = json.loads(value) + elif prop == 'timer_presets': + presets = json.loads(value) + if presets: + for name, setupstr in presets.items(): + pattern = lib.Pattern.parse(setupstr) + while name.lower() in user.presets: + name += '_' + user.presets[name.lower()] = lib.Preset(name=name, patternid=pattern.patternid) +print("Read {} user properties.".format(count)) + + +# Insert the user rows +with target_database_conn as conn: + cursor = conn.cursor() + cursor.executemany( + "INSERT INTO users (userid, notify_level) VALUES (?, ?)", + ( + (userid, user.notify_level) + for userid, user in users.items() + ) + ) +print("Inserted {} users.".format(len(users))) + + +# Migrate savedata +save_data = {} +if orig_savefile_path: + with open(orig_savefile_path) as f: + old_data = json.load(f) + flat_timers = old_data['timers'] + flat_subscribers = old_data['subscribers'] + flat_channels = old_data['timer_channels'] + + channels = {} + for channel in flat_channels: + channel_data = {} + channel_data['channelid'] = channel['id'] + channel_data['pinned_msg_id'] = channel['msgid'] + channel_data['timers'] = [] + channels[channel['id']] = channel_data + + for timer in flat_timers: + timer_data = {} + + if timer['stages']: + stages = [ + lib.Stage(stage['name'], stage['duration'], stage['message'], False) + for stage in timer['stages'] + ] + stage_str = json.dumps(stages) + if stage_str in lib.Pattern.pattern_cache: + pattern = lib.Pattern.pattern_cache[stage_str] + else: + lib.Pattern.lastid += 1 + pattern = lib.Pattern(stages=stages, patternid=lib.Pattern.lastid, stage_str=stage_str) + lib.Pattern.pattern_cache[stage_str] = pattern + patternid = pattern.patternid + else: + patternid = 0 + timer_data['roleid'] = timer['roleid'] + timer_data['state'] = timer['state'] + timer_data['patternid'] = patternid + timer_data['stage_index'] = timer['current_stage'] or 0 + timer_data['stage_start'] = lib.adjust_timestamp(timer['current_stage_start'] or 0) + timer_data['message_ids'] = timer['messages'] + timer_data['subscribers'] = [] + timer_data['last_voice_update'] = 0 + + _timer = timers[timer['roleid']] + if _timer: + timer_data['guildid'] = _timer.guildid + if _timer.channelid not in channels: + channels[_timer.channelid] = { + 'channelid': _timer.channelid, + 'pinned_msg_id': None, + 'timers': [], + } + channels[_timer.channelid]['timers'].append(timer_data) + + for channelid, channel in channels.items(): + if channel['timers']: + guildid = channel['timers'][0]['guildid'] + if guildid not in save_data: + guild_channels = save_data[guildid] = [] + else: + guild_channels = save_data[guildid] + guild_channels.append(channel) +print("Read and parsed old savefile.") + + +# Write patterns +with target_database_conn as conn: + cursor = conn.cursor() + cursor.executemany( + "INSERT INTO patterns (patternid, short_repr, stage_str) VALUES (?, ?, ?)", + ( + (pattern.patternid, False, pattern.stage_str) + for pattern in lib.Pattern.pattern_cache.values() + ) + ) +print("Created {} patterns.".format(len(lib.Pattern.pattern_cache))) + + +# Write user presets +with target_database_conn as conn: + cursor = conn.cursor() + cursor.executemany( + "INSERT INTO user_presets (userid, patternid, preset_name) VALUES (?, ?, ?)", + ( + (userid, preset.patternid, preset.name) + for userid, user in users.items() + for preset in user.presets.values() + ) + ) +print("Transferred user presets.") + + +# Write guild presets +with target_database_conn as conn: + cursor = conn.cursor() + cursor.executemany( + "INSERT INTO guild_presets (guildid, patternid, preset_name, created_by) VALUES (?, ?, ?, ?)", + ( + (guildid, preset.patternid, preset.name, 0) + for guildid, guild in guilds.items() + for preset in guild.presets.values() + ) + ) +print("Transferred guild presets.") + + +# Write new save file, if required +if target_savefile_path: + with open(target_savefile_path, 'wb') as savefile: + pickle.dump(save_data, savefile, pickle.HIGHEST_PROTOCOL) + print("Written new save file.") + + +# Transfer the session data +print("Migrating session data....", end='') +with orig_session_conn as conn: + cursor = conn.cursor() + cursor.execute( + "SELECT * FROM sessions" + ) + with target_database_conn as tconn: + tcursor = tconn.cursor() + tcursor.executemany( + "INSERT INTO sessions (guildid, userid, roleid, start_time, duration) VALUES (?, ?, ?, ?, ?)", + ( + (row[1], row[0], row[2], lib.adjust_timestamp(row[3]), row[4]) + for row in cursor.fetchall() + if row[4] > 60 + ) + ) +print("Done") + +print("Data migration v0 -> v1 complete!") diff --git a/data/migration/v0-v1/v1_schema.sql b/data/migration/v0-v1/v1_schema.sql new file mode 100644 index 0000000..ad151c5 --- /dev/null +++ b/data/migration/v0-v1/v1_schema.sql @@ -0,0 +1,157 @@ +PRAGMA foreign_keys = 1; + +--Meta +CREATE TABLE VersionHistory ( + version INTEGER NOT NULL, + time INTEGER NOT NULL, + author TEXT +); + +INSERT INTO VersionHistory VALUES (1, strftime('%s', 'now'), 'Initial Creation'); + + +-- Guild configuration +CREATE TABLE guilds ( + guildid INTEGER NOT NULL PRIMARY KEY, + timer_admin_roleid INTEGER, + show_tips BOOLEAN, + globalgroups BOOLEAN, + autoclean INTEGER, + studyrole_roleid INTEGER, + timezone TEXT, + prefix TEXT +); + + +-- User configuration +CREATE TABLE users ( + userid INTEGER NOT NULL PRIMARY KEY, + notify_level INTEGER, + timezone TEXT, + name TEXT +); + + +-- Timer patterns +CREATE TABLE patterns ( + patternid INTEGER PRIMARY KEY AUTOINCREMENT, + short_repr BOOL NOT NULL, + stage_str TEXT NOT NULL, + created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')) +); +CREATE UNIQUE INDEX pattern_strings ON patterns(stage_str); +INSERT INTO patterns(patternid, short_repr, stage_str) + VALUES (0, 1, '[["Study \ud83d\udd25", 25, "Good luck!", false], ["Break\ud83c\udf1b", 5, "Have a rest.", false], ["Study \ud83d\udd25", 25, "Good luck!", false], ["Break \ud83c\udf1c", 5, "Have a rest.", false], ["Study \ud83d\udd25", 25, "Good luck!", false], ["Long Break \ud83c\udf1d", 10, "Have a rest.", false]]'); + + + +-- Timer pattern presets +CREATE TABLE user_presets ( + userid INTEGER NOT NULL, + preset_name TEXT NOT NULL COLLATE NOCASE, + patternid INTEGER NOT NULL, + created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')), + FOREIGN KEY (patternid) REFERENCES patterns (patternid) ON DELETE CASCADE +); +CREATE UNIQUE INDEX user_preset_names ON user_presets(userid, preset_name); + +CREATE TABLE guild_presets ( + guildid INTEGER NOT NULL, + preset_name TEXT NOT NULL COLLATE NOCASE, + created_by INTEGER NOT NULL, + patternid INTEGER NOT NULL, + created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')), + FOREIGN KEY (patternid) REFERENCES patterns (patternid) ON DELETE CASCADE +); +CREATE UNIQUE INDEX guild_preset_names ON guild_presets(guildid, preset_name); + +CREATE VIEW user_preset_patterns AS + SELECT + userid, + preset_name, + patternid, + patterns.stage_str AS preset_string + FROM user_presets + INNER JOIN patterns USING (patternid); + +CREATE VIEW guild_preset_patterns AS + SELECT + guildid, + preset_name, + created_by, + patternid, + patterns.stage_str AS preset_string + FROM guild_presets + INNER JOIN patterns USING (patternid); + + +-- Timers +CREATE TABLE timers ( + roleid INTEGER NOT NULL PRIMARY KEY, + guildid INTEGER NOT NULL, + name TEXT NOT NULL, + channelid INTEGER NOT NULL, + patternid INTEGER NOT NULL DEFAULT 0, + brief BOOLEAN, + voice_channelid INTEGER, + voice_alert BOOLEAN, + track_voice_join BOOLEAN, + track_voice_leave BOOLEAN, + auto_reset BOOLEAN, + admin_locked BOOLEAN, + track_role BOOLEAN, + compact BOOLEAN, + voice_channel_name TEXT, + FOREIGN KEY (patternid) REFERENCES patterns (patternid) ON DELETE SET NULL +); + + +CREATE TABLE timer_pattern_history ( + timerid INTEGER NOT NULL, + patternid INTEGER NOT NULL, + modified_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')), + modified_by INTEGER, + FOREIGN KEY (patternid) REFERENCES patterns (patternid) ON DELETE CASCADE, + FOREIGN KEY (timerid) REFERENCES timers (roleid) ON DELETE CASCADE +); + +CREATE INDEX idx_timerid_modified_at on timer_pattern_history (timerid, modified_at); + + +CREATE VIEW timer_patterns AS + SELECT * + FROM patterns + INNER JOIN timers USING (patternid); + + +CREATE VIEW current_timer_patterns AS + SELECT + timerid, + patternid, + max(modified_at) + FROM timer_pattern_history + GROUP BY timerid; + + +-- Session storage +CREATE TABLE sessions ( + guildid INTEGER NOT NULL, + userid INTEGER NOT NULL, + roleid INTEGER NOT NULL, + start_time INTEGER NOT NULL, + duration INTEGER NOT NULL, + focused_duration INTEGER, + patternid INTEGER, + stages TEXT, + FOREIGN KEY (patternid) REFERENCES patterns (patternid) ON DELETE SET NULL +); +CREATE INDEX idx_sessions_guildid_userid on sessions (guildid, userid); + +CREATE VIEW session_patterns AS + SELECT + *, + patterns.stage_str AS stage_str, + users.name AS user_name + FROM sessions + LEFT JOIN patterns USING (patternid) + LEFT JOIN users USING (userid); diff --git a/data/schema.sql b/data/schema.sql new file mode 100644 index 0000000..ad151c5 --- /dev/null +++ b/data/schema.sql @@ -0,0 +1,157 @@ +PRAGMA foreign_keys = 1; + +--Meta +CREATE TABLE VersionHistory ( + version INTEGER NOT NULL, + time INTEGER NOT NULL, + author TEXT +); + +INSERT INTO VersionHistory VALUES (1, strftime('%s', 'now'), 'Initial Creation'); + + +-- Guild configuration +CREATE TABLE guilds ( + guildid INTEGER NOT NULL PRIMARY KEY, + timer_admin_roleid INTEGER, + show_tips BOOLEAN, + globalgroups BOOLEAN, + autoclean INTEGER, + studyrole_roleid INTEGER, + timezone TEXT, + prefix TEXT +); + + +-- User configuration +CREATE TABLE users ( + userid INTEGER NOT NULL PRIMARY KEY, + notify_level INTEGER, + timezone TEXT, + name TEXT +); + + +-- Timer patterns +CREATE TABLE patterns ( + patternid INTEGER PRIMARY KEY AUTOINCREMENT, + short_repr BOOL NOT NULL, + stage_str TEXT NOT NULL, + created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')) +); +CREATE UNIQUE INDEX pattern_strings ON patterns(stage_str); +INSERT INTO patterns(patternid, short_repr, stage_str) + VALUES (0, 1, '[["Study \ud83d\udd25", 25, "Good luck!", false], ["Break\ud83c\udf1b", 5, "Have a rest.", false], ["Study \ud83d\udd25", 25, "Good luck!", false], ["Break \ud83c\udf1c", 5, "Have a rest.", false], ["Study \ud83d\udd25", 25, "Good luck!", false], ["Long Break \ud83c\udf1d", 10, "Have a rest.", false]]'); + + + +-- Timer pattern presets +CREATE TABLE user_presets ( + userid INTEGER NOT NULL, + preset_name TEXT NOT NULL COLLATE NOCASE, + patternid INTEGER NOT NULL, + created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')), + FOREIGN KEY (patternid) REFERENCES patterns (patternid) ON DELETE CASCADE +); +CREATE UNIQUE INDEX user_preset_names ON user_presets(userid, preset_name); + +CREATE TABLE guild_presets ( + guildid INTEGER NOT NULL, + preset_name TEXT NOT NULL COLLATE NOCASE, + created_by INTEGER NOT NULL, + patternid INTEGER NOT NULL, + created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')), + FOREIGN KEY (patternid) REFERENCES patterns (patternid) ON DELETE CASCADE +); +CREATE UNIQUE INDEX guild_preset_names ON guild_presets(guildid, preset_name); + +CREATE VIEW user_preset_patterns AS + SELECT + userid, + preset_name, + patternid, + patterns.stage_str AS preset_string + FROM user_presets + INNER JOIN patterns USING (patternid); + +CREATE VIEW guild_preset_patterns AS + SELECT + guildid, + preset_name, + created_by, + patternid, + patterns.stage_str AS preset_string + FROM guild_presets + INNER JOIN patterns USING (patternid); + + +-- Timers +CREATE TABLE timers ( + roleid INTEGER NOT NULL PRIMARY KEY, + guildid INTEGER NOT NULL, + name TEXT NOT NULL, + channelid INTEGER NOT NULL, + patternid INTEGER NOT NULL DEFAULT 0, + brief BOOLEAN, + voice_channelid INTEGER, + voice_alert BOOLEAN, + track_voice_join BOOLEAN, + track_voice_leave BOOLEAN, + auto_reset BOOLEAN, + admin_locked BOOLEAN, + track_role BOOLEAN, + compact BOOLEAN, + voice_channel_name TEXT, + FOREIGN KEY (patternid) REFERENCES patterns (patternid) ON DELETE SET NULL +); + + +CREATE TABLE timer_pattern_history ( + timerid INTEGER NOT NULL, + patternid INTEGER NOT NULL, + modified_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')), + modified_by INTEGER, + FOREIGN KEY (patternid) REFERENCES patterns (patternid) ON DELETE CASCADE, + FOREIGN KEY (timerid) REFERENCES timers (roleid) ON DELETE CASCADE +); + +CREATE INDEX idx_timerid_modified_at on timer_pattern_history (timerid, modified_at); + + +CREATE VIEW timer_patterns AS + SELECT * + FROM patterns + INNER JOIN timers USING (patternid); + + +CREATE VIEW current_timer_patterns AS + SELECT + timerid, + patternid, + max(modified_at) + FROM timer_pattern_history + GROUP BY timerid; + + +-- Session storage +CREATE TABLE sessions ( + guildid INTEGER NOT NULL, + userid INTEGER NOT NULL, + roleid INTEGER NOT NULL, + start_time INTEGER NOT NULL, + duration INTEGER NOT NULL, + focused_duration INTEGER, + patternid INTEGER, + stages TEXT, + FOREIGN KEY (patternid) REFERENCES patterns (patternid) ON DELETE SET NULL +); +CREATE INDEX idx_sessions_guildid_userid on sessions (guildid, userid); + +CREATE VIEW session_patterns AS + SELECT + *, + patterns.stage_str AS stage_str, + users.name AS user_name + FROM sessions + LEFT JOIN patterns USING (patternid) + LEFT JOIN users USING (userid); diff --git a/data/timerstatus/.gitignore b/data/timerstatus/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/requirements.txt b/requirements.txt index 9da8333..dbfe9af 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,4 @@ discord.py +pyNacl cachetools +pytz From b6ff86920929297dca90c535e042321dbb2827c3 Mon Sep 17 00:00:00 2001 From: Interitio Date: Thu, 10 Jun 2021 00:39:20 +0300 Subject: [PATCH 04/31] fix (settings): Don't run non-existent `write_ward`. --- bot/settings/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/settings/base.py b/bot/settings/base.py index 1851c91..b58d4aa 100644 --- a/bot/settings/base.py +++ b/bot/settings/base.py @@ -192,7 +192,7 @@ async def command(cls, ctx, id): await ctx.reply(embed=cls.get(id).embed) else: # Check the write ward - if not await cls.write_ward.run(ctx): + if cls.write_ward and not await cls.write_ward.run(ctx): await ctx.error_reply(cls.msg) else: # Attempt to set config cls From 5ed8259d83294b014c3c71866c1ffcb7b9015a9e Mon Sep 17 00:00:00 2001 From: Interitio Date: Thu, 10 Jun 2021 00:48:09 +0300 Subject: [PATCH 05/31] fix (user_config): Repair typo in `notify` cmd. --- bot/commands/user_config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/commands/user_config.py b/bot/commands/user_config.py index 101f0db..aabc145 100644 --- a/bot/commands/user_config.py +++ b/bot/commands/user_config.py @@ -41,4 +41,4 @@ async def cmd_notify(ctx): Accepted Values: {ctx.author_settings.settings.notify_level.accepted_table} """ - await UserSettings.notify_level.command(ctx, ctx.author.id) + await UserSettings.settings.notify_level.command(ctx, ctx.author.id) From 68ae1b97a47e209844152ce0cdaf6b6bee2539d3 Mon Sep 17 00:00:00 2001 From: Interitio Date: Thu, 10 Jun 2021 00:56:39 +0300 Subject: [PATCH 06/31] fix (cmd_newgroup): Repair typo in manual. --- bot/commands/existential_cmds.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bot/commands/existential_cmds.py b/bot/commands/existential_cmds.py index 10c5905..42ebfb8 100644 --- a/bot/commands/existential_cmds.py +++ b/bot/commands/existential_cmds.py @@ -20,7 +20,7 @@ @timer_ready() async def cmd_newgroup(ctx: Context, flags): """ - Usage:`` + Usage``: {prefix}newgroup [flags] Description: Create a new study group (also called a timer) in the guild. @@ -232,7 +232,7 @@ async def cmd_newgroup(ctx: Context, flags): @timer_ready() async def cmd_delgroup(ctx): """ - Usage:`` + Usage``: {prefix}delgroup Description: Delete a guild study group. From 94a12ce709c415902de32ab900bccf86e4d20d7f Mon Sep 17 00:00:00 2001 From: Interitio Date: Thu, 10 Jun 2021 01:03:49 +0300 Subject: [PATCH 07/31] fix (tconfig): Properly handle quoted timer names. --- bot/commands/timer_config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/commands/timer_config.py b/bot/commands/timer_config.py index 173f18c..f91adc5 100644 --- a/bot/commands/timer_config.py +++ b/bot/commands/timer_config.py @@ -76,7 +76,7 @@ async def cmd_tconfig(ctx): timer = await ctx.get_timers_matching(maybe_name, channel_only=False, info=True) if not timer: return await ctx.error_reply("No groups found matching `{}`.".format(maybe_name)) - if len(splits) > 1: + if len(splits) > 1 and splits[1]: remaining_splits = splits[1].split(maxsplit=1) setting = setting_displaynames.get(remaining_splits[0].lower(), None) if setting is None: From 1b23f0714af4ffcc0109d0a26e2be3834548e320 Mon Sep 17 00:00:00 2001 From: Interitio Date: Thu, 10 Jun 2021 01:31:11 +0300 Subject: [PATCH 08/31] tweak (Timer): Don't update vc for empty timers. --- bot/Timer/core.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bot/Timer/core.py b/bot/Timer/core.py index 45768f8..bb59f90 100644 --- a/bot/Timer/core.py +++ b/bot/Timer/core.py @@ -445,7 +445,7 @@ async def post(self, *args, **kwargs): except discord.Forbidden: # We are not allowed to send to the timer channel # Stop the timer - await self.stop() + self.stop() except discord.HTTPException: # An unknown discord error occured # Silently continue @@ -836,7 +836,7 @@ async def runloop(self): context="rid:{}".format(self.role.id), level=logging.ERROR, add_exc_info=True) - elif remaining > 600: + elif remaining > 600 and self.subscribers: await self.update_voice() self._loop_wait_task = asyncio.create_task(asyncio.sleep(min(600, remaining))) From 28887e0b54e5bfcd57966c7b714074a1331d5113 Mon Sep 17 00:00:00 2001 From: Interitio Date: Thu, 10 Jun 2021 02:20:05 +0300 Subject: [PATCH 09/31] style (pagingtor): Ensure space after number. --- bot/utils/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/utils/lib.py b/bot/utils/lib.py index ae308b4..dd0f94e 100644 --- a/bot/utils/lib.py +++ b/bot/utils/lib.py @@ -47,7 +47,7 @@ def paginate_list(item_list, block_length=20, style="markdown", title=None): List of pages, each formatted into a codeblock, and containing at most `block_length` of the provided strings. """ - lines = ["{0:<5}{1:<5}".format("{}.".format(i + 1), str(line)) for i, line in enumerate(item_list)] + lines = ["{0:<5}{1:<5}".format("{}. ".format(i + 1), str(line)) for i, line in enumerate(item_list)] page_blocks = [lines[i:i + block_length] for i in range(0, len(lines), block_length)] pages = [] for i, block in enumerate(page_blocks): From 5af8d90f138cdfac2e63184ab23243750b7ad01e Mon Sep 17 00:00:00 2001 From: Interitio Date: Thu, 10 Jun 2021 02:53:22 +0300 Subject: [PATCH 10/31] fix (setting cmds): Repair typo in check error. --- bot/settings/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/settings/base.py b/bot/settings/base.py index b58d4aa..1e6c42f 100644 --- a/bot/settings/base.py +++ b/bot/settings/base.py @@ -193,7 +193,7 @@ async def command(cls, ctx, id): else: # Check the write ward if cls.write_ward and not await cls.write_ward.run(ctx): - await ctx.error_reply(cls.msg) + await ctx.error_reply(cls.write_ward.msg) else: # Attempt to set config cls try: From 207fbf9c28fbe64af539a6a06089818f293c6961 Mon Sep 17 00:00:00 2001 From: Interitio Date: Thu, 10 Jun 2021 02:55:55 +0300 Subject: [PATCH 11/31] fix (forcekick): Handle empty args. --- bot/commands/timer_admin.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/bot/commands/timer_admin.py b/bot/commands/timer_admin.py index b61fc10..6c5e564 100644 --- a/bot/commands/timer_admin.py +++ b/bot/commands/timer_admin.py @@ -21,6 +21,11 @@ async def cmd_forcekick(ctx): Examples``: {prefix}forcekick {ctx.author.name} """ + if not ctx.args: + return await ctx.error_reply( + "**Usage:** `{}forcekick `\n" + "Please provided a user to kick.".format(ctx.best_prefix) + ) subscribers = [ sub for timer in ctx.timers.get_timers_in(ctx.guild.id) for sub in timer.subscribers.values() ] From fd80ae2d9db75c7a10ea837b319717c222b1cd7b Mon Sep 17 00:00:00 2001 From: Interitio Date: Thu, 10 Jun 2021 03:04:23 +0300 Subject: [PATCH 12/31] fix (core): Repair typo in tchannel error logic. --- bot/Timer/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/Timer/core.py b/bot/Timer/core.py index bb59f90..2d90530 100644 --- a/bot/Timer/core.py +++ b/bot/Timer/core.py @@ -947,7 +947,7 @@ async def update_pin(self, force=False): timer.stop() perms = self.channel.permissions_for(self.channel.guild.me) - if perms.send_messges and not perms.embed_links: + if perms.send_messages and not perms.embed_links: try: await self.channel.send( "I require the `embed links` permission in this channel! Timers stopped." From a0bf6d23b2728572365533361ff97b6cc5c4a780 Mon Sep 17 00:00:00 2001 From: Interitio Date: Thu, 10 Jun 2021 11:54:41 +0300 Subject: [PATCH 13/31] fix (timer cmds): Repair admin lock check. --- bot/commands/timer.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/bot/commands/timer.py b/bot/commands/timer.py index 0d8e87d..7517d23 100644 --- a/bot/commands/timer.py +++ b/bot/commands/timer.py @@ -180,7 +180,7 @@ async def cmd_set(ctx): "You are not in a study group! Join one with `{prefix}join`.".format(prefix=ctx.best_prefix) ) - if sub.timer.settings.admin_locked and not await is_timer_admin(ctx.author): + if sub.timer.settings.admin_locked.value and not await is_timer_admin(ctx.author): return await ctx.error_reply("This timer may only be setup by timer admins.") if not ctx.args: @@ -231,7 +231,7 @@ async def cmd_reset(ctx): "You are not in a study group! Join one with `{prefix}join`.".format(prefix=ctx.best_prefix) ) - if sub.timer.settings.admin_locked and not await is_timer_admin(ctx.author): + if sub.timer.settings.admin_locked.value and not await is_timer_admin(ctx.author): return await ctx.error_reply("This timer may only be reset by timer admins.") if sub.timer.state == TimerState.RUNNING: @@ -289,7 +289,7 @@ async def cmd_start(ctx): "You are not in a study group! Join one with `{prefix}join`.".format(prefix=ctx.best_prefix) ) if sub.timer.state == TimerState.RUNNING: - if sub.timer.settings.admin_locked and not await is_timer_admin(ctx.author): + if sub.timer.settings.admin_locked.value and not await is_timer_admin(ctx.author): return await ctx.error_reply("This timer may only be restarted by timer admins.") if await ctx.ask("Are you sure you want to **restart** your study group timer?"): @@ -347,7 +347,7 @@ async def cmd_stop(ctx): "You are not in a study group! Join one with `{prefix}join`.".format(prefix=ctx.best_prefix) ) - if sub.timer.settings.admin_locked and not await is_timer_admin(ctx.author): + if sub.timer.settings.admin_locked.value and not await is_timer_admin(ctx.author): return await ctx.error_reply("This timer may only be stopped by timer admins.") if sub.timer.state != TimerState.RUNNING: @@ -514,7 +514,7 @@ async def cmd_shift(ctx): "You are not in a study group!" ) - if sub.timer.settings.admin_locked and not await is_timer_admin(ctx.author): + if sub.timer.settings.admin_locked.value and not await is_timer_admin(ctx.author): return await ctx.error_reply("This timer may only be shifted by timer admins.") if sub.timer.state != TimerState.RUNNING: @@ -571,7 +571,7 @@ async def cmd_skip(ctx): ) timer = sub.timer - if timer.settings.admin_locked and not await is_timer_admin(ctx.author): + if timer.settings.admin_locked.value and not await is_timer_admin(ctx.author): return await ctx.error_reply("This timer may only be skipped by timer admins.") if timer.state != TimerState.RUNNING: @@ -651,7 +651,7 @@ async def cmd_syncwith(ctx, flags): ) timer = sub.timer - if timer.settings.admin_locked and not await is_timer_admin(ctx.author): + if timer.settings.admin_locked.value and not await is_timer_admin(ctx.author): return await ctx.error_reply("This timer may only be synced by a timer admin.") if timer.state != TimerState.RUNNING: From 3cf937cf0d516d7ca5acad157bf06e6a69c3a058 Mon Sep 17 00:00:00 2001 From: Interitio Date: Thu, 10 Jun 2021 12:01:47 +0300 Subject: [PATCH 14/31] fix (tconfig): Repair write ward check. --- bot/commands/timer_config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/commands/timer_config.py b/bot/commands/timer_config.py index f91adc5..a339d9c 100644 --- a/bot/commands/timer_config.py +++ b/bot/commands/timer_config.py @@ -140,7 +140,7 @@ async def cmd_tconfig(ctx): else: # Check the write ward if not await setting.write_ward.run(ctx): - return await ctx.error_reply(setting.msg) + return await ctx.error_reply(setting.write_ward.msg) # Write the setting value try: From 7a42625d5f2975664d9b66542ed8d9024d2ff4b6 Mon Sep 17 00:00:00 2001 From: Interitio Date: Thu, 10 Jun 2021 12:13:11 +0300 Subject: [PATCH 15/31] fix (TimerChannel): Repair pinned error handling. --- bot/Timer/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/Timer/core.py b/bot/Timer/core.py index 2d90530..0b6d703 100644 --- a/bot/Timer/core.py +++ b/bot/Timer/core.py @@ -930,7 +930,7 @@ async def update_pin(self, force=False): try: await self.pinned_msg.edit(embed=embed) except discord.NotFound: - self.msg = None + self.pinned_msg = None except discord.HTTPException: # An obscure permission error or discord dying? self.failure_count += 1 From 5c03f9cb97bd0fc99d1c33f39881637afcbdc408 Mon Sep 17 00:00:00 2001 From: Interitio Date: Fri, 11 Jun 2021 00:08:38 +0300 Subject: [PATCH 16/31] fix (settings): Repair attr name collision. --- bot/settings/user_settings.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bot/settings/user_settings.py b/bot/settings/user_settings.py index 08a9122..ad60e1b 100644 --- a/bot/settings/user_settings.py +++ b/bot/settings/user_settings.py @@ -63,7 +63,7 @@ class notify_level(IntegerEnum, UserSetting): } accepted_table = prop_tabulate(*zip(*accepted_dict.items())) - success_response = { + success_responses = { _enum.ALL: "You will receive all stage changes and status updates via DM.", _enum.WARNING: "You will only receive a DM for inactivity warnings.", _enum.FINAL: "You will only receive a DM after being kicked for inactivity.", @@ -83,7 +83,7 @@ def embed(self): def success_response(self): return ( "Your notification level is now {}.\n{}" - ).format(self.formatted, self.success_response[self.value]) + ).format(self.formatted, self.success_responses[self.value]) @classmethod async def _parse_userstr(cls, ctx, id, userstr, **kwargs): From 6a07d389754a051cffc56cb18f0e5da152222794 Mon Sep 17 00:00:00 2001 From: Interitio Date: Fri, 11 Jun 2021 00:27:05 +0300 Subject: [PATCH 17/31] fix (notify_cmd): Change `none` to `never`. Change the `none` notify level to `never` to avoid conflict with setting none. --- bot/Timer/lib.py | 2 +- bot/settings/user_settings.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bot/Timer/lib.py b/bot/Timer/lib.py index c9e1212..d6376cc 100644 --- a/bot/Timer/lib.py +++ b/bot/Timer/lib.py @@ -61,7 +61,7 @@ class NotifyLevel(IntEnum): WARNING: Send direct messages for unsubscription warnings. ALL: Send direct messages for all stage updates. """ - NONE = 1 + NEVER = 1 FINAL = 2 WARNING = 3 ALL = 4 diff --git a/bot/settings/user_settings.py b/bot/settings/user_settings.py index ad60e1b..5aa7d81 100644 --- a/bot/settings/user_settings.py +++ b/bot/settings/user_settings.py @@ -59,7 +59,7 @@ class notify_level(IntegerEnum, UserSetting): 'all': "Receive all stage changes and status updates via DM.", 'warning': "Only receive a DM for inactivity warnings.", 'final': "Only receive a DM after being kicked for inactivity.", - 'none': "Never receive status updates via DM." + 'never': "Never receive status updates via DM." } accepted_table = prop_tabulate(*zip(*accepted_dict.items())) @@ -67,7 +67,7 @@ class notify_level(IntegerEnum, UserSetting): _enum.ALL: "You will receive all stage changes and status updates via DM.", _enum.WARNING: "You will only receive a DM for inactivity warnings.", _enum.FINAL: "You will only receive a DM after being kicked for inactivity.", - _enum.NONE: "You will never receive status updates via DM." + _enum.NEVER: "You will never receive status updates via DM." } @property From d1e19cc9e0c19e2ac3bd2038a125ecda7eb9578f Mon Sep 17 00:00:00 2001 From: Interitio Date: Fri, 11 Jun 2021 10:43:38 +0300 Subject: [PATCH 18/31] fix (timer): Refresh timer data when reloading. --- bot/Timer/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/Timer/core.py b/bot/Timer/core.py index 0b6d703..428f8c4 100644 --- a/bot/Timer/core.py +++ b/bot/Timer/core.py @@ -408,7 +408,7 @@ def load(self): `True` if the timer successfully loaded. `False` if the guild, channel, or role no longer exist. """ - data = self.data + data = self.data = tables.timers.fetch(self.data.roleid) self.guild = client.get_guild(data.guildid) if not self.guild: From 1c3dbbc0a23dda6aec0fcd05bafcc41c9e5828a3 Mon Sep 17 00:00:00 2001 From: Interitio Date: Fri, 11 Jun 2021 10:45:14 +0300 Subject: [PATCH 19/31] tweak (voice): Use timer settings instead of data. --- bot/Timer/voice_events.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bot/Timer/voice_events.py b/bot/Timer/voice_events.py index 4e8689d..49f4676 100644 --- a/bot/Timer/voice_events.py +++ b/bot/Timer/voice_events.py @@ -16,10 +16,10 @@ async def vc_update_handler(client, member, before, after): left = voice_channels.get(before.channel.id, None) if before.channel else None joined = voice_channels.get(after.channel.id, None) if after.channel else None - leave = (left and member.id in left and left.data.track_voice_join is not False) + leave = (left and member.id in left and left.settings.track_voice_join.value) join = ( joined and - joined.data.track_voice_leave is not False and + joined.settings.track_voice_leave.value and (leave or not client.interface.get_subscriber(member.id, member.guild.id)) ) existing_sub = None From 3b8051efdbbe8fb938b00a419896b341f8504330 Mon Sep 17 00:00:00 2001 From: Interitio Date: Fri, 11 Jun 2021 20:22:36 +0300 Subject: [PATCH 20/31] fix (tconfig): Repair channel configuration. Ensure channel re-configuration updates the interface guild channels. --- bot/Timer/__init__.py | 14 ++++++++++++++ bot/settings/timer_settings.py | 5 +++++ 2 files changed, 19 insertions(+) diff --git a/bot/Timer/__init__.py b/bot/Timer/__init__.py index 5d9d178..511fb58 100644 --- a/bot/Timer/__init__.py +++ b/bot/Timer/__init__.py @@ -170,6 +170,20 @@ async def obliterate_timer(self, timer): # Refresh the pinned message await channel.update_pin(force=True) + def move_timer(self, timer, new_channelid): + """ + Bind a timer to a new channelid. + """ + channels = self.guild_channels[timer.data.guildid] + old_channel = channels[timer.data.channelid] + old_channel.timers.remove(timer) + timer.data.channelid = new_channelid + timer.load() + + if new_channelid not in channels: + channels[new_channelid] = TimerChannel(timer.channel) + channels[new_channelid].timers.append(timer) + def fetch_timer(self, roleid): row = tables.timers.fetch(roleid) if row: diff --git a/bot/settings/timer_settings.py b/bot/settings/timer_settings.py index 1c542d1..8c57559 100644 --- a/bot/settings/timer_settings.py +++ b/bot/settings/timer_settings.py @@ -95,8 +95,13 @@ async def _parse_userstr(cls, ctx, id: int, userstr: str, **kwargs): raise UserInputError("Cannot send embeds in {}.".format(channel.mention)) elif not chan_perms.manage_messages: raise UserInputError("Cannot manage messages in {}.".format(channel.mention)) + else: + raise UserInputError("Timer channel cannot be empty!") return channelid + def write(self, **kwargs): + self.client.interface.move_timer(self.timer, self.data) + @TimerSettings.attach_setting class default_pattern(PatternType, TimerSetting): From 3df39a87d10d8a39b40c94cc5b45177e83104480 Mon Sep 17 00:00:00 2001 From: Interitio Date: Fri, 11 Jun 2021 22:16:17 +0300 Subject: [PATCH 21/31] (subscription): Handle role permission errors. --- bot/Timer/core.py | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/bot/Timer/core.py b/bot/Timer/core.py index 428f8c4..09751c0 100644 --- a/bot/Timer/core.py +++ b/bot/Timer/core.py @@ -730,10 +730,32 @@ async def subscribe(self, member, post=False): """ log("Subscribing {!r}.".format(member), context="rid:{}".format(self.data.roleid)) studyrole = GuildSettings(member.guild.id).studyrole.value - if studyrole: - await member.add_roles(self.role, studyrole, reason="Applying study group role and global studyrole.") - else: - await member.add_roles(self.role, reason="Applying study group role.") + try: + if studyrole: + await member.add_roles(self.role, studyrole, reason="Applying study group role and global studyrole.") + else: + await member.add_roles(self.role, reason="Applying study group role.") + except discord.Forbidden: + desc = ( + "I don't have enough permissions to subscribe {} to {}!\n" + ).format(member.mention, self.role.mention) + if not self.guild.me.guild_permissions.manage_roles: + desc += "I require the `manage_roles` permission!" + elif not self.guild.me.top_role > self.role: + desc += ( + "My top role needs to be higher in the role list than the study group role {}." + ).format(self.role.mention) + elif studyrole and not self.guild.me.top_role > studyrole: + desc += ( + "My top role needs to be higher in the role list than the studyrole {}." + ).format(studyrole.mention) + + await self.post( + embed=discord.Embed( + description=desc, + colour=discord.Colour.red() + ) + ) subscriber = TimerSubscriber(self, member.id, member=member) if self.state == TimerState.RUNNING: subscriber.new_session() From 348269e3c89269dc518692afd288fdf459786064 Mon Sep 17 00:00:00 2001 From: Interitio Date: Fri, 11 Jun 2021 22:29:09 +0300 Subject: [PATCH 22/31] (interface): Cleaner shutdown method. Add `shutdown` method to the interface to cancel update loops. --- bot/Timer/__init__.py | 13 +++++++++++-- bot/plugins/exec-cmds.py | 3 ++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/bot/Timer/__init__.py b/bot/Timer/__init__.py index 511fb58..e08c2b3 100644 --- a/bot/Timer/__init__.py +++ b/bot/Timer/__init__.py @@ -32,6 +32,9 @@ def __init__(self, **kwargs): self.init_task(self.core_init) self.launch_task(self.core_launch) + self.runloop_task = None + self.saveloop_task = None + def core_init(self, _client): _client.interface = self Context.timers = module @@ -47,8 +50,14 @@ def core_init(self, _client): async def core_launch(self, _client): await self.load_timers() self.restore_from_save() - asyncio.create_task(self._runloop()) - asyncio.create_task(self._saveloop()) + self.runloop_task = asyncio.create_task(self._runloop()) + self.saveloop_task = asyncio.create_task(self._saveloop()) + + def shutdown(self): + if self.saveloop_task and not self.saveloop_task.done(): + self.saveloop_task.cancel() + if self.runloop_task and not self.runloop_task.done(): + self.runloop_task.cancel() async def _runloop(self): while True: diff --git a/bot/plugins/exec-cmds.py b/bot/plugins/exec-cmds.py index 69e28c5..d3af7f2 100644 --- a/bot/plugins/exec-cmds.py +++ b/bot/plugins/exec-cmds.py @@ -28,8 +28,9 @@ async def cmd_reboot(ctx): Update the timer status save file and reboot the client. """ ctx.client.interface.update_save("reboot") + ctx.client.interface.shutdown() await ctx.reply("Saved state. Rebooting now!") - await ctx.client.logout() + await ctx.client.close() @cmd("async") From 12c7cec322585b75e5b17238a49bd5dd67cda72f Mon Sep 17 00:00:00 2001 From: Interitio Date: Sat, 12 Jun 2021 19:05:01 +0300 Subject: [PATCH 23/31] docs (history): Add timezone setting tip. --- bot/commands/registry.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/bot/commands/registry.py b/bot/commands/registry.py index f49ac81..717ab05 100644 --- a/bot/commands/registry.py +++ b/bot/commands/registry.py @@ -126,7 +126,7 @@ async def cmd_leaderboard(ctx): _history_pattern = """\ -```md +{tip}```md {day} ({tz}) (Page {page}/{page_count}) Period | Duration | Focused | Pattern @@ -159,6 +159,10 @@ async def cmd_history(ctx): The times are determined using your personal timezone (see `{prefix}mytimezone`). """ timezone = ctx.author_settings.timezone.value + has_default_tz = (tables.users.fetch_or_create(ctx.author.id).timezone is None) + tip = ( + "**Tip:** Use `{}mytimezone` to view your sessions in your own timezone!".format(ctx.best_prefix) + ) if has_default_tz else '' # Get the saved session rows, ordered by newest first rows = tables.session_patterns.select_where( @@ -245,6 +249,7 @@ async def cmd_history(ctx): # Add to page list pages.append( _history_pattern.format( + tip=tip, day=day, tz=timezone, page=i+1, From a6633e98f5491e19ce9d8cf1361459bddafda9b8 Mon Sep 17 00:00:00 2001 From: Interitio Date: Sat, 12 Jun 2021 19:10:32 +0300 Subject: [PATCH 24/31] (pager util): Improve permission error responses. --- bot/utils/interactive.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/bot/utils/interactive.py b/bot/utils/interactive.py index d86c045..af0be6c 100644 --- a/bot/utils/interactive.py +++ b/bot/utils/interactive.py @@ -273,7 +273,24 @@ async def _pager(ctx, out_msg, pages, locked, start_at, add_cancel, **kwargs): except discord.Forbidden: # We don't have permission to add paging emojis # Die as gracefully as we can - await ctx.error_reply("Cannot page results because I do not have permissions to react!") + if ctx.guild: + perms = ctx.ch.permissions_for(ctx.guild.me) + if not perms.add_reactions: + await ctx.error_reply( + "Cannot page results because I do not have the `add_reactions` permission!" + ) + elif not perms.read_message_history: + await ctx.error_reply( + "Cannot page results because I do not have the `read_message_history` permission!" + ) + else: + await ctx.error_reply( + "Cannot page results due to insufficient permissions!" + ) + else: + await ctx.error_reply( + "Cannot page results!" + ) return # Check function to determine whether a reaction is valid From 455d749016ac0971cf112390219d17c859cdbb7c Mon Sep 17 00:00:00 2001 From: Interitio Date: Sun, 13 Jun 2021 11:16:09 +0300 Subject: [PATCH 25/31] tweak (history): Truncate displayed patterns. --- bot/commands/registry.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/bot/commands/registry.py b/bot/commands/registry.py index 717ab05..ab1c22a 100644 --- a/bot/commands/registry.py +++ b/bot/commands/registry.py @@ -210,7 +210,11 @@ async def cmd_history(ctx): .astimezone(timezone) ) end = start + dt.timedelta(seconds=row['duration']) - pattern = '/'.join(str(stage[1]) for stage in json.loads(row['stage_str'])) if row['stage_str'] else '' + if row['stage_str']: + stages = json.loads(row['stage_str']) + pattern = '/'.join(str(stage[1]) if i < 6 else '...' for i, stage in enumerate(stages[:7])) + else: + pattern = '' row_lines.append( _history_session_pattern.format( start=start.strftime("%H:%M"), From b63a93f3753c89dd8bd4b2e6dfc66250696effc1 Mon Sep 17 00:00:00 2001 From: Interitio Date: Sun, 13 Jun 2021 11:44:12 +0300 Subject: [PATCH 26/31] (patterns): Search presets before parsing. Restores support for presets with pattern-like names (e.g. `50/10`). --- bot/Timer/core.py | 166 +++++++++++++++++++++++----------------------- 1 file changed, 83 insertions(+), 83 deletions(-) diff --git a/bot/Timer/core.py b/bot/Timer/core.py index 09751c0..3c5d44f 100644 --- a/bot/Timer/core.py +++ b/bot/Timer/core.py @@ -67,95 +67,95 @@ def from_userstr(cls, string, timerid=None, userid=None, guildid=None): if not string: raise InvalidPattern("No pattern provided!") - # Parsing step pattern = None - stages = None - if ';' in string or ',' in string: - # Long form - # Accepts stages as 'name, length' or 'name, length, message' - short_repr = False - stage_blocks = string.strip(';').split(';') - stages = [] - for block in stage_blocks: - # Extract stage components - parts = block.split(',', maxsplit=2) - if len(parts) == 1: - raise InvalidPattern( - "`{}` is not of the form `name, length` or `name, length, message`.".format(block) - ) - elif len(parts) == 2: - name, dur = parts - message = None - else: - name, dur, message = parts - - # Parse duration - dur = dur.strip() - focus = dur.startswith('*') or dur.endswith('*') - if focus: - dur = dur.strip('* ') - if not dur.isdigit(): - raise InvalidPattern( - "`{}` in `{}` couldn't be parsed as a duration.".format(dur, block.strip()) - ) + # First try presets + if userid: + row = tables.user_presets.select_one_where(userid=userid, preset_name=string) + if row: + pattern = cls.get(row['patternid']) + + if not pattern and guildid: + row = tables.guild_presets.select_one_where(guildid=guildid, preset_name=string) + if row: + pattern = cls.get(row['patternid']) + + # Then try string parsing + if not pattern: + stages = None + if ';' in string or ',' in string: + # Long form + # Accepts stages as 'name, length' or 'name, length, message' + short_repr = False + stage_blocks = string.strip(';').split(';') + stages = [] + for block in stage_blocks: + # Extract stage components + parts = block.split(',', maxsplit=2) + if len(parts) == 1: + raise InvalidPattern( + "`{}` is not of the form `name, length` or `name, length, message`.".format(block) + ) + elif len(parts) == 2: + name, dur = parts + message = None + else: + name, dur, message = parts + + # Parse duration + dur = dur.strip() + focus = dur.startswith('*') or dur.endswith('*') + if focus: + dur = dur.strip('* ') + + if not dur.isdigit(): + raise InvalidPattern( + "`{}` in `{}` couldn't be parsed as a duration.".format(dur, block.strip()) + ) - # Build and add stage - stages.append(Stage(name.strip(), int(dur), (message or '').strip(), focus)) - elif '/' in string: - # Short form - # Only accepts numerical stages - short_repr = True - stage_blocks = string.strip('/').split('/') - stages = [] - - is_work = True # Whether the current stage is a work or break stage - default_focus = '*' not in string # Whether to use default focus flags - for block in stage_blocks: - # Parse duration - dur = block.strip() - focus = dur.startswith('*') or dur.endswith('*') - if focus: - dur = dur.strip('* ') - - if not dur.isdigit(): - raise InvalidPattern( - "`{}` couldn't be parsed as a duration.".format(dur) - ) + # Build and add stage + stages.append(Stage(name.strip(), int(dur), (message or '').strip(), focus)) + elif '/' in string: + # Short form + # Only accepts numerical stages + short_repr = True + stage_blocks = string.strip('/').split('/') + stages = [] + + is_work = True # Whether the current stage is a work or break stage + default_focus = '*' not in string # Whether to use default focus flags + for block in stage_blocks: + # Parse duration + dur = block.strip() + focus = dur.startswith('*') or dur.endswith('*') + if focus: + dur = dur.strip('* ') + + if not dur.isdigit(): + raise InvalidPattern( + "`{}` couldn't be parsed as a duration.".format(dur) + ) - # Build and add stage - if is_work: - stages.append(Stage( - cls.default_work_stage, - int(dur), - cls.default_work_message, - focus=True if default_focus else focus - )) - else: - stages.append(Stage( - cls.default_break_stage, - int(dur), - cls.default_break_message, - focus=False if default_focus else focus - )) - - is_work = not is_work - else: - # Attempt to find a matching preset - if userid: - row = tables.user_presets.select_one_where(userid=userid, preset_name=string) - if row: - pattern = cls.get(row['patternid']) - - if not pattern and guildid: - row = tables.guild_presets.select_one_where(guildid=guildid, preset_name=string) - if row: - pattern = cls.get(row['patternid']) - - if not pattern: + # Build and add stage + if is_work: + stages.append(Stage( + cls.default_work_stage, + int(dur), + cls.default_work_message, + focus=True if default_focus else focus + )) + else: + stages.append(Stage( + cls.default_break_stage, + int(dur), + cls.default_break_message, + focus=False if default_focus else focus + )) + + is_work = not is_work + else: raise InvalidPattern("Patterns must have more than one stage!") - if stages: # Create the stage string stage_str = json.dumps(stages) From 847625219cb584f6e296c245c010c239a32f689d Mon Sep 17 00:00:00 2001 From: Interitio Date: Sun, 13 Jun 2021 12:04:09 +0300 Subject: [PATCH 27/31] (history): Add current session to `hist`. --- bot/commands/registry.py | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/bot/commands/registry.py b/bot/commands/registry.py index ab1c22a..adb34e2 100644 --- a/bot/commands/registry.py +++ b/bot/commands/registry.py @@ -6,7 +6,7 @@ from cmdClient.checks import in_guild from Timer import module, Pattern -from Timer.lib import parse_dur +from Timer.lib import parse_dur, now from data import tables from data.queries import get_session_user_totals @@ -171,6 +171,28 @@ async def cmd_history(ctx): userid=ctx.author.id ) + # Add the current session, if it exists + sub = ctx.timers.get_subscriber(ctx.author.id, ctx.guild.id) + if sub and sub.session: + start_time = sub.session_started + current_duration = sub.unsaved_time + focused_duration = sum( + t[0] * stage.duration * 60 + t[1] + for t, stage in zip(sub.session, sub.timer.current_pattern) + if stage.focus + ) + if sub.timer.current_stage.focus: + focused_duration += (now() - sub.timer.stage_start) + + pattern = tables.patterns.fetch(sub.timer.current_pattern.row.patternid).stage_str + current_row = { + 'start_time': start_time, + 'duration': current_duration, + 'focused_duration': focused_duration, + 'stage_str': pattern + } + rows = [current_row, *rows] + if not rows: return await ctx.reply( "You have no recorded sessions! Join a running timer to start recording study time!" From 866b3840c9bc216d5260046389ce53abad2e9fc7 Mon Sep 17 00:00:00 2001 From: Interitio Date: Sun, 27 Jun 2021 09:57:27 +0300 Subject: [PATCH 28/31] fix (newgroup): Typo in voice hint. --- bot/commands/existential_cmds.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/commands/existential_cmds.py b/bot/commands/existential_cmds.py index 42ebfb8..0933a00 100644 --- a/bot/commands/existential_cmds.py +++ b/bot/commands/existential_cmds.py @@ -129,7 +129,7 @@ async def cmd_newgroup(ctx: Context, flags): if voice is None: voice_line = ( "To associate a voice channel (for voice alerts or to auto-join members) " - "use `{}tconfig \"{}\" voice `." + "use `{}tconfig \"{}\" voice_channel `." ).format(ctx.best_prefix, name) else: other = next( From 889571dd74f91edce87b17023c5207e991a28992 Mon Sep 17 00:00:00 2001 From: Interitio Date: Sun, 27 Jun 2021 10:04:11 +0300 Subject: [PATCH 29/31] fix (Timer load): Clear timer vc when needed. Fixes #43. --- bot/Timer/core.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bot/Timer/core.py b/bot/Timer/core.py index 3c5d44f..4f23b4e 100644 --- a/bot/Timer/core.py +++ b/bot/Timer/core.py @@ -430,6 +430,8 @@ def load(self): if data.voice_channelid: self.voice_channel = self.guild.get_channel(data.voice_channelid) + else: + self.voice_channel = None return True From 52c56d9f88faa93d2e69247a14fe87dd9a0233d5 Mon Sep 17 00:00:00 2001 From: Interitio Date: Sun, 27 Jun 2021 10:10:41 +0300 Subject: [PATCH 30/31] fix (stats): Fix streak calculation. Fixes an issue where the streak calculator ignored the last session. --- bot/commands/registry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/commands/registry.py b/bot/commands/registry.py index adb34e2..49cbe3f 100644 --- a/bot/commands/registry.py +++ b/bot/commands/registry.py @@ -418,7 +418,7 @@ async def cmd_stats(ctx): session_periods = ((row['start_time'], row['end_time']) for row in session_rows) # Account for the current day - start_time, end_time = next(session_periods, (0, 0)) + start_time, end_time = (session_rows[0]['start_time'], session_rows[0]['end_time']) if session_rows else (0, 0) if sub or end_time > ts_window[0]: streak += 1 day_window = (day_window[0] - dt.timedelta(days=1), day_window[0]) From aa535700e7e152a0dc1ac808ac66eb2dac132601 Mon Sep 17 00:00:00 2001 From: Interitio Date: Sun, 27 Jun 2021 10:12:52 +0300 Subject: [PATCH 31/31] docs (data): Add sample `v0-v1` migration config. --- data/migration/v0-v1/sample-migration.conf | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 data/migration/v0-v1/sample-migration.conf diff --git a/data/migration/v0-v1/sample-migration.conf b/data/migration/v0-v1/sample-migration.conf new file mode 100644 index 0000000..d4e80b4 --- /dev/null +++ b/data/migration/v0-v1/sample-migration.conf @@ -0,0 +1,13 @@ +# Copy this file to `migration.conf`. +# Don't change any values unless the database files have been moved. +# All paths are relative to `bot/data`. +[Original] +settings_db = config_data.db +session_db = sessions.db + +# This can also be None if the session data doesn't need to be transferred +savefile = timerstatus.json + +[Target] +database = data.db +savefile = timerstatus/timerstatus.pickle

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 literal 0 HcmV?d00001 diff --git a/assets/sounds/slow-spring-board.wav b/assets/sounds/slow-spring-board.wav new file mode 100644 index 0000000000000000000000000000000000000000..14f2779caeb02d88d89b83959a32554587c89849 GIT binary patch literal 299646 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