From 4529ad3d4a29e45209f0aed1290756c9ba8e00b0 Mon Sep 17 00:00:00 2001 From: RogueException Date: Sun, 13 Sep 2015 11:46:26 -0300 Subject: [PATCH] Refactor all the things! --- Discord.Net.sln | 31 - src/Discord.Net.Commands/DiscordBotClient.cs | 2 +- src/Discord.Net.Commands/project.json | 4 +- src/Discord.Net.Net45/Discord.Net.csproj | 146 +-- src/Discord.Net.Net45/lib/libopus.so | Bin 0 -> 260748 bytes src/Discord.Net.Net45/lib/opus.dll | Bin 0 -> 271872 bytes src/Discord.Net.Net45/packages.config | 1 + src/Discord.Net/API/DiscordAPI.cs | 152 ---- src/Discord.Net/API/Endpoints.cs | 56 -- src/Discord.Net/API/Models/APIResponses.cs | 101 --- src/Discord.Net/Audio/Opus.cs | 71 ++ .../{lib/Opus => Audio}/OpusEncoder.cs | 32 +- .../Collections/AsyncCollection.cs | 118 +++ src/Discord.Net/Collections/Channels.cs | 61 ++ src/Discord.Net/Collections/Members.cs | 67 ++ src/Discord.Net/Collections/Messages.cs | 33 + src/Discord.Net/Collections/Roles.cs | 44 + src/Discord.Net/Collections/Servers.cs | 40 + src/Discord.Net/Collections/Users.cs | 54 ++ src/Discord.Net/DiscordClient.API.cs | 176 ++-- src/Discord.Net/DiscordClient.Cache.cs | 481 +--------- src/Discord.Net/DiscordClient.Events.cs | 101 +-- src/Discord.Net/DiscordClient.Voice.cs | 57 ++ src/Discord.Net/DiscordClient.cs | 836 ++++++------------ src/Discord.Net/DiscordClientConfig.cs | 60 +- src/Discord.Net/DiscordDataSocket.Events.cs | 25 - src/Discord.Net/DiscordDataSocket.cs | 140 --- src/Discord.Net/DiscordWebSocket.Events.cs | 35 - src/Discord.Net/DiscordWebSocket.cs | 286 ------ src/Discord.Net/Enums/Regions.cs | 8 +- src/Discord.Net/Enums/UserStatus.cs | 2 +- src/Discord.Net/Format.cs | 22 +- src/Discord.Net/Helpers/AsyncCache.cs | 107 --- src/Discord.Net/Helpers/Extensions.cs | 6 +- .../Helpers/JsonHttpClient.Events.cs | 14 - src/Discord.Net/Helpers/JsonHttpClient.cs | 207 ----- src/Discord.Net/Helpers/MessageCleaner.cs | 43 + src/Discord.Net/Helpers/TaskHelper.cs | 4 +- src/Discord.Net/Mention.cs | 24 + src/Discord.Net/Models/Channel.cs | 68 +- src/Discord.Net/Models/Invite.cs | 39 +- src/Discord.Net/Models/Member.cs | 64 +- src/Discord.Net/Models/Message.cs | 98 +- src/Discord.Net/Models/PackedPermissions.cs | 61 +- src/Discord.Net/Models/Role.cs | 16 +- src/Discord.Net/Models/Server.cs | 223 +++-- src/Discord.Net/Models/User.cs | 52 +- .../{API/Models => Net/API}/Common.cs | 83 +- src/Discord.Net/Net/API/DiscordAPIClient.cs | 158 ++++ src/Discord.Net/Net/API/Endpoints.cs | 42 + .../APIRequests.cs => Net/API/Requests.cs} | 64 +- src/Discord.Net/Net/API/Responses.cs | 85 ++ src/Discord.Net/{ => Net}/HttpException.cs | 6 +- src/Discord.Net/Net/RestClient.BuiltIn.cs | 65 ++ src/Discord.Net/Net/RestClient.Events.cs | 30 + src/Discord.Net/Net/RestClient.SharpRest.cs | 68 ++ src/Discord.Net/Net/RestClient.cs | 184 ++++ .../WebSockets/Commands.cs} | 33 +- .../Net/WebSockets/DataWebSocket.cs | 102 +++ .../WebSockets/Events.cs} | 58 +- .../WebSockets/VoiceCommands.cs} | 35 +- .../WebSockets/VoiceEvents.cs} | 4 +- .../WebSockets/VoiceWebSocket.cs} | 342 ++++--- .../Net/WebSockets/WebSocket.BuiltIn.cs | 153 ++++ .../Net/WebSockets/WebSocket.Events.cs | 34 + src/Discord.Net/Net/WebSockets/WebSocket.cs | 192 ++++ .../Net/WebSockets/WebSocketMessage.cs | 31 + src/Discord.Net/lib/Opus/API.cs | 78 -- src/Discord.Net/project.json | 8 +- .../Discord.Net.Tests.csproj | 6 - 70 files changed, 3070 insertions(+), 3059 deletions(-) create mode 100644 src/Discord.Net.Net45/lib/libopus.so create mode 100644 src/Discord.Net.Net45/lib/opus.dll delete mode 100644 src/Discord.Net/API/DiscordAPI.cs delete mode 100644 src/Discord.Net/API/Endpoints.cs delete mode 100644 src/Discord.Net/API/Models/APIResponses.cs create mode 100644 src/Discord.Net/Audio/Opus.cs rename src/Discord.Net/{lib/Opus => Audio}/OpusEncoder.cs (79%) create mode 100644 src/Discord.Net/Collections/AsyncCollection.cs create mode 100644 src/Discord.Net/Collections/Channels.cs create mode 100644 src/Discord.Net/Collections/Members.cs create mode 100644 src/Discord.Net/Collections/Messages.cs create mode 100644 src/Discord.Net/Collections/Roles.cs create mode 100644 src/Discord.Net/Collections/Servers.cs create mode 100644 src/Discord.Net/Collections/Users.cs create mode 100644 src/Discord.Net/DiscordClient.Voice.cs delete mode 100644 src/Discord.Net/DiscordDataSocket.Events.cs delete mode 100644 src/Discord.Net/DiscordDataSocket.cs delete mode 100644 src/Discord.Net/DiscordWebSocket.Events.cs delete mode 100644 src/Discord.Net/DiscordWebSocket.cs delete mode 100644 src/Discord.Net/Helpers/AsyncCache.cs delete mode 100644 src/Discord.Net/Helpers/JsonHttpClient.Events.cs delete mode 100644 src/Discord.Net/Helpers/JsonHttpClient.cs create mode 100644 src/Discord.Net/Helpers/MessageCleaner.cs create mode 100644 src/Discord.Net/Mention.cs rename src/Discord.Net/{API/Models => Net/API}/Common.cs (81%) create mode 100644 src/Discord.Net/Net/API/DiscordAPIClient.cs create mode 100644 src/Discord.Net/Net/API/Endpoints.cs rename src/Discord.Net/{API/Models/APIRequests.cs => Net/API/Requests.cs} (62%) create mode 100644 src/Discord.Net/Net/API/Responses.cs rename src/Discord.Net/{ => Net}/HttpException.cs (87%) create mode 100644 src/Discord.Net/Net/RestClient.BuiltIn.cs create mode 100644 src/Discord.Net/Net/RestClient.Events.cs create mode 100644 src/Discord.Net/Net/RestClient.SharpRest.cs create mode 100644 src/Discord.Net/Net/RestClient.cs rename src/Discord.Net/{API/Models/TextWebSocketCommands.cs => Net/WebSockets/Commands.cs} (61%) create mode 100644 src/Discord.Net/Net/WebSockets/DataWebSocket.cs rename src/Discord.Net/{API/Models/TextWebSocketEvents.cs => Net/WebSockets/Events.cs} (66%) rename src/Discord.Net/{API/Models/VoiceWebSocketCommands.cs => Net/WebSockets/VoiceCommands.cs} (66%) rename src/Discord.Net/{API/Models/VoiceWebSocketEvents.cs => Net/WebSockets/VoiceEvents.cs} (92%) rename src/Discord.Net/{DiscordVoiceSocket.cs => Net/WebSockets/VoiceWebSocket.cs} (56%) create mode 100644 src/Discord.Net/Net/WebSockets/WebSocket.BuiltIn.cs create mode 100644 src/Discord.Net/Net/WebSockets/WebSocket.Events.cs create mode 100644 src/Discord.Net/Net/WebSockets/WebSocket.cs create mode 100644 src/Discord.Net/Net/WebSockets/WebSocketMessage.cs delete mode 100644 src/Discord.Net/lib/Opus/API.cs diff --git a/Discord.Net.sln b/Discord.Net.sln index 1d7cbfd25..b424296b0 100644 --- a/Discord.Net.sln +++ b/Discord.Net.sln @@ -12,18 +12,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution global.json = global.json EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Discord.Net.Tests", "test\Discord.Net.Tests\Discord.Net.Tests.csproj", "{855D6B1D-847B-42DA-BE6A-23683EA89511}" -EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net", "src\Discord.Net\Discord.Net.xproj", "{ACFB060B-EC8A-4926-B293-04C01E17EE23}" EndProject -Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net.Commands", "src\Discord.Net.Commands\Discord.Net.Commands.xproj", "{19793545-EF89-48F4-8100-3EBAAD0A9141}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "net45", "net45", "{B47C4063-C4EB-46AA-886D-B868DA1BF0A0}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Discord.Net", "src\Discord.Net.Net45\Discord.Net.csproj", "{8D71A857-879A-4A10-859E-5FF824ED6688}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Discord.Net.Commands", "src\Discord.Net.Commands.Net45\Discord.Net.Commands.csproj", "{1B5603B4-6F8F-4289-B945-7BAAE523D740}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "dotnet", "dotnet", "{EA68EBE2-51C8-4440-9EF7-D633C90A5D35}" EndProject Global @@ -32,37 +22,16 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {855D6B1D-847B-42DA-BE6A-23683EA89511}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {855D6B1D-847B-42DA-BE6A-23683EA89511}.Debug|Any CPU.Build.0 = Debug|Any CPU - {855D6B1D-847B-42DA-BE6A-23683EA89511}.Release|Any CPU.ActiveCfg = Release|Any CPU - {855D6B1D-847B-42DA-BE6A-23683EA89511}.Release|Any CPU.Build.0 = Release|Any CPU {ACFB060B-EC8A-4926-B293-04C01E17EE23}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {ACFB060B-EC8A-4926-B293-04C01E17EE23}.Debug|Any CPU.Build.0 = Debug|Any CPU {ACFB060B-EC8A-4926-B293-04C01E17EE23}.Release|Any CPU.ActiveCfg = Release|Any CPU {ACFB060B-EC8A-4926-B293-04C01E17EE23}.Release|Any CPU.Build.0 = Release|Any CPU - {19793545-EF89-48F4-8100-3EBAAD0A9141}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {19793545-EF89-48F4-8100-3EBAAD0A9141}.Debug|Any CPU.Build.0 = Debug|Any CPU - {19793545-EF89-48F4-8100-3EBAAD0A9141}.Release|Any CPU.ActiveCfg = Release|Any CPU - {19793545-EF89-48F4-8100-3EBAAD0A9141}.Release|Any CPU.Build.0 = Release|Any CPU - {8D71A857-879A-4A10-859E-5FF824ED6688}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8D71A857-879A-4A10-859E-5FF824ED6688}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8D71A857-879A-4A10-859E-5FF824ED6688}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8D71A857-879A-4A10-859E-5FF824ED6688}.Release|Any CPU.Build.0 = Release|Any CPU - {1B5603B4-6F8F-4289-B945-7BAAE523D740}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1B5603B4-6F8F-4289-B945-7BAAE523D740}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1B5603B4-6F8F-4289-B945-7BAAE523D740}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1B5603B4-6F8F-4289-B945-7BAAE523D740}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution - {855D6B1D-847B-42DA-BE6A-23683EA89511} = {6317A2E6-8E36-4C3E-949B-3F10EC888AB9} {ACFB060B-EC8A-4926-B293-04C01E17EE23} = {EA68EBE2-51C8-4440-9EF7-D633C90A5D35} - {19793545-EF89-48F4-8100-3EBAAD0A9141} = {EA68EBE2-51C8-4440-9EF7-D633C90A5D35} - {B47C4063-C4EB-46AA-886D-B868DA1BF0A0} = {8D7989F0-66CE-4DBB-8230-D8C811E9B1D7} - {8D71A857-879A-4A10-859E-5FF824ED6688} = {B47C4063-C4EB-46AA-886D-B868DA1BF0A0} - {1B5603B4-6F8F-4289-B945-7BAAE523D740} = {B47C4063-C4EB-46AA-886D-B868DA1BF0A0} {EA68EBE2-51C8-4440-9EF7-D633C90A5D35} = {8D7989F0-66CE-4DBB-8230-D8C811E9B1D7} EndGlobalSection EndGlobal diff --git a/src/Discord.Net.Commands/DiscordBotClient.cs b/src/Discord.Net.Commands/DiscordBotClient.cs index 403b002e7..2ccd70828 100644 --- a/src/Discord.Net.Commands/DiscordBotClient.cs +++ b/src/Discord.Net.Commands/DiscordBotClient.cs @@ -33,7 +33,7 @@ namespace Discord return; //Ignore messages from ourselves - if (e.Message.UserId == _myId) + if (e.Message.UserId == CurrentUserId) return; //Check for the command character diff --git a/src/Discord.Net.Commands/project.json b/src/Discord.Net.Commands/project.json index 11d1ebda6..49c4ed2b4 100644 --- a/src/Discord.Net.Commands/project.json +++ b/src/Discord.Net.Commands/project.json @@ -1,5 +1,5 @@ { - "version": "0.6.1-beta2", + "version": "0.7.0-beta1", "description": "A small Discord.Net extension to make bot creation easier.", "authors": [ "RogueException" ], "tags": [ "discord", "discordapp" ], @@ -13,7 +13,7 @@ "warningsAsErrors": true }, "dependencies": { - "Discord.Net": "0.6.1-beta2" + "Discord.Net": "0.7.0-beta1" }, "frameworks": { "net45": { }, diff --git a/src/Discord.Net.Net45/Discord.Net.csproj b/src/Discord.Net.Net45/Discord.Net.csproj index 12f47e3b1..3bf5d75c9 100644 --- a/src/Discord.Net.Net45/Discord.Net.csproj +++ b/src/Discord.Net.Net45/Discord.Net.csproj @@ -37,42 +37,52 @@ - ..\..\packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll + ..\..\..\DiscordBot\packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll + True + + + ..\..\..\DiscordBot\packages\RestSharp.105.2.3\lib\net45\RestSharp.dll True + + PreserveNewest + + + PreserveNewest + - - API\DiscordAPI.cs + + Audio\Opus.cs - - API\Endpoints.cs + + Audio\OpusEncoder.cs - - API\Models\APIRequests.cs + + Collections\AsyncCollection.cs - - API\Models\APIResponses.cs + + Collections\Channels.cs - - API\Models\Common.cs + + Collections\Members.cs - - API\Models\TextWebSocketCommands.cs + + Collections\Messages.cs - - API\Models\TextWebSocketEvents.cs + + Collections\Roles.cs - - API\Models\VoiceWebSocketCommands.cs + + Collections\Servers.cs - - API\Models\VoiceWebSocketEvents.cs + + Collections\Users.cs DiscordClient.API.cs @@ -86,24 +96,12 @@ DiscordClient.Events.cs + + DiscordClient.Voice.cs + DiscordClientConfig.cs - - DiscordDataSocket.cs - - - DiscordDataSocket.Events.cs - - - DiscordVoiceSocket.cs - - - DiscordWebSocket.cs - - - DiscordWebSocket.Events.cs - Enums\ChannelTypes.cs @@ -116,29 +114,17 @@ Format.cs - - Helpers\AsyncCache.cs - Helpers\Extensions.cs - - Helpers\JsonHttpClient.cs - - - Helpers\JsonHttpClient.Events.cs + + Helpers\MessageCleaner.cs Helpers\TaskHelper.cs - - HttpException.cs - - - lib\Opus\API.cs - - - lib\Opus\OpusEncoder.cs + + Mention.cs Models\Channel.cs @@ -164,6 +150,66 @@ Models\User.cs + + Net\API\Common.cs + + + Net\API\DiscordAPIClient.cs + + + Net\API\Endpoints.cs + + + Net\API\Requests.cs + + + Net\API\Responses.cs + + + Net\HttpException.cs + + + Net\RestClient.BuiltIn.cs + + + Net\RestClient.cs + + + Net\RestClient.Events.cs + + + Net\RestClient.SharpRest.cs + + + Net\WebSockets\Commands.cs + + + Net\WebSockets\DataWebSocket.cs + + + Net\WebSockets\Events.cs + + + Net\WebSockets\VoiceCommands.cs + + + Net\WebSockets\VoiceEvents.cs + + + Net\WebSockets\VoiceWebSocket.cs + + + Net\WebSockets\WebSocket.BuiltIn.cs + + + Net\WebSockets\WebSocket.cs + + + Net\WebSockets\WebSocket.Events.cs + + + Net\WebSockets\WebSocketMessage.cs + diff --git a/src/Discord.Net.Net45/lib/libopus.so b/src/Discord.Net.Net45/lib/libopus.so new file mode 100644 index 0000000000000000000000000000000000000000..4d24cdc2bdafb7d1963521432121e3d44de34c41 GIT binary patch literal 260748 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 QJeQb`#rGR}Kn}Z746@^~1Gu~L8Zz(nyGIF2! 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

7d7)3?JNR>!2OpC^V9@h3kz{M(qldH$ z67eFw$h-KIz$aD!a@2=_;fK(}^^i&Hd?IT!pIPw7t?Vt~+@?flKNN^3oWmLx%x<$L zhP*&DDSdnKX6)hUSJt>I3cnr$UqJe1U_$#(%wc`-J(gS@Bp7>_bgz&m%Dynw;XRKY zZC?#~Rtt*u^WL^kV=vr9UisGTO5@ukx7dh(0n;fsNiT#~+`S!@`i~FwFchJ7f>zfG8YxZpk=PjJC8qRr`tD*VSTx#CL&uPvy zcbcEC{txDE={e?3bEf%wfU&+G98Wt~^Tfza-i)8~Y)BW^#f2+?Vc_en+5`UXe)N%n zed&1bZpizx@3!1M(s2)C;yr`!644R!XzNvcTfth7E?i;mv2XA{WOMF6X$v$j_5`GR zJlgDwR-4Uu$RA*{Bi|*@zCItA%R=ADrsisNw57A!On>DAu8DF-!3SEvBD4dxE3Act z=+?F{9!@j=l}54$E7ulChN<=ohI*zP@Y^qecAU(b;a%%UcuHr2I@@CJHFvP)bq3Tk zCGR84d`$HW^)Zx_lx-T96~5LOz>6N+j&9A6?(dHT>*(TYJT-nw zqkR8B8TM4Z^E`q)`aYCqc)3rIZhoRPmXblMugIpWRevjE)-z=f<4(*@{%Ow9jjR@a zLWi@MU0aepEWWuEGgotaB2C|c-;*i$L;AwF`Av+=+p~!ome;<*8D`>UlcZfXHXt?e zrA9E&W-au$bKYqsygu0j^dcV-jubxbb3JsW!CUw`?4@k!>sGL~tO{Tv1?Z48C`dV`E z^^Qc=oX8yTPqCMrbd&7oZ6?dr)^A6-8b|sjtwtI@fvhyEB)*#(UN|);%J7nbrQHsvnQs6%W^@T^nk%3qJO66;LSK2 zy@&aVI%@~SgNx3Ix*G;owF$N}z!)-%1aQgP6Fu3v1l#}?X1~Wj%COk8a!+j8N9-cvDRTyGK~v_R4BICso6r<{d+2AWCN_Bdr;cU)y?JcdJ(f#P|3^e=MpS>IQMgarU&IWt%#S!d{d)!XH zz<5@%ep<9XENm&&r(S$n zowVa?tSRIh_kCibzmjz@d<}?K>T%Dq27r6T@!H|~?0F?$GK!-kJk?|R{2i%2UpD`0 zSDJGP$r&VLIDstT1ThM2N4nbhOT|7T=?s-$@@>p{(htIVk$+4*N8OSM?r`@Rl?yD5 zt2eAp0R==5w@wh#1OT{)lt{!I`Frq|6v_rx@eg0TGG`3!|WHd zW$v)HAm1prMRI&}2gk*Y(tVM1UmR=8UuRt95o3n%56JDzxvcJ@W@u% zZGTejlKi=zby7!}_(;SDVX0kByqDS^9W#B{v+^{M$Dxf&c)o;~g3YD&7T?FSTYd)K z?`>_WaP7{=MQ;g!`#u8+ASz^4|PoZ^){SO6q7NkL;+vWU-Z5-ul>y z&HXnc)6)Ic@_lLf{+055dHMe3@_kwPZa5Ij|Al3>K5ki=f3U0=x;QcBPV@@<>Jy^T zqH%Al^gqO>*TZKYHG&CrC(QkZ{#3s?86J2{2fFifizuN~1A|5SR=pRT8BIPs@D~3o zoDgTz53hL7`&%Ma@s>y|zXO@VZ0X?~(cY*0vOjp^qus2B()soYp1a`V-|v`Wjk~yz z9TWAtrgZS1^`SGiavj2hipi+@Sl`FyIa`7e3*pJEhX*SC4Fg#x!}*`i!RGk?k<3AN z+-j^SbT7V2Dw6P)i#`7{i>>3in;p{?=?KvAI;QxfWd9Q4iJ2hcfrAY2!%jHUQbI}P5Xjt}$x*6as@KWriX;K+r3#q3VgnS^Ht zPi&~EF&F%=Pa_{CEn_{um)L3}t>gWZ9|&HHm-JiKO26gGMP@^^!gMkwis9J48kz+@ zvj%@6se{l_>_>{>I-m0p`7M@=KxNM7-}q|u>_#r~2nmF`QC^>7hunK!IkSrL^xCN ziWjR7q#vI4D18doiseP26UVVnp4MX#thzmGgSyPxpgFZRSTLs>`=iekn$q|)GHZhx zZ*36IN%u%R-P+)_ThL7<-I5(0!Kt>6!0p)_yxF-WIQsRDpx^HZ-q_j^oLJitY`g(~ z3rjojbJ-DiPXUK{`jolfOigc=jJ0|h>qD^1q06|V$giXq1v0OGvsT|{7ylVpuMy5g zp43KrYZ#MA3OGtKE;-NN_IsTxp%%q#g0`dkT&f%2hev01nvi z9jyoLZ{&6P_c%5T7x7K++j-6*n|d$r*PeGzxQ-n1H8byHl*UE3n0MQt$8B-PA4c4L z0sW>I(4Be#-KiHiV|*dFfa^V6@8yc@N7lvob^hclcq7R7+Loj_RHb}z!G~{aUpm7h!yYVK7uT}N9!<=}GW6mqi9 zaT|LQV?Uj7Xy$Th-%PF)*VJ|IjH||4a{+#opBZTUxy2i)e>?l}5G*1mEy}K9IQ)H) zw5qSBhjr~S207-Pdb1NaqiR@e?xg;9?rMYTR6AU*sXIz@JM%1UP(2;P`hw0}F+!Qm zl$AB~g>uvnm8U+=80FkfId8HrcXB_>9y!#PCf=(Zuc-LVvnVIZdJ?<~ z#;@U@`ZT%+xGncd@I2I~p7j6sKGjg(=P36)>b&zTb*eqnhqZ4T?=w$DtmoP97sS}} zVeBlB-d$W*LLv5iq{MSAU20jkEM2;^oA0lYgoSC{zsIubxZn0u_^}uH2933x_c^;P z>o9li^?ShMJK2wjzZZ;ZU=J|t0n?a2Y*552ErS;vHMxBtJe!3NOYv>AMXV;rJJ^*+ zZwA@jsL5Ret>D&G--NElFD!DW10&M+o8~-cx8zyEUAmXE+|{o*$g68O%Msp{d@7n} zAEC~s1ZTNP^en;EG3yh^m^SrW=y69JUJCT&DZ6EkP6GLwwVLD*B=GPU#|y>fJDq;5j@uqd(G>eHnkIo2K@f4*Hg1 zd=$&-ZnHU;!!Lsd8iwB z;$B2V^0WmHq3`!2Bdt;zXx|LDR(+|Gk6C?7W()6UkGTlhvff)6^HEN=pmY58ibcNN zw5Wi*<+zO>Fqi*Yv(k6fxvTD%P|mD8&#bi?<{?~TDda2Gbk@YfXIWn>{$zbkqfVFl zVu@PTSG>Hw;1y4=uLjC;POq<#)G63kT}kR%4vw&iaVYo)55H?og+5x}wWfBzYfZ_A z@X(r4naz|rk1}(VdA9Pz!@YZrp1VAam*7P{`z{7fI_P62k6r+EXsxVo%o~gO3t_yD z=c+Dq7ylbrJ8Si?shO5q+hb`ju$yMV4dV14r-onH->=Z^USU{7up;jbFBS{ zML+59+#5P2mn%T0Nq2kP;B@$5&SM5ySK`g;Jmvy9haFF%^KMo5f}1jtthxO!IcHC1 zka=zn4$CfiUMK&r_oGQ~tJc}Qq?7Eh8rW*&{mEpTKZ|wT6PF%Lbs;*r%?Ph0&Dfyb zAx4;0&AH=dbULvcu~OL0JUn1kvad3K>zFI?lh9+n>?GvxyLSx!w#sb~udg-CmVU%Z zYYj6meJ`+weKZ)!9E^s)5q{DBvqNct2b=!KsbBb1eGqT1_Ov^aF|1`QH={$oI=G`; z_K~*-!~HjsAU0}#YOr?h9?C^_-PC0!)tAaXh|G6<0+<^q8nUBs8SRtr1Dylnw;SE1 z7kzk4>Czy#D8Xfnc0hI<>v3NLPTLF28?x(XUl%_j8Kd+)c9vy)>KA&D$4WSBeTcUZ zh8>|l{sP)Qow^hwTYK-iH})FY#>JYl=JImi1rt9BaVYW*)(Es&2yizPd-@1E9|CS!rjt=Q```> z-R8f1M5*2CYZdEUIH8$xBWdyj1Dg9GobX?iW##cJNgJEf#QC*oFKukmzV!XyEsu_x(~Exh^l)#<_3uG9lDVU; zQ1t%gRqdgUw&qji?4+F1{teB<-ZWIse9DfxRhqZycn6ervN3f?d)PCu6 z?f#dDwcWO?w+HRemyYOt$(~%wk{^^8^RyqBoUFBn3@Vmc8{~W7iHR$^+e3~>nju}x z6R89K$>agQ@@8l{&XX$5mrO-snOSrEa=$x{Em3-n*={$&H+Gm2`8G3aALGI~o9d7* z$>`h)6U%g(CHbMVg1d{67rZC2Ir)Jdzs0m%K>D%{ig4|Ye8Ha$5&O+2u~GSf zrM4rNk2T7_oae- z^N#!xIO7G4!xxJm;Q6`-IgjW4YR=}7Zys?H>yJvdAR8I+Lt1+yG9_C@t-A`x^JO>F z!?^v8H~6}IT~y^)2Y-)_sg++FbX_vRkMw%}rS*N}H#61>29P}di+zp=7MTR-W6d*}I*9ZpYd*|+9~ z%l(+iBHz>5?48BPb>5TMaQ;9zR6dz&kc9{a|31;>%lAnQXGZ_bm<^q`=!`}Fib6k1 zcU@hOF5akxErrU3=unzO;Xz~|vBWch&ZgJx-DjGy38;WJ5WO1udB?|RpVMxnyEvk@ zrC|5w2G&h~i8V$nWblkVIWuqq`MBzECA*)0HME;`q`IZ0(2+oImpV>zhPxqBW7b8Z zM)j!d7oYR|KCLZaC>YI87&eP4E}mvOIQP7fRS)%>Ob`B97{y<%tdW`B(Fifh=jDv$@$?pH0|W9S`@ zP6$4`(5bPG$ya_wzI4{Bvzurh-A3k1G23JLM}iS%jnWKrUh+WOo<+Q!@ zVSl-dC(x}rrtwj`YG_w0ZHm%1wF}rjHi74<<#wK}jlUpGJ^x3Q^S~D$bAN;83F|(` z^(a?8muM#K*>T2%KKYh~90R&ALcG+IiC>$O`G5DLjB^Jz8w(<(eJoD9luo{b<5$Qg zc@g{iJ2uJk^CUl27h8)88D}x_vWR&yj!Y3d?o2dmluvu%1?+kAk#$5_voz&M1pQF` zvt6&4pUCMc;Wa@Y3*w3ib&7dQa`R=b| z58si033u6p<04K5nz7Bqq46|Uc3$*bo7s{2Hs_)*8f2TkbWbA=zO^E&e;ajgHuArq zGlg4zT9Ow>9rECRL9i@(a);BeJp1PfKV^SYhhg=*5Nl zskYlvm`dM(HO@&$uX%9oLiCP|>7ai#wGR%+CSN|IqUd&u=B+`GZY4CYY?H>5$3}l4 zo~E5C^jFf*CYE^Z)q!|}HuOHklS;38{lq?Fw=6E~y{ggdy=sOT`(xtEB|lZD1Mgph zo|tsRwl9!x(n)?~&dr6+*`B|_fwtI18N2Q?6SyMwO~p~1W1^q%q{c$@RUfqd32YQ) z$0}RI^^JYz;{2DPp&4VxE{r$wvjnX|T8G$x(%Tnb9~lcDjhw~l`7-dQJsTNOvZWwh zkyv6zA>MNY4i6A zGoIDB{cWMo?eQ1U&p373@U-$l(f1wbQrd9@Skc;$Tw?8|6U_RB`144&o1r?TS1(yJ z@#PAYsjLwnfc;KkXr46R;LPgrq-(S`2Y0cSBJtdS@RHC*f*GL8v{Jsp~2I{S6R0XGBl>>JhX2BHF-4*k=c7GBTWg$(dn)3@53$2hWX`iKX)r!ASyR|B6XokJ#aEqe!W838}W z5|4*+G`sqXMcVMR+K{3AG&rZy^86Ognugwo>Jjb|oT#42MCwUse6QYP``J9W;s~*Z z`lXjF9IW*jqnsCX0wsMMch_A_5+;7HWKeK8c;{$&qKUejXM<0rJ zi6``03elc*12($zC*40V@4pLX)Omgz<+tV0kG3B2Pv+r!>W~8>tCSs?H5GkV!8LFz z9B_j5to&eJpWP-1OxFFvL>weB<9SE3)YY87qVSl9Cv^1Tm6yS*rx}l=L;WHy zN)0?gEU~h%$?7%U4;k-AOY5%p@71<<)}0NmUqQR7oRw$q7vqvGk4xW~<8qFECDl%? zE&8Q#7Ja5OYR-MAoA#=o^vOzfU+}W_1Lm+^{Q{PIlFU^a-a7+M%a!~G#1?jf$94s= z{NKY5Edn1bE{t@brNB3Kd=WTm5#tBmiFfwv3`Xa$G53$Az;vX7&Tk}ly$@5#p6EkD8D;0M_;-qsyf z!4G46#_#Eu-n`1ySrV|LGZ_D^6y;~ID`I@B)mPf_+S~Y^V%(4+?0@fN^b5JDzE|-7 zle95h)AT*!d}>c6{fxP5`-it_hg)C_=7cf|`J0KO1BLzExKjs8yu?u7R>e#Gin$N` zNq*r{$vA+6V;TqXU-Ob%0^yPx;O01dx!^vO(E|6fAr#!N!tX+p*0t6Ecge$VC2#zf z5$n}w?2vyGjsEs`AcF9P0+loPSKDl>>uEdWYbqySUC1(rEcL zO_Bw#aW>1`#WE#Y7aEnAAJ~J^XYpS>$s_2>9rC~CKKXj&&lacdZv;J6=@ERD zeN=jQJ-NL7XY6Itf%`i5(AE~&-YVlA96>uoYq6&kdG|-B+RVwsYax$|pZPFG!rO_p zzVhx(c)onq+=)-I(eyWp9isTIFHuh1EuXs^aM0$j2l{TC{*eaD_x z`gEezBIsfYhxOpYE#`Q=m4-DN_xi*~oIvOAWd3dJHeNxlg((M?@o5Fc(q$?0=s!h0`l$ zuQaFJU-~EGzw}i`6uGSGR++~rugXzb#3x1SLDKljB}~3e}fp$;R5-nU~SQMV2Qq1aj*Dkd?HztyYySwhvU0aYfe7EE@6$;<5Tim=Urcf52+>l zxGLVI8~m;F=AG6j{KI*SC3+&zprFyY#SH2CBU^$$kdN<&>`*4pzUT0ao?@6j&vRa~ z`gy)(@oag0*L-BLQM{wa;X`DXS&IK4`n%n=(pdYogE!5Q%J{sPK&ymmh|IvpN=r|1ZA&Ona z@IL&8HLgA!Ak9AbnspT~nstqNGwaI5Cfb`dd-31YmG}I_MEP&Jo;bfNi2uF<-vcXx z4}zmQX)EoHvhK%@w!Em7_M+#bI|b~It(v@|g6o0{tkV8=onQ+3J2!PtC|9}Q^UOQ4 z74emEdqrU9Gkb(PH-`4qn&YwD zGu)-`3#=3y9k198yt5}CquwIpP>cfC^gVbMyfB9U;&tj(2jdoX*AmB#vqkkK?a3zN z*ibq1NC%EO7U940&E#FO75%U0U4C(;mp7E&<^3q{(+;q&q8B0E(@js=q3-FcKd~+O zq1t3y7HXH)Jg&FcM4Gw3sS7!Sm0%8NYvb%(Fupez%(FTRHOZSp{YUhg{EXFMDx8wu zTr}nRAHN&>PIQ03uf?g1gX}gNxkH~7HGbpK(R(!0QD`TRq&tPAFDgPSzC;xPn(2i~8e+_&O=NTD;%Bv!Mj5M;X8c+R~My=ycZFivO3TGwx z?lH0>R~m(93yKe_~ZQ@*051FSQK22H`p_Eb`~T5ZS+O^#fO0RN(0{RIqW~> z9rmBj9fod@Y@&2Nvnz3!aa~#1HcB?Yn}YVgxs>%r+?Q`$>Syn~)PL@F)*iO_&(Vhp zXraC7AzAj;tAR&IccVVtYN?CkgCj5Y`;Y-&}aG{qd#hY<-#s=zV!e$ z-(5$w7mF5u@zIV`8$%Y`5g)Tlh8)|ewZ5g^_aVral)nnzCDKS{ozybtsDy4ya@8*W8v15*D!m8Gj3ynaQ*KUI=Gb^KFEcFIwm^*USc< zJ^Wx`&ts=bdd$KR=)3x|cboD}bW|;Pt(NW}J{g`o*HLi!F1SR7daMz8{Sdff4EL{b zm))7{rlcb!`#I6`O?=Bb?M8KgyN<|4D_V;meCk$R;+g8HNA|%rx2-2mJ@Jl7QxPW( za5@70$3$8&Qm~;;GG4615o!}@9z&K=H<>t>tDv7KtDgF%)4qDX=}aM{<&D?p#xL5( z`;pIL2OF8Jx{3J(AHTp_AIwj$4|Y0-ft^ldU|q(GIQ~<-uZ*yc_&?7fHb@?t7@Dhw z_x8IdU^BZucsi-|moZ(FnprQz;7qO$c3X#`L-FsyduM(Ud?MwEqkA&xs~S|;N#m{ilCh9O2&G7d3&-nFWiO@Ntjh?BSzlHT9nsxBlsnf=*+ zob&mdKcH8w%X@j==Xu`e{(8+kdtoKd3a!ArGe{&lgAV$M0`t8S-}G9^Me7~ZceBLI zicUhAn%IQQ@ zyqn2i@V<_|@gz0I>aAraYhweR)@It0+8fY$@zK-)_ZYn0qW18f)FWP9&v*K}k*%Kt zr@Q#AGhMR>9B&Wzste2^#yS+J^_%Pr+Ugwd1+L?R*TeX`m)~u56TE%AKin#P`$O(^ zO*=v!Z)ea_*W?M7-G$e^?67g(Oz!PLUun7S5--X150YF{D{d;+QwqGFy2N`S{<_z( z0GRT7FV8#XI^Kj7Fy)=DE>731+r@lM;_eo zfRoT}1UOfZDZ}~we53^U-tAnEPO4hUi#%#i9-5gP8GLLI-WGDK@RrzsV{-=awu8PS zoLASL>4b;CD;(wo9?Ev>RygcU%!}V^DyUz_xP`ATm=aE|`qm|0)-W${TRP*0c~@}$ zaVO_CmGD+|j$6$bZy4{~$9E;1-C(kpIn=N14DO-NE^tnLjpDcZ$nbp#WAFVw?%?}9 z;CH!?alEsF_kUmuze|0L=bdHNAl_P~KGL)={9Vkm1mCEidU8Ye(pEF~L@SGUzEACd zqpLEPc%scIeD_1{ZAe|=r7Ud8d>a9#$}8;}^Cqz|eCv<#k_KG33p&xa%xNay&f?uY zlNdX+vLOn-aeY3$kVp0>^pZ@J*YP}hVc(EXQ}nW#HGC1fig@5GtHkG&I~qkc{kLxn zoICqjdqF5;WRP*--Q$ok#8<1J%_$bV+Rg)u+Q908r?HP#6%I@9H1Vc~;U~`rcGC4` ziVw%(m%OKSuS;|WAAmo~ZiCH+IFZ`0&U{@OT1|Z0SjZ>x>qw4K4zKWUYMim5*CFff zjxrY2PY~~2vkThh9Yj!Mx6c=^Vy_m>FGfyJ@;sW|)c;Ix{iWr-YhL%><@s;0Rc{zF z#QTjo;{8U?wpreL&|61+o%cI_zsvnI#As$;t@F(tt?enD&(7b!x07EsI%7oqx+>9TqGq`{Ti70aukH%3(%oQc3b&cl zDb71QNX>v$hdH=_{4L-s+r&X|OuEya^e-3-GAj10?(}r;Kx+HJ`ZRVqNBg#Hsb4Eb zKIcA9&h*og|Dx6xz%%NCrh2hmuMf!OtT_|^xyhsV*ow7Ce#K}!L)r_NQ1LKSp*Pw7AOjz%!e9pScFTgEFNx z7>`kH5tEV00jnHv;@_Yw`4ZB?cb8e#Z*X#pK3_)(T=xmf`n#DxQIRDX2lae8_RF- zhq&mA;d)yq6q~1K6?#^v=u`e>tZ;Fk&U%j(YWifuSKqbp!pM?re>L9?F~FszrN0V( zGi5)mNB*hJ>pl2d7dX%F zVO{uOofcE$W<$QFIG9-Gkv_}$4*KjZ9%r3CD_wxO?iG&e99Z&)lt27zT?(8{qmLkm z#|?Pt2%j~-tHXCn^Sc5#YJL|E&Tm{Po|ySXR%ZJ(C&jX8u7-J)?T{6;CAcYWPVJU` zky^cChHdvrC=Bh*ab{;^ngXrS7Jw)L%ISMsoDaR#1ZSmYPn z5Ptgwa=o+ciJHq=zkY5@Ked=g$nPY%5nK7Ql|3_j*^(ko2Wy60?CP4o%rBB<3{BYC zSihKQ#V1%=t0-%zb*(qVG4NiF_uE+aVWzYv$(Gukd5hXk$Gop&`$p&W$dv_G(dk%6 z>BC*@3HLjtXRPJ1P-kWh6(bq7=#P2-pn%N9yzj@ht~xZ~oKx4JiXrmVT|Nd@k4z&aJE321X|Y`#t=XeKgcF(dVEm&r0H>$V~I4 z*LA1=)02LLe|&GZv(UTO{OBFkHd5X--jZi4;*txo|6||2tDv>JRllE=Ty0x-7rUKl zVf>xqcXEj=`_zIk_g(&<)98C@`?riiu_bnT27TUKWKE6( z9hPx-R?+@s!6x-D-k!q0c&jSDamcUA81I3G!-;eMv&xPy9R4-NaJ?zZO#ft0(QkuuiP5amGJQ_=kw->y_tVqn_*2( z2-Y#y;b>_+ii%ATPpn%>oFIFe@_a;V*qMrU5?IF{?zgh8H|ZJdP-(aL0oV18i|=9& z?Tq)D-UNQmE0-GOgjr6jc`d#@cn#jJ{Z;(c&hvfO!~L>Xsm*H3^+wZHO^UcT?p0b( z^_TB|i2HTO`@>n281exzRwLT!t38eV!H_3|ohTNcNiCr^-@?XHofs9g(x>dWF>6$i z%dodQ8_aJrj}t46j6+`$=gZjsv;Gs6W4zW(1e)Fv$PQDTs6#jG@UbC|P~J~Wb}8mU zJ|T>77u&8`h!@r%dZ z7b4ayzENFNy#xLhfuomAZ4Zv37r%~vA)BcDijVg+nW(d+zdFG?y9Q1w2fdOxL^E6Z zUyGwVIKYr+{}VXPDL+eX=|94=ios`Rc&2ak?i<|K`~N<8Pw%`rc<=PI7mxQpLvB0c z(;Un5i!+~iLGdKtMn=Nt=xpl$1@9gHhrxM(E0J``KWPJRn>IzmI+vR`6+fDEy(DtV zrPvlKd8TXX+AGe%Z{l9H_zm}DtCR0I>bN`Vu~FFhh#$$XHAAo;rT&94LbrG<=()1V^jtaJNS0uZ zyQK&GIL|ontH9fijnJ7Og*5zUaj}M+>`&v{usILpbPurqvD;W+j*I#CK&Hw+S|A_4 z>JH8UaJ={s*ZVta%nmb;1<%@-d(lasA?{#vP%}A)tUSMXS5{{OEn#1G-^dPk=&vn6 z?mE^un*N+6^f#}FpN75~if!0hBfsQ)O5pkcu}Zf#z!%h4v{dK0fw);al-Jznu@1n+8$sL5UAkJ2E%};z5%ji3`cqwpNCV&X zl)jsL5wL%mIl?CpV-HC`B`+fUu3Ee!?(S}2?H}Mfn`g+1)O}g3cXsHR_7|-?dk+1C z`r!`sRpkC~=u7_YNY>qbA37lRYfrw@)q6^Mi&ZH3POU8C&O?9jWa%)~*u=$Wq{~DS z;xjG8uHgU2Zxtz^$2iDF{Fa`XtbXj55!Eb>yG6@!i{0+;y?c=fzR6zt_2jSKsW;2b zKKO_911qsL*s#R%qQp4~e-vXPUlTa`tnJJRB$JX4QH-YGZ^=191Rpiy3ioKm3dtt^ z^~|r{xd7UTu;0}%A7r`{k;G!oL7=`@{QK{0viFCyaF_F|cOs3%D>aB`6VphIS?M&E zb2x~?ixp2B%eVKTSB_AQpp_to-Py>z#g|*o=^9h^M9%Tyn-?xzsQqgyFj792Y7vX3 zR5MfOUo4}YCE&l}>e3EpSOTLue18tV6_;fp2e%}oudfeU`J3SWc&-)0r*nXY^IJJ0 zq8rKIxx{qrH~4U|b#-Ns8S<}td)(K>|AH5?uVYUn#+>Jh-O!m2mB|@lOq6m{uqCh` z<&0|Djx&!1U(ZkzDn(6H`UqpckTD{_p^LLIB=>bWuM%haE^+b5L`m9D{=!o{e@kqM z*E-y0kNb_+O1?j9V!E9-JmEs?haz6?@~GDpf6eRV9N@B!x6(oGLr1R_KN9{A!It)q z%5`6A?(Q0RT5*{>Yd?zpgYzl6UWIRwhrqrmpX0x>9>ck>xJ1#7Vh=SR<@=5UR?S=w zHKFJYwP1es}pAVc# z`QUU9>rb1Llf*W29@5U(SjIHJ7(u>=o_aJ7;Vjp??@s*c>ARipKixB_5R3!2TOIaI zd`D-fAAKLVqrDi~FRFd`vEp1#EGFWm8D=1V$G8kWlb(`XE?;|HHd~4rq<#DlZTTjL-i2@6?5&c_)xROk;fR@1&GZPhKxwO5 z^M!T}>09v%%|Y!&lB`$C^#n8FL3R2L}D&+fVypB8OZ!bfyQlXOS< zO2U88LWx$vX=p;V<8_`1d;naS;VbUYO1s_!_T*0yOQqZu#puSbz0<1z7kaPl^=yYY z=eYKhd0{>ARFr;c13CCuP0vhp7uDvzp4iDVp=>F?@q9ZO#^3;BPv@UhZk!q7|Jpn_ z56_GwmUNMG{`V31<077?CbK7UR+(ZxhB&K@>}qM+A6{TT?rID5qdFH`X@R=81!Tn* zYQz|D$(j|~b0Qh)dRWA}XTA(Q!4uJ0&ZOoU#lA>JzSWyyB&Sy~wuKXkPsL88I1I_i zk;099ODsxywGj`viGC~TBlg*4CWakOxVi{9OD_^n6>i5ftVuR!YI!c47d>d*KCX33 z%nALH(Ih+@9FvT5#v%qE+O0tr&r%<99y~|+JmL+ucx9r!Upiqd@dP{$nh|Ub-@~tM z%Po#d(?9Li<$)zO(opA4!8f{)hw`hS9a^B2YsW!udwjBzpdtavVH9;DTX zGl$qeV$B)ntKe&`bkj62PxBXeEQ&La6tQZ+C3fvH6Xl-ZRgr4JkAc3*edS)tjs-so zc|@9Poev!2zIg46ALc9uJqI>XtF0gWL?>On)h*d^*K^y$L)-AAb%L@h*$?sTi5{YuFQSFA_WXi8lW*XTn+RyU;{24u9&qtTT9V zXm6l>bO`tQF3SbxqTEq<*RhNFT>~%U_d)I-)Y@;px-(F}H&Op6?i>jDmaY4hok8D4 zIrPWUGrkF49Mc<=-_aiEbglQl`-@NevWJVtRYPiAWJ_@QDbA!QWc`Z2UG2xQT~tr! ztZ!^&)SdIk?Ozo<^ApZ&jBf~?Sc^rHkvxR-QBbdf`8r~Q<`JT;h z?zJUXn>uuC%SOlMeZ@zJ=la82kfx%g>e$U;9KFD1^!=`^<7)S{iX_h$|YP+?<PO#I@Xe`@ZwWTvTY~Q+!1pk){WP$C z2z*)t-Dz*L!9R^>yyoQOg3Ie>`!`*)2H3AQRoPbKV9UuntNrW#JL`@OeHgS}Lm%*F zZ_kA7K^yB(-Ll%;^r_YC+d2P>>)RD;>@{Pv)7aJaRJF}_b^?AWnJ|fc-(cUzek;FO zcyeBqt!D?>0CKt{tuJ(Wfa_fk5bLXBj;b6vMr)X zbC>h|cdmsFuXWs=&D7u3w^mZ;Uob!Bq2F3pjrWF+fwNpUvR4jc9y{?1E4JqkN7zTf zN!g2$fe)p*)>sfPj%lo#pYT39d9_(W|5KTx;3~VSa7lfH{n1AqyjF74$ZI;nJ*7F` z;inVCf`F5fF|{|HwmSR~7kBsD9jiR6a$ z!R5eO&xKE#Px@yAA>OKQ!CUjyJ6Z?9+KP7uHt+_fC!YBjI5db48o$;^^U5c{k@J49 zWiF%mUh5dPIryGvPJNFbd{_7r19s6QYfRf}Q+=1<4qTGC;4R=fq)@^E;j;R(tQ>Oa zblzkCr|p5$r+OgcP%L`Tf*i&fI{X{b6E?zIQy%9-%#MbV*RN6-gCnyee-6}mra@YaIlbO@nOC*uMZdeQUKRW$8vG_PGv)a|^EZMc z?PC|R(hmi*IUltyag#qPUDBN+x6k%R)~|!M)Qe*A-ha9Q|iS(~6m(ccF{ItghczA4l#HJ)${oD%G`ej3;PM@#e= zt`#uRdu86U`O+=H5a>nxLiqdZpRiX(ws79VD*ttKza#NC@I{Ii)(1l@_KTkqYe>F^ zYQu;8inI9yYwBcqRhgVY)*hjH*vbLO+19h_E5uF4VJF7=*fomuL!5AQzj)aN;Dq{( zIwg7x-vy^NXVFA2JU09BO2%f;y$9*2j6=1A7#5Rk)^Qo>~18?=mi{0oiiWra*e4 z;{D&fYB1iQ;_f1Lq35H`iob5{Q*MX+7gIidbGTm4)Po^U5e!5F;o1O43*6Sdu%Gy! zu1D2JThHwU*rLL{Kzw!7YbE>Z*@i8_F0S>xXhZ%k@h)tjCxrLIwdR>l_Vz`^Z$eXo zfpRSVnf*i86+G8(eLtM_IY+0APq-pF6^-7L_|ZF~2I-tKU^U{GYX2NPrf2wSBV4a! zE~qqrfnUx6XUT1~lAOyi$S-5!m+2C}eE<8-Kr|QE*uhUL&bgA#Bf)(up;hh=8@zvx zt_NXO9?K$f^e#Uv8te%E8{g=?YQ72SJd+EYsYluWpdIi9xuPgq(mo=()IHId#-e#^ zJ|+HE=39;4mxymxn{^t;AZ?A(b+SZf>MN32hAjzM0=kHRi)w4Q`UI}|D$7LTOO5ww z!hR&<7~wnp<-o8ewLxZbjXiHRd;c|CmoqW@S2ZVmGK0=ynxK<;=j+?3?6|2KI3kKheC##`P?%W$bn&hW=! zJB($thyKHH`Q86~53NeRzlUm_E*n)^E9ngPe55gG7XP{y5&f@OJ+z3#)aebq|r z;J)ZJq?5!O(A~k{PT*L^$ve0f9Lv8~@caMIa_k+zat?J?R7>$EbEu6X<0Ai+b>bU! z-k%%lYQ+d}wW;B&Gl*i;?)}lE1K26BiKJL>cYRp1RrXlf)?>|^gD2DCcRb5NbL_8n z9=*@Oe#jXWr#S2JVb0)84A$ml->O+OcrD#TXF93g=KWlkX+Sk$bq~AW2<(={3gqla z96F(IjY0iRp^aq)txstT)lId?{H1Z!(YKvgW9q2Ce4PHjPXEV=w>m}4)Hu~wPR!%k zNNnHot8fOHGxF%^_{gJkCr^Lv-9+tC)pyd|B_rH*QaKDGgX_`n%DS`k>I_K0`$$2G9=)*-0$T04->rETYxEgAMvy5dpe{FDa7=LvRCTCXrqqx?5 zs_0L$OLYUan`u{myi>0Z`YH#76)dX6BrB zqjOMYx2$=ubblE4qs}U95$#6zNAWz$v-g0{S>R;FZ}X$hJi{5E3uE@k>*kMf=aL^k z-HI=rE*PdF?lkT%w34%_9DA4ewo~!i;73m! z2yFNBK6T12AHPKFFfx#DNHCVqQt-YASeJ1?dD2=N{M(1rUMf1zBX@l7zw?cLvtI1+ zoVPJ~i8)0dtXq)hnd;srv6n|2Yy;@ym9l3prp~?2L0^xpBWDP@I2Pla$UE}bBl6V7NwY3l z>@#`SyFansPqHr2B(xy=nOn+DeUIz+u$Sc+k95w{{GOR&E!hv`dzr;|vbpZzZ?ub? zD*lqsp)=m$Q`8onvOU;A%(eV6q0fP~VyWk$8DcrPSLJZVDs67~?ZC-ZsTUo$O5y1=XC(7{156g=_C& zUzAO&Lhp914}SAMOYhC1jaRCOn*+XvfINLjuJO;@VU!)7ROgHbS%%m z6Qtwc!KXA98!)+EJZqUW#)}QMJDu~-5-HVx4EqV+so-0kNvp9))~BYL&WtP8Gj7c@ z#3yV0aeX6u&<(UVC%x`xDJBIw2j$uvWt$sD<|IKF~Ufjd_a8GivWgZXQ zddK@TutMe#uIy#)z0c^(ql98_7*pXk{9Gm9jzyg6yu-vzH7K{el-syH<2J7Ewjdqw%%9jLpG0`jx$2Ns`C4b#3Jtxh)sJ$pXw$haF`>aJ@8Eh7` z^zaY!K1T_F0Y5?s1PJeyFBmFV>j^e>oV@fpncae_*)0DR(GRD(6<+25T zp|``#Te?~{=tHV4VyE5b#p6VS^0`sNru&0;g4fyW{h?dYD;~JhyAl7ooxY)T_SkaQ z`#5VL-}=*5llMqdFTQoo^1`>Cn|M8E>WuY19G)#k%!1|i&aZNh*#9wbQZShUJT=z{ z=LbYt!<@C0b!t`J%wu{kdKb**WH^@;80cQqv~Z3ObZ3w&m8S$fTdY-$mGjTUf6chb zuXb9)obIq@htm6$cc~i3qKtA9{ji) z{~;SI^>J=9@O{sk`*6S7-E`F}VIF2Rd6xKv=x+l#pB)BXdFb%96UFJy`oAdGNBc?ct{HND%Dy_*=tclK7izSzvcyhC!n|qyE z#hQFK+?N+X&#XzadqlQA!B^`+t@~rwY&6B#?#F@^OVMd3K4`=f1^2}{VyaR(KjO~t z=fEq)3tj%+!5YcVfX>*5?&8a@^+npT5)TLSS;OVX{~v-bo#^)9?--kWEoHyu#2Y%JPdZw{F7olc+q9x3O@8cWs z%M#z5?{)ui;7}K`yy#ZE^ZQ(@UPh$B2#&?M#5XI4I@~v?fg)d@=mi)&+sLym8UwL8 z?9W9zzGNu;y1|Y|BfW_h3;r=w`wshigJRQEFPS|wxSl@BJS&=XZTMA`xSr5PBidE#ZNS0UWCSuZ+}BU<4Bxf!4aO(9Uhkb_&2)HPFT7AZFWjT3y`VG6&K8jSncv16t&{H| zYin;hTNoLL7jA+VhVx55G{_4t@m7Sq@a7_G-n;N6_sD6+v@6XSGmP(454I}Ctub92 zFU|24<|qE9__>M#aN+rLOOUG^`F8Vgd1})T^HQ!~5uR=LOXOdJ{A&)gmn;tLeuBq+ z#8dqQnLHNT93)v+>2`vb{5i-`p7MPpi~sehfs-$y$B93KF6Z<+)%MeP#S`ym4~zrL z-`^oQ5t{kVTK{`jw)x%EdEJM8IpONH=WHNf<9uG(YLk)8!CS2R%z2xG_q@%)RK{?& zg>`&#b8zW|wf<=As6CA3%?xKFWU9QIKDE|AY;6w?Z`>SYzu|c8;9()ZopTy(Z=e2N zbQ$=i{G(R?qRjW-kq=Yj|AdqC?*}KQvc7d0$wAEAFff*F6`F~{ zBN|_1++}@5JWMjB&MTY`d>0c_DY`7h#w`?X_dr626gP&QoYMq&h`ielj#W%s+h1Yls8_ZMKC>{4$J&-18MrC(q_FqNk1YG#!%bsC zAAB|Qs&e)hBk&@{OCdw}9l&lQIJ=eh1kaZA41>Akn3Y66!(Z9qa`r3ph7V8+;*oyw ztV+g}ybxO`?N8wyt%u_3OYzpXc&+KxCg-jT<)rYe8i#mG3p+P7F=dbzPETwPQk+S( z2>YY#i!&L+FzS_TroWlV+5Yv9u`kn)`qSLwnRb6ddaeKM-xDLQF#v}c?Mff3_6AN0 z9{<+`^ua!(afNj2AVXnq*Z1;~3MQ(n7CW#dXc^s3Y|HxK0etiqSbL67u-1}~x;AKb z){=X+7JK)e<5RhpBjx$J@NOCJN|#nW2H}@@kM!_rt{r4n+0+*BPN=sLhmlIQ`%kjAT4(v$ zVyyk>E86{|;DLN@bNFs^req76#k$}0;WhX{S$AyJH-2gjzFfYc9mO3ePD$U7gYV7$ z*c$)Vw+BvE@msuI_H%;3PPWnaln6M}>yja18&-hz{Iq3a1^2r9yG%-DEf&y@8pGYl$G_kI11pu>VGJ^_E~AimN5NQ?i3<#@T|xbQr;7bm@e zPi215jj>N+hr5q9-rJbx`_7;ui#|>bgm!($J2rKaI*9RX{}_4>INwT}xoKm)wajfM zS7>D;+!-1XXcCV5D7|AAi>Hn?RHI-};1a*Gpq z@46GL*GA4z*@4WJ<$jV_wH+g&dGrOw*Ve$f@Plpr4mgzCDz~FHnrQ?1m%6E?+No+I zN}QZ{@!5&r38v7uYU1y_mimzJ<{|Jz;=cFR{A8B@Zr%3aIpFgne4K(s0l#N~J@&Yr z1Mf;_AS~khnT+FyllF)3O7iOi8{Cu)TfEG6+~QQu7+j@mtKGCQNBIyxdb@0UZN$Qr-q*7T@9J5C_tRq@f-gP9 zo;k0WORO=X+20dSG>rSr-0uJu_@#~uhpTC`3pjs^Jx+3cGx#xsxjbQyp-*6v$hqw6 z-P`7JCi67p3C7mJcg@JwlJ{FUqr8G~Okj?>Cp+WX4`82ck{$Xs-?kcQ!!%528hIiA zD)u`r*R>ahcAHcwpJnK1@=L5GtXG)dTjN;S?z=Qs@=VSSVP6|Em}lZdLL1@H6t$-! zU-DWmyo4I6rG2$G0nbA3n>UudwNc;B^E#NbYz&c8GH=hQPp%kW2YAQ#I@w7J6<*K#d%q*}=jNZz|4%w?I0yeC=kbJtrZpR~q!kukUVtxc}?!NiySO@$uL zFo4GlZ^4Gdc_yz=!ylZLonN%Rcry04c!hs3&e=p`3*r84Rkxa2J5&4|bBs(h#d|_{ zD>fDTFKe)w-wz%iIJs!<$T0S!dAiQ%W1phWN53*~ax2fvvQrE1H4i05gy)ySvve(9 zrT541yKtvt3h;#q*w>}AJxQ*(jnA}AFo!?%ei3>3i`N<1vo3^J2p8=0c*Q8@EP3I{ zLAzE(YB}{K%DjU5AdwQUSa6klq;YEQ^;bT={ov$8?sd&++r-vzjhgRLY{?Da$o!&s zd?WCwLVlRTb(9)dQEJv`9cK1$#=XII%9%uUR{WQW%gSAW?Hr#+DJR4VVopPlyJ9SR zwjJi8?kcbcXbs1)Cyr(P{#;&P@P4i9sXfr~b=Jw}=tpzf<&A_-H+fs&rS__x6o;_`!q2O#U znxn`=1^6B9)Op|Oud^TNfAz{MOyOF`xa^PLyOJ7G&>K1?c0=mf#nBsy&5Neoo+@-i z<$G?viZdb`gbxe7qqM1baO{G|gx9hQ*t9>z1NMV4eRWp;c!(Qeu28nmB44P47cG5? zF?;3C@#dBCeweu4AD%1eZCZ~n1N&XpcrVhr!f$VJy^GOrw>qW$)Cz6EHy5?1UW8qI zG<-zsKX18v2dG{jNyU1ziQ!MRN)+HU= z9&|3$-mib}+o^}Hh9_C8jBqSDb9=Cy_0CO?daiWLL7JByE1K8%Me~j5s1EZe)B9HN zQS@GUj@}(_f2bcUET$H^NB%J9ORpcFvHjr6WZUanC+&MN2RlK3Nrx-{Uc~R2j8*in zd>7Gr#-`Q_`0}ST-|$P(dbqBNWuV4a%(3|wA6#22qnvO*3%_gUoBeyDe(R295d>t&@wi$q1ePk z>nUg<4U9!Qg5&wLz5rSmkNaoFkm4P|bpc}$tSGu_D(X6q2NroPY%jtV_A!uBzum*rkTQC z#QTzyqVranEZ6b<+k@$&bJ#Lf1FpqihrKJ7*xWbcD(I1RwQtKFbiC=j`aU1Q?&hd& z%Hw_Aj`9Bd-F*|!d-tz+ml`WNS4z3r%GKBS?q>{Vk#prQ6W`ESB&#WQI?Ft?|I7Dx z57!0epl^mTr-z9J33c@*_)+{0U;fW|?3rrgF5Z*fC4wy`CbcA9l| zalO4~(>GPZMyu;uoPo8W|p3gOj3CfXQzFhYdlVe${_wa6EVBkBCe9rm_#?x~D zhdx_-7mEgse#{H%V7^$XeS>Af$e0;IK>dyzC=fS_U?yraIH`gojw-Fj?0(ND2 z_0Rnx!a;rW27iabd)_U#E1Qw<0-Tr&ZfNe$h3}Teu5~Ssy^-h0Pd>7f|9!p}PRHVp zB9~Ls&4!Ml1>tyj#t3!}$^UuiWC=Mc17`==nROfAAvbBN zE4hKQS>XQ_j@Q9GwSBfldfB4lzcZfu_+8z@_~74l26=(KbW`)ea387wUeQF}fTJ_C zAJqe2Gj|`O)Z;;M+iyji92k?gWdOpgtL7V+<(beqm89r5>ktZLH zLwz%r-TwwXpYPvOTOSVlDUCtzsEto>udGk}j6s?z=|&7%Vsd+gzHqp{hi{p?umR2iNV8!x%E z0oy|P9HlAP&1`FE@m;>5R#@YH3w{jT9^j0Vqj9&`LLR96ZNkxKqL+nsUyV_AkbU(1 zU+p3Ii&={j-0X0T@V#oz7rNdfeD{AsKf+t#uV_fL)HiKYFaaFdws3T??ZVMP^doL@ zN9Cp76P07UiqvhUWVf8FTCA+a`*6m0e1iSAdnNk0YJ-VB-l{0kV&`;xNO|}S@DVS$ z&qYp%?+v1yJK4drD*TrAM@qCR{C>)ELRdtsv_akme?RBNC$5uEey`ds)7(+|k#9(O zjdO?zh#0NUVq%Yo)!O_EFV)3adaT8RPAhrv^FsS^m-9b7*?~K-OUX_gQ_LKH|Ji}J z@wc`qYZT*q#(l$77xF<3>mK2(mSN}<7PaNtIKQnfew&GrAJ;;STh-d_YWRiM+B3#$ zq%VsxL{j*w)5KI|I7hYcct9Ng?m#v6q?Z`8a_XSA`zv|*=G zKFpr%Z@i9gjPqo-`VV0I0&TY;Urrwf?Ze}-v3NB*bp8qRWlvG<0ZX}b{EfL!@OORi zQ`T?XU@Sw~cQ!}9vqM?GE(sprwkAcbL{9a_K9_0GtTF2%%dJNyIF61|ek9(PUZkxC@Sirs5EbMpo*5Hnz z+k+dXwRwH;=l zKRHBM+MY$9`^J31i_%u*lAM{t?-w?G!Bfpe#i?27=aDRXDRC(64-!lGQEDT7QgrkA z;#mHU=iV6ZiH}t<&iArg$f4fiwZ~R?zfNrN_P14gxwfb`K3nSNIri*R)f2pyA2)eA z??TzckE%VUn|L9VgU*UEUCt<_#M&vW^k&4#rP4~F}RYEGaR5Eu8fVzy==UzhcV z7o1hdZX5bkyGnR1SPRbjd)DjZY!$&H=cm9coogqU-&e>14{`v=s}Vk^KDO{#u&E?( zVHVfmTc{&U;aWA4r8@|3Z-M3(@Vnm28TE0W^LSu?B8T5McW$5j^EFVdFv0%(wfGrz zyB>ukk>fdVBM#d z`qwbVo#5%%KH?LI?fBF*>*U#{&B$rkawob+@R=P&tPW!!j&@0Jz&{CoZ+(J)oA}pC z%^2EN9CU2bw&2~B+xT{V@nQ3lx#BqZa(sjN8sCS0n8b{sw@xPBC{Z5)zC&M4XltHHng)2kt{(yP5c!*&<36j_d8^a7`3>RXD=QnJM^jxR{2Yp4V-kQ zzKLy{ng^2(68G~MK7!5Unal|DZ*TelK5lZb6yH+LzryD1>-&G&Pt3-cHv{GNMu1t8 zaf(O1UU54z@P0(*-Kl#Ytp7?&fBEhl>*hi;q zFqa}rYdu7JoVRkU@mIh#{Z-&f9H?NLUfq2+@d~ze@i~~j3Z1J))6Ky8LHtb{iqtWO zZ&VAWD;_ESSdTwi@RvRz*=Qc)u5j?dV9T%@Rv9aS z|Asiia(rA%40w1U14P_5jo|E!+6Jd>)_kXKKsP@GL(BFL-&Vf%W z&hnOG96KK|8S2Zkl5TNLDr?S|w^MXQUmWiTvZ>&epGp% z@#}jw_RB`sKpaRt@6;FE-KxVqXTR$`Tvr#`hAOZP)oH9rcSj|EEBULw4}A832RM$! z*?%hxzMm}gxbX&mZ9Z!v1

j&U*2f?Elb()!rvKR@pcBm4#)-TGGlG*+&YJXQunH zLU#~LJ`T^sPZD#&xEIM^$^n#|P_+XXB1_Dlls0yndNS~YO7e+f=7YuIA1*9rHWqRte}Z3JILd&E+R&P6lA1DhIYvR^3H zLeC@2VQvIpxovu>$#S$Y;vQKrDEC;%U=>N%dk9>sbg>}a`BlDRZ7?L`;@{#OOY#9Q=Q@l@;94?KCGhL< z$;nBy`Lj~^vG}IS0!JLp{Knt&wc{+ULUa`>0#0 zeX<%^TRv;;bK3tj)}Qm9WQG`Uh~bma{u(ng$T=bpbuR1f>ZGA)U z|^a#@ZAF5i#a#=PxAW?elO#<@M=7N{gw(6^zAs;F66l9bP(@ImP!==_8Wr>`!xDky-+L=X>kW4KHMk7I1Go&+X*({)IDv z(cD~r**|glwenBk)0(t2tT{dcUf3Dm&-v`o<#^HG&BZ4ASpr^%J`V?96EQ%-&x?G^k*+z7>gN^|g5N>D<#q)$e!W)hCZoVH+`>UV_-3w!xu}O!qm(ZPT z#Pg92WLK#ckCa^?UbYRF@*^5}&Uyu`m)A)s-ciBt4l&86W$;_Iou|M8#BL~1&!H}WdYZz0EA19# z1E|SBOL_926Yy91TG3F#{hBQ6l_Kxb`A1~BrKWOXTff?})2rD}=2L%&^#>OvBk9b% z7~>PK)Y?%KurFq9>kHS-95OYm*%gE9wt_yYSO;`*#o?S-wgk8qsK;xlc|MJ6N3qh6 z2OV=C>fdDbm`zs|@MDe)LO&^dUw*$ClUks?dF+#IebU{rM}+gV^Yi^N_+^H8=!lFp z=5j8fn=|#3mtu&9=T)j3ui3up89q{J0qTGnyA`Q1ZJt zrEh22C104}ulm_`19nr3bAhQHt{8~?9mE(gKFN>222ZffoGA0s;`~y)nBvtZq}ThI z0(l`8vT>Y$bCG-CL6Ib~hQ&HKZc#CjQP$?wCDcx}O3&N5e~918p@EkitIQ*dC)&(7 z^5djWyoZfI`QXYu(KTltz0=C?x+F2g+^f#8-y)~$Y+&WCjwA0!I34*oF$uIMxx~uS zH~q1nkPG@qfAz$*{i;j&g2A?OF}kJMMhr90IkRX&vUFDQ#+e7rIBJfL`3igCGwZzQ zmsfi;zJWjgA6@S)r{L8jH<{YhPV-)};JwE0N`7n1Rosh^D^^WS80n+Y#1iv@^9#-- zEO;&xtPiV`N?xMNB$9J;VP{Vx0J?60>!#&BB*amAZW>-9a9{n-`zvF zR*gaOK%g>byGE<`_8oLrHl#txjOwHha-+!5yo|x?4aEAVfq^ePK0$0>8soi@0{yLHWBq9+07bkzfpT5 z?Ui%-%I%3RqXypvb5uPja$!%LA}>{Sw(APtHZ!0Z?1;q;$n7tru`$2mHr`7;1@W~f zDlhe3BBrR$X^r8<(x5E##jy{Jl4gy&oI9 zj^`ab|6zV3IB=VJB}**Wkn!FRvsvnQ9`(xai`JhQ zGRD)HkbAh#-nCor5?{7+*O0MZ6?TU`i38pb5=Xsi^pWSH;1D)Horysm?GdW$-Au7( z=`X6;>J(Tj#xDM-u?SZzXiT*~J@Sy~ zZt?WTg00s2U|Sser9I(ZdeUQo>u}C{EMk5~?AW$jRa0|rc<%KRQEP;1J4)^wi@Zq8 zDQEDl#|Ld4id%OVe;&1-jk9*--v$?ubD(F7!P&)Mr4BUud*||(%`W8kDw8v9Jex{C zc4C7c&ISBQG9U4VT@%*_r}(!yvp)F8^uS4tN$ZEKMlKpO&pUP3Y!6n9LC%kF>0ie4 zD6wEM_+yTFN18VFM;N1WgZ0c&KMpz{&&LnmTgJC~e;mKt_(nWNxpBkO(9O!6`TVu( z!`fNoNQF7YYDfG|vUViC+E?9D<-2KJrlK3_xyA^Lu!XqA33{IA8yB9&_4q6~s9pR{ z}HVpmkoYD76` zoP&G{Txq7=sd3#;p&{{IP?6ulX7!A+kYIc<&gi)T8fXKTgyCo zX6LQtc&%mLEeiHpbLD4Cucj`hzw8P4L>8MC-w1ZHGe)|I9pW44w>TA8s+MKU`I1qr zOD*g2S#UK$yr|>c;x|-gS*NYR+(>q=Vrwm6QK54e&|PAQ_P)~t1K{cZ6CP#03+{w_ z4!)uhqkzp+#&w)M3cB;EQtWw*Hwx@7u>PL2@}A@0pa0!*eA&Fmf?Xc^BKx1t(>ulW z#jLkzHsVtM4xIekE7wDZ%$f1uK6<@x(|(?|r^Kl@tUj0MZii`FGGh$Zn;eExc)=Zp0X0 zxa9mK?^h+#=x_ALxDxsXx{ebgThm1hyn&yjp{Wcp^gOF)9-_^^v6b^ZzxXBAX9eHY z}HiKL8cZ6%3SZ!wU`%IzJQ+?z!R;M?IZ%2(q$Axb& zmry^IkEP=Pe9CdXEOrfI*p8__`qY$iijhiRI_IyX#)h zZtCn8&MSAj^`9qr8$Kyp)0)0+>g~2h9eg^_3Hwg0YinN(^t1MPJ!hYyf9fiXn2@LD zENzij?aPKWs+Vy_$u&rk4^6wHc&2gGr|CbbwP4NTIbZcwwYSFdKL}!7|G;{*5=X;1 zg^873N9RZQ`!4eBhQ93cAN68gtNN%bb?g*!&#sN^)xb>kde5g}!Bada$$jYr@?oha ztn>iEpBR)8TDKuCYo~8{R+Gw^Wn716jTfW~bOu&tQ-DlyEXsQecs3;tP6EG}A(jLIMfWnKX9+?}F%hj*)pe~x9Q1d7XbxL1|MCi5k1p6odnjcqksR=jLt z`A-HHHMW>7UwPSVnTbs23bt9`ner|oAO6DP26K%0N7CBg8G8ynp7B?4W|3?iITyK` zF&_Htov-{bQev6^U0;%<$SV6e6rUuuIY@aqk!Cq zJ+0l4AI+GyFs6EJTM_uU;!+ipdRaK8dBqCGqg-~|hTe$1hGu&vLSu1w$Z%?%gtBB} z&{iLB>Ast>wSQ1GEQS`5J?zMh_^F+eZFvFhgnnx5%dKO_;iDpck@3W=ta+9*kqf+6 zXMrp6rGCM%XOHK+3h-9)tza=tHkB^qz^v=_0*i3|+}B>I`ofF&HkL>PI=iEDNR8Pv zBxZWCA?^Pn{)=ZWH=Qfin61}cYBtSUWkkzW?mAPETy54tCvEU|odFZ0u7E`?XZFxT zqS-~xW5EpZ8+^C&xsA82@Z5C4I0no^+DL)papVO0uZE|@63vWZOVHW!W3y=qcnSWk zrH?iA5p!12ep`TU7PcdqL+ht@#BUd9T?elhaV@&k{bgLI2d~pyi;qg4(VigwD!;`F zuA2w1o4HO5UMIM|mbw$e_+8gk+A~5PvZKU9#Mi~s#kV*9ao}0a?YDm%2>JFbXjnW& z{fU2wXHVd-cy@-r;@M8Bq=!V}tIV&Rjh^}yynbES=q={Db(iZ6Z3Lg;hgM=qn3H9( zr)W((USXeu&&HhB0^Xv+-%}8+`G3+DyUwcz2fjSa44Pz6$TI<9Ez`9=SyN+Y9VfzhNx* zc=0;uH1k;Sa9;K=>9vmq9~sZNiA}lw24e73Gh&qW81Yrm^saTp?5{(X6ArHG`{f{? z7mwcszt{daf$z`f^;!q<_9XYk>m}2U=eKA^YxXen(m30QDbpBxjqv!^;%9-k1sx@1 zqd|_kgx^`di?oo33LTdDi)=wwo_W)jV)~Z;abU!C{KaN8qML6eheUz<@3|-5V`ceG zo}PG<@TC0hn><%r5%Ms~F>Q){l#NMxdl)|lUtu3SBpJ6tu_rv=$9HN+?S;O)7PV=@ z@%%{7tlNCq@YSyJ|K~Zvhk3|;r8v3BpWChU{5(EJJ-0(v(k;GCTZ$DzH$9{=@!bx1 z@NR6~*tQmW(%-`z5qx19+rM!CPj~}-LGTsb{0{p0|C*NT=s%R9x*iM0UbEh>nV2)P z$D-RcMN4}E@RIH!nMJ&%s$s_KHCl6MO5jGXu^z3l{+AFH;8NTK?B`KMlvD?fu zu6b_ji)Ho%e=^t9QtawGf`@Z2u@`Y>%c2dYbLp$5eoCvk6uKUNe3e0e7$KQ-vjsh4 zySDLxkHr_emGNoc*hXKAb&daj{lBdb>w^eJoJDd>I4Ib(rAu|91dA2GQG3G$z(X(% z^P+)4NbA5rd^NuG`VuVC>w|v+C$taVr?@oQWqn>>j30mO*@4m;Am8vi*uZn)fb7)Y z;@W8%9URfO=m_DNY1mU@nWR4x`@#p3$VP@d$TWN-1?{1?bmfm6ksmc?Np6ByOEJz2ME1tobPRPMxV?Q9oEVtXN`WpX@(T#wR>izHJQsI+l1Upf*cs zT;G2GZ13M=I~j$aYZSh{x*o~+9p>#`e0W^H!e19V*8%=^0Gn#l;n(9c8kT%C7-m-c z$0u_>EPCWF{%)?18pGcy{*$OZ?&9E{ij03U_(CMsiL&YnDko|Blw1eJ`y4X2m;9 zMSWIij`_$hp=ZkZtmRo@vT`i5 zCYs5bm+!ocfA8nxT=WpN9=E$8p*{sMA%!GoVEZlpVY!kdUq z`b^?J;=VW|Lo$ElHc$3ZovE#Hi$DCy*zrFpXGr-j$`w2}_Q9N?ZN1d!0FN4&!&b&5 z9j2VmcK$rJF;?Zcuup$JlpB<9QO=tnrd50W%jgpFXGg%}7(Cr(Z;wgtKv&aTz82@~ ztigHcyr?I+*F~SL;MJ3?vG!HHm*Tg}Z))jQ+T|JO zQ2oEg9E8Jl;OrBYgY7BYcfA7lpB_Ei5B(v0&m2#jNt}hg=6id27EA07BtMHr#Fr)8 zSk#!8PGa$X<7YTC0=g3}UutIi-A>UH%}Xv&fAW{vmRo#*`BybuhaV|to`%+AiS8hW zKSwgbUVBDJr{hzz{fex3BL6b{D=<&K?dCj{?!?zQN9t{G0$*LIf2FW>&k11yjbe|) zuZE5GjCIBvSAd@>_qV6RWG2-e4DImomi>8PEuLL&cTZz&a5+A*?)bgf*}oR{A;02Z z>L;8p{nRt(*}(iv;n(0sEB!1kwiMi+eNI~sYqno=*ZFAjrh?%?mO8>iAS8lTDBSCrrRn0SK5cG7>D@CAEMSt zy{ootV$ihT6qb&>@FT>Oe-ivq?&F2qu?y0--q-i1`9}TP;dw^6Fwe43w$->Z{P^X> z#`0YBTCZ582)t%7IGoHG(dlI$AEeX5z&lTIcJ(ICJsgt1-PGqF@msKcv?cNXow~$B z_@EBJ=cF(6E_}p2@{Rn!&qP~$@;Og*7iDiADqfQOO(-|57^js=`OxVh zjmHIsI^#xg(zw*mo^^rUZ7?EBaPe8Km>^Pby@KYefwXzva7 zmZOrT%X=@vNw^Qkfh+G-^kNICPCQsqZ9N*i_^$&e53`?X4aE~Kh;m+Q3VP09Bc(>q za~fx2Q@_??4}EKGeolMhSz5<8`KNmK@px&kYqf9lP61cZ=qT=K+~aBI74CJ~Q6qXC zNBoFjU$q1OIqhF{%}u_YZZ+DoegTeGB3B&XTC^)3dLTz^@!gz7ABC?^>+IimH*j$u z4cr|4pc{gRy@I0+J;V~b0^7Jfn*ZBQ9-gWFnSGbN@$AI;UK{+a5?-pi=Ixr)(Eu~L63_%;0}GcJK>ka@ax>eh;%^slATq)?z}KIPJ5%);&x!Ebtp8J z;M8K>9kiBZ+NZ1u;?etcmau{`tK4V;(!IY}Lro z;TgV`MNSCc_0f)M?Q35@&hJ;oegWL-Jr|$M*=gDrd;SVN8T>^)cXwYcT^@Z~dbgGM zN~xAYr(`SY&M?MMmk;&$&v`=}^ksaULwOEOzcU0qV+e9S_j~X^g*vsK!&k%HlyVN6 zYz4}3lrCOs^DpG4@LYK=|3XfV;!ltX4{6^4hjuXLuuaiPB;|THGG?7qaXL{tx36<( zZ&-T)It_-=_uIhXt?0-=@O|QQ$c4gpV5C|;(%l575Jvp968q1=XBO{kf1mbK_V~nW zp3a7|RA&(%JU+If)**t;Q#m=>>qQSy*)0lgQSx~OxV@_pnVohG@Ne&}4chpZ<=_4M zyN`eO@NfDr22R#8&+>PA@6W%D6;=gL@t)0lPtJ*iapH#|jJ!>h3ujnL4}J?RcsYTO zocCZY=HEX4P37M%{;B`+zNYbfhFVx1y7z|o!2DtzH6%=QWB`Bjve0fdxd!F*q zdvgak4&1ya8%r|0bP>gzMw}LN#yVnj&Y$8Dx~)^8Y*6N*2n^?V=zk7g4+Hi&>>83! zTIuhU+6GsJ6XInD#%liy>8+T9&RKWw=g2nNd&SGGh_!d$;5E;7i~dBD2O34!e&}S3_qjw~qX^)Z~ zBOO?AqPmv8D1IZoWE*4G9;rAP$)N`sFLuwO_}3|P``0?@2R^18%ENpUN{={O7%UIkPp_WPA6fzyD5U;wzH#DBGp+QW%S3HdefN2i-#oXxt{{V&$u1xl*w%KtyNZgtZXEmYH> z*pPJbP|%Psk9>82M7Ea5zR&Z3jPnD~2IGDWA(^$)&)8Tn( za&iD8ttquOTxyx4>hl5jkoQ)okFPU$rR&w20G!QhovA*QG46q%JN*!^?Mwgu7{4P} zpNW6|F}@_YPa2y(Y%Z(`*xPyvmf88h@^BAWVwd|EEOn+t36^`QvnMRo-yQTreGm)< zJK?QhF8ug7OrHg&8>-nO8?N=&k2ObMA)V&ZURyF-GxBbs{IsRH6wq*QeJJD#CJA8T zQl?~e$-J8L8cT@PzCaF{jT{1=l>-yC!HPHLdN(Te$Q(CwJLNhL`l1D!sH>t8_^mJC zf#gfkM2(9{>8#jhSM&KA@n~eG+w~95+b%pTM%mO~J`vqhqS4*XeR!hQdFwX+cW<*GmiCl*(dbw4pTG;a6XwtE+G*}B2SAK{B6 zKO?PgL|ew6UF+|%8AIsFD?I-eIZ1S0`|ItjvsttjKFoiCZs4D}S+q_#C?8D8^iSdo zCOmkD_Lbx$je~5|n~+5^w|_{7FQVR8Nqd+&_tWN0`CzQkN#Y;ru5$NGhTcur+6j9M za!VM0DA?Lg`Gte?PlU70M-GsWabw?QqdOG&>tOaFZ^K;aQrJI%%!&?|v$(Gh(xYSE zDZQo2Ar^R-#z|xPbWv6b_0%b_KTW&?){0I6Sw%F8F)Ya<(kTS{W{NUyK~^d3u|;>z z@&`R#HS#$9v+*H3!hL(ZBaq1?kDYj#HQs@10-dq=#~|=uXa2}$F23*xJcG8qES#hb zHEX>}t*B*8(}7k0hF`mvAiqwm$bigAVST8EU@AOFQSJC0m99A1L1rZbN1ih7!c zHsON={{@%iE3%9K;63leg9F}4-&qsv+e6+W4-NS3Fk~IkO08ui_lUMNQ!YOGUEgh%<5??f*FZ5k}QGJ)L@HNsso*(QFI;Ykj#P?A2A@qL_y)M0sZ4SOZ z>dabnF8}`8?d;901lBs=yTqRZew_V~FN-HDUwbG00d^K=L~p7FXL&DLRiF9y*BXnw zT75mh`x?Q3v6KEi#6HL7cq2Yv2i=D&MfIaPl#4{ZweV8|csadi0Vv;1cU8T=en zKDfMrIV<}FwoL3&1s@38G`jL@3dh-F9epyG!|ZisZ%b3Kf2m}9TR|D4u%jrZy!=Xj zL`*2rtjYv26j;|;jK6HKvWtb=CkMViO^uADBbz}JJZ-hV|E|&K4Q|fuzv~M3jJwdA za?azBk?148IowrDYUw%@-%Pp=D|vYkms+trwJ+%Ch4?a1wrH(3EvZ147Wlzr$~y3c!G{68M+NaEuc5S_{1+_6c*pr+u{g z27FEM$reo)9oeTcl#aC|^p3Oz8!xg}m*Qtir?@26_8ix521Pl4I@b8Q#;COZ)4DA1 zpEkT3)8uL|MmX9Go)>hI8eg6B9X71}h$;T~fr5Q4zzg!)L-7N zIw$mDP9FbS;VNGJKANbBt?j~?m`&mQWV>@Rmu}>Dg(w*QcajtXW?8Kb> zYC~1pN3+Y9?OJgz*i*wetvRAKfb!f%8T17{Dx8;}6!uxoNpL57LCke7V6O>$qc{DT z5B#-f%}TL`WgNowjCEa;+xi5yA;vkgnf=je<^wqU0O0~5aWCR@Kc*ifKNAgql8bK30A?Lgi>Fm!_nR_3tJBK<1zZ;y)2khKl=k7 zWXhyLWsah%zRW-6^^*T1We#V*m>v^xMq3u}Lf!n?Ich_qT?;MYweEYcH@P}*vfG}w_y!Dp$fYQH+X*hiBh2nSl0scqBAC8s2Gp}ky z$G~ULv^)9V-uQ=OzrC^TSm>K`-16DXR}UJe@mg@`Sa13ySeb^Gk3HHj@_4r4si02j z=)#QCvW6>;({|4^bRLrqgSz%vSO4WLlaO9yhW9DbtP2~?xLW3_48PbW&dJ#KT=9MY zG%kdV%2@1Gp+4H*Wf~5gO3S+BRNB6z>qC0c3yr1LU|WHS&a#njkF}udE%xm1JAutr z_0!jg%3Nnxx|Nh~8ZyU9eYj%5+t*#O-~^)b^!#dYqB2R0n;^}BV79y2QdYOjEwt}9A4#JY9aCfrkuC-5R- zHJo`Yb7QaLFEorj{`!I)#{=1V!LsA{f)sFuu>cNT=L>i7?a+7AkUb_GfJQ_vZ@Bol z@ML7ecbEeOxUm=M6s`Ob2Z%QyV9yv*>d+ew;)JvYc#DnI)1a_vI7wM&oIy?pJQsAv>>lXGLU_lzgR5!Biw$582)swU>CzeS*7o1|Rsx@iSRbvk;i*^V9;W>l8FPtZB*4$;>hydH zT(ZpXe8KAlV^+$4Q1Vi{+3i^|tyku_^@e>r;NMK})nFG2A=B6<YYtx@*x4>Xi7StB%Y=@~jxN^5QHZI{d> zd3ii})y8n*)@Vh#g{@o71I6F;uG}~lSCwh+99UW88^SNO$)Zha=kLL<@}ZY={yRR8 zG0_WXzDt`~Z!XpxioHU=r{!-6!rAdH?%J&LtWN_c;F!0vulSWiu~o)8SeJdx{g&1n zz=62~9B#_@rk&p=?WS_*9`CVL>{Ne)7{v4$ygoG6MNaIv9Q?tbKfA^SJG9uX+=1YB2n^~V8va-MPC&Vg|(UEmhj_d}Jb*PDNJS~}LZM-iL zt;}^tt8yXd${cj1MmP~-{H6bZKJ84KA^96<|Rm~$oOm+5M07KzbyXo}ilTPQVX$-Vp z*u--v23|V!4>)MN1zYt)%Ya@9xbj{w-4<{3wg&aym`A5^C=~sjZv(Gx z`LZpV4`h1T_(!Vj zpr57-_#f$Zt`6G#3g13pd_TY!iutmyK4`;@xe4r@`Fe2g<}c=7uu(fc&wcvm#{3nb zBFdh%Py+Y?%P4eZ5O}KnkfI@41B#!AUG_j_SNnhXfIXwkv1oB`n)bTMH`g|+-`oOS zO8^7qu~Uw>hJI>Jk9SxzYK|fMEKNFII-~h_L!NcaA!C@IF3(x%)Y*T6Tqt?CPkmt@ zR-XiW_u>x(de2VQEq4u>o73LJcFr>I#%3t{LOEm79{+*YINbJ>raj)`^+rcm2Hz6T z4I!&2HkZf!fFgZR-$h4b?W=RadRP4iuEZ4fTaFPI>Q|2Uka@|yE<|kBASOhXzABcwsc-dXrLhe~u@ey=;&j{Wd#Pigy9&IK?y3^mL*vXo z0{s30eV6VT^HzU`(K*QM3(vIrU5&*4xwOq+6eWfhdzS5Joohv%>Q$6mCSN%0zE;%w zwdVhF-Z6xSlvetP3jEEqHhWe4AkG}2OovPBsMZ7MIk=ad4|4Hj2)^HX;6we@a@wSG zRCETs9q;cxPTF4Bkz4p5if?^AzHf{8uJUJuOpR>5)vUKyk?+wt=xWo|?nAs+T^03? zw~&1Euz9xwlkHqvdEUYQe6BcsuDWz`?lkb*W*!ZUInA_ZWpp%ar`Wx&J#^jcElKpR zLv*&A$D0T&Wo$C+KZ`0qC9!N1) zAU=@{X@MVf0vjl{ry!Olcy7hOamp_P*B298OlJkL{!=_VdxomrT!$uA)6YW*zKK4qBdyLj5$rW5 zupb3yqbw$W3pk-QB<;0NBR1VRW3~ zn}0QZn@M@?lslGlwS{-W_a6Xz$(BRahI;kqO9fkaigOjxgZ*W|Q#=Pe>@0VeXKifD z<=Lg#GW!2ybdy)or#~58!&J-ajjG;YEuF4LfL){i- z|2lcLMRS4eN&CLlhM<1_E5SVzZFU{*vH4LydYxkZJE?z^z5}1()IaOohg}QnB2e9*nK zB7otS^6N~r#ccr{ z-5OPYk^yd7X~z;B)7aY3IN|f-(9Dzl-~rKAKhxW||8*YcS&}}2-b(a3#=h)8rl@!9 z3i_=1KAQS9=LZ0@>B@hp_GGl6tHEYmghMpmk`pb|nMd{fr$iH-;#p))Vf~$=eD!;s zO`m~{ow}QK_N7xTqpQaC*>rK{l?|ZBdgm=74Pk)Y{?!L zXWBu3su}NUlXh(3oyKB2S38$tA8B2aQ@tA30BUPwL2A>NCG5YH}NT)&1$pyv>utYdYtC%hdfbpO)`e&v-G9n zse<#P?{^>FLVY>xJ0WE5y!>RSgKt4ScD;Q6`uGd!kH%sLSCzg)J2W5vhx9Ee#-09@ zQ}$N+qyKlSS>IB|7M@j}V5;}3dolOI4=dUPZWQRY_=NcS-0yZDofX5+j?K5Z}^RlP;yW^s3kl&HL z7~j&IE-&(n<@6hPfNSZG;Ob#wPX=-%W3Kg{WC+1c`0!S7OvD53q|UO8bFeSvNzN?K zI8XFp9h=E82WW@nM&5S_pTU(*%6M6{kAK^W+o;s0=gDtrY^G4>V#ZbZ?D#ZnZOE_G zS1#ET-#5}fyqxFjsbBbT3P;H!9F_d|J-%rk+>9KDtorNZxIyt{J4& z+#@g#ag>RAM9t)aj zOu=@lHE#uLSCfz&f;|)XX?*xz4R2?k5jGUsXftj$vcLL2iT>)n_7&KyEh`Qb*1_XC zm|w*HJVBY2(m$KJD(UBXGuKA&{^V)s6nLjO0Br3$dDeev&U}vgtT-{BXya#D1F20R z>J9-XD}^rOi?j`xGCqOKl;T-!{_VqsHlIuWBx4e;=eyb+Yhlh6+8u&#g<{KIOPZEk zMY6>o%~hFd@dt55xmUk_Q7nh`cug5~XpA-1e-tj@yUoNt^?oL}ayjp3@c%rz%e7~B zxE)fjRrc?>X7dE0_-PG0v)gO6K8aan>i z?Bmk=p8e}<&U>?-zG;kwBigHK&^es^!sMLn>b=2#lf-W;MXWOB-3Z!hzkP4!eq@t4 zS1Z?gF7$m}Yq>I9_j0Y_!lu%-it8S(m0ZYWUCX(aaox?el&giSnG2lhn!uIjx|$1< zSQk^NYYrE3Sr>ew>$_a&M!Obpq1)`bgKH7j?OfmD!lKqCe3L)7_Q4D0!Y95(1oKhQ zGr?VWP@j1E4gE_8{z-XmF6I%v|D-%4!0*xEu;zgS%okP_^a78tCN4KE?w|mNWn)B_ z!naQ1Qm(D;bSt8*e&=WBxZ~sCKKS|fv}IDUEx|hT?`;1?#XML3jrM<1pGT4BaqxK@ zZIte&mgf=5U&Pn;kHI%|CjR#obpect55^DzW9Itb(?X|Nz9!utI%ny7lrk#NA70kivGsf{^t+Br<%ju3x@zWX@J)KVR_4yG z^bh?VN$J>U<{uW_M2-aqtE~(&f5xe{Q<76Epx+z4Pe;2eL ze|~H|YX9{7+Q=pL>bPunLXIJO;#E35uUHu08 z{&R}Gr?r`6z&_9r<;(K@GkhD8WuNa~1ly!&{m#)*&HtHs`4dX#dL6KfNJfe}k68E& zj6jZ(u8O@*w))5Xs~L-2ptb95#k5n}TYbrOdzp)6=>;zGcp!u8JY>a#F=?J@gB63G z#U1b8x(oGBX>WN7+K!AP9T$D1E$b_6H_kj6!Ts1wVa|+qTKp5k|5JR@mt4?`J~hX+ zU)S;e^<<$e@MiOrWXq3Qnd7nrSK_O%&|)597t0K1FC%F?t7p4w&y`NNE-#)Zy9_$~ zh~zLU%>_--8P^vge`=nqO=ad*cYQD0HPjuZAF5aVP~B^absJ!Ze&ew}fTItvK9P=M z9Q}#`55YOk+#kWc`lG&#=2>&C+)CssGLDyaR=MZ5<10e_C*uEseEk*Au>p;>4 z?TF5I1)D#{{u=aMvB$%i0$c@;e=4>GUBzSaL)4rp!9wFdrM`vrQvo)~Oq+izX{Myn z6Y%W`rKi6dKa*UIpLU^Kjg{6?rEyfh(dQqmOe?|xtH)3`UsKf2}}lw6%tJb(EM zMY(#>7Gtk|Lu)(r=Ql;2R$tms5xw7EYbIcb(V7xb17CFS?iggfTvJvR^XK{Ewud_ZBi%)p?UbePfeCyD!*nH4SsD zrqQnTFxO@0PwF2U=2WF(P1T|7)c&hFn!F|EzqQX5UIeWT;E8`DzKPeOLnW_Ax}4Iz zYbxcp-*vAhH;pLSt~Gx|i)Alb4xJF(?IdGx4)h`8c;iyowhjJIwE4YJ#A-q}E_?TQ z+N(Jo6C7gL_R`45;P80x+VFh^c?~ui(JJALVh!(zKlGvfXcs=y*n^tO=6d&BT(B2y zYi|$MbCSvlnI!x6K@Y7EJS+O<^t98=A09zQ2r1n6{}sj6?o@(--~0 z57D)2uVssScRl~E{2S4qM4cyS=aaqA;c1WYws9ap{u zz398iw)ptS>$XV{C;t8pmH!zTt!BTKJyx zj+w+Xo%fS74)ZGmEa5Nkze9@Q7fw#ht)ou~?xlB-p4M(mV-rA@0EVXB$=~%r+p)V= z{(-CQn0=e%8P*7qwqy5P!+M^%B%hElJX5ik!{$+6wihe{$9hL%$KQx-4}6#KOE|Q* zcz21w?%DGpTrwW-*-jZ z%J6rnkg)+IJT1JB#+oS6TXD^fo!0wkoY%$9Mo2WD@AWRY8nLQxBh_ zA7{{ykwHIp_==CBeuS~Rl(pYOKbET>jLCz4S3gR7K*IPN=VZf zUA}8I+3)@I_^0T9wxRIeqV95Vq5>LI$`75ox@SJ-W{uKh`~38{(pCIR+cA|bUV1g< zesFd7QSE)z{a?BF1%neY@bV+S#4qrn*5cPmIg&*p1{xp59}^ib3=Un{(0+XISbUU$`KShSO6E8nzvzzh z3x56bFSg-H=JJp)x@5;SuV>(s+IdWLOy@G21pJOZK%eqf?24TD@M`c^an|g1u%!_;k|GK9)#|4 zIr?g3EzN;%v94Ts`mufuHynF?WnsK;UD^IpAD#zaMju`Rj>tD*cbVJQTjlP>N6^k9 z_x+@=Z=Ub_ocwpofK3T5;-lckjUUg~3;d?sydUGa88bfu2Sw* zz`FlM1z3ln&w_VmW%)Cb^IluMeeU@Cx{ulqwB$axv?XUKe@nj=*p^#zqfB#d zU9s#NDEs7j>@{|s{GCq|$Io%{E!kFo9`$UQu+bl7T9Ch6u<5oCYqo{Bal|B#wXk1Q zx&hX$)K|$hsJ+FHIjpJO7S@u5^{nQ%=GfM+Gd`JQ?jNWN8zO$OT$F$R!tSH8PwM$S zc;z~tfmPmm6TaUL?V~NB-fqwyH{RyEq**^&=WlelIumw3zF7y*A$4B3(A}SE^C#|J z=>GhsrMW$%Q%rUzjvqsxJ>CY|TF6g`1)^4s@zA62^zv5VUcu%^B9)E@G@O1i!Ag6=Jl&tuq$_+FCNLmzvV zep_<2Y!WuiaVyxqb+$3Fkt_^~dNuj>4#LS#davcIP_?E%G4Z@N439aBj2nv32>DDXiX;CZq*YH7W|BHnN*Ur1^sxh7-Jg8&UK~c;)go&a`G~nYgE^p z!8a$b`<^=TAPsVy-hUiE8uvKz#roVH&Rl0dV}7BFF2h3yzHe_*y5BmFwL&Dqci(f&Q?)fp{V1g8;M5*4~&Wb;{%N?eE@k1_{JVB>K5Lq44vntI0W*ai;fvwW)kZ`{V(NjrT%-7 zC3y$D)*lHa=DC@x3$mtkOQD40{kWmmaq+BP4SSCdAzexKwHh9K%W8P@Ev;$Tf3|bT zP+~Fgy%N~UhZFmx1OMGN8r!`+w8>o*>+fr=JUYtrm`1lgX8T`cyj14tz++GhpG|O0 z&)DR0>PrP>*%!`**E^ngQ%Mefz2W`itFS4`_hwVo{$P*5{?57X=qh5<(eKP9XB^kL z5wr0@uz-*1>>p#k0gsTy+L194PZP-~?pepRjYTqjK!tCFQt@2~^=L}+gFs@$w zAU;IsEyc@A@6ZqJl#WvScbBE-Aj7|WEVOd{u^%^l?)b2fVu5jS&1=k## z2wt28ek2;MId<=ePsWL}7!y0rIT(y{1Lcnd50NvDw{d?qd4&hUfl0(e(m6%}oWRAu z1*dt?t^iKMJ_;v|S5H`-4y?kbV5RZt?+*a}f*Jb{{GHU_v+fbJMfD>$Ggs3S)-gu| zeFgIrnKZE7g$#5N9I`W<^-O;+)EtPipAVf3G~&szA?zr;*Bb4v$~1bXH1mjcZJISS z^?t9t`?Yg5N1Q3SbJ1Bv9$xD9M~({_&J{@>@RpX%^QKJ1wvNx=wrtW{8oD`%({~y3 zbkc105LC|bCQWQ{f8rkS*7Pmh%Rg{cXkn0UYTZ(I($KrzozVl{j_d(!Ea}{u%5RWw zp?96P)ScSVu}2y2_n`{CeHeas( zf=3(uUiEWaJDzfX+-IcsNT1=}vic?$VaLt(%X%-|^#XW>&tD80i~mrV^DutRoT*69 zb+`8UjCW6av-{$+FOmkiq<)1TZeQU?m>bW;fA7gh(nuf3oC;akeaAFA%#Y3TPnDjb z#9#MegRZYv9KenK3%XBqzxExz?+I+yj<<;W4(^wQXM4-S^N^uvcS>v38KTRK5k9%- zbHcP?F1|Oh3dc*E6fuZXxhKhYN8;JnzMY~ijCI)D0__x?O>MKHE!s2brhBE_-wroN zmy%~!;`d%SNz7?{GSlEhqGGOF7Ug^;zK4N{+O{k43s3Jx8N~!MM?avii>O2Xqu_)G zZG2)8z9G-Vf8oh~aAq29?p?;*_I^Q+6N4u#g&(|ev<;mNc$<}87k?brc_PC;^GzP- z74VcHN?%u_pyUL{Kr|RLkDiezwV(>U$;eR>!645Qh4zrmP>2`*Xi6v`uX^U zrji-oxcI&bVr-|hM9@t4*aclhY$H&Vuke`xbd>E@9}I_c%~RpmL;v!H zK--$2+aZhnPn2g<)-=l5MB5}+Q2*xj;jp`j^sR{%{s!Q&i9GYN8-4k;R*qT4{*I0Q zh%S7DDQ5um|3dZ#Hw`W5=rfatyIVSlF9R%RhUw3_j0gNX#eF)awoJ>vRgTWFinUjg z^Jw2>9Yc9b>zdr9L+7}YMik1~Oa4!hXCZk`_WM@;T<#gW{H`(+IZWHOW@#IK%CbL? z<=YbaDL7xv{r==eWL)4B)E&f;h(x*Hi{Hn(LA%}E;$7^4*c#vXqr;zXdunTJ<5OG4 z^!d@2p?#hr-&0#CD;&{zMAP%TKHoNQc@^-FuNb%i7&-LqLf+qbn)C7l2rzaLc1}kD zeyb>NFXb|ZPjUSyD35&7mm$Cf<69mh9t?PSggz!{=P|}<27PgWv+yTNANBo?a{5YJ z)Q;EjSr^aTPv76=dWY*3t`l4XiHo~~vi5NA(s#+K=yhw7#k{{DMxgNbaMfV{JjP49 zNOMkGVN9!cyUU{+{Z3#r3AjiW02clh$P$yr^9{c#JBa&O<;cyES?Ns|m1Bp{nw|F*oJEJMU-K%kTMT~epkIR9 ztFaYNE#i3te$%d{Jq_Q=^KPIGkAS~*7WSSLY<{l-k40z5epHaDRn~m++2}i}py~R4 zS>NgTXSqLlS`e#fU-3EeMv!l&^vV0;#WJ2@uHQ*I;J$Ba@!iwB+X{R(OjwWa{d(xH zQ>`}9mcWi@mzx^Vn|JU%&I}#we=xuM=w7qYzcOe?(rW_d!o?QGM>sH*_Z|3$tncXL zyuUf_R?0v8yxM%D_bT(@^PKxrSFh%=)({nMCeibU%3STKeTe6IJ)R%n`NAI0_wyW@-NfGTyRlQu z@YeCn+MfICy?ePAog9zeP_#4rxdN=ArPa{Ufsf+{8MvGoOWjdungX7NjW=|Mw$4vD)ddN~i8g^gw382(xE?i)`! z`EZi%Wjza?HZsFy=Q~eAQ~V9Id;h!0xKY;Pw9A-I@Qn4(fkM0;rN@T($U7t2{9)j` zeD|?`bZLH1Ekma>uz;7yAk|hZ1WHZIlXf6+!XvMdZXtDup4<*hD`Jk<4i1Soyg`hO z7UV_s)3N&aU#G9cna!6Wt0{J)!+w(V*bn(mitkR3=UF`8j2)Di^RCWwdUzP|@2Kyx zFF1M40nL%Cu^qjEU-LNf;lJYP-DSL+$+PfZ&l7o`!n5wbKz`xp`Ovs}-f1qd=Zvu-pA{AM-RQ_d51%GrEB_HW{soZ17mhkE=e-A5C^Z*O!XZP8hb8#xO@d%QHq z*kb`MEQzkbcOLo>TY>&%BYR3#_!hR}QO;`iL#!BhAiknZdy!koC*RgEe5}HOm!{Dj z-QMPJ0`^W7G!z`(5B_Mqr!f>CPb4KHm`E${72AB)@V2rgu@z-4)HBbe4XM?>=HL%r z>TqBBZ^xV1{~PWPcE99iIw&WJJ~|6d^RG2ht7WR=-<)hlR%flia~b`czN@?EeyYN=Jan-Fk zB7c#4PM42OGQW!YRi0#$CBWX{e-Z!iq+l#WQ^hwo9AvEoe%-L(E5}l-$5O1BUz7a> z`^=4f+K6lM)8nSk#m66{U)erSACE;=9Qz^uz6WT(AoE^N8dqr6yPuV2=VqEd>0)8_iT86*-wxGMw4Ig z2*;;mV{@t{*Ga~Oo>$|S8?uwcTt*&HTO|)j?*nb}1hXUbZAoqq<> z?Buc#i0A*OF+aG(m@o2Oy0bkHQ0W&f%b4FWF+RU0KTdwpR+@L%>twT&Pu+D~+Vfk- zHR!v-<93?NmQl&j=vytuVf5x{t%PZ}8FVv&o=X-da^4ypoMLI?^4-Mx%`=Th<_SU^F91LgI`R#f}ueO)} z*0qYW*5qc$-%8rOb+!J9_?--FNY6U`wKeXv=ZPT=zn)KQ zQmq>nPOE+Dwq*CCqLGpxMDyB{&8)4`uIz=Hf13Ltule<0&Sjee8iWolr?$&pqW2mT z&4vBs39q9)_3)6OjxTw$p$qlHY8hk7LAM}zLhB}tZ5i_2kpye0fVRxYYyKT!zKR#A zZ_D}D{1+U#31mS%Gr5s{8B^$s+A-?0h5me-bwiw(pgX8rYcBTmRjXa>O{(q(4H3VZ zmr|KGasqRu10CR2y*k-lpTw1-dgO){5wp62X2(k&cY z>mOijV$1{0746xnNUea^u0YOQQC0^0R~6IfyI>m|Tgx7v6@HTUHe>rI<_r0d?Y@K^ ze1-oU&u>xZE0nuu0BK@#T;Y`baDx4T$c!gHRp5(h=)gGgG_j5z5M9BUwc&9W86OMUl#-+V6ZCCA_JQ91t#q_mLUH;DVnnKcHoIHP02-c38^!ohC^P6@b2JbeI2AuL( zb9TnnZg5w62k6-#>F`EYEtcHL7rNo~f%=o)v$^7nFCpzni{* zv-$5LkCf7v(&8Jl?|XbV^-f;v-*-pWo{HfhonI-A1H5774B~?ZaU8}pxoP%?JPv&3 z4T2WZ_tS4?{|5Kyi-PC=JnQ`u-WlwaeR-B#b+X^|JkBOH^MFYU&kHGYeVOg9L)Rib z%kIz44d|`-hWL2v!4LWP25?IARy4{`S{qGV#{-+bWkKR`va+jEv9P#9t`>b_<;ZEMa^O@g!?~N190*~sKA@zI- zoh-aw0q44TA<}og>c0ePb6^fluNk2>LZ26|PWHWXqMU~c-m>! zXDP=!OdW$RDU|(BkLo@gqi>Y)b@B^df|Gbm zY47tI<2#%=QpG-b!Gw7# zS#~+!h)L#e<=&1B3(negE@<=@eWrFG@@Cn1-tQL=gWjkOZ&|97nC4ODHf?#uS>d~U zKM-A^cpQhc7LrcGin=c&rU&P5Z`V6w6^rkP=PajPiua%FJ7gr@~^X3zrNXRrN5bYhdX3{tv`Ge@`Ax{ zaTI##3!8J3tC2s>nnYc5b8Dx)st;0yycTl5SNjlNP#mv)su0D$FCwgNlx3h|8bCy6yYk+sH$1;= z=zHCf8u32n^SiWVewMZj>U15_>{Wa4F$-zkfGlB5liLFBET)|MP1p^ab*wdJyH_#y zL`%mKBUkiP^W)vriolPp446#)LT#XneCJQ--*^tj9N0fXU#zH;zp39fIY)3wtUzbQ z9)cveVUWK_EBQ-oX>T3c!VfvUm&y2_Ay zS>L*RrlXIK+>5Nr{sYF+j?Qrpkf)J3lIbYM_>0bV(WSde_ZHu5tyg<|(>g?Jh7r*f zPf5@7hxgEj(53+w)S@p==DqWYD;Cu{ayIKO^cJC^ZkqMtMCQrvA^6NsAf9!MSZeT; z@1bYB>e4iOLYs5iH(rvN)HlgYwe)WTWyp?F#$0!S_agEOw@T-zxTI_0oxZ6|H7kCWe`F?!g;4zQgc^dTpQs{wdli-lDZUb|OCn47F}#-L+YA{K;QB`9O{z&Rp$HETy5WNpo3Ox;T}U z7&^z5OfR}1d45oqc5qctu6UBj9|7yf;-ruB zJ`)P@9{vyP&|5zu7^Cmr9Oy&oubzSH<_!}XeQYTHD}P}R0OK#&E)E?*Hnt^ip3Zyd zJu=UD!r>?HBx6u?=}<}d*J!lzkuN$+Ve^Fu_Use_^*{7 zm6L1+?9r7Gag26;4SFTn{8s3c_)S^t<=4yLQ=%Q>?X#gnR_u1#H!Xh?Izo$B#7Zw8 z-y8WR-Ya{Mc>kI?$X1kLndQm(i@8+r;_Q(JA{qDykGoMF4#v3R9=rg>>pZYt%OT2!3T|tJ>`5WGXif7ryx+a=-Jy~r0$A6) z!TpG$9JiLXsjhcPUkMCf{ATyj#Ij^KF}>Gb?E z&swYNJ``(lWwUyTcO@Dt8A7&S$r)P96k@~#a&NvDa?h?ye(xO~(B{i;M*5He$bd6R zD?Zo#9xx2K!FDCD6Y~zT7nopX#!|PrlUrR9u*L@=S+b*#`S}|2AYX;w5B} z_iHDfl5d_TooL59^g;N$>l=mj>5>cK`>f|?iZ=00_$!_|miOalOQzA;3XQI4)S0Qv zbK>LA&z|UZj>Tt;F|kvMg?K|gl)^_YD68-eo|NB3`|Pyi-3mOXQtq9nP0LTGo@kJc zxRczUL?A!zXd(wCL{gw}Mk$d^T~SQp*x%C|!?TYF(c7QNPb@04#MxL?QEEF2@5 zEWy}vzF#=(z7KDd-|6QT{NdQ=u&v#=U~sS(;_QZUY;3o(2j~6cnXAt@{wLZdzh})q z?9W}sIQd4}CirSkne=#T`|oz&U`zzt-RSeIRO)}hRAp$~O0c#@%Z7G&?kC0ZV_yhy zhoQ~jyI}Y4Ja6Y&@YnOd@Z8frch3JozF7KuubK3vGlw)c1jD1W*`%EOaPk;q0=t#$ zOS1nYFE7}PWS@#-OOmb1{THMv{$(pVdt|}3Bs-GqM~dAeJCkfmuRd#xd@i^1EL&8! zO2)n=8W?Q*)I4Gj$W9zZhR_~w?VXp+So(`{=E^$O@=8OQ zUdD$1#8}Uv^XnDco!433qlo1q`EeZate`=M%G0#L!B>|(Qlr@mGa5hWvoou6y#UcD z_O6Y)v{0VT7rW1{1jlB1+svETP*}r9cD(ifwwDqKH_v$&zU8|4DD?~o?w$M${A1-S zUYWhuReUu0bX271Q@z$rtaGB0)qn|cn6$=@kxzDRf=VBaqh~Jdtz>Mk;0pHtY5&50 zHZZ>RT){U{ux`h%@guNy3$Wgl1lBIF2F|TfV0~IkZZzXlmIU6|D+F)(&j#?OZtbO) zZ?j}K+2>2}PNsk8N86qJSm16sZeFs(JQw)W_93J*=@0u=&KUKM!?W5ckFsR1AK=h; zx6tRu=-cQ6tAl;j>WkUa-Cc^yGmo|nVvVv$d#3Px)H-D`cEL~*AISJ>e;DgR^>;Ai z^3EvMGJO97H0dJNQxWWvdhSGi{|?XL;`2Z9T!$~A_EhQnV4io46Ai8N@L3D?Eqy$O zPAzHbqxfPnCy90HUk#qDD%Q7`Zx>L0S+V?I^4!R?U3@;oa}~TfRZM@7_RTETcd%4n zvJ|uD`n>uj-M}ljlc)esKX_wBZxs z^)hMH=D1)6ylm>7#~AmFJG7`+-vr8MO?7B~@fp}fQpNID{EhMG-Z zDesu- z#mv4L`F`SKSgqJo->2Mpl(mSx?$Qr@f%0YsW#hATo>QGXyRA$x*S@fasdq}TUWAy) z_+mX5eXPym(Q5Opq)GRH=Q{EWPlPX9DDSi)o~+V)^yW*Tr|7qGHw$*O?Mt*RO}kWo zb20A{=4q+?FH-)d9_4GVfy(ch2KE#g+at|wJ?nyZtDZ}#=gML|U+eK*aAMD604MDi z6tBm{;n1C|i|?FJ(9h4K>~UwYzs(AvXTuK@y}$e zJCe>hwI9-k$vxVo^`qJi%Ul^Rw)-DS9Ta!-HIyU2 z4bhQymBm<$VBK=vrIYYMlpSIke06%Y#z*);ET5Gb+7u>@aAL(x1)5Z$z7+96Fjw8o zokR1ePi;DgjcQ>L#>id&Ys`Ov+_ta>j1|{7oXYqUkdd^m+X_!w{tw+pGu4H9|A~6b zQ}}pa(vp+U53vw);{S*5ZP0$g?Jw@}wno`2 z2u@2k(6*C)pzqUuLgB$HpU_5!HWG6q_%2%ZVvjV$BMIiu4}i~T;PX!ZLOUlG;Uj(T z$MKvw6dB#4yq|tT8s<BnPZ`CyLKnw6b-roktPgbVv8LdxkY%6H zw&|27Jn8<&0)P4bC#1pWGV*GV_TN!zKl7sMJm??zi7#PZbAmWaE9WEkFQy;DE8toq z9jL}k^iH;tFnB%h6Y60O5mV>G@%u`TH1dHJevhR5QNTxS{&k_v`MOejz_FA4CI_?_ z-;IFAfMckFob_jtZ%F~ay~{|y7#iM_rd3h*svdQZ?@@P&HZq4Iqk5$IY>#?N^rQQO z?v-PU^y4#pPZj6XDDIzvxID-8Gp>K;l6?LM*Lx5W`Kg^eml&MoU}_ChYP z;wS!QdNS3p=QcR2UGrWsuCz`o{G)5Z4?%S!>piSJoaosf3*m=W8Gji&0(M$(iC7u^ zZIh0wtWte?Pq!kH5tUDRQPE$O8v;lER{4EDUOsD>|G(=1^p7c?l=@NOX7Ce#+3$-@ zTVh_nYcv{YEJDihe`DL={{F0FAjPD^9#jZ3ccEaEVgPqKmFLmAL7wXw_ zsxIhgmikWBr7=-FCdD%NfH?z9)>~Pp`Caxpj&g~^1-xuvK;Jtxr;yca?Ccum+AQru z*e5xa$`M_b?X{HtE$Y3v znCFkhx%nnoL|b_xzE*LLpN ztvbu86CFgCVqP+rSwDSPH#U$%s;^YP%B-YJ)u}$H9Mwy_rl8*W#d;?N?fXjpD7>T( z?GY?=c2Ur0>L}cEHs}~);POrIyb$<>@ynB(bpiSH?n=EIHqUKhkA&Wz!+YxLk`J+B zA&8GCMwsGQjZLfknR%_f8UMWYj>lD}<9LJ6tCXdP*NJZB40J2@#3r|~z7VUbKl&AS z9C$!n6vbD2%B=KsURN)4#jqCQBDH=bE)wJWk@!w(PoM`0Vtsw62a(RnB=E~F zUuA#h2#Y<-=0RWfPdf?xoCk?V%ihO{yyuE?RdQ8ug}DsZdkM~jzk(QyvHN|Eh4wR; z40CcQdnqI3?hUNvM<8b@-d70NPGf&jb@&GKTks3!xb}51FNh2CpuaLEJ@VbIjXpU7 zU6Fr*^itZ#U)~{K-rae0TDeFJeR;6A|3P0mt1!5#eg4v2CD7~Xemw0S%Y6&=Xzx!H zJyIF6I`)lV?ZDdh0ln`<-lk$2@FmZBs782XL-Rt6=^*-1$ra`rK$%g>tl%#t@I>67s|nxlz${E)?UO^(gV_;yLKX};a@uW6m1G&ub9X42%h70v=6)5IAAxG zcWK^L8qQ8nPR_kcTW9g^a%@mD`G1t>dj6O2zli^7{Lklq6#q%9kAEd)FX34_@i=W6 zjgGn8Ft@?Q8097=VjIdn=vUyk66U=HtY(l#{Zc%GD@mjGk{3sDKZP>nYuYPDTKa1; z=*ZHm{XY|5Lv0_&_aUsSm&K8s_dy&(GN}Sh&tXC=z}?*D4>n<9cL~C zaaRSGaBMQ`ux;dP%x$!7HzN5d-lUjoH7UMJ{|_xYG>@{xzrX=6oG!$sQrl+D#&$$o z$B}jf^{NlD6_nHVfqcIVedj>hq_m*~@s0Sdbl{DrcvhMR&FsTg|Ia1HM>~7ix5Z9) zw_&U4o-jG5^Y?6UQTrRM&rIBn4DfszvP63<8=PNMM!rIx`XJ9;#Mf3Hok0@pi7e&` z_sBDow#MjBXw3T8;luexXoGwjSM$#rAit4+ja!_)sn2!-9W#9@jjM2P`^B6AM4BkD z*L%gq77prc)DZJB&OPg>n)&3b@4(hfnk63gG0y4!WBfhw4B+f+mVHThFIhwQ5b6!A zqLXu9p2{o`|U3r^#lRrLM6v1!;B5r-9dDiM;5 z_3Ud|;4E3oWS#tC`VopxW$r>-kjsL(8~YPHEsc$Wb$_fgpi}3<)4z}GARqVA92QMK z7hF&|X<#Dw*-89Zh7NZZTZ8?}zes$O6nY1DQf^c|v5#V{NBV*zlHuj=YN1!V&l>86 z&|z((&TZ+pJ>h65Hs4jf!rOC;vfR1&ipi(G!=ihpvO;_c< zn|by%$rEQ&{x0rKRhDwB zf5%tznhD6_Y4+^jc};Fa{S~?C`0tN^HiU^~w_z|bMGWcF&3*;*xvu!FSF8YYr5!}t zMWuB0&He|U>pnW3yz`3hPV)RV{_tVaNxx;2SNR5TuNX|$1DvaY{U}`D>W?MQh|E>U zCMtiKdz8E_1J3kgFLM4@KiQP`yB*m6_J4M=YqHJm5UUkfa{iMG{|jjQ>Nop;x+N!D zm0(cm%6{P! zp?zE9G$!zq3S6;Z@i~ifG4kv-*v1@W= zW{x|S{rfsw#<&mpCfnq$WuNY*zLl(vCHu{F&4?!2wa{(s-{cx}j*rm>`G=i@@4IoG z{54+(=dsU^<0{YMj|;s}yVM>NTj!@2IbIt7yDJuv-*EmB?U>xM9DG0)s9*IIIQ2vs zc8|(2($z^maH`m&YY}H?ph>SPE9%1WrE8?U_)8G8z;@T@Jhz%OxF*?c2(qI3{%6Uu z^ck@~(h@$?FX2Mkr17^|kFT@iHPHT*ipxqnBf#|AJS*Ke!5N&f;pMi`nQ(zz81T^w z_RXr#|IN5mFJ!#w!w~v=A#_ROKT6}yyvY6-8%w$|r+2a9JG;IyH;w0b@mb@n=dpSQ zPjqj`_i~PL&*vBN z@HFpiVw;Q&o-J3pamhiWu%V3_+4daQZ*mv0&Wvq9_9lK4>$@=biub< z6FjF1@02F`*X{sM1yjv0(XMzujqMdPdsc$Mt$6?%`awCI%D80K00P3tJlNt4|a ztPM`h(`X^Dp*=xfc!0_=djIhVWNG|Ny%qG~ebRwbofbM^`P4bzNpo_M2M!_?C z*bR6P;ap~w6~qz%hg!-NR{(y345&Q;!TZb#*D#)nF)$C>q51-v3eGD|YlQrRtIScS zU)s$k9q)4FHsSRKY)|++=bxpWoZH#;O>jTJZ^{qw+kl6{*%b8y7vb99!f)FD5&Tx# z{~JzT$apHv-3A=goS~kym2%6+Byt1L5ojNE+BCUAImPdn@V||-<+aAz7A>CdiVf$~ zT!R08mHz5^e5uS4{R6s)EMKFtM(921dX7m8<8W%68FTTfUquTvF`)n8@nT{kWb6?7 zCj1J4mvEurtppdJQeGtcUro*51+pZtPNwKarlnzQ;v5PK4Xis1-Whs zv^&7NBx6ARk|(vsR=ql#rm~1n>XYKV=u8mFkD@mL?clxODjXGTCqu)2M){?FmWOT$ zG$@rsQm`_YfNWNZ4l{_!14Y%(TxoiS0~&15kt{h@41@;Q@TNj56kjs73@ z-aD+xW$PQBNdh5&fPxA(KvBSoioF0fY$$fch6Rx*f(4Z*B%w=D>|J9Qbt|@Q*}4UL z@0D#yf)Pa$5p`Sl=KZakaE|A^&vTyZ`~G{c>%nW8x#ymHX3bh_X4b4(Gec`r#b_nJ z;+fW`zCf|1H72|zeZhQ3NHFXYiTAenEytO8xc8dwLh!G!^K;PrY3mAlbEYYLf-vq( z)r&(f7Ot|egubQH_lru=OZ>wS&MVBG>?YXn8Pd`kgoBXRAjlAn9k7$3z0zJb=l~Tq zY|y{~ws%8*0-UN1zE4H}1*C7&DT5^&Bu4c3Yz5^|2R)Ei-tbzZV0?tH6i$ zL?MsIu`Qs#OWd@SUwK8GGtpU$GBEcBjnU@t?BIX;^{Lj7K|E91Bg^5hM|cKftrK%B zx`To4jq%EZJs9%d~I(l@l4 zJHX)re)BPpIg2%n3lBg`T2q8b;ZAgFFUi2a2lPJV;Zp)ggkDyW2i>nh?}fVhqzZL} z8y#q`r8_5J2dwy=zYPA3IHULH^%%;7)?g~`%otPX0N%6*jYTL|gn1#Y$6y{-U|w%Q z3&5D*lT_Umb$0-tDF1DnfgkF@vlrc?yKu+`13Kw=v>@L)G2}rUig2(-*ABAeL9(t1 z)?=N(TClZ)-hwR;c6VAEskBF0_-J&^DJ}LxU`++GiZ!3$r0O(wz?Y*!w#+!tJ51%R z>7G1#4;&2-k%q$0Tj9)Q4em0=b7^j<7TK zLy&BdA9BBe$@){EJK6aph^M#*c*Zzh@Wr>{`3RoXn9EeGF$~vz!ZZ9J3!a+alP{cL zcg45cNyfk{nvau>m1LUm@WedQsVdg4e5h|rFrdFIw!v6O<$-zD&;zepnxDJj8J*Hq zk@@)vo*B(0D)V(n#JrsRq-H_40^M=W0P@`ie?OcDj5ZA3Tanxi{O8?}-DiD&S9RBP(IsN1q4$J@D%WxPy0=tPwQXr(e7@OoI>UV8ewdA zLS3#=9MxM>NzV|0p6DdtS^-*Sr-RNb4b&H+FD(#Q_ar<@r*UDli%%z7fKPPb8IMGAB4##Xgt-;dVKn9vhK}T!QQQy+c zQ`8IkqaZJo4z}v6`~MqWh7vCS#>;Jl!~eibx=)jMDd1wpfq0o;i39iuA4Bj7d_`SF zJ`K=^{gbD(CQWyrSJLDpWKf_9>PGFC>b76VOQZ8fI!Vh4y5}NY1JM18PsKCIZe{z= zK^V9f)BC69_uCOi=NAw!&|O)t$9oHuvEnmm<%T}N0XAuO`0JxAiqH8uzWzjE4SEsY zx#GKT;B5x#v;<+YS5lst$TJ1cmGwx&bHY#08!uJ(8<>PrSo`ta< zIxhJ>(VhqLiz449(q+lNiF~QZXX*fee4|L$CB2n=r)spU7(Ymta`4}1z#s_G97=XH z;f}vL*k59+MZfG${!D0NRW&*l>iQ#ix!}u^9rDk6=7ckV6=#hMx)0icmey2Bm!te3 z-$H)$J>^Ao#~Ly8vjhXB#keM=-+hF?ssjHUgz4Tw(u1gs?&DMR z%wO<@spxAkhLG$_K}Qk##TE1|%{Aah@FnrIGkuFYG%$A=s>8i~=x5=pgs@n(=n(qN z!{Ei9%I~3@6ej)K_muwU?-}qzzK|)c@cZBDw4fW&g6?5y>i<9Mv~^K;;Vf_=Z;btB z|D$d2Kl)EP?P}l~ML1^%>(QL7y=cq}tpQN7+ z+&m%olg(pJ1fJoGUNF-99ERs$b9hYU7>O}Z82|n;Mv`3~`~?JSkJ%{-xs<0;*b{nL1=T5jm`bGh07b-DgOmFsLSmvj{>TZXca{!})NH<)jr|F7u3 z;U8g5_B-N{1;RAei%|zb7BB~;U%^ik_|cd~W7`%XKb&7l{XWSW#{CL?mF^+UGSJ+g zXv0Er4nAl>bbzncKj~-YL#2L2b0nf!Z_EiOt$@2mrIl(_w^M$?9WsdV80VIt%!={& zpZR0Ys-R=tGLJ>Dy%*CsbO~?+Ai@Nl#-X!-%JtS$kYk#&Y-3OY^f5XK){Zv;hfM4xbHMv^*!M|$#m}{zs7*v4O>`4u@2BOzt?N-AwgBJdGV+sv z9|6L!uR>QER(J$)Yp@@$`;sL68t|Up6P-xT6rj@)H>|}6L$(|e^tlL&9r6pskXN$x z9|4}+%}rQW-G^`z@XZDO#}TG=pd;A(wlA-?L8HPRIOu-#J^HkKvKLhJC$@J*7;C7F z?8Z00QTfDwpJ5meHI6i2B;TU#+2rSug)?b@3;Gh8I}%^k5FPTeg!yDro{b?`hjm>c zO)&6Vgfl27X}2R^4eCejf%wX@wS}~%z*U-i7#9kpKIk`86~0ZfQ1lPkWGADwqZQD} zKz9}TuIP&MPpG_(urXPpYzf}cI>eKY75@noKVF7%}PfCJ%2smJFK{TSg zD!)M=bJY^{t@H0P-(YF^`iC~X|%6TMhQ&xZzA&oB=}h zl{ip;q0!AOs2Jy=lb)pbG}LV$^4B!S{ua!G(AFG@9y;`SVvN<5v=IFi&hw|WtKh<5 zzT)-yX!1o_Z#XEd!=YV+CN$R9H1{cc@yqco^z>qihi)m@ZOK;a06ozS_71XNIb*D^ z1AU3?9IgCejBrr!uie95f;u63M#6CtJ7^ zThP0LK{tQUJzJy0{K1;J-N9V=w$M-hV2*_i(atSEBeDZ+1bvt)Snr0sBzf>Lz&aW3 z&(YG|oc1^y2m0+*@BwF>XX+$Tr zhn!uoQYGd|R7n@;4sOAQ<#UJnLGbpxAr9wS_6H7LJjw6`G$h_srlT;8H~nx1r!ek7 z{v_B_LSed_a~i($CH%oN+;M4T<$wD1=Vq7_5q%&xN0Fa|Wee$rHT^uCdy}WxMt%Mk z=uxD*_-Zy99)lhvn?ytM=eSQclVGeR!e?-@56(o$ouIYA93jykeWe@LoZw>9{f_jP2W;F*ww5v9B?*j=pfy9==x|2=XfaWy+})K?lRJT@L8)X z%e#-ZJw!Jj`AU3}^ch;*osBk9=5P=5oIyI|UyIIFrFqHv>5bsif^iS_4jLnmLN=wq zpZPq&IwEv)pTt6v50TI1mpu29!2>u@dZg7dEzTpw^Ks}_9R3>1-Og#b!z0K?qBbu| zqdPZgik9Xi{yu87^-G4WR2K&=e74h&?($3WAtygoOGe`c^ej5FUX`dPA9^%gYwkd8 zXi%Y)CTuii>;dEj{y|S*`=qr!RhpLiE}{`+7{8;`3MYc?mca$a6kNOZP@DpR{gZR%;e|oOK*R03D zS33jpTNXM-N4)!Yo>1R}d2p>n;QQVoTjv9Ml79zmOW?sD%5oDX+57Ar(I1z=wEGeRW1>t&aG=4Zn<;Wq_$FWPm{i zz(?2${viV#GJtt}trfsS?V!>g(%#p7xqe3%4}VA6`%?q-ghdqfRq4M&H{eWlmd5`I z`*=mUurZ&cvUnctE678=sl3aEFSd7tJIU#cu}6=wzDqn(C2OfIGPLK7_+32BvVyLH z{0`PxP+ktm%S=>B+u8e4;X` zpQQ4VKrdQ5rF(8$nCSw%#57hn0c`~TgwTpJ14vip(cSeHxfNr6ZB3TmEgSx4u%D7% zR|oPSrSTr}OXEFMaLC$Fp-wCpytB@FLq6Y?b>Qgpt$}AOWQpoQV>tBtJxMf{pe>V) z$O(Bmm}yDpwb404RDa@2WjZnHR2ioF3F|LZzu-aoncyYqJ8r07FVv6vPMV8?wy4Vx zVSY>g5G{PLM;mL#w4UsXFhiX^NRCvXV>ap=jrFKp*yB*=I!J@H0AbC?61Y$wLAtI3 z=uGu!0Di1NoCdlL&3TA7Hh4eO{2t$)bi;e0pGO}?b|Z$nu8D3I!8ltTbR&C;IC`X3 zC>L`HC*VwZwMD&&&ZHMYcY5QDFwv9rc9Ku28`gag=TS*hv^%WZ?3qb22bxm7B}iZS zOth;E6YXfON3!97JuB-Wdz~R4P7Lb@*td=GMR)ybws6jcI2Y%ySR;(R!({JqM7_m6 zOW;$3vnoKh>sJ>WdIP6c7LXwyoRxt)n@_>MNB8K`J=tQUw^iIQ+5pxfjh_eBZ>e60 z-`h12z7~mwEf%so1H7x6sNL8l3w{Zh zSHow(24g+VQ7O*L9Onx>sjs8Brj_a3G+Rjjq&PQooHO#T^i?99R8wH*^~umS!x?mq zEZ9jZ(+O!r9TV{lHvDBKI_9E2;};^}Le* zWlEfxUUX4@8gVs*vwOH)ZTu3SV8~X~G!gEl!2JiFtuQWB8f<)-A9OLp*5KH9_C3c`xWx8CrVhVSXp}f!+a`pg1pc9NAfEosi<1 zn&XH+*rRT+ualy;0X=@^!}zh#sX_Pl=p#yVGxVxo6AeT+>Idt97v7=pOX-UHvrz{z z;=DlHajvjwbV<=yJNd#`t$4GsS^<6w@Ou)l2jOq{{f6J-7e?td?7@mgek!!}Y)zqu z1KNHreDy;U_2UoVZUwY=Cj4pr#z!ym*>QjB5ch-4{K90pUH-7Yat7d+__cmli!V>R zGN$SpoXMGQ3}f@7=p&Wio#fepOU}l0M)-G%=^@WG^$007Wyvi~zl8JxpBkF{9~H1(g7*ymrc9U=bM%wF0bz=;eB4nQPX6_ z!2y#Mx#K8(PsRHIJ(1oMccHw+J>nF%xbNfvb|DQ1Y*%=gEJDIeeBFJc0r$?oSvTMG ze%%TqrMa@MgDDouTbQEM6ece@Hro>PNtwfRpb&=vydMDA}1stSU@FLDc0qo4Skte7nDm*yA;(C<(_sp+1AT>i{!iYI zKc$(Go;wcr(hrEGc;J5FAMvG#FCEYi@t(N1htd))<>`=j#M5(S-j%qMzF3j&6Vl}( zUGBf6tHfa((lO{6zUaTyu-$d0KG$c0)(OvI+;y9byVbF0PPm8uc1QUAqW@ctGxMi( zQR`j$tMy?U)xx;o0C{kR-1y`B>u%U`YI>%`+pXUH;$&gM+|#OTe}*95l>@b zf4s9t{&YqS=@p#eG2XXFU%ZK@2yvQuBjJfgmeZD|2^7gL2CmUx>POvb+zi2 zsW^i#P|I4tM^v%gnBBNy9gW6_U636q+Bio%Jxh>=o*j^eo`rYv(vlpc<9&aW>m^Sy?b_4EbXD;lZLdP! zsUIi0od>-wzxkOGv4=4cbI{Qk_r)y}w33i>#uS{}eiePgu5U_HsW)( z<_+khXoqiCj740~PV|$4UZqG1;?~e|8}O;do~-Tiafl_GW5NgYw|4aY7+bk zt(P0~eyURz@a+wn&^n{%)y^h{zUC8Xp-|BGjZIN|EAFDQ-rLS}^|ynje!o@V-_Iu3 zv;g1cE%H{T^c^k;Ow4k>MZK)RNNj|72=~z<8CH9;Z5}we{3TZ6&F^}bbjW-)f ze{jSe4AT9IP*x}4k_z1~p|Ph))VRK>Z{N*ErqL934cQDm@R`v%8)ql>^)>bETWaD= zQ|Qz9g|Wf3&BhkBUKmfQHXHo4@Y_@=mf%KG=jm$u@idYM~D-t}(o5(*g0_&g<6F z-wD5mWlE2@y4j7&U;WYC`cZ?xZ=X&H-Un=jQtFr(bBlcjfX{emD0VL%hoG z)oXXamaah$t^9%>rgbHq@MXwHQxf<~JT-v7VSP)5@rH*Cc!r`1xMF}YJfw77{g8&x z2`l6$EF|~&1>B8h=y=Rzxd3w2sUjW|wt_8+Fw;x33}Rk~Ge|pwqLBLYc%(IqW$jeial$JdqVpSz_=WMvAU5w z6f|#Vx?~>jddgjmQ=q@}lD9K;!PsyS@pkfOM(Qi5ylFuRdb-<<$?qG*ibUg1;9{rP z7z7<>z+{Fwo8mnD0kFrgr7P|P&B9v85^XQ^DLsNW8it}zrnRdiZI+&N#_`DC-vNA< zf52Vohf0Xwsfu1EvcYjhNeS6!Fc+#d73m^?EA?5@Z%a(KuP!(2D!yrw!FJ+Vy!d7~ z-bWX^KD!0H2I4O8#R{pZGt&A3=NQye96V7=_y;4-Vi4}P9gF?Kie%#gzK=xa4iz&-%AzI`6ebJO&`d8FgQ zP8*H6OF^>H3%|}R%Mh%=d_5Yrj$rbI(&%>d9dFzb;tIeo_{25fIr4SVrsHDk0xl_{r3|1RC3-?_j{CqCe8;ilAH3{32Mh z(IAgZ5x%4Tj4M8s*vdDeKV6P~b_&Yw1w7~~;vu@v$jc6O#T-oc2kQC<>go}Sx~cj> zMvj)aD5@J}ieW|`@}VRg_+M1Cf!-HxBAn@NQ?7VjLh>R;KS*aadI4zMmT5sJ74nVN z=-z>L?~pIg#+(H_l3;y>D^7qmUqBnwQGTrCXWB?X8!Ee@VuUFSxK`4Sa3*>X-C*kw zY^8R{6Khw6bmj^6VO@0!M%}w$4I4Pn{me9PBHe<8ZZJ@vF9}`zl7(VT9sRx}H1j2k zUTTnpX1|m~C%lw|s$U9q0p3=aYtb2=KaX|SaW0+(>=s<{wuIr4NP8ap=Or3OIwa!!+eAY!OEmOGJ|1mc4H1ib z>cwnX(NN^s-vMhZ*grE`#NPf$hd#8<+4w#Eh0zMSzZd2P?`v;1(i}s)&dMnDDu4S4 zV~j{X68w!YO@&@thB0+F=2)HznW+@xA?Xjj6!W3OFw<`EkM!TSNRtBk(zyEuc~ct{ z$*s^ndt=<4Vrmb1zL#${UNrZKEjSAaw{9SgLvJJ*y48dzSnHbK8OUGOY_OEmQVE!QNr zb~aYNKZAIxKSLhZemiCgNBQ*LMq!KZJEA>YGF9@@DH?q)WRmzPhW@y>d5WHRDT2=x z7{cUl_#>z@5eg#e1PmQXad|uh7~?2xN?G3_CHWWQzOH zX`mY`UY0P$T_H?9M_i#7%8P)!ixiH=Vfdy9b8X`Bag3>C)9kSTGJ`V>MDn}FSACC} zF7>N;Pqse7ON{UO%9k1gAw#_|PWrT5YUqhP$WBA!G3`<1z@sg6AK-H^u7zeB%}uF{ zU5X1NUjtI1*Yt&r$H!f5XpHiHDkFr-h#1#X-v?!g(Z4k}m(%pWD5o<2+9>Pa%D72o z^g$U-QHB%M(>$I7w{w_#-E%-WzHFo6a>u2l6Hp)Pc)g>#I>$VR`VBh2yv{%DD<1yP zqwrVaSE*-_-b1=kZ{0dS(z9HXr*&=JdbVH9V}sgT89Kv^?@xNu?|`mfMb(1=w{e6s z;;wgzs+QRCHuSGIk^2Xdo<{l_>0{Q9wvjEykL-BjmKA<|B;S`Cs|?PXM1%NSqLHC* zTK^1Z$-o}%ka4<%?58bZpPU5#;11Bj8L;D4$M{cn%o_L=?34H4EZH?v(G+&@O9KlDVA-PhuP58g??t(G92gWt(cd$lXTsg-Gm(PDb#X9#n z-O)pRS`fz5K=f%>kj5jJ?C<0Uh4XySpVJs_tJsLX)e}0{8bQ{n-=wjR#sgZn3&VF~ z8@n2DzJIYBXy60eD9x#1AGO%f($&JNWr798H#NtDPSpVxN8DX4AdeR4L#qKq9`0%j zk^G)fj`@4^9#>=K9Cudwc;htGv#)ioX>}vq;|yHq;JMPqJP$e)$>##p;~#crd4-)h z>|1#O%`=EktB^0&zVzAFQs~YV-%$ER_>TCOPJG38lor0U#q=HVl%6a3Iu3m8i}~AD z;DI%`;>vy>zP(r1BG0hC^Du7bnkHi$N&;P`nelCbFnzZI&nne=BOM#vz|zX!uML;b;J(6~Rs6c>bFqWLBG z&L!e30g`W$Z7YB9A{stip}5OUwcOwXTeFqF8gXjGsS&3(Z0dGX>wq-QNb7`j4tVE; zcg{%HIbfwhgtJl3!Z)fr?#+Nc4Zo-%h3&Bqpfh~`C9rkF-|lP!ohF~ogtKeeJ+Hlc z@&&xRa0%-!m)*LZrDvQEUg!$^@48LL+-)$emlV`PyAbA^TC!i@Om_4mT=9De>Atu} z5xV(2(#=o8&z<_b!x_)MXWTyeOB=HHa|J7m#OvDw&1zS2lGzrOMZ zMoY-;PUzc#y8w&fc^mD2pg}zuHspFPq7>Rc04E>kR-lnytG0|E2shEQ>=wB-F>emw6Wv|Jm>;{|@ z+!C~F7|x<~OweZYv)X)D+@DmR?iH%I-;y^@)@$Y17YDf@eOl~41v)*(O5FMB4;{*a zVND9>7vzbvUf1iY+2){u9V!?8cmsL>myL$S*xN?yZuMrQ(3xvjX>Ee+K{S>(MOfQ0 z0X_j0cY1gT_+q^SX9D=5pQHUCp{OhQx=uT0c+I-3DBJq))8zrCY%I7XGz$DIesp5zXbTqXtMM+OQBO@4lfH%(ejx%4*~uG z5{%z0eT7kuJjk|Uhx!pOY5XRCEYEbQFfWt&FV?;`+doFbMt#K`#=3n$UiwjC+>@hU z=$(FWfQ_}4Y4XVzLE}!o7`wkA^d9TeuzNU|#4XdbjOL-%$4wE?{raXeQxSBn2(;61 zg_Wrh^a81(w`s31rWcn;aW=*S$b}W?XO&HT*{H$}C@&Co4Mbf7QP)7!H4t?TL|ti3 z+%_7zJp3k{2cdnbp_d1vzDwZ)rRk0JC#-ug)EDiB>f9Q6fful&C1FoD*0B`-oR3!4 z+u9#@5u59Zw(^g9*Z*(o-3aw|L%pN_rQXS?H*8iv)jLqAH|)QtFV))<^=~57yQoBl zvl#B7-Vs=t<2lZ|Ev6pChnMCxV zEKszf%%a-IvYVppGKu6v*)7r1G7G1VWv}Sj@J=geHX2SD3gki6_D#_cRM<-Yi z6plOtEXpJ}hk(lJMR|!T^0JUr^ttZn8KSp9K0%E= zJ`#P1P9l^i1|HB$_fVb1^xhNi+co@1brxAIFXL8jWlVJZ1HGqlBdF>2k3pSxpblg7 zVyk6k=vS{+u}b);Lb)`r6agRfi&wL4>Y2!Az!!CqXjZLl6~FRv@4I*Hq0eP)K0?)rL}u7u)|e%}JAt^{%W*2#28f1Z3pZX#}Rx|QxG;!Jp^ z{PyE}6XN#c+c(5Jw`F?hg9V53?rS;ai~Qinl1JrKmO(o5O~ljt#mMuJ=7IJm%EC8! zi;*|^Qf4B58ZWNXd$$MLOq7wUy`P`C4EWIZc%Pg5KnI(RZgGN@ZZXRDMSZE?BR)~z zNc0!8>V=%aR|9>w1Uz6`P2nNc1Nd%%4xuYvTBVVXz{-~wU)$+$Ceq8DZ}6*H`SKF| zg2uZjK6J%-*n_WnQCjffQ0RS4%4@qV;K5epl}9{C^zWff4DP9=d)(-JF7$&3xx*m6 zM4Rw(3YCR0_NJVKZ$cr92B-kpnnLI@f*&N!xh?UH6yHnfEE)X5&+4EIzp&w8yx97h z@_@}?j|gY-v_xJ)eKg1?7vmgohR=pjC#p}%Zfl`F7mwQr^>{_kNVD?8CF9BbVr zgp2Tt^yhaW9r}T*mFYCln0#$KzV=$T53m>=1}zgJRuFrKa~AIdkJx= zNK;7@YcoyY194DI42%p)krPe2FJQeTWa>$Eyl{ zY)`G=)Al0zjb5=GX|{t$dkBAgyB*(dCmsRst@sAKs^A;=CGd{;M7$wB(cFN$4I&?U zK8LZ69Pg%Kzpsc@)xqp5oOf5XMU+nL(@pE@GaF|-Bs!38A%4FEEQK$fJ$UZ6Rjao5 zcjM+TM_6rVj<-X+Im{7OS24#|LA*K45mwup<82Xd4s(RnHs*L6#GAt$VYRh6-Wu`d zFh^J|GsnviZw_;W)mG+sE5w__9AUN894|$@Im{7OTbko75pNE2gw+=2cnidv!yI9? z#2ha{ygAGfR*TK?V#J%n9AUM{94|t=Im{7Ob8|dLygAGfR+AhNof+cIVUDl}JRrX) zv~zT!8b=Bfzg*oW>U~6&XISHW5~g)rEzDI13HzhP=o?kk{`^nBUdC{imP3G+z9k(= z4Zia{H!X9R(2Zn+){~h}F6O_X-N@by*8o92W+L7ZWmUD00&f0VSGQSu=!hg2g`$7R z2jzAfU-VUDoc z!W`cQ@#Zi`SS>Nf`y$>P<_N3B=J?i#H-|aGYLPj<72?fdj<6c{4-52ciFk9EBdi8r zgm@pso5S#ncN6v|vCwW6yzPuMm0?Zm>-HMXnM(Y#%uxE)keh#|3oaDlKn4-l9EOeo z-DnW`_*;|w#QYcXBSJj-?!vwGaV7@FB=U)dAM|SuC5rwaq{5#F|5F_D6#g)-3GXmB z65^pRFiL}dUHF3@BBaIGC8V>#FMWeEI)wBx{L;7RKZWn0Lov#O`Csi>yMOuMgpk7% zw@wRP+fr)MHeOh<+%R6`dR_TWy<^G?5?>@Sh|SB&478PjG&&70vCHfvVYEF_;EF)=DSX0}_)=J?laV%VGs&89@m51UL` z&5oHgDQxy^7BOS)gy|8J-6qTmkBJPMfv%Uai7`{Agw1lB6BXq)J95HwbiWMg=7i0f zF=2XT=98jk&5DVh6E@jx_MBN^6Cx?itgx9eVYBDB&4`-g77-aeJ&ZC$ag}Avo-<)i z7}72fAPcYruoSQyuoAEuuokc$uo18sum!LUumi9Q zkOSBY_yuqPa1d}9a1?M1a02if;56V2ARllJZ~<@$a0PG;a074)a0hS?@Br`#@C5J& z;2GcrKnExU7yw0pH-NVQBj6q2J>Uc26W}x8E8sieFF-i}iYOBUEC5n~3}6GO0Isb zECA3Q&=U{{=mY2n2m%ZQ3<3-R3p0<5$_ouR$n0mJ~gx=@s_0AlSy6U@-y*ub__h1|k`+0YSu0-S(xYEgC6 z35K#ZH9>Q1sCo~0fQp^21(~W1xr2c#t1jdeMkTc?zztz{#MQ^U21w(9v<>l1BYf+L zJV1~(jge;)dZV4P(0hiXmsSR+0G5$(B;MxH=cLeU8PzMlmWmnY6 z4|NMb9lN2fJy7SKsCyu2&>OVq3!3x;ZGu3f0iabdXf_zM8v+`F5ub*GrXxXH7~H>( z2Cc_}=Ho&83E;s*@M1D}G6lSu3LeS9t7+g_BzPAE9)gL(W`n0O;O#u{I2OEK06C3= z%qjtDgcA{$jCZL>vj}N3@J$xJU4lFy07sW2&y~n~4a!)DvNoX1O(zB7g8^dnK@!vla#k+I zGvuiVa`Y9lqjUt%AsbT2fr~r(4)B>b!~53A8~i%c9eDM}_n^7U1f-vi=lOUK`g~oE z{I(+fVSEq#T^^#mH^}cVysyStEE=sJ8e%{QXQ^{IbAdxp`93^fcJu=oiAc8n?>x*CCnMhM69T;n8o%Jvoi`Y%ik+zsn5mC#ZkhPeIzVjEn!8!N!ZuV z5?0>Eg7LW)OnTgcx%{wTe%&mYI^B{L-M3`1?o#F#EoA{{SgB4{%tda+%JZ#QKphz? znlEEtAIX@qgEdocv}RI?4ReXKVP_uOFdk^jijLW`*d|rjnJraVK6b^XuCik;)$N&b zl|7yvSkV>-_O(e>R(`xH<9(_z>C=3syAM zhsCN}GQUl&SiqUqDBqX4*tKQlo!YU08SPoo_73dpi;hg`+L@^*bz#zdU75>QKX#^X z0OMP_v7+zYS?uVZ?97>7EZ?g)>feXC$oes*sz1yB5yXn(2C}c>L9Bf7V8&gCGU<_F z%w@m`=4Twq)JsEHQO7Z;|5)a?aXbqcJ^}Qf#9SUvX62eGEFfwsE9xd^Uu#ci%5RZE zU!4Ovm03Wx(Dwz{AzTA}5Bj3~=8&%dw8N2zLz`8v0h~klGva(PUtGvp{w>aOd_+v9 z5wQSQ$jbpSlMRuu9BT^}@YsT>Pg^qYQ&J{-XvKIH98WjNhRF(SnfDw!rf%)P0%}!b zIgOo|EW8HGzvIjTB5N}l+&Oam-C2H|2jhQwGVgs&m~2}Mrhe9%smHZvGVdB$Xb{VZ9)dhau>egd+Hp9O4T@qp-R7}?%}S=0r!nu{OPOrUdIrB{#@psH+2r4t zxAP^Y9)6DnG|;k~1S6BherNgC);yrI3zt>%<~i9Nc>bxroUb3ly?vs%Or5~h%T{r9 z#9l75$>-h+A8~%x#PfBQq8zQONOr!1C}8POQGQUgNLHRH%Gt9=6fpdXNG*LM@;)yU z%a%0}^LYcrJa(2?wrRE4yX3T39bpg$)UlJ~ShttRI!%(~A6_B}Sa4b*TT>#*aj0vN z|8{@{_erque)+3~%-(3BKIUPmemT}swsDQ6_v`zX{6;lt{@}sV9My8EY}`X>Kv^xT z{2mjmWMgxza=Lx63izw5OuZsg=I!uQCY#^Pn%`Gh^QI52WogZ9yh~GT)YJ7g0T%wY zIcGQ9%6{Qh@~?+g321!1itMJZT~6L+yL{^^_WbBVd+*C8ds)yV2X+4f2lcHXRb?-3 zRrO99P>o-{RW1MU5XYS00!P{INlpRh-Z|y(jjt}7=1?QYdPj|b6`gCUt3ImfojJ!@ zW>?*XZ#nM52ac~Lt1qkV?eS}E_4p}u0-n3n&DnFiuIyHNz5HRlT?6{a++;^?xaDY8 zy63x0sLvbwH1K{Z@{o0U;-MaNxZ!g2qdg&`_UJRKqMsZ+0^`ME=z-{)J3zsh^op~U zhegaaU(DR=TCly3EZH-Wj7bmMuq%JtvDW3)nBi7U)?!Ir)^eN&`ySeq{qlPowt9;{ z8`Um|i4TruZMsiqevOnYVATrN@7_KZ=YE-aUU|jd)R6Ieqg(J~<0w9`(IP%5>@;6* z_n8M}v=trevrrU!`m$)-_&VYT`{#+rxA;RmVOpT%ZP_VF-7%dl0=r+dko6mFIlIme z%ZA}O(kR^|tC_u<$~uLLt&JbvSU-B8wORY(wQct@kzLH6E$o#~qaD;6E>_KG+sg6u zp(9S=?gMLhuNOHx2HbYJ(D_8|C3(NsT`JYP{xQs>e%6;1kIy}AJWp&q#U|c`FwI(;<;aD9(5_4cc%W>___@+?gnAp#TffnBhJEXMeNNyF0UZA_j7fa@t7BjS=OG7>K(+qzlF1gUly@0myWPOjf&Yp$EN(*-Gw~4>^Z+O zc$nx!lR{B-r%Z8>)lf zT5-R1$9ewKNRj=m@#2|_H%ZDDbg|^W4zXIY;fMA62_xL2FffoU?XF@~KR;u< z*KE$0g@~&1qI!fSpU# z4F=U<7ciF8JTZ-}{qr%Kwn)RPoc~+o%7ZQ3pVXDLp6+A+VC#10UEx-q-hah)UU0N% zblrJ%7ah?9H`?|&SPG}!DVnpbf(Fa88 z$DWp-lg3qf^Y<^cl^Ji_s*f3_))a5Z&$lhN>mX0A?_yfoPd&fMrq(*y(3Q~1+F>jx zhpr`oP8EIuZSoPui({lvo)99|)?)p*j9qaXC zZJt`(_I8B0r0x*$-KAZ`t~I>H?yIW{7SrrL??pCav?5Wn+oGnqd7@qg2StBrwu$b4 zTPd=AktSN*bD>D&87+!fI9U|oH%e4fK0s9ZXLr#9Z3mI4fsaUZqLJvAO?5@?x1B^M z``C&ctR$jR=?^}t*GIk}uY~WLZQwJ;KIdTz9`TXaZgZc0S9$4|bNu+-)BN1SV|=ps z5a%QI@mjZca=*we+^N|HKG1G8KVrX(!-t=bpOMDrT}kBQ{gvG9u7cl;o6A$X&Ej)i zXYd=2a(=Pi6u!%EBL6LB93P$+!td1^!B=Dt;kVkd?nlGrj;Dh3U;+SHMB32Qi=%}cr_!j#l_IT{-*qGQMu`OfmV&BfcJb(Lq)%-E@ z+s&^w-#G8mye;z<%o{qd**wd6&*%O+ck$e?xn1Tu&Ml6~i&+&jEyh2ldQ9=0-{&lw z6E>&)9Gf}MXXnmNm_2lM!`Xk%x-@I`tngXwW?9a9ICJ~V*)s!X+RuC%y(fBJboXe7 z=%-P;qGm^Rjgm#(o3U|5_>7h_zD1sk%#0iwSts)4^nKH3PwzOLO}{iPYueyx&eNXB zx5}r;8_P`*hazGk+DCk!`ukMX)NWHHQ!j+4ga?L8!!J)sof0@jI^|+mQdsve9(HDO z+~kgvzf3wZY38J6lZq$qm^fi#or#YoESu1Og5`v~P(^6#P~-R=K>`Vadtbl=d?Lv4nh95QuC%^?>C&mQbP_|~9>gS-bl3{DL834S^- zZJ_T!?SPB{Z3pOrGK1O$>H25%Z`1!pzqEd>`u)*2sc+N15BkLQY1rpR@0i|odY=!R z7FaFtc(3ujEPCzjIjHC79vgdf>rvD_vwO?#54$P4xpq4j5FTJ1u-|`>|3|;oejWXu zbyaq)-}QW#DP63(aMTTy3W}Yw*VzR|o z$zn+Z$#HRiu}(BsWFyjWUw(y+V;{;>%j=XM{JYoR1%E~TCHZUZkLEwlejo9@z$e*u2#eJ&wY1hZjAMbt$|L~)1X_;r)srQ557nR19 zRx928uIsydrU+BHaiy_|F~4L)iScdXTbH*7ihC75eKYHg?9JxaZC~FgnpE`d)zVj9 zug(~T8;mcLUeReLn5E`1yusZJynDI^}8k zA8Y?;^~be>u!6szta;Mv$+gE}kINsed*u7*=EI1Gl82ifba-(8e$;)N`#bLi+UUH|s)w?^Oka&zU)RyS|mh`eEaWB2u5*I!;sxaM~4%+;}1e_UCA zrQ?++m*-!0zI^P`h)bU@uD;mr;^PbRFSuMdd4BZyALlln^E;Ics5kJ?{%|J7gS|I+A}2m2EDwc4lKo4wb6Z|NS*p5Q%f&%vCDIgU9O zcF*1IwYy+f)-JzYWjlB59JSMSXWouEJG^#0+rDgj;PxNe4sDydt=_i#TQjzH+xm4& z?v|-r+_pT{WNZ3pM4I0=&)M93^XpApH;vuoyy@P?#T)x>wAgrdL;QwL8@{YRx_;Jr zpY^77zpR_KuJO7zYjf64UF*H}&6=Dw@-RpY|640%f2klUz)LW*izS}#o0%*RoVTs zYi1WN$z2k+r0@L%$k{RGEQY= zXH3dymtm8kO+T8Ro*tUsHr+N|zv#rG>_t--`7d%_RGM}rZEM=Xv|(vJY1V0m)V$R7 zsq<2Yq_#-4O?{JcDP?;~Ldt{`{}k7hzmuOQ|DL=lS(zM~+%365vPJUiq-#n0la?mM zB#lbynp8hYnq*A8mv|y^b7D$jWa6;IE{P2j?Gpb?&?a0-IGnICAuVBc!nlOK32hVV zC)g+aP`^<>QeRLXR&P}=RjbuA)f3c%)ZNsr)eY4()iU*Wl}V*nJy2ayomL%E?NV(} zEmNhbl&Tn2q-wHijB2Q=zpAIItE#=Km8z-AQ&nG8SLLFruBxiCQ`xAjRF*1){I-jlhD=C+$_h{=xW6!UUU=A71Z9?xDl+jVyStO>K&tW7gJ&3qUg z9W9IA64fs1#*7IwK13!*R*&31z18$H)A~(&ARjL;ikK1caq9f3Kf>d~%cm%({2jJ1 z?Ca#1$?qmbOwvspIq~L%9utm-HVNH0zRLKxaV29z#$FoJYRtyb($Uc&k4AMJwSA;) zD=sWK#D@P$FHgJOg44)h+V8t^EnX^^V_{eGVPV*6h0Q>%}> z_lZDh;Lu*0o+Uln^-y=e+Rd@s=zy*MZ~R*L&FOl)%a6`}ofA5p>nQ2ivqN(G^X|QOZ>UxKZ z_V4ZL*acRZX}iJZqIIdPmaK=>4CxxnvlbJmS3glHK*#SG>4zFSopL72#_+;^^#m7+}lFAOeFDSJr^?5hOlx93!^5U)S+xEp_ZV~*}W!jB9)oO7uB;IM=H zaxHSl9XPhX+Wr~8T-)chFJchA>q=&1X4TAN z8RIf6Gjh{I(xvG~7EM~@xafS^ytF21+SJvl15>S1&!)tubWZt}d@^}Ka@S;0@};E3 zNyC#|lZq36O-xK2oLE2cW5UIR^$F1lfpFparhcG4s9vIuQV&$OR99DjQ)yL~RR>k; zRjH~us!6ILsvfF#XwU8{XS8G+v|eHpgFU@K11{HQMuRLr;&4`bM__uD{f^tuwXOd1u#}^Q+%+^sc6I zcxczOirVIZtg%&`e2q zuUZ%~3yXDqwR@l2J)8B%<0pQPcRzG|pt|?&PU!7>H+$bWbFIVGqnBG>I&z`i`O{~6 z<=;IM`uqD+$-mV(dF=SmV;_&MIpTM?_~7c?J_r8(<=1_)_O{yd=kC+H(sqvA(PX=5 z+p{gFHJZ(7o91i`+YqvT$hzRQL)MI19kwcF<)Rhamgg^fxzuK9`|OA%n#F%))ybNW zxi8~;dT{!lMY2VaX^&ESrJhV_opLC-ZE{}Hz@);&xWqb%`3ch!YA4)QXQ=zBtE!)= z_N$Ur6I9(*ja4;O*5GitvJ5kzWy%2MllU?5m*eWjja`_wV1r_PZ1Vh3^PK0Nn-eno z>CC>-+h@F;?mEpsVqkc`u(p%zCp;RrWK6q|n= zZTHvTUjC|^_uTIf+sC^5r|)dJx%gVr<)jOX&u%+&?$o;zUdJMjoH^)vAZwrXo>jYA z?Rd6zt!B!m?i*UIYrUq|s_83sEPJ0FyyRR~-%LaL%0(e*zNrmTJd*>GW+wicP&FY@ zZKK|;8jF!iree%i@ILZ^KavBh!@QU;e2JWx8~l;l!_%S)R{R=bW!VC&({-`(*9NQB z)tNiioIAi1$qt7|HiF-hC9B5jvBs<|oG5Fs`m8zY2;U@otX_L!MOungZC9*Rx5H|8 zP1XQw)mTJ9sXp*@3PovdC@GjtMfqOLAD&Oqz@-lG=+7ntI}g^G4Q11TQ&ZN1jbU?u zl?yQH!zKV1ci=GyE74VfTL4z9X9Cl@z%mG{)K!3a7pzi70^?>_^&X3rXevqJ=;5fhC)V3XU`-tMOsuqz7goc4P}gjhhg$lhh8lJo^$un# zmdmuLuP^Glf}KY#d!UBf*?rV|C`)2T*h^5TJ*cyWT?Wm1v-xZ{dkjjAWNGX;dkqS8 z26Z;D>!4YGwvg>(PeG|MER&sLC7@MH&}k{wzN>;hGualba=U|KYOI#)K&v*O(@Ly} zR|kD!u(JLDG#iE$`lDFomVverScSg=diTLUIHTH)8d?E~p)c zRrqJ1@>s0qpT>%~2vnZPma(&-ZZ}YNE4vGN4`vDM5YvOsZ9(Hz>>{Y!3zXf-9)jM( z;qmq>dj-yP1XtGKz|UIXl!EQSR*S~qPdeP|i@}+$;L0X;6TBM0;@N)o92^?Q7PH@( z2|Q{I{w&8jza#iE8~y_Kz^ftfJ2;HBdnq^+4&Q=H;7lO6vJ30{p5Rm}`~`}@)lT5( zdiVs?2A3DWx8M(OcQkwye#1JyJ$M=gKZIN0^Fa7B9Dq+i3-ERcd<)(|o9?czeje7dDa~=Oe*+XFi`l<`T%wdFH{BxCf-=CF{Yr^B#~N z37^c*b6ZHpb=H(;@}`i764syZ_n9wW!F?fnAK6fTgb#(pIq^CC zKCc5=ddfQU4ZJg?>jxXdPw_F3sXAC*ddfMZDxbM?HFt+B>9Ah41xr#}80QmsK9@mm zuE0NG5%-3)yn&}d4(|i`k?{zAg;#|f-hsEmQr;4h`5t};2l*h#V^uzr-{CHhv?uT( zSj#&=_P)Xk;RGKAiF4tx{0X;!yk39@L^5v(DK)^CVF&LCxwYV7`~t56iM#=Sh%DX= za%hBS!!JAtl39gE@*BK5B>VxqBUbV@koiyWb2!R}LDH-982*6Qg$zG~uf#^)1ycPN zydF;Tv5@7uJdQu(B1rRDR-Y&E`jBBg>&Cb8ZjfpbpUBU0Ysl+W)|jXB#*ort)|c<$ zeId8jT+XlZYLNfCtQB9zTcJgivBCThAB=WTjnCqDc`ZnI0X8M9;~gRM-&hDg$wMIN zIO>EKa7(n4i>wh(;f>HbUa?+$C+~&!V#%lQi`)(^;U;U&7xU(je-j(P_wxa05q5kA zzsYN$Wj$nV`6}KPZS7Asod3#)qlMMrbNNGF4{hf;>&iFru4qkvvvK@)J`QcD9*^hG zxfreJ9K1~uc>}baLimkr%NERgLB!U78%1jp-B$z-21j!iz z$w-(XNE8(VDk!2N2E@og(hNb5iiVnQ%0iV_qA=IsLc?)kp;-F5F<@4Y|XTDOKD zm3CF_uCCf$RlTd4A__h5Ht4!+qZmTJ`wChsXDG{|CyG*{DYq#y(4!xL=F1+6DfICl zpu=*JvKD%_3?+&3h@uRA_9f`S9Hb!V#T?LRX{Go;-&LkCC@(1-&~un%xK0i;s>k0k zM#t_mTt=H1hkleZMuw9Zjzi9jgWnYy!vpU#oW9m(9O;kD`1MIU!|mhK^xF4@>3r{u z(*xc-PHXQ=N>l0;OH1#mN`3jlIMwob^QP*j1C6UT=<_TX&{9+cW*>DvuRaitgG+Uaz4XKDN52GT80%K{eN! zitd%A2ez)r+i$TvwY=XYu56F<#(i|BxV^ngllNpg^LvJc92 zWsS-vX04Xd%&L{{&g7HYmFXklnprP4CsSCokLfQ`$Gj+1$P^X~V*2viG3$8snS4BQ zOfRYs^E8Ev34)mEI?2VX9v5Pcj?H1ZjOsHF{jg(>3A%W^Xl$>$Wy<|%-?xOp@91~yC9`2sBqwhSW)=(j-of$lC}g~H7b7d z=U{Qj<%X^O9ogFw+gHKBK(k~Ud$L58^=wC7Gkd2~Q}wRCh8??^7qd!L>y!6fu8Z9p zej$3F^!d0lxVz#FVX?b+KtcQkGdzC2jm`=@cS|JCFz zudiJy3cW5-^z}wiVNw?xu2`{P#~o(A%H0on=kKk}vwd(qxBH<@?#9O*n+3b=HXnU@ z8*0ug=f#U}P?LpFqdM8GeO+08ZxW%VHM45ocW1hO+?6@{$u;v(|C~(6uYJs+fjVZz z_d@25VLBWi@9o&0(Y2;SxXUOdQ#`Wo!4hx;I(7=@CPvk z1q+zCq61y^aq@HA+lGe;}l}Ut_)s=NXPA3~F6hMm`$j(;w z%28GA%4t=z-R!B+v3W?-I(Mt~^;`|zwRw$tAM%{_nfcG>DHLolWEYIj4=T(v5-Soj z=_o2)n6yRS)TsD?*FPFydr8!0KD|L=5`{c5zoLru}zj#IIfyXOPR;apOIvC{kva)(L<#6vB ztt0a8%d1v;q#w=pJY9WuUEi@MUJ55T-X14ae9CHQzAsL#_R~5Y?jL(5C7|u>=0M5w z#X(^gwgq3RD-Kbr&kto>ObhF4hzfUX@`$+5Y#FJ<(%4YU=8EF9c1OjxpN{_AkscF# z*?wd1pCYmTSG!_gU(1gRy-tt+dgDucQdfBb_id}h9e4T?RqisA&fk+swta9Sx%=Uw zl#P#DQUtrnO-G+LZL)m6AoazIs?_uzQILj-X{~*a)BN5TgOn^xuX+D8-SwkZ#^|TW zj6?mk8IE7yWeg1{GAh11GnNh|GKPlB8I?bp7|x^jK?=WRRE-ZaR!vTVbmlN>DU%FO z>M(NcED z)81aU%}NJTU;|XxU`k7EOo0tP#-_8gv!&&1v6ap=!Sw%*j*faDkDr$-+gZri+)D)955ZmR8m_ws!XR zI#Q~-^A|3rTUgoJFLiQWwqn)lwI1ud*8BPgg@i>!M#aP?B&DRLWiWMQw2f)jOP9N@ zUANvZFgPqCIxaCKEh95KuV`z@uF`$w2ai;rICc8m1$Bi5mdiW>B4U#>a`KBycJDiQ zwB~GGBdhId*S*KjUcGt$b@-RG>XPMtG3f=n4pg76XI<*N{iN^H;4g7?v?e^YXz$T; z&41o{_~LE<*c@e3r*&Zo%)%XIhmN1FYh+)$^XSE!PXogoF%3(PDCYKqwT+i=Kk5DW zeN0H#F@(A2RO?@`(O6*a@~FZiO}G0-gthGhQny#tUwQcY+k}Ar%8gr(w>|vC;alLz zsBC@qgV)$6_vDR_e1>aN%Ny?Z^P2b-oW3)_vp9TD+dCff=!)xuJfF>h zxynEPrYTY(9{&U>f|LamlHx#FL79n}v8O2pU`~U7B>dJ=d|-qgNtub6v8O2(Ks^5W zz%Lf&aTzeq{s)8qaNu2C_$M~?lZH9EEX=jlU}XUB|C55bY$VJg1Yx%Jg_6o8OT9wf z%)Odt5wA9%F8>mNb%I-jZVJoKN)de{<|FY|GF4hZ=DKW-+){-(N`uOeR4=PHYF^ZC z(Y-qN@w~5wf=0R~PK%<=b}wnB^^*LS^Q>3cM%x!HJ>v9-OULqSE3dk;SJ$rH=@Gro z%3ILqif^*NV&KIf>yS&Ki^BIu3`bf-rAODs+}_w1`!Viy{GEieiCIYw$&{2cDJwU< z+Z3BRm>QP$Fl}-AuJkwQh8eLLwHdE6#297_FUBUuZbl7*1*>jP7_S+hVf}4{F$Sxw zlT(@s)A|Z{bpCIQ=L#oVWVS#@O;Jh$^mnICRG_EZGJDG<#`CmQK7rP#+K1We!%fMTXicbHs(=Cgd`bV?+Gbt)33mRacY|6NE#l zv(h$31o^nHa<;Qr3^}XHiBk5K6y~HRM2Go%ty$q{O*dUIS3`<&xO~^v&5V?|$Y5Vj zHy3*-50o`d9j(~A4aSX$G2wyh*RI4&&y%|L+gxdHxp=qczyD~vJ8&+)od3T^7 ztnkc&79a(8UjgpE4y>EeVC5qaU&553!hcW|g|&}qo9AEr|5^B@1i`glhGlbLx)uye zBo}((}GC?ESO%)yq0?{^IRUU8>bVilOvZSS0a8$_^jYLA=wM_3m4@r z%2}9ew%IhtG>0B*=j-V1wQb@h5<4Uh3m+9c&&T3r^PdxtIlm|l1zY*s1~_`yuUooqna7$HURK@~ z!R8^fP+EdXs(zvRW@SGumw9wgZ(5jMgl^Qlc&&|EFY zu$jM&_l&?nLFx16QTA(`R=c^bvvOa$*1^lk-yv*C#G*KpSkoA@OyeA#Qq?Vr{^~Az zdMUnEk#nPU67*A5k~NZ48LGKzMbbNE_DWUEDU+*~t`%z#X%H+GDH5H19>qGYa&z@s z6+-$t_*w@ojd0j#nrM=4l4z1(nzx`p`+!P`qMxdZPDr5F>Xj}IcFUZW+Anjob+B`E zwx?kO_lV#Z5tEdbk(HBIP*hS@QBhS>Q`gYY)C3iDSOc5hCuA5C8y#R65R(uV6&wv= zc0qpe39&(O;3QOn0^?$^9TgNE1-A8y)v(t#AT%sKC;$_|_5xUGo8Ci=+dxA`SxQ+- zaBxG0aopJO#~b!WdRpqL%8Cl|a&u&7Hp0R-SYaVS0e(JS9&Rdb5d_f_qc4h`ll^Tu zb~2z}KHghS2R(IuF%{Tfnx2-Kk|C1=IzkzYO`EfFgA$WUGS;LerzV}Ojo7duIZ=3H zTtZTS`Pyg{9karK%X6*wx>f#T@^o#UVLf+l$|TK_cbxNUsTt*mn9g$7B+QsMMc63fv>2F0^kP?li7( zs+ODVJ1!AD5oPIdGXoAsefgl|sHOXl?^4_=7xYOHPSmeFKlHtRQYL2GDv$Z-4)JJP zi;Yu=v1HR{gZi7e->>8mqVj>#w4l4lY;74~j)sN&*r=fCTyYA8pF-gsc3k*l$kcg= ze}d{QKQ5XT=!%U05Y?MAi!w>-{ut4;9H)+PTk-uAo250On$66G_va-gC8gzH@HnTs zy0*5yt-h_Tt*fo8$JRS8F}tw1u(Y(iw7jyqQZ&1;u)MssR%|}(MR|4aYV(<@eOq%< zW5ayiS2$QKHqzHpRg{$y6Bgj%BCa0Yp0>f)&7N+ory?WDPuQEyRgxCwB`yTOcYr~9 z@B;9U`?uW%cmG1+mR);x@7r@E-g=d}mqVb3*OVS%HgCz))AzHtsPB}c!*!1RbL=|2 zRL$xz*mpCQ&Rtv!j?Q%Z`Agt)#+>S?rHjc>IDF_=s->w-AbiPfa^lM@+^{&4MRsKG@F2=4`Nyjhf|Z8#j&k;=M&DbDA3-7vzi{Z!8K84z#g3-`&`Fa_Dh$^U0y+ z=BI@rJe?$B&iehuT$*)D#pIly^TZx_^FGW~A@x zRm)xI=8MgY7tEihr>mo-p{5MAFGz(886Nul;nkBH?6YSM?aoh+4PLj>o@O#vT>*Bc z3k&fO!s;~F$eaUpw_b~?n7Ra|?>W=?N>b0tBevjh!(VTO=NLKoCGR|S?WLT)t!HfB z!Md(jBC{8+2+!JI*Y#R-j=qIw+}7ilUr5T5s{)ftjKY>v|)jxX3T9@W_R~-Y99Ac_e05 zoWJ%?T5$nd9liBP`wRK`E+LyME?j=}UeVAoAZ^dtE6@5>_2|ytDdneczfxSdHYUI9 zWXqjCStS#3%Mo{9N;wpMN}pK9~_xyzsxb2xp$qVQ#OFb`a19pF4G z4yV*v6+Hx-o#Harv=JP`1@?2z;b=k*C)>)%Dgr({Rvqtbx-cCACMSl3f4BJ_t!c-v zzZOro`Jqd;Q;uKn8{GJ&dci=`npsnhujkh(@!-bacP>y8LVg=cU@)|C4K?4Yh>FI# z#-d;gE9e6RMdeOWaqHg3@=)KJ(CXme;GC%xVq(OK4a*`Li$a3#+%GPw-Ip4I$C6+u zAU?2BvB^tKN(!;{w+Ts3OboX7&&@y3P`|Gz_s;R3PONPy5h@FyhY{jjcRv-SHWq~C z`san#2VShn4Yaedv$9glwx|xa(E$E(!B$hV%DF#MQ4|reHaPHU`lf=!ket}Og0Px! zBeklB#oHS9q=dAoO_fWzv%#@Bcl2OEa8z01+MuK$kdzu4RyOLsRl$K81efZu!qdTl zZd_b}5y?p!f65ZCZgw_YJX2A&FE1%>W7LKKf1l+xCPwq;%~esBmk^{9oL`^b-tW9z zf2{h*p2D=KP|sz~cD9S>smV$U;RhSRC!@8#Cx!f-KWh{RWq`74T#I$0r%stp06Tex>ifq_(U@cN{?K;_3E4W98DAC z-~3<$_p%A0~0pyJaoGG+JnCD63Qkv?jZ>og}aZO!~BUU8q%FT!;^sE zEaW#JDy?orU%JLGDk-!0%$4qbJ5ZvY+vhY(IhZM{psy&{?dKrTzki7L^L$C6zU@c3 z`>PbW1Ybr_1w+2iTGRDf5!(r?vV=X#xaVNoPJDr2Kh<9f+udA`_(rePieg)>eicvm zSd}2Q19imsK)e$@ZIw)43l-u)4RiHeXzpEQ!LUPyAljG~M9pgO(_l!iLcoZA0IWb_ z@Oc|(?-TI0halcp2ogXB?*L&>@}=tZDA@CPqm&>Z2=@Ao3=e(0d%g48pQl%uo0%>$ zn2m>Su$vK1xVg@LT>rAHcmLbu&zpvBj+;<(h4Li>=L9P{s#s}PEtG(nq!vg;19>`2)wIYadS2(2-2ApD51i^$uwz1JshiieoXH{nKjU)$xsPiF+Q-%K# zZ=nohOb{ra_2E0uCcuLYK^*s+j^Bu2KWNelx}mzf)GPsRqUCIPVUnNqTsau%zU;hs zY;R#&w7>guJJQTZUt3*SURGK{Y?iPfKkx5B=-oTFZd`4@czkQ9qlqSDA;`+vYMMy$cSz{i@z-rmr^`UoWNvr4? zTXEKhZitCX{JGF}g}YBM&W(#lV3w4;y8a?d7the7JP64mt}P+nWyujk4bH;E=)~eGi8Y3hNXhU*rQBc9?aJo%-6uROfyj~Vq)k?)p{*K z3NbO<%gV8rhmq36$h#W{!^~uO@ss4!gA1Ei8%kn!CkLN*989p&6rL*j=f^wHQs>W} z;&O6q;2CRogtdm)G+)04`#P(co~DW-JX6HuBOf1J+__=t+}VQo*_BD$HX=S}N)f}2 z4uZt!gG@bMVyySXT6u2b*N2WQQi_`x`+U1Hz)&1y^uUw)?3KDgn1K(S2P5cGILFZs zw@xzG7|8&;oUt!Yns!CnX-i;+#|L|_LCI!HBDh+|p?>OiL^&JI5yIYypZ!lejxfDR zO-X)S)01Q0db`e*L@zg1nyOGH@lcQW#FU2n=paP=e8|+}BYwU*xnd3v@$2)Ij71Vu zV(iPkgR9g5G*`sBp(W+{f3fFC)kW zOb)!f0x?Te#W0t@hTh%%V@H&e!5m>+vlBnRJilDU^rERtOz|}_`u)uv%=dixSy(t^ zI~FfrQRowz>l3gp-P<`eE)~Y{?DIKm9d&0BFWV1n316%&0qszEaYSWT7amc}7+XW@ z=<0t8S>?FgX9KgOvbL+MtB1qs;q;_Cn%nvn*UuyYyEbn1QfIHYlG?5wAi<_OcVlo+ za71EyVM%#4Of$Mld+Kw8T$iu*OU6{wL_KY#$w6Ko-oc5vmGy0~F3aiZsV~n>+z=WZ z9-WvCWoEq)zG)iH#OT*&t^1Q! zFV>V1oe~e6iC-iAFK=Afo96Fis-p-ym0|StGwucW5*OmVxSmEsqLQiUZ7kL6?K z1?Bh4>B-g2Su&?ZmL^*-V<1yGTWofQ^n0mgQfDQ_B%>s*i7Sc6gZ}7j(ZE@!MScof z2u_yAACRDeed}G z@;jS%SKd0mefTEu&B*JmuXSEu=?m|Z=wtWB^lJ3JcvbPr_m$48k)A6(6+Ov4?mc9W zPS5Nf!5*$2P+x(~#Jp5)9)1cJewv8#!&Yz@wTcMx@CwYLh{>uEzxX-49NuxkF~Lc& ze+m8&p5UA08x?_}BnSU-D3e^1lUx&A)QNHGI5+n%p0QuNqr5!)ut$0NX#!(va7$>M1NuoWNH9YHJDD*7{KK&v_~YW@P$wrR;XpE+3l4-IbrQn3xhHrg#(Bqid4BPa z{TdY@fSvmK-+vDhe`GOe%jzDhlhH5XuVn^CxGkFkG-?+ za!ybVjnxTB4)1>JAu*_{%wE^^ESr|>XH&NEIhag6hx<3L$D&M&A5=VSCkjqd|2 z1tg6@SWiE<5S}6;jM;zp>YG> z(~txkH!wUNdJl_hE9~IRWMGCL#%I{ZG6mZ|^`_(TxC+zF#QScTj-N@t7Z&UOp3eY` z3;$huJU07#KL5%S!KF_*3L0(mpeBW;S}m@Ke>T(Y|3AX8e=6*E1XSO&A9@Hj*pETL zse;(T|EB!h(*fLn_roF=_D>PwcphG8Q@{PZ;OFMyd+TXpmbA=x@x3RVi+7Y5FS+Tv zWr^Rf&L#UY1k7(Rjm;lu2AC%qY%xDxc*5Maq0>CC`m_0a0|DCUU`5&wE@RqG&QjWP zcYoS?@idx2NfC{8bsx>^)N$G%t$~)Aa*g)I=Lv1`z(-nu-UMwSA3t5}rZ~OpwLIN# zg%sN{EE ze)4L$F!|gT5~2*JsIl(sg8$ z<3;j)RuehOVv!=_ZDcR)GP#C%m9)Qdo!mX^7WvElFL;@MpZqrXh*Yq7N}3 ziabVtL%QyKPYQqhM4qwyN-C6nCua=~llji0q+0bj@JFDprzoh!gNjmkdC>RsyhzuF zAB_nLqBZqGNZ(Hcr3r~5$v?!Z}yuZcZDU0zRw(GOVW{E5s5%pAn903WctbqCArw3OZB$slY%{(WH_MDFPEYn zG$-_Gw=??q$pxj5%h9&t6=?30l}J(56~zU)A(86U=u__+G%oLs3|D%fxGYc9d1f7& z^~4KVPkJMFWgp~3^F`xsekeT5A8k$vK(x()sB%jX%GeeR{D+{Xyihcf7KZk32uFn; z5$G?ANVHsi1LCGcp>I#3&|qyeQb>diw4@+eauYhxvW}wVO2I4AbpgWBWq;QXc{(Q?oH-;F< zaDoB#$Hd3yO!aXPiSP`Xepi8I@hkvdi-jYW;9V5~Z>tDG1)u}K>q{_g1np*!l>lD= zp3`Cp^M5nHO33dT0QZys6Wr0PBlj$>B?XQiB**%T$gWdC&C!t9ERgC4W`9u za9W%fE(4d1`M~Aka&X?bOw0@B5%Y!lz~y2dFmIS=+yZ#o)#)G7kI*$shv>cWYUC*e zNAkJmM$)9Ih^$MhAYZqhC2R9LNX^cBr0k~Gr0nh?@|qwQQb-d(OAm;l>lQ%xWFl z`oVdxg$}bkVgv6mz zk9Z`!Bmr>*5>esxMAV#?gxXY-k$+P%I$)oIoUW%J4eL!HTQ?zdxm0v5B^9aM1zSB0 znMS9f?{#Tt{csxUnV*h6hoqy6W$CD{Gac~`rz3I23?yrnftCekpnKUFXreL$^*3jr zOZPL-g-;piE5Sf{QVdkA#X#1k40O_tf%@DSNYo$te-r}+rZ5mYn}I;^AlXs|GOJ*q zj-w28t`UHuMCttz(CdC80gY>25KE-peB$(yM`HP{RjhT z|6riofTW)cGy&K(%0L2RAin@Z0FPe`R12VtGmsl#FW@Oa31rv`Ko;N+z&n7zBmUHB8*YL7U-1IM{vEzT z`2Q}jeE#oZ4)hWHt{KbUS?o5}DdlqZ?ku_1(%p7!w^)+BgDTg`yO(5fReogE&%MuT zj8$q4XCG!O@ENcp2Dh<1D`ZI3+P_G_U}2O_YayjnC)nFgSR?+VPpyK324uocXUi3? zMXiya54Zjyoz<=o(QbLz|9l(g-F&v;a&30StspjMZ3nBIwwrb1XC7PMuZwj<)su0@^{>|(8H{7FV~bCGL;caX0qGic$z9?=hOwYJE& z+ldzIWVYISerzprbF;X+a9x|o#31XvwiauSaRE-qEs~YJ(g`+ltdavU4ZeQach2nmZii| zT?^Mc@@)(Ga@*2N^APPvHxjz2?XY>#%s)*~NUNDHH3a$(vH~KrmEb6 zdIoFALl57#4zK&x&VTBh#rQeSl5pt>mgev-mTqu1Yy5*JOFj2FOKP;0HMf5&%c`hUxSf>o2=V7ru}EK}W5 z`bLvTy6dM2dgCKk*3P0#wymKI+UOxoe(^a(-hSdwo;xnfUhd&dGJc(BPvl*;Jd~Z$ zDrBXIzOO26mv>&+X3I2fb-R3yJ&`p`GQXs%I$@;>mo69 zvhH@v`CMLBiuxdZnbvyt4^ope^lk8WmNk^aHbbv?uimyu-WddRl6#P4jqvZ1o| zkDlGFdVPf!7IIOYqrqodZ#rwRM2@c|i@yr74=xrWE!M7KDJU$WU%eec`)ZbFep0@o zrT3fTM50!A@()owBpr zeAWwFXiMF0+rI1xI&o-mYfQHl&E=LHeZwbz`i87V+MxAzmN#{zr9N#b&EZ3SGg+~P zm2N@MpBkyqJ0hFN??>BN87o?79V?C4ue*fNW9s*jr0P?;L_QzMzpK$AJuwum zxD?Xretk{bPcv)s_8}T+6tCZUXZ;59fnp5H(lL^J9Mr=eXip_&9Okp@Q!mg{;>=oC zhij3mn~P}MV(-us-d-UeXq`k{-wILWx#(8oV=`@40*~6Z2Ya=Bd&F-M|3`r3Dw_iq zQa5bd#3ba~$4}a{R=s|MoYej3;;XbtG4mtziI>9kduIYz{pXK1KMCGPi|XW|s~u+1 z&Lk<5Wun$}r{pa59?ng4SHYz2u-Pv2{OK4K;RIwdw zTm23)-fmaxF~z=?vVDr}V%}wK#>X~T6!j!o$Q|*sfJf8+nE$DP|K~L@Uk>Jtct8Zk zxfs3@3kU_I0k!}t0S$m_fY)28L{$lu@ZCivWcN^soBM!nKj@eoq!QaJsf6<(D$xcI zK1?NS0D*usKt6!kJU#D~hH@~vBH^DHp^|?EYhF#yr}4a42v7;Dw>g)o#906n))H}i z{5Nib2H*zV9N;G2S3$loi9UM?%I^TWb}DFx!aN%$+B55Jpg{y_Ao5$`FS34 zZlX@j$8kD5ug7CT9+<^rEg7!=ByG%v>2w1?i_-y%Qzh`gY@Z4jg-iT*W%|}lr|SW} zaXP$)fL}B55WbL31=9TcGA-(-(}i|Y33q@Q!1p?o1c(851K7s_`G6t7YrtiI3h4Vt z83a=H-=q>=0NEQ!K2;?!tWsKrAuz z7XH7Z5(?ws`I~`DB33~KV%r~Vye5Lzj`8|P3!LAI*K288;M`uY3AlgqU~vPW`4QaU z$7?3oKRn5a{s}g|hx?&|@I843Hw9jo6L56vRQtedEtnUx!RfaVF;FO8d%0gW4`gYbB;37{YN3(5ui&O0tbA9#_4xHRmC_*39M1!;vJQwaws zYZrti1Gphi4a_%$Rf2yD02>G3tpU;oVn4_^GoXn9|K8v6bzrx_wYbBzj6>dMAs@40 zZo=N$dWnjT{j$}%DkcVt4UANb<}WZ=U}R*Xq7xGv6dM#72{$T_~ z2b?^MJe>$$K7NV-MUW~?k>Hl1NK<5}vJ^Qkd8z_MjbcL~4B|rJ8$j`X{)7R1`6tLA zBr3tcKOrnKa9&s-G35*Oiwh+T0#l;mQZ`K6@v+k}i9xY(@ByHI^7ud;Fofx+9Wjyd zgaMpx1pniMlHq?acpxe|&@bMPFbE3u36AyK5HwBW;};w2moiN=;|>piEHUv^zOm6$ zJos%03xIN?A;0N-{NZy*1_99NHq1XEep;0qSO0%$@G}9+SlnOl z|Aq$GGiIhIDnY==?|zMC2*zuW4ZmB}lzhRo_$J^yaei2~V6^@1{SBtUGG;yir^R$w z7GYo+G-I%=!^R9SBTuj_!DxW6nR0P@3|jz}KiG|B5QYV~ad|jw<~rQKE$}-G%P7+4l!IxH_|=z_tGdBv<_S&V_@@{E}&*AD__=w{>@4Kw!J zU^)!^=$@hD2Z4@Z5;w!$G;cUb?BD3H?8m@-&zPBVlOX`-hl{cS8Vong%x^P8#{~Zj z-QIrapMaS-OgiH(1UG*EOmPIy7GmJv)0F}ZUI)T-_!-3gpU@qKcw8=~;|Dta|Ag+? z-{^#aP8jI^wf=wvPMVm$7dSus9C7##@(kQF{9_x}6ALg?E`HXT4MK{4b3Z!%P6u+y z3_dV1lNUt&!+j0hIA2^Y4d`h9LWj%3rVD`G^RWjErqiBwPW{Jm7!LuESIkr({0jiO aDFwgZ0WOkjb?S~dKs6y&cL)g)y>*8mMz9b9CExe+oY^F{ z_VfAt0bg1(GiT16^PJ~A=lOlk7JsMCk?L?bRQxwO>TvABmHy@P-~aQUD!jgE#;X@O zUYhdf>vkzcf4;7C#Y2x~u6pFhKX~N(KgxXI`wu_-<6!3f4`x0RdN}i;hcgTAUYPl# zA1{0G=IPU?dhMo50uD!!GR2YI*sw6!+koSuf19SHT;cd3AiExr;s2}Lc$<#@stDSW z`}saa!mXf$;OBpJvi{eUG#;13ktx5U59oXR<4eBk*fbdrC90z+jf+43PnGIOxIX_C zm8z>$N5<#89!9&be@=O_=;q*qYlCdX(4U{S&yZ`p0@p^KZWY(MRdac6ycl4;|7A{Ht;-jo$pA zY>#o2k9tnLnDp8D`Ht-Tn;(+(7>5|MV-o6J^iTDoH_!W4DYczNf0p7sjl=yp^@1xI zzD%kI+;Y9kKd0U!53c<20}eSiRd|09>Sz2@z1yjo|Nq~=#=yex@r*XrTZI~lt;WK9IcH1qb_y6_<{0UBPV^Rw5$;gR*w-uLNwCKLuT306i=iWtV#G{wF zA_qfjpLJxH-XDo-wYQ)`IHBm*c<(_YRH}bvHf{%9=q`cQM&Ah^SKA7_L|6`7RcaCM zcA&q}p73#3BpQ1A*_ZGX9!}9}j>)$(a7m0lnCf5vj0Zpc?tR}YZ*e)oBMO&r`HBS(Q4-5JAT6WYA#Ja zgop9UkEcklR?~+ro?SY+5GV*rTu>RO}1h_AE^V6`d`_G_{5YRe%2)O}{j9Fn9_NqX+%{t3MIB z-h{O*)zOwlwQ0a@Y1EXwBV_U&pyI2I?aO9jVi+6m)3vCsxqix3quxL5r% zxYS&E|S+oP{Iszyf_8g(R=VeC$gC>qw5riVR&+CrS&l@FH>wC{74b?y(-q$5%o!Dd0km+VULHr6y80R#!6*z* z7~NxhpW8om-Z@xJ?^!rx>OpU|zRsJgFY(Sc)i;!3Q{7<3;+^rdVf06L{V_9Yj`Sw} zBhY7>OT3<(s9E6kM0#b7!z2A?D>J!2^{4SpQ?2GNJU^v@#6!pW;(hT!mHvkJWMT=o z_QlN(G?UJMU40&}DDq~2mfVS}?*Y2;0D5wxi*)?wGCK!#o8HRqT_oOI2=;osNO$Ou zIrY0-_-rO>2StI`tItzG=~-wkTfZvP^cV%k3)wQ^tZRB-h`<$-rA!pBs=uuOLtMN>w#?cHLp@9*N{@W+`-*HdTC8pZGVW3S|6 zy0ryuyNP**hx4@s?cuYjmDh>(@W5nk_v?Ml}zeC;CEGfK1n;f1PoUXLS~ zj&8F31>P)X>ss+Jp1lAk@^uM9X3j?fvZUg9G(M_eAlX_?MjCWK`dW)Lp;y4XO3`X> zg{-C}Yuuqk4r(>0@QCV8w3jJXf-Rzzwr${rFR^?TiP*qJPn{X%cEnz-W*hZaguXkp znyt7e+4%$6~SjChC1vmA@ZOwWe?_e?p@?V-Qi8tGqQT=xZkfznd(T%oyZz3BkszK}nw4Po3rul*|5k51cA#pk0h?5Ok z^AKcyW*Gm?0EuQA3qBV6dj4wM{js+Dbk9+3cPF4)hB@`lT8Fpa9DV4W-oK5G#y*@D zdk=HaQQbn##zwT|)h*|*)m}cRZGQ*tc*n|>cjXsj1rsWcmHFP)c7q7s${DSWaeHSq zbUPCbdBm;a5mnso6yJ1-AEt}%xWjvjSVGaD!^hHJ!AKoCq$rq^l+TjTYVRV(el9jR zLpj%d&RD1zu5TIYmB2(Ez9c%R`7OTm_i42{t|DQSjr!ugLrS~Si8Y!xHo>Ks;3Zy{ z0Je|509j1@87(Xmxn9(9;dZTEcPXxFqTHc4#9kC<(kC;}$ryEcG3na&kE{1OGJ&(d zUnD85<_Umq%?J09@EZj`N;OLUZmp&g@DDVVM-zZW=%F5xO}jDoL{`9*#~%~>wAveh zi{?dTWo2hRid~o?{^I)^hPUPKAY{8uAoQ~X3(A4rNn~V`?KtcY{N}U775PYu{3l?| z8P#?cc~|nX%J~Rwu7jxYjIMV$dInx`L?ONs%5{^V#?1z*me+|9Kw|3%vp#nmWNL;?)8dst~18($FsgThECT=`$X`hS63?e#UG^U-AIXa=35UxFat z;ka(nE`rC}32-r+%f!hXnzUTtYpI@3%8V1mS)?4`I$LbxfrDTkJv64pKrQCY3wS)* z_MYw$f7iOl#GAf-hWfX&)R*ctzNb)aBkStKt#9y5dr)C-_ea##pf4c~LD+T=`}T>U z&&OL3eONcrDws&y)-X^a;%B;ytbFB!k@aOOjsip73o3CCXn$^&of#`e(bw4A4E`$T zBb#0iGYw!^E5ftWE3?CodD1Jt05p0dy+I#}GeHi{6l!jrWRCywJ3z9PaRqwL8MPkA zy3HHqS&wTjlloO7F7BN0nFMR4gy)E^TW#Mz9T1|lpFn3PU zw|VQ(hPJay+v&bM+~V>d(>DJB*bRTAyfz7Mayo#Q*+!|NbdP8|^Dg)QUAwzeThJ07 zR6i4bIHveIVFBFh`N{QA znXV;%p>$#$pd`4QDvj4z``7UBWYMB+xtL^6(3=}Mt<}t+dkl#eTY=ZD*LWW%3_vYF zF)<6`aD)cpJJ|vg{-W5*yc~U04wrU;%+-P^Y_dfdIpTbQ8b9P;<&CI>c70z zYKA}@Ky?Gk$p(2Dh{I%NK?Z&s9nIG7_U0VauTg)#@P6tOpk@K7RGan5hdQP&ztUl* zIq)6}I2Y_vHyn=OmDOi?9o5bRh_67+arjKp z+H=kq*Ee&IpeC`OSQm-R*6wru{o37TP87}NMtaddZy+TVMJ{|mb4(-0P?v+@&%*4c ze$Nv=N2@Ly^IV{9xtnOm#^Wc+hD$-IE4|L1Vd`S6`H5{4i8Bf>Lc16-5qbC7o`Gp- z*t!{vh89Hl=;YXNYPiJ-y6riN(0YUA5+sv#AcC}-lFp~mN~l#{2&R|@!JR;0Jm zjk?)}yP*yJ<7HF@&pHL7gHMUfZeCAG1~SR&I0yM+c8raekO>Ut+2j6y9rIOaW^BwX z-}7f-Ii%j<>G>bRYIrz9+cE%n?2eLX{m1zNLNRBbKVLf`KBpwC_o=JLdC9*4A|M52 zYz1H?q*IxpUJ^d4OzE=uf$`Z@LF)!JT~w3sjMx8qL6n?AcT2D!Xb~*&7UF*8#6^HI zS`=6a)@3xwSSLn7Y65E^AIvHcx2A&8qBI{^#hWV=(C-Uk=r-SaQr6Fky_YIFUm?cr zGkZo1R|@RO4sc>?57CC4RX?Z)62mKojNGRmI!OEpQ(N6aOiA>_&ba|c5#T5yAvF~0 z6R6s5Jp^3gaz8G%=C>QYI3SPSTEzBOmQErxT^s?;d6?$rWr!Z!zy%2y>Z3Uyh1Yu~ zi+$^_!5G|RU(gB)?^XOK*4<980AG6L`@oJH9)U$9mkF&fI#SION36cM_GA>b(IRp8 z3<2(5`3xHPvB&8@QSk=8&WB7V3E(kVOqdzrJ<^_Ha%Fx@U!rApUQ!DOekEHl6OpJn z5S(mYZT%2fhCQ0V?e}nND{{6!WYI$OlL0z(VG!xSq#OJOJyzasDme#_SgWTDUSz62 zKVq$!Of3CS=%l}^BApCibpwxKe#>|7Ypp~J)#u~OHk>y*zI&ft-U{E;!M?cY8tfbQ zzdQt3X@=5hcF6yc)4?V>@%4O2-%@~6ps&c(^Ru+=ZLxtApofHEQO;?I*n8`=-31{S zSEZp8ywDNlp#a?Da`yY z^qJv(%A6pmA|v#B$mvu})|?`5$+}+v$&6pw?BHuK>uviTGnx4AZMOZ6hr!qDV6Q`a zAqc%tkMd}eiHzj8G%@s~ua)E3E5}1q3ZY%O66p5gp0p#3cD|Dc*-$s+oE9pTI}MM5 zS2LBEu6i-%H7TaL16KYTZTny1F>U+a_*@qT4isEU6Vfr}hxr6{>ZHCn@iRHd+)#6v zhy;zg6f{ho&LNq(`QMp1!h6zr$e0fvPae80L8TI4Z6!wGp;2ht@5$2hGx2IEir}0A zizo%%qD{k92w_C8mKkaxO1y?D{Y3@rSX+!DV!oMZ&x2ZSnrUr!hImuk-KuRb@UB!^ z;Qh&avhs?YgKg^1scj;+7O0f?(#WmDWtktmq@n_7UWzBCS8Y334oG4Jr~!?j7aCfL zw!M(9OBBmgH^tRIM7tN>-1D{E62n(xH(KZS)P_PuR&cw`kFmK{qxHW}THf{Hk*(o5S z*`#mu3<%=5*$_UP(V(ub#Zx+#5d1*6n+?7>J3c)VsGEqu`QPA0-~j^jfWW-?bT4Y4 zZnpHIX+KK=tn5jX@0m~mbtd{CA3akKp`U?#-CIL_m(Zl8!x+T~*0T4mV3)+(cOV@U)>Dq6H z9=R22#?3j=hdv@ECt7-rDkly1lhu2P-JRL%0O)2c1W%zEW!Is3Rp@P?!$XrPm#RCo zjr@qTP~a_A2j_tfffZW1K~PU7p$L?*O4||x^{_K)70QXs)>0)Vn_wmGaxs51tFbmcShQvGpJFvs5W}=pEqNE#?7TxuEHqBrFsP-P@CBo&{Xz z1Im2;Ze}s-yh{KaJeuycSlw$c_*!VS!Q>2&IJMe#V2jq?GK_~q<7QeGV8#>c`nbA* zP6rJU_e^TIG7qZ*UY9`4s6CDf=r9xBc&-anNO=mYS=$Zg+WHh9KxU(e!8Jfiw&f~l}WR!oX$Duzo0d?D7`sR z|2eHOs&pt_C_77KU9kZt=HE?hCOxcr6Sxo_(S)xT|g2&?#!lc0KMHP}i9=uwr zZT=`pLyMuJ&~`JnL%V;ma$@Oe}5V zI63apQM2%=GwZx=`p|Iai^;oS^EhTfgvSOZ8>K0?^K)z@B|LKXdS_DDR*#IL){d0Q z6L30=tfv`%I+}b*y@!V{#i+HNQ89V&NfZ#UOJ>~xYk{>VqKBoDXJuIVG~lRemE$@% zeLT2=J4tUU=91)4X7`0iN97_k1%EUe&5-_J!?PE_I7nKn-pJ|7n}E-p4#PcJ48z|M zgP1T=5CtIraE21^x6G@QL;g=f??Yfflt5-=hB_z|pi0ipfy;^UL-u&A)I&~D)}>z| z0=q=JUhdY{yG7_FJ>(K)ho3S79#OtSFV8T`w~1mzPclU)s)y3gR7ArSZ!4nwb-mmx zDh`N1yH-;SEkmz30KIfnyR!{JoIqTQEFx>TB95Y(Y77REl(;x2+6M26oDN+KoAy_)C+MTlWB5*t>-M>TC{RS6vA5RKZ-U(lVA$Cmh%cjuJHgI~~# z4@WwJFaBd|vMEGxfW8F9-v)JYPA`k=fp)zjD!O|HdPZMRAb0|Mw8-xX&cFfCop=Vf zXW}S@& z37F6^jhOGiuEgYz07CiUNH6@xkx4ZEGHMk-Jh+Xqb4~;h0O6$xvASBgdC;qG zk3lOH^Snc=>BU3>O2d61C+&iE` z8|u#$m{vd6%i5RQ{yd`Rc9_Qd5YB5?=Y$T2I(4)H3M_kxBYKzPh9i1AJ`>Q>AKb=M z8T?94uQ-GO)pd`GA#qme?MZ;Dr|e74pgk84lNDEX7^>c!vIC(WJrD;Q%EPx6hs6=S zybJ#8vrMSk&Yk(7zMi9qF(paXlO)-hXk|`C9N$+QrU}vmulIZikJs7YZM}iFlN!ka z;Hl5l%ilK3KQIyM?5(^S(~%R^D_*B?547ivERsW{Jv*U2^Rl&lY2;KNN`@=Rb@vEaWNPw0?k3KbMYxltj@{v!MYKDoeZ_F|}F$a)Gb*yi|nzQv-2)yGLg(gUt= z1*BNH6X+`+dPO=!R$!lql;VYv z_Y>{qD@1c9?nToo{MB~1XxsNub9s0|R}jbA?xNc+XxrZu7eu>nSg+Uwn{&cybliee zi}I*mKARV0riheKbM3w%y@~J259sACBX@%y@PfnW<)xx2iy9AQAgZmW>gCx+Zmnoq z$7O}cY7pfJlDqxo=^_-@jMgcTf;$a@mj?nLwP?)d%9(AELfp_)v_;n9j=A}Ov3OLE z@R%A&=5Nz`(}0c!>!~x{kO+ibehEx$#FKr)2&s5=?i*F7>I@_9z!^6S zF1GC@y(~{J-mI%XL;e=>68ztEX|=oI?@0xYnwFsPnqE$;&JZIP9DMgIiDK}46Umv2U``skc;c#fA~h&6ET z|2M8WkVxq*k&cjuhS*3`sqKcw8KGg$V)`)LVZD)B-NiUDs17*3N9V}|hf3G$33|jP z4A)b7BVkVq#Kc)t%Fshar0D5PiQVU{ zhf@|`ozm-xHuNSUpKxHavdJ@5ZzMw5R4@&sw*q&mlaxS#q5ho^Gl$7W`!(xrjC(z> zNnz3jA%p`pDPg8WHT(ynLS;_Ct<`kU#06?FNIkT9PE$5sND9--Jlf-}+Rk&LnNT9$ z(zXwio^T;&H1^jN? ze-=OCjlZ}A(+DZ0)l!<(TyosMLVu9?pz|Vm6_OgM5UJYTEg}*mumS~7ooVK~dcsI* zia(~^4Iv9>U}3v3Xx6R$=Rw5L;5~XHaU^*3@_BHubL*L&>hq|WG7uVR@*t3yn#?pZ zsgW-OPg|IzQ#DDauF#3Tcv~QssV-0j3a&X!G|MzHt%;^q@&l>>5!gh)g|7eCbgYKu znvm2i`4$na`ZU6xXt+G81%`BB1(U!oDeoF6v8ayG2!W3_ZQUGls^n$|(yYTXs^XIE4^!WEDhjm!n zyo9u|z;k9}IzfL{u41#%O-~*&Ct+G*snW0_>!^^_JPb{8I2sOyi;HV)e6*kvX`g~~ zG*WM=<%bbcF(cd}F9|b3eO2B7M?@2$Q0EaE1=l7(MgBS@ij z>b%(5_!Xb@41>!^v;#QGa7|jk?3t9XQIj^Ne@I?@rW})JcM3QTaW{^9xXv8_@|iIB2gw95K8+OT;V)cI_t>%q zDKW!hpXA(k?$cRMk?T!q6rSo<@bG%Zds&sJrw$+=X;C&haSsF1P;r&qN~^h(MFCf5j%7OHcd2H+8lNUn zgW+)q3M(H@3p)k{f!YhQSI_h&Mr*JE2dz5{&tjchMy^U+;!#9g)Ki4|mHblnsxx?I z<6@K(Gc;gly1bki#$1T$e*#|rPn;5l^x+d|o(D>r4-KW4JB}Bs=2fD+t~XE@hvp<1 zl=&UiWbPst_oaqm~cE&LyJ{2_3(S}5?>v6m>K3g z#*u|e@qBuz8?*)}pm+0mZpm=(`fQ#HR{1ex1HgZ2ub|>8IbAkS-T6L{f!rW#59JJ$ zK~CGV$V#C-k&&;#*lFX@p}SF;>HaLr%+CHnv*XAyZ6mP~Ge&u-+RqHk3*4?9X8Gf0 z;BguHNxq}J8T%cWb@HuNyNm+D+$ZyGz)Pm?e&XM;_*~l~t5%OxeH}>@Us-=;^+^52PZvIR-~z z5c)JF;I7@Y#CsFoi$1iDRa($j(+d@uqFzf#tHF1xP?fSQ)&*}y8r4mX;7x>3RLF#4 zSd&i66K%zgrl8)E@Sc3~4_X~CBMr}be6GT>H^Z5~Sa^TD)5u~-QJQ(CkA%D#_VNqd zXMl|LnRqfhtMN(XAj&-Uhm>~gwCmEnOV-34QjNc)n(CKuIgkH_O^;Wrt-*3PJVaZL zoYTG|@DRQwRw5j<(wj9401ZyT>>}%+6PJS5C}5_V1x=24yBUptu?i3Xf9`lkd|F)9 z!>xGd&A_5djn5nI??6g*2VyB6tgraIPx%@A2MWRgyn%*a^W2$BvE)F{??chD7zIVh)8`ok) z=>*OfCvjf(7Vf`D1N{U3%U(E4XqOA=54ksc|<$yR7L-p!EUSS098-=qh*Q>9|4DU;!&$R7u z{o}Uu8d&2C&Lb>^P~V~p;b^|ElX5pRDSF}UIf{WI_pv253>WuPXc-<}e|&u`>9ZbN zFQ8X-S3XoZk(Zc)LJzjNMy zEOZhcwif@Pii;+M#F2r4MUjfE89!Ll`rY9@OE}7aE(#ILh+vcy8d-&?-6}IJ@k=aG zl6>kW1Vnh#0%vL>x^3MHu#uwi43Wh;Jw$PB$|cSu1pr}TtR^2O%K8?$V+!Ojg6vUC9q zQ>rewH4}#s`L8w$B3wz^Kz0VA(Fnv`C!a4Z6q!EbT4K zz)(1mvi1g}pvHylC@1o05WbesEo ztu-0wk;n8^=8qVYi^c;RtCvP-?^_tsYUsUL@Ka>xSVu|008GHz1bnU6i2YcklgoM% zfoPm#y<<(0hG0?J!o>LlxzTz?mXa8z(4QBpz^h=-;0%NsuEyI8C{W4#2w)ga6vRD| zdIv{$?bCQfl}vcH$&!WV$xIst)!Heac;y9CfJo`+O#pKy@^)GjWzf7*i6OxhIHzR| zSKzm)CU|t(5Pj@|w};!!7`%)|W~P0LVtk2!UAzBe(1%InBAS;qeT2o;clRAa#pqb2 z5RhnZ_sD`)MNrZ}kn2S7LrisW)-x8@K`T5GBe~9b@p0)=IWmTV->)w zq$jx_tNQo>qGov4Lm6#>7Z}!jC#FPu8m=XBCDFTlSJJuSM0WIICpo9t4cC}UHK3>> ztE0S5L=KR;7I490e?01J*;KYE^d&gS^#FWK^D_1CAvJvMwfenT{w}TNGB~tyK>Om| zo)7(PK{ukAezWoum?yi?<|qRmKC85`!;3h9_DrM)&G$S{4myFn^wH6Mk#3YA3%j1m z^a`hL5Xho_89gF(dw=ZxsiKiKZNe99y#6GG42;sNvH_H6UWH1=l3%*OqNS6XN26^w zMdKcPLBS2&p2w{?r|cJvhpDgqFsMc2Hr$e0ets^HSMJf87kc!&+~Jcmj3rYCL?daM zP(lRTMzLnHR8p%ioP!VHHTt97eM&Utp>S3cy%J3Y_=_N{eqT1+Lp6afpwauXIGgy|x{M8^%BTl*vj~BWnfQ7E2#stu z?ixN0nn5G)+RsKfuDvSN!Lf}J5on;Ag_&m6F2E$>vjTPSQmn*RzzOV|R;~8;GXX=u zg*=cbl5A~%mCdHH&W`mWZA7ko@IT5GA@@)PJ_2jTeV?gq*n}snIa8n5Izj`6`&YnP z?KHeLPauO2JxKXsrqHK{y2sjYQIRz^q5b!;ZI{|}wnZEO7O*?aIvN5z5&8Kx2euJg z^W(3QY|wd?L8T~jg`?>ruo+*AGTKU0;Y=ejWrq=l2DLi<4poPnB>(<$Mjyspm4q}vJsWupD9h4ar0~hdCKM%t%9H>*nY(-(G z_4+3m{Sfh5%^xRIxPz!SRjYjt5A>>uM%NY~@#&bh${U)dmt-K|RrB@+QM7w$zY=U~iAc_HQ0^TBD_ z%a<<~SG-DZUr>URp}bzcNxec3HtwJqV00Q!P&;}PvzmO>z($M^Hooo$&Xsl~;^^0B#Y409i#|^g)3MY}3&1YxKuFf&Q?^ z;c+NWY3Ct0^um=Q?rVQ4r_0xcb*)7GaipizL}|l=oC)M`_d%hFr>(+A@-MZK4;_q~ zVftPT;~{*o&&F{G~)$uB*1raVH%(lqu#93*$6;Ep~2cCY(wOj;# zZT{%jz2#fYA3d!Hw!*oHT`LH8Q1Y!-2x1@w0e(M(0(t{mgUg?O!U8X)ih>OSMNH47642+x9mf|`Ij1uF#%ssyGvm{~M_L9O1gM0J^x7R|kh~Q_&;Uq=+l}c>on*2vvhaU;+7?c_rMZ zFIZ{LUuEf-LCLRmIaLI4`DQ(mMc{k95MF30F#~Pr+<0&M>Rngnuu1|Fg z*6D$5A~HbKAs0b}DCLNyGZ)e}wn-fbi-eWijjzyi6H#Sbqyc|v(kK^VI{8h&sun|$ z30)?m%HgQy=lH3%CG#utb)g6L57=HIvAwxq`w&Ep?c5sa9MoDH&okZW%_OxcIcg)f z?$JZgC-Ob|B6oObhEb4y)Na1^2lSpp(UkX*Pg(-=#90wia3*lZPc8v=SaI>~5%F;J z5+mp>!47*Fgi9R6>*NdM*3k=2KS;d7-{k`wDQe->WxQ)Of4G%L7GCR_tZiw@qMoT* zTjXsUJ90r)S;;PSFI_QVWQQXdN92<}MwqU#nPgHOpG=ZS z4K$UV$B95!>i4O3q>$Ggs74ZK3kNtMG%-PI*F{Ey8D>=#I_yi^#Qcq?cEb$-7&UsC z8hZ0r_NCPkZ{jS;zC&v+IXt}q&$8}W_!i}6`iaCqjEW}U6y6e{)AMUr;}0I#LkY!- zz}0_5Xd>~CsfKqCF?%GEOkR$KpSd4*rK0PDYV-1VT6fi zG5^Y#-l=y=#rE< z=yYUUh{&htgjx3v+@O6Cp>C;{DH9rd0cCBx9JmS408A35FIrx>bLlK|fq1AW)B$9& zs&mX-r_oVaJVi>_{(OAEFS+e6QEv58dT(iYcbvuuz7|EE=-%#G349;EPaNab!47T zX?mflhqe>4lGr$R(KybB02r-<{F$Ujl>mB#IZtE5Y(3rU2EA`nt7$M0qOgEZa&7E~ z7tBnw$Ye}Dl~wIQ9V-3+pJ2}>o~T$tKb@ctIoZ9S*V7Qfl}l5!;`vMAZ{uZ*gykI6 z;Y1!NGmT^*no*exMC4_rWnj7aI1vY_6GA3gG*M^RBw!P%kWueY9sE}Y>5VW<*v1;n zgU!5vY-Me?0&_VWgOBki=Mk$6Y*qG=V$dPVx1u3`J&C{mRusXb3k%`jp(23;nQEPx zwucw|nwPWt5t)t#FY1f;ateq1 zPDWjIMkmZ856wi{NUT85Wh#Up9NxBMH`bVBl|mA4}k>g!`m1)_t9SuPG{ z72uCD&q^+nEP9J&a{Y0FQH$LBC5K}l`w$$~!1Y+hJGqxkF4`2>B-w&gpI~rI5U5+H zjW4ZiYPD6$Kdfvw`a&dev4jLL@uHR6ilKD{WjV}Rvf&Au(oUipf|r?E5pI~NkqjrM zmOG&@H+=)WRrL+|?X(lDX|J~VeyCY&>P8mhjuh2J9-JQ}-qC6~1$%6T-ig;?j(p!F zxnl+}UwMTrnj?!`DAKm1BZOr}NK1)-k?3ALUq$nmHk&42hS$;#;2sg$H2C6 z={qxSW0B&KE!}pBA$69-G%kQs6#5M|iIEy()SH;9l@Bh;|ruEt(W$~WPaf_#&4`kw*% zr^FYnb`=<@c>=bJ`wcTXIGq+ge3$*P;5AsA?A%z5U@Z#G0Td$-hDD&(D2-~X zgtLv;HCi1-n4kq5{&MDO>;jeVXkM7^7_ZZ$&Gb0s_N6j7QYLFPZ=;@_M(~CR)tTj+ zL7o*29Pc6wz(cOpv|k4#uo5l}d*7MQ>_})x?jc}z$ow}kxsUUsNTqHteL125?FmK=q7GEk_kJDNmr{mfqOkdi%xGO zTJ^VTH8+wlv>m$iDO4xxO41aWuYlRX=kX3iKCaMJph+C8yjGMq=*`5RbT*EWA1|}k zyF#b+@`kp+CZHaTCY6)>u(=?q5R%M*2N{7QYLE@vJS0qCNTt&~D4qp&A6<;QTz;R9 zyKI^4i@PjIaJZW}rp9?8N1!Wvz>YIzQX1|u^ro5A5j(nA6pX89q*Flw$2*sFt|GpX zn1&6@G~(4WMW}&qyzB(<=&Mx_kCFE`9uiq+^KI)a+)OSi#R#Sk==HP?u8f8UQo>OL z!nK-@!D@}tNlH$y|6T2ut%7+d?M2w^d)E&Xv3^8NR{lU#)FI2A(K&dH?N1c0XCxPZ zL+K2RxU@Ot4Z*pFdy>Db?vbMyzw;K?GeBa}2=II#;Il~8UC&Hm$I1jV5)cmV&=~}B zG5;iuCWj!F1|_`1ODT%4?nsqt+s?%)voo}sdr&WF;~-%cy(YW+!&2&Qvp>*4PGiVU zcGe-M+r3KWA0crQgkX4{)|;3^xcL${6F|BED+Bv3Nw0B8=PjkN4uh^qy#6KCCd7r(-5C1= ziRcttQa?``1xYdN2qI^zo{waB_BZ2jGd7aAFk9xT+rn%yQLMfyUtc>D!ddd3amrto z?6Aakb4PX%C{jG$!7oK;s=;~ytA!2&8;2{WvuhH2k5zvs;!~rQi(W+%zQcD?lx;QE zq`gXm1;;egwn7q9{P0M@!qcKV-l1*BVR9_Mi?_yG;@y&<(IVv%xZMh6IhR|yBN+!ID2Ce2a z%zvqVm?!*a#8BlDIW2lU>6Lc;0{Pe8pHheTuX{{XCn!p=3bmwBXMX{DAbkRnXB$Af ze%tzll$#{pBT>m!wZ{Vu5EJ!*O`)k(6%ClZ_T;kzUCy~t`t20y?-g+w>jPoqbjLcY3RgM< zFKW6sXuH1&?eHoyEg!Qfr`xyR)=RYQOo^z~^M3eTnzm&b81K0AxOgifda?IYd1R3r z@lI#19T}z3LJNvqf-;3oeDLOQONwyuDUyM@%Ab(axt4N%!o$K`juNpdcBs2maY~O45=RsixO)K8eg0j#e{5D|0vsscmTkj>d=2a|E8k z>~md*8`#S!B>d?5uiMl_d{ z!^HTc2qXqg91~9yJQY(9V-DH1$#J^^RI*)GC8cN_qO#KKubc_bD^xzMHlB$>Ht>x# zimlEx5|raBy^ULAS6J?&f^*El5o~|2OgZPh2|90~fqjn5AJM)GIbiXSqLj3yEp%nV7b-BPprhA{%hkx6y`l?4>P)B}d#TR4dNt^@L>cri4F& zBS-5(UnBQ-aYL9*%oLHUQ$U@SMgOCusZ@U1n2)^=B)BZr{tzF06CcW%!)_Vu6JZgF zNQK}nT!ViAl1wHbn+a((3#SuAb+3_F2}i#)C$OpFHj--6hy5E`QBYYRWGQSz8bL1! z*>NXk|KhO`-SREqmT4jxR@Q2Ngm$I0d5md^9X_y-wd&Me#Se+XMBNf^Z5jm68T`=g#l>>lpeL;0#+F zT`QWG5zu$8!;Vd_+g)B`69``fjpZTT* zR8$y4wj*+2iduB0Ke?Q7t`4P1GI?P;Ja#WKn~R-I!Hgv5V`5uiZpGb5pt!p$+4(eR z;%J}kUb+79R3EEurhJdlvoJN(nvUQCo%27w=%}EDO2iPKo`I25I+(Vx^*WkuRCAeW zONl7>d=t^dPSt;yfWTH9MPPRxvLn>eha}U)Rxi+-sO#zrjx`GOiszub@1k4Z5JH_N zDlhGe&m}0#v}Nc?tNj_?#|?5b{_}X@(S>w#U>q1zqz+;brm&%Y!{4HFf(`OWCCRi6C+s6-9x?$NHP9o7U#ai-kU^Bn}Y2t0E%Aq z5~ycx2bk6RYoK8jJB<=09KB5ZoXTya%{>ktm_&2%0r2oyh70ha6#-dQ+>l!L0&4es zNSuLh^{QYiIjI@G%c$X}#8!O#`Qk;~^Tso;Oti|`_ zi%iA#X2`DCUnh|yn_mPlyOe!Z&6JzvzyapcAEfS02iSm6o|MrR`!E$~@m-);NB%mR z!3UrQ7`H?6IrTdc3ZamzgZ4lnO%*l4ZXspzsSqUvBrGZ8ReE>GN01DdJwbHe|!Q^ zkQYP>ftp50GjLgGKsb!ExQD#koW?YN=@?k#+6ENZVcUQUQf=7j!5)a2gh=u{K!KV+ z6FnjK4tKIoPMIQaM8y{3>f}^6ZqsO{Jb%M1mhtK6PV#q3_HR5=qL=N^H_@1|OHq_< z)_EIea$Be~c65?&M9(ixnx(Ued}L^yNePY@X^0@GMWhADih!j;X|?02K#n9IY6x&N z8}IV$Z81g2V#XPe(yM^-EUED0Zl*4GEiOUE0@4DJovSxyQscA%PUadR)CH9YbC6Bt zJO@B8p~J<9CUij0Da2c3^fDcQ#uKZfSpTCz+W*LbSJVx!MOgJ|-q<^C^uJL;_(0|p zX^P|r!AQtWgpfM!GI59aI+=(C=>xufD8eo)rf~A+GaVrk$8*M!IG(d4XmZXzhfb!@ zoY^cYc)i}lA(7LS89JvI!sG4!Qyb=KAjY0iczMxA(*UNps+l5*aK9&TF&D2At5)hw z9zx8jVDLuW$i*#E3NRc3MDEnXdA}#&QuR}nckoBC5ZT*S0O9{{iZw#t9A?dgA6a~L%_9*DFvMQK%H;QN6( zKj(q1e@dFA(>U<};Au4~Ecb9ePMjj&7f8q>=9dt=0CW0pB7%@-7fi2FBviujRUi`+ zITZYe~ z+U1$E`hr{pT3HCd-(BD0>*6%eH-K<#K2On#prlLnIRp5z*8PQtB7>v#toU%m(0T`Z z(1_GaX40`uwdwB>a)}+VSz@L|A#mYO0c9$2WGd5fYS7eBIPSctmJvkcL0jDeu*O+i zyk6UKy@`&mz@O!HZTV|y2-c_MxCWj_Hk;F=#IYCP5U7wyOHrB?F*_Ai|{a(oT;l^ywL z&BNw_3GWZ!eFsik@xR;O5?lrnM4#ql9B_vezZiB=A2r|X9bEl8>@gak)>m)CU1u^r zfsoF?c>Aljn)~s&tohOE-_X7!JRdLz&^~oTeT25sz$Td;i7GZA~d?%&*KthId^=%zLlkXV`-f10ok`-aVi<>vjsKR(!yl1e1#pZdIK3>NHsYhdr5GUs z6;7jK8$_W>!j4oFu&Nh-fSDzcQiS*PV@a+a=$@zmF|>@nlGU9?*a6L#zSy=BUlcnz zyKxrM+i(cMZL25|kSO!EmDJ4m=QMmxqCw^CTHZ|TO8nQu4o7vxwhQR1axxN6=>}lL zr>`U>I6n`|yfRZd?ZU(A;f~6S!^1d)?n!1!FsM$D1tdPz%ePqzvHWplChJt_$zfgk zA@M=7wQ%|zuV$G+V7+n#Xp_k{0cYeOST+v-M#rQTmcs)8oVU#{`y_Q@Z>@U$P5QSNS zV(KfI5VZpbpHwdr|<<==x-8Q zGm0%*U!#pBTY*h1F8umSk6F-z{Xf_RiU0azhg;(B!+veWEB7zS$&bA}Xp|>G=L1_1 zN4ejLR}0focNwDB$P0{#(fbxdvf zb7tVVIJ4_;MV%|&oDO_ywL?&m;*kGfF$37r|L#kNAcc zh@|Dt^^NaMMKA{MzkTrpoX8q!+fc7B%}KQ(+_VjyFpStYY~%5i{D$*Gb*692!cx-f zNvKL^4tURkE_ihA8S@S(?^p&h)Y#cBolfK~l71NLQS4mbhtq9n(PLSG*onr$43Z|| zCk0rZ7xj@M@-|R_Dh|sHt%Z_owA#g8=r?~Ye9l*fIV_$^hb1|y*A}pQre-$YW3iA* zZe`(~RDs(3^Z1BmC7gB0N|y+SJ4wEjsh6(n5~pKmOcX|{B>I3tb`*7 zyn;Cxg@c#jj-Vh*M(W|==n~w)T}{)8y9xGM&NcvpGY#jLS|337z}nzccgsVk-C_U5 zgt(#AiVD2m$UQ`B!uliB3Fxz_91##lVxyDhuoGC@@+hz=xY#-f<`zDet<`=9c!*Dr z)AWwHjFKS-WSTrSSt~peJC|xz-~$P(5~!2ZNTvzX;Z5ifY?S?SB=p*;wT}Li5Q-cr zfnAQnHRK)_I<9j&9S|1h{Q#0e`$sTFUZZB32OkrieF^q8jVY5Glkq#U0^2>VFRs9w(^C1X|MH@Y6JZ1h(M#=(f{6I&IrC-WvKN z+$YrHgpgI|;IqiyV@V(X3gct~h7Hf1vG-N$#=p|J45T?&@x!2lGct$6^7j+K))ri3 zQUow42ZXE$>l_ucVkk4Quq#UDnL>htM&b3fW3=PBrj7ptm&m~}kuDlvAWZ&Kj;R#! zpi9Bc!;)vmKfKeNUy)eeNfe<6Yl>lzL7PtcOHVB zQl;Iz7^EpGzm;7jN-=`5Y`hr~H#t$8I@B=zk`$YFU39!)GL4%I9$L`j48cz5{?pq; zn#h60F9SgDmn_|E{51wI2~eg%TA#i{_3&dqA?V!LJrizA!RBx5S6wddy=2J_jXXb_A;=u?#T7jL@0#eK16(8 zecF2S12o1Sdz+-pv_cR*^i9Ik;+a}a74)XvWV+z|)JI2sM<5ALS0b;ZkY=EwfzEuE z$?n)F=w4{JY45gljuzd19v(!09Ot-yM7tDuX4k_%%g%+QJ3XF8?m(MRIqJ-D!3`%N z37Ow>S-%B%;?EQQkfbhsrF5!Mf2`jMeia)|0d%~o24EuYIo`^~1z628SC-cH&9t^j zGQ;j;rM#qGc@B}qRt+$8hJtYI@M4_p{D(fB8 zHq?S_X!<~e98{Fd&2+Ox+Oqz%Tnln@mc14dJw(_vc-m@7L!E%DzkGvaNC9>D&c3nu zZ|x5U@C|2C>T*XCYq&DrjV(u?C@na9Us^?o>qBAVecW{BwdqEjA5ZZc@_A^I)0^&a zJZnR8gEzP;5ZF#EW>rH{}bCOCVDD{H15pcl7-xd4MtWJsnN9O*o~I_@8F~~fQ5bN+RlSULB@HJE)W1B z(xGDzZkAY+SQ@-UFAA&To97|r@r#l;XEJzE_*VRmmR_`hiU&U=DIgOJIIUUd9R=`6 zAp=wl`A=3(*K>7|YYRqp%p%=!l?OY$tgn$Ljrq`cA_HO|RuoHid?P~}FL)Y}bx8{` zk_@ay;?=(+{tsSlDUgg(-Wlb)z!98!z>O?g;th!}%?4K}_gIhOQ@N=E?b26!bHA9x#w*P9YaK zIac5U+#&gWsk{T`SJE9Zcxeh^zfEHyigdYezqmof6Gg8Qdh%sO+jT*c%}uf83C)Lo zcnHdoGqK=T+#mugS@svwrO28Sg;S(v#N?7h@~!J=!Ln06TIu_&W~A4XJ_ipk=|_~y zG>0pOJ_6q-$FheEstNg&W83m->@ZsZqeN1CA5;SSRezf1vDs%YR}7T5$R;b=#SInp*) zr5kx`rR@BCs8(Q5|97a!K-M;1kGj2sYdn3uOj}Wcg52C!pj=WLxsd?A|3c{JKzMnB z4fUY5aSzqO5hL1$65Ii5xC*7pgXBBuGbN?x=Drli;pqvIfb5Yl%NlGU2Nd9>w)XfJ z2k^4b?ZvL-gySD;2R0Ezs3<*;Yci=D)TE|+aIWcRnhua`L}m0aL>(q@fCsM090Srj z`(soEt3oOian2}#^GC|vf!@L19w4LBY@kdNEVc)**i5_FIz^O7#}d#o{3|`2u$pnn zK`Q)VEMLZ=UOz-dbi^e#10izHx`0gJXQ8(Ke*hz_Lh`{(R8o)u$~%{=+o1WNN++^F zHf-ZA8LtBto%{y=fUmLF;|Dlj3rQ)2JnJAhiW#+jP8W59^@BJXNZUp2r|rOBIJB@a z+>8Pd^GUrra;(o}mEz)$iRqXFM~Y2XI4@nAf~Vf~8E<^#+52MzDfZ(xsChqZE$7G64536ML%q_KY62Ef^D z9$%MoW3JW39Y`Lz_*3f4x(k=&#tHMp;f}-DIEzz8EncaUD&W2 zOnaw9gm+57S*tybC3}}b1QuwpG-d@fnkV1IXdJR{b~1oK`#_-UQQrH9wHQJlKIybJ zva^TBPhV^}ju%9sV@v5t9AQqYzLe%^G2|$aR@jeD$IeTVzc)d@{X;)Kojtc5Hkej} zo%oK*WpiiTL=Si3LEF*;q|MFXf*)eZX|+EEyO?_w7f@32To2twrjufXNIbbzIotQr zIk!8tEtKsm=FMDQ2idX5!>72&c*NY7xWiwgh4ucqzvG7s=t`^gqnEkA=Ys#D_Ftsi z$RBV^?T3e#X&c|dgP4~$`1`rLx!!jEFpECi!vz=9hkNrQdkI-DdW^eVj83!OqI zBTX-K37r{dVY<*abBSB%b##-TA;J_OvQA#0^^|Ol;+6Ws5`cCHi0N}mnkBkZ6CsqRBu0Ky!5970NTiV(a1faKctFS?7gW; zWlh_jz=mNBCj-nYF^Z<&A`qGHG~D$&fFVktNIdyIk`z+)g-$wWi#Li~#u_Yzf;T2Z z!Cj%jw(t&gLjT(4A#0W%At$x>$b%E2x`$i5x!)X#C`L+_(!5 zIR|N@)zg$$#XEq+!VEDl-KxMhRL>|G4v(a38x2$y^YWRkFg?)ANObL5o#?jW-^Dzy zb*KG}m^a&+J9b}S}nyny-m@9!o>vE*x<=YE7oP$@lP~X6Tn< zsy~K~H1|`COxyAR%_zR#h1zgh;f0)E`{&zm*cqwFfYyO!nn{?Sc&gcrMKIhiVCZk7 z0#YDx;$SP?_10-MuhKUqDkbSm#WB42g_~R8#@4pqL8r-Lih%$epwLBO6ZDW6d=GPV zhbrcXN1ftPmzbyOcevW-Wdce}fS2mG&C8`HtY?fit8E^_pj61e_|-O#G_O%Huc&Qa zwyX@#Hof*hV*(h>p!hPYg5mKNu>_Hbu|!#((`S+rW{1 z#QX|y#Mfy!HP_MnSAIt%M4v$aXlEwQJ)_s`nk(X;5T)NPBaJg1ub1iz^FZY;-Cd2R zTvA67+*D2ypvc+8(5y_LWbAyioR8={!=CVQN`=BNWMBaKPvO+Z**NGd7yC3}qXWxm z$wh-VawQHZ2MNDvN_(s2W>xxWvuw}+O7n0$S}wLRyoh^McfY9B3Vew~lj|r(ol7Z! z9dY$nP>G{Bp%q83Jp-4ne?RToqpwZRi#YNO5xmN5oLT77p_*S{RN*&RVaCC~X-5QRj=w_>CM)*{1Brhd7qRU=pzG-N=TG<2rj_G z#}e8Q#i!G#x$%=B=sToFV%4|^Ivkzpc`^93^=-6D>rI|_RwG-cA$eaP<3qL=k9{577WIIOX@RJ7wDT6m2! zL2nT+-YCQ2X8K$~*o&%1()sT?{H?qJ+mmrc$}9fj5SCK@YAqg8`CAdGqSsuB4mG2P z!fYd0SE`q|_5AekSyj~Mp;UVYhi1*@y7VZF4E&`K%jH;mHOPg%2v_*FQOZ%$Aq>)v zlD_>W4IO)ovS7_|3-qmc-XUA4If8LX2#m%A%Ak@FjvHyzbpB0b72QO51fSWU`Z?fCmH4 zPw~!ZszH&6BUm+D$6ZboYLr>CkxaBYv zo+_CT>S4pBLz~2Ty08z_I^>k$mrLm^$m7zBeIq6dDdTp`l4t}xfal9>Tt;m~_-|tg zfkglsL~=M-!%#J1SBW3>LfROFAjXU6NDRzlMEVl z^o;GargpZ(-PvtYvTf34yG+40O2(v>Xj+L(XlPrJAv{B>_5#=odUU#p*E4xLB79^7dk^o{tD9%8{sI1Kp{{VJPKnlP2=X=g1k^Z^&zJ9O$*^+bS zobUI0zJH$Q`99C{ef|*7h`;zL@V8_C2!r{ez{FeEO-E_d*`zv1sVIX4FND*5XdzcD zk%@Vt2a}xrb0s}p&&p!X)6i#--r6*YJ6#~1ZFk3bpV{NMsz}bEt^i?9v%v>4{qr7D zRRrpu7YOi(pSQl)A+T{n7s^Tc{6))dI)Bj+@o9oV6ILy(JMvaMXU9Xe^ zKB>O7t^}37KcR0B$((0=(C!bMHZdOkG_r4Drw=mbqt#g7)iJwBrrqBsB*=%e948h9 zS{`yod3N~NveBS(O>Op?`PotD^VJH@1CW)ejvu7QY@YO5S{%+{h2Prj%;p9-y5BV< zj-^CDr*~}P@t_1; zlxqsI*9J486wtTPe95R_)JSq4iEmWHkPr^>E6oqV|LyS(kw|OZZou4o;cV+@;gRE$ zVvqXf=7Hgkp~~1&^4blqv>tzsq=vE+(OjorD)hq)JTWj$9!;lxBB=?qf2#F#mT{6{ zNZUkwS;)F!c}P|W^LZ1Wv=%BdCbxcTe`Q*0M}H}Dt6`ApQ-YKC)mMt#;9?yTs1HP$ z(O*OSNqtP1hjkxgk91t&1!j2sQHFKJP&irfF`P!X$Oz znMW~gcZrHCUc7EFQzyAp*xIqUP=Pqhhl{rB6T`0%`)RwD1>alKZ@0P=t4tnL`TVK? zp!0s3?M|PL*bm*#tHT}on~gq$GAjNe`K?U_;0zQdRS70cURxe6x{)Wqf?Roh)ERKLrPxt+;^(QrAJjz3zFSZeIgmg>e+~rwGCZqE((d z{KEAyky&q+c!t(eE5IFEOS9@CVIDD4uQ!$IEiCL`eV6Z;tx(OAgNWzklQa^9z^z8+x#WC7AMR)dAGwlU^`ovp8|Xeyr^E zkdPA;T5uj};qTC-PYEM2vg{R~N^WfOU2MdYypO&AhF zOB$(Z#%nVe^N`KvbKnK!^y_(uM7w(AwUyWfq(TP@|X4Q7^}G2w8NC*W3#ycsp9wQ?A( z&JPSJ&@epq#;Fs*0{}E{22=r7fvN`R8e*cUyR3!|)=3---OYBYf|bx-zhJ~QTfZ7| zJJv8vRk%*KNcz1uKyRngOj*X`_<#_SSCd1 z5l`u@%1L~R;iJvuqc7Lq7qL9L5}1q|-qmvdht3PM->?~(K;DE1&ppUPav>kj4a@e` z<8^B^X%R6ZTs^MFM9k5bG{+tt(&2yyg~emtUtr>dxt!r^w{zRgh|J$v3wdJXYc!Aq zDCUh%b<&wV*su+7ByOUpq$WR*93ev$3uQPdNjQdnXlX`2i&mk5sn$Tzu3l*rky5W+ z(2IIaXP3O;=R^-bBMb(myLHN7A`Ur;60*B*BZ`!5qLa!KJXWa4_xBuulyLXBwY+osd@-6ZA|2>jz5Gg zSNy~*SMPujKN%7Snb>x-+(m!8C@`36U4@hE5!*uak3# zyQFY5e#Cm-_|}}p0VE|jP5O-c|ky=B8D05!MC$)WrV0c~&<8cVT#RDh3J`A%8``-9>>weAxOM zo#T306G{D8j-ygjJE2V4n(X1KxBavA@!8&yli!+My@AFEe==yopR8}GbR&Uy)7pEo z8W_UG*z-6H(%GtA>c&OMHCjQsFy84m=$I^xQbe{?dGm+gr)QvWHv@Ac|7Oxf-C8*J z$`)B&oA`}txJ?av9h~pS_-!W1q*sZ6!^AdOZ(^G)S2I(wO@1S&iTXtc)vwwx;Y>Uz z*@%k9vtwa<5clKH{ag%`SlqlrUb`4@&+Nnu7-h@!YTmt(g)zpD?^?e9!1*A*b~=Cc zY=@1ljgHj;cSg59!dD4_j$@4$blw6Mcdg}qK%~b6LDvRttWCVEVaO(M8Vfw9vV1&@ zO)a!%?YJHX{i2^&Qo@Ap(tKEtCsqxQrY7;u-^p|6N{^TJjNCKG!-+L+@Q|zl--66I z0F%;g%5Qbu%c47(s>9=@=;IpJ5W{w#TVEVM(OT&hi$~YA{ju2^@&It~ebnFtl|lU+ zgJcXraJ}wMA{%P(u+a@`Z7N|qv_hlL$UxwoqMvKp=@p^Mcq)f_GuV_dUN4NB>1c~g zO@hfr;6=5WUaPg+3k!Y@3y`1f#CrGIkKrJ*cF`qtX@0Eg?Xhz8$fS_MoVvVwl{rf{ zK9UA%d>_ef-$!y6p2O44|0&J;>j=F!hvoUj7M5OSUYDO#tQc=ufZlpdHrHDJTB9qJ zV*ZqG8OF?H2bu8&OYdIZ!m7?}vrCSIt*#hajZ%4?Dgiu@`88F)`JQO&D?%cf%I zT94M~&Z9LZ2$Hz^Hqn~qRqW6zj#q*RmcC_pt=~(prrJ1ktESJ;t1Q9H-D~9sX+I>l z*0IaB6&8@W&PIk`#22P8hFBl4lbC52XqLuUXs79sJU%SbV&fY0P_b^B9!+%-t*691 ze9V4GrZ|53wnR8RdO`VdHVx9ue;I9xpO^>t%03g)N)zjK%@06f8&^$xx0h@(fPw3p zz>sIdZF>n#7sVD%OlV9umQ|f?Wkrqodmx*GP)KTs#r+g7BJpV$TmBD>` zZ(|qg9^Tl^*#bU+@^3rd7sC8-74*I>Vc$lt}DT zOYDW*gj}>QN9E@{A$agp)V*yFLekqV7n>%9$SA5Eli!0KfFSzq61l#kF-qjh5cJez zkOt*3GAQB7eAkn#09pjRxpYKvt0S?%AcG5z{j^^~)?yw9+CFH?VB}`ZSiAxP{?(N6 zve9wO;4xke?RIOF_=b%>vxCWpNG~z{*Lxo1ZluFZejhMq_*=Xw0`~EiOC&b7uJ;lw z{HDx^ghRsE=2a^Py=MF#w??P5(t&QTyM+Y8_wx!TR_Vy>K`!1vl9imMCFgjkBsDwK zR(>XuyWsJ=0~s%AkKy!plVT*}5ftJ%8Q{@uXmUJXCZ{2vjoGa6*e9?pW$?<*QM+mL zQ}8+kPwS7DO(M0j+OisMtWK?*%k`GIBIY+%+uU#x=9v{b*UhK(m{V8FrPQgb=Mv4< z&6l%Nwys9{E9bfj5NS$T&d=Uj(=m`W`Mm9G=Q`Kc+Sk^@3LAgfY~y^pvD$N|u^VfNKVrOcvx{r&#(I0% zTpdEJYF)(<@X1^~$@RZ<6^E z&rNpc!-j7r7$@uGhCbvOf=Q-Bg`3Lzsje2R4@#yJ#F;o_I4T-gW3)O}(B^MEg`0sz zk!BMxsnDGdQk%|ImsE8XHLo#8xNI=N`-NG?R8a_~){o9>Slf0rJkjrJ037;#E%f8_ z+q|Bj>J9Ww9CuDjQ)I*u!@6Ub3A|bzDUMh3aB8hf>Q~cR%~N~ekcl;G;!xqbocp5Q zF+$cB+I4LVmYw|M(@fg&o6h=k*;PZ-U`#UvPt(%6(hXvWb!> z3~_F#Ks7cM@USco{$2=tg8@PWLjT9qe}e?V1!%n43j574jl{uS$f>J_YMU_qAnRDi z185meoiVzODQ>qEAx1)lVWUOkMeC>7)=@8NG*M(EY?w^N+>z^dJ(utd=_9|h7=${X zBiMEmQW_#b7}bS@TNCi0((^zD*DE|$ofgX+LO79-ZK`3s^(ih^!+3aKr!5ODo(5~Q zc*#{<@H$zp2T%bg+0Fy34$YQl1b?lxsKJLZ2g$8NOX0~uAfjXgYu!ssh=)EPr6-jO z?|uV|j+tz#zfZu+T}fVEJXWU-kgSe^&sh{-)#USkyp5P;W)5(<9CUX?TY6rScWbmmmH=w$Df zi1Y-K{_=RgBs?l99)P}<3G-$nWlXq`oWz)suBwDf?$5pXoWivj?GZ71slVjE50;ER zB0QSwL(7H2!cBJK-=rNk5wh|2VHU@Jz>MTBnoU6kyz-4rL=JK9xD(x^wu&l# z=0qQ`D`N7Ioo%1R{p4)mWXIue?u-wvyvDtiyo*lt6DrAp*TRfb5u?eXiXV3jto#>d zq8OW@YKYKW?q$o}oj%*UOOc^*GWY)B{gJx*C!4ZgL|24o)xss$G73^n67VZPpLO6NM)ZT{TqRKd<%=l-Vg_rP6BR zgqdhpm7U0y@xgoEEPu}awf!867b+i|b9XHKiM!FceC15)jX-KVh_P5GNSBh|Wpp(T z9dKJdG|76@xQjlc7}A4^A)TGbUbUOdUo7mwp*LDCw2w>9yS99yd>46}P$x`e$`4iT zwy&^HR-vcn=%K4R+<5;z)#d%_Ro^wDF&^t}?BPnBMVFq5 z58nF)`xNj1+$>_ygZKPLP8P9M1L{&5hAXU2+0PR7{xo_nC**-&=mWI;W_f@4^J;qz z;V7!!w7+@Aem-}JYQJvG+-Jb}%};3;x(T=rIk!epC_P=uZ)^Ko%9nGZ{3I&OXT;tN zkK5V6eiE&b+;QAW=Vo8Ddw85VOpcWg-u9*G1i(&XboFV@oeREkmj^m{0|uRCWzN!4 zI}~&3NjQmPXi56zVtbtfDzr4w&e?RUR;uDnN7x!=xMKkNpC z51IL!!nd2$cVosEau!FK^~FSdqN>RbKm3Y&Juw_rzvly8SM`^t?f$9btZnSn1qC;& z0ewBfM92m)j58H6Tpu4UZ!GP2J83!fQ5?bRu@TQTLeVRI)QbzzqI@9w?D(6tlw;d2=l+@vlrqHtJ*mmf(n)@%!{wZA^h!kPvZkk*WcJ2+u z$L{^4c2e!H*E3u3Q};%l_u?dMUtesK_V0B0ZhOdyV2lvC*iI9%wyXG}gQ>}qwxRMd zJI4-SpY&`Lj$x&6QwZ&J;I4>3>8j(?U-r)j4L+D%Ph+ZpM3lK-hacSqn^@kRI!#=b zHO%`Cff1nvkK3n)$3!U7&I0M}!Z;tY2tWZl_fBU)DZAdpe=w9Vh01rvDe-~*URzs)+?mqVoLtvE8$JISN|{RF%_Mx;}mE z3cTR{gpN8lm9k{11rdVx6%*NnrXe?1tEhcbDZJ%HK8!sK7N}5lV%LW}9B!JXH!^+~ z;}xS?nfn!J&dxl|SvlKX5P)6MX{^z}vT2N^4Gzn$zq#ibLymUY8T-WW*zoIMJ~rPn z7Z*FGN`Ua#dMel_JW4nU5xgn`EvSIVNXRYoxZ`G%?Ry%2Zt2usdb0t8o9I|rqjA1n zVxK5KuJF0ABEv5Or+@BTU)C|O{C)I4GO-^0l}ClkcE3(QM<-z$YcV=9t(S3@vPP#5(CWKS&)_y?VNg1NEc@)P)stch5HC`#NVn7g-_$#W)~+ zg&nWAd^FFL+9!vO*#`rI69-{_BY~@SGSHS&q@i)o#5U`4OR;02Mf_|X zyANGzX{&YGO##@a#T8kS1JfRdKQ~ctiz?iGFz5#RocRYiTd|jt;lrMb>GeRigtD@y z3M%&m(&KXV)kMZc#l6KeIew8(|6tp;F1kPDE)G~bAd=7BnObrGWdVEP#^vbJr~0RE zT02(!V}7U#Z7()rZJZigLs3&gz}45+YS=lTgF=|(Og84{uu_Pv4K zSn+|W=0Su9{3s!+AzC9pyAEAgN_UpHks5}NS$jbNk%M`9hd*`PHoEh7>IJv;Hi3nM z+|**r>&}<%MvoWLnHri|3e+~*y8@GLWG=A|+-RJK70RXcQ9jB2-F&3lRQk0IDZ(R& zf0e%e7vB}Eu8N5ShzO9q?o6tunEn={H%%066bCEC|=Db&&H!K}jM?kOaaB^^2;hT8T-o_f4) zSHxr#Qd7zEkb0K~{hc%!=8H=_>?qC?7Nn)B!yT{NK)eOf3$@(<{2}B3$^fKR7>Epz zN+)oFN!hJ`iXr5K+-}w&omo1`l#LMAiz&<0OtMg!8e>sAp8PkQwKVu3(^DJ|#**u% zN8KUzZgsE7quKbUM<&{Us35#ktX!WsfqJDvJ!fU5LAoiV4%>aka<7PLB^tDc;>K7{ zD^km-NAROUGNerz z0ITG;lwyvV;B@9y^Y1wmzOivK`z))`h*wGsv}Vfr`)sBnmff4Vo6qk9b-p+WY8?e& zy;Z`~#jF$kuArqJAhj637D4|@9`xt(XckA!XLX#_Vla(>b(F%TTG<$2T}mniUgbU_ zu>T#jJ`AmgCo$}E1u$x##4DJDCGcR`+RKR|m;=V$3PY&LU&#_l_qK$EFscu40i)9k zSSe6V$>{uxq@oh`lDHX_#OpxtK`~X_CWCa>z0WgVA)4 zcXlVc--6tVNv7>d;l=+AzftW|4ExzUAy`~N+|6jr;s2psgpOR`EZG&5x@OD{J#R00 zKA1epTKwnp7Z3ba{vu-di?2@c7oVEqFaG2|fxp=Q|37~b|IgtsNZtk%!yWKH?d{Ru z#bu~q45%G2ghCcu#>=8Vz_|9^DA%2)8+(o%GmJx& zMm!e6XEi7#He)B{v?r@4dx84-M_w^(hha2eD}IB~=zTY%q3F~eqw(C4cQYD$p^RV= zY(=TZR_KeM_A&n{J>p%AQtxIoyvVpSSq zRrnqCS&bf#)#&zE4F+!iCaduhRN(is8n6CtR>KjianW=>x@msnIOGDV|2z4Od;d@Q z4G8WMF&y%JuRz^f`EK^Yeik|&vim*i9mcU}5(78bj5$mTT*v9Q&t?mKZX@6`8$PSy zQR$f=1<5p|6ox~j^gK>u>-n6#cwF!cXzgzf4`64*h5J0J@6X?=DL@vhToXxHqzoY z49dXvozH8S{J_7R*SHKe1YVn_bw^7CsQ5gHDmG2wBBG83?!(CWhNVsC2 z#)sR2Siar`?h6tZn+3#aI-Yr~OB~cSG-St3MF` z59>P*^2;bL$3uD;y)6F1J%0kz`uVePTh`;xPgP8g+Q-V#cm)RCYENtFhCUvD_TEoV zWH`ZR)zq`q?`V`y>5p~Y9=K`Quo@ud3HsIIv7gEikkd!9b?`;Lte7z2AIo z=6R~;pO$)UA;wcUzeb(rkP{|1Hg?n4MkTuJL6Vx;iHyR}6rvAGjQRm5{&KBvyp69~ zzl{)T(z!Rxc)E@AA>7cKjvh`N&0O6MJ5DYI&mP(hz1MBO0O0+M8%-%-V#8jiA!0Z8 zGL4HaKjSo%q|!xp!+~_`fkLO@fXwAO;Jxe<6Ink3iqo*yRyNrFzQhOH z$pcP9zth^OL8Iyl1QL786Qm$;k`cP*E?jrUNoM9G55$k#$&uF2JITGi+mzkV=`@Vk zty}EGf$}4`>Zyyp`tAh?p{X~D5!JH6K5^{*kkI59`KKB>=QJO1TDOqQG|;>^aHzbw z*J>EQ(w%)}ET4E@$K$?bGW)^ZIQr>P&$cbWKD>$LCLt)mW5(3;81XN7i; z5v0!yzmg69*nY+z=2Y=Ws>PYHCcoZcr&&5Yv5LB6sKD3&r&1F9Xu}=~h zI{U$*<^!p(jRca9!k~TmSDpr>sh@z@QXa|ndK<>b=Z&Sd)3cv~yp8Y9|HZ_BZBrcdhPWi#rL6yo`FqnHK>EP0fye0wv}$sGXQHEA-7ZF zfaZoJ-fK69m>#DYwDs`2DKz|wopqWs*+L9E`iK+cm|3}?C^ zD@de}BrA;M1|t)AbA&j&JN32E0JF6CO!CK0vLp+L;(yr`9XjhqH|yKzRr3p6yWUL7 zuVq!4=m$yM_DcAJ&p63&HgUj>+^?Z5E8!p`2ag#5PM~1GV(X|4kTyi6X8Im@o-MNb zq|aG!24(-T;%x94r*)*fZ}^wo(eu_!W_P*piwB+-EHCYJgX!68Lp*&pGtOv0L_~kX z(A*lLvYl^ZN`JNoaZFlCZ@i<~TZ;i^sWay#My-x}&DLu|6D4Fh)Li0*OvoALAU!_2 zsJSH2KlDyfLxiB&S2IemSNtOS4kY&OfHcoz3N!^W=Yqg${E$Axzh?~b zHpavWpodw{W$0|`Eb*=XUE;x9_pYFQEIX^8{LIwVg3QnQji%J`tO1nsmWQ%QOMcd zv4v_Pao*Cz0t$^^sA#&1 z@kV&Ha4H?nUUMAuWeteY6XP+b^hcx>;TZ2%*=v5K26q7~-DZx9A&$M-eH02G*$4IC zDYeu!lkstMK)X?@?4g|y@+AB#CE3XN7p8Wv+SZBEP978+J&IB6K+>eT(O=Z8=y9pS zLhI{l=vf*HvaF!+02eU)NUzr4T00l?gq|6D5;roNTRFCdxyH1`aE->7#1^(yh%pKh zJ6Y``C8(+C_VwjH)AgNNV`>$fS|6QR3nE6miK#yIny#pybVmI*@qhWYsrkp*fH~F5 zk3i-2nBjBj2I?<9(oKa!3^bJL3lqUYnzH**_3@We$NM>@2+l(pX082*plJroxHp~w zK-`ck^o%czHMXJZjnxy-2vDeHrIBv}djk8A<~TTgfM6hVSoQ(sqx$PiXq~8L8VY?P z`$5*59Vh-ylAybJ=_31vpy0MM-U|?D{NHJEVXOwg5ncj1kcKcYRbb{(locovxqxC7 zDN)q(3H{qu#jK z*2<~(y}VWIz6dlhg7A^(oxu&gz0&7~A}~uZt8cgE?ftXC9zSX0@#I@#W840~hJ8D%O*Bvn|rg(h)$2Wp-%>m|7hYAX)llg2zmm}0&G@07*^l}pIF700$oIN z=rqOD^WbVp_lL@f95HBTHS)9=?laW%)7>!xiYoN3DL4itA28hfAg;tfNzeHMIZOlI zKoWyyAafF%Wh-{NFqGDkKA8_Kk0pVE6wx0??{?iO$wb*p(BdWX#f>B({++ zk{j|M19G!Qx%Ir;;tbCHQ+~E5CtvyLq;!stX>OQx4&x^~0_SKPaGqtK?qKN7xN6Lnxb<2wMw5p9}K%=Oi96 z6L-{%&rF<{Jx!dZ+mARukW-!m&|o0~&-9I$h(Um!g7;{D290Y3!e`b2Ugt&#`NZ@ceJ{~YU%#|cR zeks$hPw@MgEWHSkKNoYfXv;kIp&I)bl;aI(hnVhT!OZ81&7!{{UbHVb-hua-nZxnR zfwN1scF=P#A&cQ+NyB-7c*!1pTnP2@gVDKlM%DK6a(~0LUqx#+ouhQOd5&wsm$wk* zuOZ~tov{-~-RP&$#vWe}vjZVQ;)JEoBu_wCoaUoN%~9yeDI=d2={*@FdEd`_$Zm6l zLwmN?!)+h1TXE<{Sr#!)*{cmLWLs@L3JGMFELPct8_>H%-RN9z{@(q)s>7+)NRhSf zGms+p+FvXE`W93=c2h~JD}+sZB7#($9xHU02i$AVaKk6;*3GgNY2E9Yg|+qvChUE7 zYnMB}gC#AHYfxgFTUThe_Dc292@qWOD~xVVW}A;X4TIV1CK0x+bsyxD5HpX%Y0CKW zCr~HIgK-RtC%X2yxa8(hkddcNBXGxwj5uTXoF?ReE6Bkr|-TmpA~d6l9sXJm-rNnJ2lQ&kRxkE_~hWB##!MKkYAScuf4?1;W=iNVMT$?3?yr9G&uJ7-VB_G9wrzkx}yK z_mDV&Qw*28^1*M;H#+EH)0blLpCpA;rD2ivSsS3FuKek0Q~1hr zn1-H@&AgkZ&+>N)4@bP}0Kp!ivcbdMTxj;lPOJqyvxDZ0wf07Xj=fI9Hc6+=TkPgR zgOsRUyM7b9%rKP0TMg|p0x<$9_yI*|CQHt!s?0qPAqlol-y{?7)HsBU&q886)Dw{I zfv*<*eqqGG>>j6iQ0zz0d&c2r>qHwe! zauE&in7(!cUpEC=lOQkW#&EF97<#^(ie6U2N;&|vpR9nXoAFrQ<1eEby$0eZ1qE2l`fpoq;JBne%z5OlE6*1zyeF=s2ET}q+5G!o= zPnL3VkRFZyPvJ!FoZ64))94%`3}`px1nek34d(|(Yj)?_uS?ifygk%;=cMQZtxY7=|SfG0g;j0@LbI(s=V| za*v^;Xr1L5lIfIx3Ik@V=OAu6ln~f)T@oLhbe6R zq&D`2QvRjRMLHvTa6@09%Ux0wDDu>{G*F9P4&z;F*TWH(Ictr^7`r{`j|mZ4gqp#p zl?YOS3GA|FmF@TVZGaC|mb9AS5vJWEIsQf^Uvn)1ao}|`vnnYq_V^f(m0c0L)!JGc zyLC(GPCmIfi#_sTDSz*d-3pJ361alW=$%mVc^p0Z!nLL9jrKKI zPm_FV{9>aY_YzUH#~!8Uj6)ZbBJJ}Len~5yj}V4;C`zL+>Z!_>Kjm(|8!1kmzGCH7 z9j_bj2J41rQ>QKTD0i&9>}kM3A@x5y#QNW{YDMe|1%z1Q6)K1U>js|bd1~@9>wyBg zq)1llzQYZ~q$PHTu|Vil6Pg)sURc_Y*`}Et4kFY9kBUoS>*cl4{UavkjzCvcH;|(- z(7uk{$1dIt$q_huYlA*v7bVMhbcgl$TNrt05wVvlJj{iKnmJ_y&V#W>Xe;wd`A&OG zF7`XK(T|t!lxckcOJ(GP*=Vk1r&i4|&XSEwAxd!%7~3w&)^1#yJ0glFGjCa(3)cDq z^YCr3+9zn2k1fr$QG#LDqUW0$tU$nevViz?bWS$o_2$=w{3>?oIoDu*oQ&~;BS>gh zFkS1}Bp!)%()8iitovs}$&SF?y$f74%AGQ=Hpgr1CCuU-R4Bli`)#_CTtH{EP=dqh zxi^|XbxX_V_23Q;9pBxLFZI)p2l#4s%v(J+Q&x{1Dfe$Y7DoH{`ZjE1L}=f|udRM< zgmO;vToW~hq#{=PGHN1FvY#|RI42QeGXVMuj6$y%BMf9=!u6tkG*tdTyeZWB$Jhd( zwQ{vpMgu3N=yN9K@tx>?(#bbj-y$ppS}RuWaS~-@JyG{TN~s>4<;3ZVqHaKdv9@;! zuUak~b+0RmCrYjF5VM&63|>Nln?!G_b??P!9(i;)QJuN8My<@6>$jz>dz=?jx}I>_ zJVd(gKowMWh@jdWqgQZ}(YzN@ZW1S#IfRKxJj`n|qDRaPd)TAq22I;WbAz%BZ7F3+ z;q1%}TL(%wo|M@{(9%r>oUVJV+3;@qs!`Ei`yQ7xWYVu)>pkG~MhD!SG_Xb5_~Qd1 zW1oiElepN_%c%@oJZHrTSp3zjL0_VmdFO2zng#pI>unZt>kd3H{OHJKAZ(goqvrdJ zqut!FGONr@$PfOh-i2)w9LPK;SWOG~VD3x2AgGFyN~x$90m$5!dcl^>JwYU;;5PSV zUc^gtPsINv=J|m@|D2`+ircK1Lq>OFCAw2xUOdZ2o{fF_D`ry691I$TvgQGW5Vj7V zLQZ#@${do2Cl1)(LKVEtzSwDa*uEC1s<*#|5k{x|Ik>Gy>~ED~A7X#5*lBpw{#MLs z-E4mjW$8xyTV+n`WA^95_O~jS)(WXy*vdeIm^Xzow<>iKdKsnfTxeP=5V;ayxPuO&FM> z1g9S}&|Lv;@@ih9+S=Gmw2L=^Ez)W(GUPo zcW&2sN~O*q_f&E;{qjXGer+_<@Sxo^l5IU?ZEZSqI8l3K;7PXErVRZc9{blahnq*T zUn5EJNMMjO2DE3nN&+XSXkFvQ?z87Qt?l+Q(!2}W?BzkHb(Ou0$eIc=WiKywOe`sU znZ4-4_VTc^eVJY@i}_K!@bKNf)I6ymW`VQaL|R==SWp3!w?u8&B0N)f&17n%)^#Udl zLZ_*cC#9M$dEYdY0nJ2@6=F{V(^vDr)KIE!Py}Ev0obP@={1GX=PW|n*!6N%d@rkn zHvISW|MGW@@3;2<4YC6Qh3IjoVGvmHK`s}^`PJwDJ$`gPOwu9}dM&oxD5G=Ozntnv zWzHbx1J4mZRj?0Nz_yif{V1)JaTXZ08fcb@`K;X7mgD8MxwMv!+5PaL@FRh- zBQG(Ffo@JI((t9X&s!3^J9}lOEL&o&tpLh!V;_v;)M-8N8b9IjK3HIxXkJWcEwLE14NFK%6vP6>jZlyv1rU?&Pyf0P$=QF-(ltn6>@7sB?oNxr7Z* zUp%UaBe38Zrc73`>!e}Tq(XiRfcYr*L2=l2b!_bG@boz3j|PTLO=PM>PAZr#*?&Z3 z(&6iWlwj`~)~^TOui6e+tZBLDg)9gX$)w?$OMI@m#Bj}XHMvV-%Uk~pmU(Wji5ceP zWi2I$E)mq3|_a+Oi8J^b?TiQ}bx8~-Kh+KnTsy)R{ z3o;1hQb2R*QE}A4v40viw05e^+@H|}qn}UtDgLJT&=T5;m-Sd@5XLL zj^n~*7TX{;IDNbIz!nDJ$eaI@l-KE=s7J;fBBK3ISr4bTIUzq4cn~tTI{^goac?PJqG^!U-EfXM1et{ zsc)O{Y_I?9t23S*^`G55;~7FjIGzYuuGBSb%GhF{$)D34f@ZWWf?lakbkrzWfYbmqeZO;#;^3~iRxk#bzzD?#fi~i>YZ~f*IPTT zBU{1yF=MSM$E55F!x%k`@U`@0kX%^{)0k=40eV7#UfdZt`9{&xGd!va>B$R--*h^V z{HHmIk@3G*e_OF)F#u>fOeg6WGR$La`xwMCI-S68_V+^$Cac~q;;~L9kezymFb9F( zd_1wmw}oNM<0W(fPWLZRwgpbiq3OPk zw7>~n-c}gs5A4ANwV}(pabJ0l-O$CkogKVf>?ng|3HIt_zOT@ZhOzLPmkhTM+M{NA7OXTYi%~t(6>oUVT4#cRv+hrZstAB>rt+2aJB3~X(fd$4>fj>x zhT-FZe2$JdH3w4yh%NZDf?Ee_)Qg|6=#K)hzE;@4#$)Wats_;3s|J8jEq}5EyOY+@ z@%IqPtO_3icYYNi-W#4{v9;2lz*7L5bnEfjAK*cNXT=3Y=8m=d!I>4zEXUeE;9SIoQJpngQ}QzQ=0lvl?2lN$;iGI2s(VI=*EVP$);7QOn&qBlIku?6TY|gLD?wpYuyRCora8SvrUL*%6&>?Fd=G7d9!o?WRFul081u0lA62fx&=Ind8B&Rzq-W zyl2H%5w9hA{5{J`U*d_)mV1hhu=1^Y_j#@tNc-^%Ky`G7{T)O2SA zIG%$9O=swvcGZ9USNf7HE#lz?BFAe((u*e^4lv7!Sf4(_PZCmfqIj4Cgw>QCfc(bj#@jKM_F;Fp%Qo*4^qF* z+OepvP~ou-1|qBJ*u2C@@|RGke9@#SdXeFmW|WWbwr(6M9|}b7_v^f(c3Ufv#sT6^ zi7b6ZiRvk`dK8Nwvv+K>c68?ykC**hdmKUe`QbFhocDC>PhRRYm2w~zzqk$428_Up zoiEJAb^hZ;O^@LLMxeN+-n$0JhfcZGm%EXjnijhWWX76@;dR+0N+%gKeRB(?gJ|ka zpDqj>qX3P4-awR8hQPZ`&on)T8#`@ML1u|7C&7~4FbE`mpedN(}BQoCpkAx^^l)8CinyGSdRxFH02jcL7CNEwavHRZy%zqIZdNfC79jdmmiK9Wm zMf-zlmlI2l<8}l;;eM%5@NZ*X&^*XOegY#nK)C2avr7_cGI79KccrRm%}eG)S5Fh^ z1LXkoUi(7RaRiA%>jCAbcSx!DS*AQx#gVlpXP@2BdsqMX(AmH~73$~^v`A{I$Y238e3+0hH!Gs%+WmN%>~yPQh{gK-~)AT3B&g9(bqU& zUL_M9PlVe(j`GiGu5^;sPU~DJk{UkEXYI)S1V|62Xr~}G|Na8&$K7^B$AOrAB@Zzx zd4-%uu%^h!eUGV|G5dntDb(ZVQ8V>Q6y)!rHB)q^<9lX7O1Uzx5A&UNpDyKU8~!rDBn$YTZLEyT9u2oJ2oJU~THLn;%Yp`Qf5DiHFgF=l+UH z`Kj1&N(YEHm+yu@7k`bK7-`q7UeosBEOs2hA9Hj*z(2(qivE~07fu#9Q72KLPJ-jZ z$!z&n* zPBGzvsZ#;+;P%*qKdO3r;uKu@PHeEW4on@%uJ-%1syq+>Car+F41Jx8vWIwVib!71XYPI*_$~)#D<;ejAs6Htx6#19U0KO z?YyJ)Mv)H1gNUw(@xjJB39p7^)Cg`8U$svrSEs;a7{kC&{MnVS+hbL4|A>V3@_A^) zs%2^}XxLh}f?3hWFaYoZS7B(tU2MD(dv&C7b6AKojZ}si9$+IB8!H0di1T+Nj9(EQ zN2Y3Q`6ONA(E6JM982D;7VV6wYigP>5tLTtU7%sUR&w{03X5r_R*XQW(;thfyovYp zkmNCa{x{#>M|yZGsA(ob{0z&)^P_ zCgQu=E-^>{RtIOh;zQ6vzkyxu`0Vv)Jd{p$)n_=%_L3ZB4T4kW3TcQ)2=k4@c*L7R zJF>`D2Qg_qJ~Ro;19b|KcY>lX;LG!#15h(z#jd8S{9tkN*SA z89|pYBk2Xz`wb1~#%avh(s~xb7zo!5%2F6Y$|UR+9iVLRZ$Ml>sXcjCvxOtwnOPe( zpI6H3G*}pz*N{oJ8>&&kv}NItGk!cWCIXu+HM$1gOWY`aSS zsq_;&oRqg(_q{q16)#19Uh&opK^ox$wNDVl4{4c*b;s5<_RoJ0!IP@5mdw7VBOoG2ev2k@OF;lnzvXKmD�m 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

P7Ry|dL3OFER0TZc7S`(
zo#J|f`gOSZngz+4IA;?Vo^W+rza|!|UzcoJgYOA^Hv-yCY$Y7%R)+Okw5fyy6^`!O
zRW*ZEf`|cKR}>Ld=#E?cR+n>^*3YHN#ch4vJm1xSDwwgQf3SMDmD7lt0OTgF$bgFO
za^z-PG0E-(Cb_IFEZp$WkWl^yYso*tmO@TR$ZFYIbc<}0f=kw~!P=_utAC6}B1)jX2TduTtMp%(>)x-k_e5-s
zocV^_;bCZ(>~lNv7B!cCv+=f}z#gbPjWLG2@=!SKEE4z`DW~%9&hb>P77HFiEJaU+4F?GZ|O*|MDI)6UWWVj((=u>%y<{
z0`957d-h^&BKC7%Sf#6rdE98jth(6W=k36toD4le;68k9$Z*|>D`W@An{_%T?2rg-
z#yW15pS*JUW&MNxo@;3GlWLb6@rQtp-6slWtVDMjBs77s9^XvwWpWGE7euOi{qQ!5
zG>!9{HgQwM&J%C9Z{iiB3XfvADhjlAB
zxi#=kw{7l`l!XnltIO6%(;D|MQwV}HI{PA8?l-M;51+Az7Wbe%5g>S7r+r*ETBWh4
z{H7H4778x`=Gw(=35jGF{~~vEn7^>$J^8`hL9AmW<9m@eWPnWShfT%n)D?R=#-)?>J-^QPt5#tfGI2)AOOF?Hji-ai
zntp(5u{WAPz~lDojgZ3RsS%C1y`y8Vrk41VN`k4S{-n}iYMDQ&
zO!spql?PKR{7DtT)Y1N=(ZSTQ{-m+N)N%f#alzCoe^OO2b%H->LNK+)pHyRwh;I{q
zORnIx{;TjC(<*^b{laxVI0K)h`=Kkr>t4BD@2cO2w{(6Uhivl$OD+hK>hldEqz?GG
zrEm~S4fpkFhvIo)KD1hiWRjMY_&^6(vk*`&6`Q&C_lt^XAz4hzu^lPZ>*tdSw
z!W{ZK+P+$gvb_pAH146V6>lvod>Z;%{?@NLoD6*}d+S$;1%|$sUig*w$qvmSs)qb9
zUhQX=O$@oEbl6|8F2_%N6>n1c$!r|UhqL}`EGp~u>ylgAx;injeiQy`7OZ1K$pqni
zHHSj)+qHI6)iTSd&i{6;x4CJJ^~5B~F(&W=nX+*sCRoNL31+Nj(7v7v1zeb!J?#kV
zU}Alnmmb412|^*ElWv6Z7Ey3GZmohKv_-+4xf|jEH{Nb23}zMiHxTNl*uSATm{sE6
zP!h~4^^xz+D)VnB3ucx3He8%77S#`-sm4Q7q=Zx|QMs`78B3T934
zZ>N^d>vzA@{ugP!sg>Wzq{1mHi0f=
zci>@$tfr`P+-XL^1X;i77g*ZzwE2jZ`2SB&n-NP`zbShe_YA~r-S3#KY^#)LGuG{-
zQg=TBHv*@*A<@6=1GpTh=>KIq-TGuRP_k`2TLpNk`%qT)ANwU?{AL_F_I!08y8wxP
zIt~?|QD`jT`Lo`GdH6BQiYX2w
z`@`$bIfi-Kv)H&E?st*`_<-wQyvM&&6`VLNbF@|~dfuVLo=O}_+`>y7O8j!UVq5G#
z?^0rMznC#|E3yA1Ocmxahm3%7JyxAp9?KsPrpDchEYO
zBbFVovLA$#fX?|(>0%?~K)eCMMm{$q0gb`2zsFnkuu9;RgaCdFcN$(YYQvIm`9DAQ
zQE$i9RKt6(>+JAqPN*6jjo*0IRkJ)^eXQ|-<|g}1Dzo$DTS0GNHsBjv#M0j0fKb3D
z4M+@4T6DfP@K^R|0HsO86Qz-z2V
z%|cMX(fGsO_U~O#f*jlxYu&&X$J?HM
zOS$viylU96p0K=c1;K@FycQc87Ht&!H89;Z=pIqvHxy)<5%ksZ%
zT82Q~s{GNkIDejR?bG@Gf5Fvl({jyU(EpEJ(EoD(#tN_Xga1YU&!WR{{U7ard92qO
zd*}W~i!na%r1D4oe+DvtexVGZ)evm{eW>nn{+Fw~RdO|znLL5N{74Buv~!I=H~y9J
z2hs;F2siDmQpCpeD;XDdIx`twkOo&N($4r-^d2rjRY|(f`#HQ%pUX@xb#Keu+pj2h
zl)v14sc>I@73Cl8-XbklE$1x1>KW(W!;bO3buye)wB8`w61yQJ%NxS)fo&8-t}%In
z|LvL_7iE0doQIZ>Q7-kkh;~9A;PY^ol)jJOoUZW4dbfXkccd-+5f0#U{@3v(j|2FT
zMC_aYG39!7r+zY1ZWS9f{#*yWTArD*P>{$>`H~>?<>waq1NTUo(i1;%+W#zv;%Uiq
z@rIGXXXhV#iTEH>Zu3;OJ2PdpT*qgojCDWb+)tH$9PzRJ9zT0O{fg@;L6W@GjWkId
zzl`Z_U2i-%Be>!uZV)kzzPzb^{J77JV$)Q_f3iI?yi+
zTviL;Az|$UvJ3F>?Cc7ts06%|+{W|UPa^4ye;#%XZ(degls%j>&UTtd$$+C#q=8}l
zdg7BV+J7pI;-z@m<=7u4voUW+{Q5=sqpz&H9^*MAFV{C_8Li)RKXU)Miz-L*fcO5n
zPN!+%=eYDUfzv&{i!%>OOVN(1nA>yfD^Dg*7NCRR5Y>OW=X8Ghxd5nbRhCTs1N_bN
zr+2jbGqx76DYGKHae%oH`y$Hf@bHe7Io7
z79B{Id((Pj3m!=R^VS$jMpxbPpD>4HYJAS$XLq%BNXR8Uy4q-eL
z-egyjcw>rzJ`)STO!vHWNzcFtOj+=z(mXaeUJcD)gL&}{F1rNJK4ND511`}Q)iw2
zQ!>n8gilflUO#ZaML`YpRSckCy6C(uICew31-ssLLCJ=Vat%FXCf+A`wuITSIa_sD
zKOOhd?*|0wbK%qi?^7APq39?k0h%|=x1Sxws(ATMN3s1U?HGn{zsJ;1O_%I7>=xo<
z^q9u_!gwvwb9&K!O03hJU=$lud*`ZAuXkh@rHjJxi}UTV7tH#=kI
za2G8;rta@XN8-Estu{F`3~PG}^g;hKv~SeXkyLHo%aV1OrZ%pTClxp#tUpw}xA8~F
zUhvF%%jXwxzoRf(S>RLlN9>AVr8W{zMFYE6^$N5|R`tsW_Q2SE#>7@&szf~$sST-_qt+5Zm9ew1$H{2zGe45tQ;4p6f
z#P2IkuGgAWb=Q`$G*Z7)GMB?kckA(6oSK|B3Myj+=Z;CqL)+-tzf9!S~fk%U10gn`u9oie`y`=JFo)dqIvpZB2h~Eib8o5Tzi)
z++8m8*7kVY9}SB_F$9@w9Y=Jnm;|1TU7}Aypku9VZV3Jz^e}pv{RU~ahld2g;s_Lk
z-l0D6JXt(`leJXnmbswP>>%R|=9LivYpXi7Q-Uy#J|)4DsxhB0CAO~G4CC9`0|>S>
zwJKXghGBE3q&x52hNNkJL8@ZCy39eHAN@`v|8joo^vqseOEY!BO3N0Mq#!5IGMf5Sou(@tEK-oL@12{F}i8CdnY%*kkX<
z_>|vK$wwAK8=doONPS-W9H3e`=(CouLn+F!rUnT{v0o;pV{~%hYD!m7>l*TsET^Ml
zE+pur-C&V`XL(PC0^Hv
zQX)%K?_FBVSnFbhz^E(m?D_yBXmOdLzVdmi2?5{FE5s
zu%;`P@j7cJ_WWk#BEKdl+5MGNR6V1JfpC}-HUYTf^^21a-EgNkpkJx_l1Qa%`im52qovtk+_h$>peNoRZM^+w2(TCN&E(SWE
zn7RTM))X*~8vKeOJ_zQaYQ#Rp>->h*f82ioW=YN~T|cY`A$KZ~b{doXcVxYqry7Kz
zbO`BaCA!a#ne!&3q^7St%V_D;GR`(qW6Gu(FBalN&Vs;l?_UIEFnD>J|I^y~gEbU<
ztvo98s+BouWf<4Iit~=T0neavpVs?Qd+%*7tFQt!74{{lsR+xyo^!+Lyh>RE1&UN$
zRuRmripm|1+M&J|gLxCq7ttiB2yKkRbp0-&Y6kCS$`6;R7-Tlp>SFt}%yse_J>4S!x
zav6_6lBLlCQ)IHz62Fj@9M@x9OM1ja@Y+j{WB*J>k=JueB)2|@KcCq7b0~g(Ls2e|
zj&+MIezo>cIEQ;q>^wvkpS8F36A*&ncKDS{yaG@DI10`iggxs$!S%dHW!vf0ls%5^
z6B3TiA6P~yA~O7*fknlLQq;R>L|)-Krg-4B7oK{DJ60pU#Yh;!#h<
zxbf$;yvRCBp1r#LYOS}{gZkUG+8DRZyK_f_#ogcMki7VS{)#0*2Z|9btwJI52LAAI
z>-a*_{@#J#@YM6`LKyezy}{QDm;FsH+mrgBx67vGUhQe$Ax>KpA(K%0_hpYUev&6efJTF!q{jdm?8
z)4OW(jX2@Er`80$k-_c4Pe$L#vU`}9=recgL`_K?7_0IvLfMXVRkD>XFX0`cr!Lq{ibD{aw#>y8B-P^r!kMNlgtKzP2tbM6UIX}ACX^L+laX72mk=jA%*T<1F1xz6>%A0M!OgFs=MmN9&u8=z%;w$V}#zS?<8~r
zt-I5o8Q!tu>5i@6S&h}(3p@G<7h&&%62>}HXRL_X?@%eZ-0508yAwA-cOFK;qi*)S
zoMx-ji+{)TCVs0?)6{w$#k6JzL5EyX_{^uw{N#`_s#uTWeGHmT;*p@&`WWP~Shdv(
zZ6Z=g0|Cct414wWl0Xyc5KqB`nM;wKC2GN#t@|eb!Jnui+*Zj`s=-%
zgJ`a?P%uA^L^A=%MdDGR!^1j^Cw?n_HD+)L+eu+dQ8{No4l%~>LLm59!hF1-zQpNR
zXAUSO@DE`sGz@`f9WxmCRF&Yn`zuszEeL;WRz_qw_>7#Xz~C`KP`*NVR-nB+ACS~H
z_{C7B?cbz~SEOEZ3Nl@`dh<(uBIMN)A*2`Wj_bkOnQ$I#szfdstSs*_%A`5|SvR-b
z#5gNgQG~6z)C=BXY84w=6D1{z+TuXQTX+kXx_O3#`1gn8fQ|bbDu^QNfSGk3{7@{J
zj}{eXj>nt_zjZ=v2FN#eOw53MmOcYNAWx2i0Zz=pzzfxx)@_sz1r!QB7>xz#ADFlJ)VN&ze4bX|&qXp(TX2c`u!PwyTWE4V3wiR!m~d_^VL
zdGKQY#1QeLPeubA(jf;p32rkx_!O!2(d;;DVBh
zm515$;?bW{M_>fI8AX5T>a+YP8};|3iddO*f0YBQWOo@%=b$(FSEY7N4+9xV8
zf4#?KOE3Mor|=&y2UW%~)BUKNI`_Be51h7hKidctPb^S6a2T%=eV|B5jDRz4+UcrW*phu+D-r4P^miWm(MRQm
z=wYPn#=)+qnBq9<#26br=ET=PzHWHtIpB=-T$KF^xj1ZL
zW4Y?Hbbxw`yme7wma%RQ9Mqh|@O$y#x%g!2go$PHJDUo+ow&A#f%(We5OK$j1Y17G
z8mc**Hj`)(2PKDC5`RI31)PTiJveZBkTQ_Ol1ZHw1
z=(@%3ym%dITXl=UuDTZ5ij4$Ye?|S%2OnaVmQH_%TcqwpQ+$5)Hf)3jP#vST>YY;V
zv7Qmgzqfij-l%CUQMa+KuWq9h-4Iu-Ya%Hxc93mvU7a^epb1E{&YbmibuEb%H9zIm
zU9--BzQ(+<@3yogUkSP^s`L@NUN3H*PMY_AIqc2}n;^b)4jl?QSQoF$*3u}0Uqx`1
zy2KQ3akPoBg~=oNdE7x1RgZEx@SZvUIbs^kmYu1l%EApIL0QEXH3Ep~gWNi-SR>%7
zlk|OyyFPM9#LCS#QSQ#_Z2U6`ZL42c
zdI(CTtlW0wO>5t@kH?8bR_BrS*X|>ggZ?J{+!gKSM>LR>E%vd3$0zLL45uq5*58XP
zThRTFuTn8os=x5kFhl-`Y}T~7RXXwCl06ZVTP`@?>s-sA(r<}hf|=jJcgG&7(i{!
zn*XEmoGfiY12{-pl^bvOV63}T{c8B+_=qwo{s@svjFblCU4!u9+jDjKI50j40jfDU
z_I^w>d)_klnPJ7HRY;gh??$vwKSz8LmXuCJ|A~s5al-Z?xBiKNe|Q21=Q57z=N@-S
z<%z{jGU{x7*6!c4Vg6cX;nA$|qGMj<4E(0Qg%TT#$Sc_74G=rS3x6i?7W!e#D1kQ>
z#Oat(Ky~VOFsc%zRo6e;x*k4Jo(fPUg`b{v%)CCK6I}3%M76+N#rwvwL=YWnj8H1^
z&zuN0=<*aj&yiSnSWqA8#H{k4i->f35HeA$q#=?LS5@X>k#3RYBN`g)Z3=`ro9HQ9
z+-sjD7TiU2Aaaq;AoDI7^CDjx7-e-2i~`^+XFW;fz*=H3puQ3WpzyWNGG8?DJ(|eX
z|EycT!JBD@aK34|-~W;{21-x3OP(OULstouefTtg|LQ_QBKFK^m3G~fAAt8fghVCL
z`s@IX${56Ecitsq?o;JM7t~k6n#~%68(qSjR7fB7Duvn#c1ekkE0I7qd7L%9v7^ML
zf|32$EJ$Br1Aol@7?*YC(53q^g6%1C!6tBu7>{=O*F#f95IFg6;0NQOl<={54&~){
zmJmb}az<>9+ioyJUl|WxDDsjKbf7?l??W
zVLz59;3&=05dJ#yrV=!Qm_Jg@tLfUX*$E%|M)7+zX!lX&37sHedCQehY
z1S*@kyCBMs!qe<6;DpsLzf*F6f;%u{Z>oE|z>t&+W7!Wno!LSY=f`*$y?k?GNl7(Y
z_LH5J!=hYc+*_JSde*zm^c~OoIA+TZOMUd{V0k}Y(x>t1T@j7CiD=YqdUxlJSKPNu|6DHMS({vA
zq2i9tr}3TD>mEEjY`v*swc@Oo+fnm%8#ZC*(RIjOGO(Q$oGo%XH`7G|6&;zj^t>HM
z+mGhe%5sW7OG}#3Zy9v{ddKhw5=)*nY30TWro@FG&`REfOtys9v1^j9s*yduAU7NM
z`^t?bS_N}CLKkm_GSs6u=XAURrrMQH@pGHq`kys-bQ2)Mi#$%3;kSR!UmS;U{4-{2
z&Wbufgg$V5E0JLF1ys8YiudPkf!jwv4Wc-!uBK|1%iigTbNiE{XP~LU>porx%4%_!
zQ9c9oMOP#9`!5NQ5<1sczgJBYP|Ez&g0Rf*48ZQIzemrT*<#n%6Ury#LrDjKzO=l*
zxj+kcR%v2M#ab`Y#ZA69o8q}1qx>F(9Id)YoxbW>FbFMap7V_1Tx@@Uk5}}#le48H
zdciI%`ut9ROtt*+PHW*WZ8ja|8sSgraz0&tcW0a3B|q~!Ioy`xXKlEx@0xI1OEzS`
z!76TUKl)^ib^E|rJ|+zD2UNu1=TMJkxAkWx-BuSE{uCKyQoEP#=EinHnQv1DITQ2m
zx6ha@n*l5HFFLQFYL1#pL#av3bK<|UiGB6rrrF7hjeb8wA$0iKC!F<*I3D*wbId!K
z(tzWj@~4t}ry#kNVQ2*i!;&{7dM_#

}#i{~|nB-6zSwGaRHWJ~`gk0`9dN64&k< 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 + \ No newline at end of file diff --git a/src/Discord.Net/API/DiscordAPI.cs b/src/Discord.Net/API/DiscordAPI.cs deleted file mode 100644 index 894c609dd..000000000 --- a/src/Discord.Net/API/DiscordAPI.cs +++ /dev/null @@ -1,152 +0,0 @@ -using Discord.API.Models; -using Discord.Helpers; -using System; -using System.IO; -using System.Threading.Tasks; - -namespace Discord.API -{ - internal class DiscordAPI - { - public const int MaxMessageSize = 2000; - private readonly JsonHttpClient _http; - - public DiscordAPI(JsonHttpClient http) - { - _http = http; - } - - //Auth - public Task GetWebSocketEndpoint() - => _http.Get(Endpoints.Gateway); - public async Task LoginAnonymous(string username) - { - var fingerprintResponse = await _http.Post(Endpoints.AuthFingerprint).ConfigureAwait(false); - var registerRequest = new APIRequests.AuthRegisterRequest { Fingerprint = fingerprintResponse.Fingerprint, Username = username }; - var registerResponse = await _http.Post(Endpoints.AuthRegister, registerRequest).ConfigureAwait(false); - return registerResponse; - } - public async Task Login(string email, string password) - { - var request = new APIRequests.AuthLogin { Email = email, Password = password }; - var response = await _http.Post(Endpoints.AuthLogin, request).ConfigureAwait(false); - return response; - } - public Task Logout() - => _http.Post(Endpoints.AuthLogout); - - //Servers - public Task CreateServer(string name, string region) - { - var request = new APIRequests.CreateServer { Name = name, Region = region }; - return _http.Post(Endpoints.Servers, request); - } - public Task LeaveServer(string id) - => _http.Delete(Endpoints.Server(id)); - - //Channels - public Task CreateChannel(string serverId, string name, string channelType) - { - var request = new APIRequests.CreateChannel { Name = name, Type = channelType }; - return _http.Post(Endpoints.ServerChannels(serverId), request); - } - public Task CreatePMChannel(string myId, string recipientId) - { - var request = new APIRequests.CreatePMChannel { RecipientId = recipientId }; - return _http.Post(Endpoints.UserChannels(myId), request); - } - public Task DestroyChannel(string channelId) - => _http.Delete(Endpoints.Channel(channelId)); - public Task GetMessages(string channelId, int count) - => _http.Get(Endpoints.ChannelMessages(channelId, count)); - - //Members - public Task Kick(string serverId, string memberId) - => _http.Delete(Endpoints.ServerMember(serverId, memberId)); - public Task Ban(string serverId, string memberId) - => _http.Put(Endpoints.ServerBan(serverId, memberId)); - public Task Unban(string serverId, string memberId) - => _http.Delete(Endpoints.ServerBan(serverId, memberId)); - - //Invites - public Task CreateInvite(string channelId, int maxAge, int maxUses, bool isTemporary, bool hasXkcdPass) - { - var request = new APIRequests.CreateInvite { MaxAge = maxAge, MaxUses = maxUses, IsTemporary = isTemporary, HasXkcdPass = hasXkcdPass }; - return _http.Post(Endpoints.ChannelInvites(channelId), request); - } - public Task GetInvite(string id) - => _http.Get(Endpoints.Invite(id)); - public Task AcceptInvite(string id) - => _http.Post(Endpoints.Invite(id)); - public Task DeleteInvite(string id) - => _http.Delete(Endpoints.Invite(id)); - - //Chat - public Task SendMessage(string channelId, string message, string[] mentions, string nonce) - { - var request = new APIRequests.SendMessage { Content = message, Mentions = mentions, Nonce = nonce }; - return _http.Post(Endpoints.ChannelMessages(channelId), request); - } - public Task EditMessage(string channelId, string messageId, string message, string[] mentions) - { - var request = new APIRequests.EditMessage { Content = message, Mentions = mentions }; - return _http.Patch(Endpoints.ChannelMessage(channelId, messageId), request); - } - public Task SendIsTyping(string channelId) - => _http.Post(Endpoints.ChannelTyping(channelId)); - public Task DeleteMessage(string channelId, string msgId) - => _http.Delete(Endpoints.ChannelMessage(channelId, msgId)); - public Task SendFile(string channelId, Stream stream, string filename = null) - => _http.File(Endpoints.ChannelMessages(channelId), stream, filename); - - //Voice - public Task GetVoiceRegions() - => _http.Get(Endpoints.VoiceRegions); - public Task GetVoiceIce() - => _http.Get(Endpoints.VoiceIce); - public Task Mute(string serverId, string memberId) - { - var request = new APIRequests.SetMemberMute { Mute = true }; - return _http.Patch(Endpoints.ServerMember(serverId, memberId)); - } - public Task Unmute(string serverId, string memberId) - { - var request = new APIRequests.SetMemberMute { Mute = false }; - return _http.Patch(Endpoints.ServerMember(serverId, memberId)); - } - public Task Deafen(string serverId, string memberId) - { - var request = new APIRequests.SetMemberDeaf { Deaf = true }; - return _http.Patch(Endpoints.ServerMember(serverId, memberId)); - } - public Task Undeafen(string serverId, string memberId) - { - var request = new APIRequests.SetMemberDeaf { Deaf = false }; - return _http.Patch(Endpoints.ServerMember(serverId, memberId)); - } - - //Profile - public Task ChangeUsername(string newUsername, string currentEmail, string currentPassword) - { - var request = new APIRequests.ChangeUsername { Username = newUsername, CurrentEmail = currentEmail, CurrentPassword = currentPassword }; - return _http.Patch(Endpoints.UserMe, request); - } - public Task ChangeEmail(string newEmail, string currentPassword) - { - var request = new APIRequests.ChangeEmail { NewEmail = newEmail, CurrentPassword = currentPassword }; - return _http.Patch(Endpoints.UserMe, request); - } - public Task ChangePassword(string newPassword, string currentEmail, string currentPassword) - { - var request = new APIRequests.ChangePassword { NewPassword = newPassword, CurrentEmail = currentEmail, CurrentPassword = currentPassword }; - return _http.Patch(Endpoints.UserMe, request); - } - public Task ChangeAvatar(AvatarImageType imageType, byte[] bytes, string currentEmail, string currentPassword) - { - string base64 = Convert.ToBase64String(bytes); - string type = imageType == AvatarImageType.Jpeg ? "image/jpeg;base64" : "image/png;base64"; - var request = new APIRequests.ChangeAvatar { Avatar = $"data:{type},/9j/{base64}", CurrentEmail = currentEmail, CurrentPassword = currentPassword }; - return _http.Patch(Endpoints.UserMe, request); - } - } -} diff --git a/src/Discord.Net/API/Endpoints.cs b/src/Discord.Net/API/Endpoints.cs deleted file mode 100644 index ca681408e..000000000 --- a/src/Discord.Net/API/Endpoints.cs +++ /dev/null @@ -1,56 +0,0 @@ -namespace Discord.API -{ - internal static class Endpoints - { - public static readonly string BaseUrl = "discordapp.com"; - public static readonly string BaseShortUrl = "discord.gg"; - public static readonly string BaseHttps = $"https://{BaseUrl}"; - public static readonly string BaseShortHttps = $"https://{BaseShortUrl}"; - - // /api - public static readonly string BaseApi = $"{BaseHttps}/api"; - //public static readonly string Track = $"{BaseApi}/track"; - public static readonly string Gateway = $"{BaseApi}/gateway"; - - // /api/auth - public static readonly string Auth = $"{BaseApi}/auth"; - public static readonly string AuthFingerprint = $"{Auth}fingerprint"; - public static readonly string AuthRegister = $"{Auth}/register"; - public static readonly string AuthLogin = $"{Auth}/login"; - public static readonly string AuthLogout = $"{Auth}/logout"; - - // /api/channels - public static readonly string Channels = $"{BaseApi}/channels"; - public static string Channel(string channelId) => $"{Channels}/{channelId}"; - public static string ChannelTyping(string channelId) => $"{Channels}/{channelId}/typing"; - public static string ChannelMessages(string channelId) => $"{Channels}/{channelId}/messages"; - public static string ChannelMessages(string channelId, int limit) => $"{Channels}/{channelId}/messages?limit={limit}"; - public static string ChannelMessage(string channelId, string msgId) => $"{Channels}/{channelId}/messages/{msgId}"; - public static string ChannelInvites(string channelId) => $"{Channels}/{channelId}/invites"; - - // /api/guilds - public static readonly string Servers = $"{BaseApi}/guilds"; - public static string Server(string serverId) => $"{Servers}/{serverId}"; - public static string ServerChannels(string serverId) => $"{Servers}/{serverId}/channels"; - public static string ServerMember(string serverId, string userId) => $"{Servers}/{serverId}/members/{userId}"; - public static string ServerBan(string serverId, string userId) => $"{Servers}/{serverId}/bans/{userId}"; - - // /api/invites - public static readonly string Invites = $"{BaseApi}/invite"; - public static string Invite(string id) => $"{Invites}/{id}"; - - // /api/users - public static readonly string Users = $"{BaseApi}/users"; - public static string UserMe => $"{Users}/@me"; - public static string UserChannels(string userId) => $"{Users}/{userId}/channels"; - public static string UserAvatar(string userId, string avatarId) => $"{Users}/{userId}/avatars/{avatarId}.jpg"; - - // /api/voice - public static readonly string Voice = $"{BaseApi}/voice"; - public static readonly string VoiceRegions = $"{Voice}/regions"; - public static readonly string VoiceIce = $"{Voice}/ice"; - - //Website - public static string InviteUrl(string code) => $"{BaseShortHttps}/{code}"; - } -} diff --git a/src/Discord.Net/API/Models/APIResponses.cs b/src/Discord.Net/API/Models/APIResponses.cs deleted file mode 100644 index 3f2a1d3e2..000000000 --- a/src/Discord.Net/API/Models/APIResponses.cs +++ /dev/null @@ -1,101 +0,0 @@ -//Ignore unused/unassigned variable warnings -#pragma warning disable CS0649 -#pragma warning disable CS0169 - -using Newtonsoft.Json; -using System; - -namespace Discord.API.Models -{ - internal static class APIResponses - { - public class Gateway - { - [JsonProperty(PropertyName = "url")] - public string Url; - } - - public class AuthFingerprint - { - [JsonProperty(PropertyName = "fingerprint")] - public string Fingerprint; - } - public class AuthRegister : AuthLogin { } - public class AuthLogin - { - [JsonProperty(PropertyName = "token")] - public string Token; - } - - public class CreateServer : ServerInfo { } - public class DeleteServer : ServerInfo { } - - public class CreateChannel : ChannelInfo { } - public class DestroyChannel : ChannelInfo { } - - public class CreateInvite : GetInvite - { - [JsonProperty(PropertyName = "max_age")] - public int MaxAge; - [JsonProperty(PropertyName = "max_uses")] - public int MaxUses; - [JsonProperty(PropertyName = "revoked")] - public bool IsRevoked; - [JsonProperty(PropertyName = "temporary")] - public bool IsTemporary; - [JsonProperty(PropertyName = "uses")] - public int Uses; - [JsonProperty(PropertyName = "created_at")] - public DateTime CreatedAt; - } - - public class GetInvite - { - [JsonProperty(PropertyName = "inviter")] - public UserReference Inviter; - [JsonProperty(PropertyName = "guild")] - public ServerReference Server; - [JsonProperty(PropertyName = "channel")] - public ChannelReference Channel; - [JsonProperty(PropertyName = "code")] - public string Code; - [JsonProperty(PropertyName = "xkcdpass")] - public string XkcdPass; - } - public class AcceptInvite : GetInvite { } - - public class SendMessage : Message { } - public class EditMessage : Message { } - public class GetMessages : Message { } - - public class GetRegions - { - [JsonProperty(PropertyName = "sample_hostname")] - public string Hostname; - [JsonProperty(PropertyName = "sample_port")] - public int Port; - [JsonProperty(PropertyName = "id")] - public string Id; - [JsonProperty(PropertyName = "name")] - public string Name; - } - - public class GetIce - { - [JsonProperty(PropertyName = "ttl")] - public string TTL; - [JsonProperty(PropertyName = "servers")] - public Server[] Servers; - - public class Server - { - [JsonProperty(PropertyName = "url")] - public string URL; - [JsonProperty(PropertyName = "username")] - public string Username; - [JsonProperty(PropertyName = "credential")] - public string Credential; - } - } - } -} diff --git a/src/Discord.Net/Audio/Opus.cs b/src/Discord.Net/Audio/Opus.cs new file mode 100644 index 000000000..b2c550e93 --- /dev/null +++ b/src/Discord.Net/Audio/Opus.cs @@ -0,0 +1,71 @@ +using System; +using System.Runtime.InteropServices; + +namespace Discord.Audio +{ + internal unsafe class Opus + { + [DllImport("lib/opus", EntryPoint = "opus_encoder_create", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr CreateEncoder(int Fs, int channels, int application, out Error error); + [DllImport("lib/opus", EntryPoint = "opus_encoder_destroy", CallingConvention = CallingConvention.Cdecl)] + public static extern void DestroyEncoder(IntPtr encoder); + [DllImport("lib/opus", EntryPoint = "opus_encode", CallingConvention = CallingConvention.Cdecl)] + public static extern int Encode(IntPtr st, byte* pcm, int frame_size, byte* data, int max_data_bytes); + + /*[DllImport("lib/opus", EntryPoint = "opus_decoder_create", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr CreateDecoder(int Fs, int channels, out Errors error); + [DllImport("lib/opus", EntryPoint = "opus_decoder_destroy", CallingConvention = CallingConvention.Cdecl)] + public static extern void DestroyDecoder(IntPtr decoder); + [DllImport("lib/opus", EntryPoint = "opus_decode", CallingConvention = CallingConvention.Cdecl)] + public static extern int Decode(IntPtr st, byte[] data, int len, IntPtr pcm, int frame_size, int decode_fec);*/ + + [DllImport("lib/opus", EntryPoint = "opus_encoder_ctl", CallingConvention = CallingConvention.Cdecl)] + public static extern int EncoderCtl(IntPtr st, Ctl request, int value); + + public enum Ctl : int + { + SetBitrateRequest = 4002, + GetBitrateRequest = 4003, + SetInbandFECRequest = 4012, + GetInbandFECRequest = 4013 + } + + ///

Supported coding modes. + public enum Application : int + { + /// + /// Gives best quality at a given bitrate for voice signals. It enhances the input signal by high-pass filtering and emphasizing formants and harmonics. + /// Optionally it includes in-band forward error correction to protect against packet loss. Use this mode for typical VoIP applications. + /// Because of the enhancement, even at high bitrates the output may sound different from the input. + /// + Voip = 2048, + /// + /// Gives best quality at a given bitrate for most non-voice signals like music. + /// Use this mode for music and mixed (music/voice) content, broadcast, and applications requiring less than 15 ms of coding delay. + /// + Audio = 2049, + /// Low-delay mode that disables the speech-optimized mode in exchange for slightly reduced delay. + Restricted_LowLatency = 2051 + } + + public enum Error : int + { + /// No error. + OK = 0, + /// One or more invalid/out of range arguments. + BadArg = -1, + /// The mode struct passed is invalid. + BufferToSmall = -2, + /// An internal error was detected. + InternalError = -3, + /// The compressed data passed is corrupted. + InvalidPacket = -4, + /// Invalid/unsupported request number. + Unimplemented = -5, + /// An encoder or decoder structure is invalid or already freed. + InvalidState = -6, + /// Memory allocation has failed. + AllocFail = -7 + } + } +} diff --git a/src/Discord.Net/lib/Opus/OpusEncoder.cs b/src/Discord.Net/Audio/OpusEncoder.cs similarity index 79% rename from src/Discord.Net/lib/Opus/OpusEncoder.cs rename to src/Discord.Net/Audio/OpusEncoder.cs index 06db38e49..56f48e3f8 100644 --- a/src/Discord.Net/lib/Opus/OpusEncoder.cs +++ b/src/Discord.Net/Audio/OpusEncoder.cs @@ -1,11 +1,11 @@ using System; -namespace Discord.Opus +namespace Discord.Audio { /// Opus codec wrapper. - public class OpusEncoder : IDisposable + internal class OpusEncoder : IDisposable { - private readonly IntPtr _encoder; + private readonly IntPtr _encoderPtr; /// Gets the bit rate of the encoder. public const int BitRate = 16; @@ -22,7 +22,7 @@ namespace Discord.Opus /// Gets the bytes per frame. public int FrameSize { get; private set; } /// Gets the coding mode of the encoder. - public Application Application { get; private set; } + public Opus.Application Application { get; private set; } /// Creates a new Opus encoder. /// Sampling rate of the input signal (Hz). Supported Values: 8000, 12000, 16000, 24000, or 48000. @@ -30,7 +30,7 @@ namespace Discord.Opus /// Length, in milliseconds, that each frame takes. Supported Values: 2.5, 5, 10, 20, 40, 60 /// Coding mode. /// A new OpusEncoder - public OpusEncoder(int samplingRate, int channels, int frameLength, Application application) + public OpusEncoder(int samplingRate, int channels, int frameLength, Opus.Application application) { if (samplingRate != 8000 && samplingRate != 12000 && samplingRate != 16000 && samplingRate != 24000 && @@ -47,9 +47,9 @@ namespace Discord.Opus SamplesPerFrame = samplingRate / 1000 * FrameLength; FrameSize = SamplesPerFrame * SampleSize; - Error error; - _encoder = API.opus_encoder_create(samplingRate, channels, (int)application, out error); - if (error != Error.OK) + Opus.Error error; + _encoderPtr = Opus.CreateEncoder(samplingRate, channels, (int)application, out error); + if (error != Opus.Error.OK) throw new InvalidOperationException($"Error occured while creating encoder: {error}"); SetForwardErrorCorrection(true); @@ -68,24 +68,25 @@ namespace Discord.Opus int result = 0; fixed (byte* inPtr = pcmSamples) fixed (byte* outPtr = outputBuffer) - result = API.opus_encode(_encoder, inPtr + offset, SamplesPerFrame, outPtr, outputBuffer.Length); + result = Opus.Encode(_encoderPtr, inPtr + offset, SamplesPerFrame, outPtr, outputBuffer.Length); if (result < 0) - throw new Exception("Encoding failed: " + ((Error)result).ToString()); + throw new Exception("Encoding failed: " + ((Opus.Error)result).ToString()); return result; } /// Gets or sets whether Forward Error Correction is enabled. public void SetForwardErrorCorrection(bool value) { - if (_encoder == IntPtr.Zero) + if (disposed) throw new ObjectDisposedException("OpusEncoder"); - var result = API.opus_encoder_ctl(_encoder, Ctl.SetInbandFECRequest, value ? 1 : 0); + var result = Opus.EncoderCtl(_encoderPtr, Opus.Ctl.SetInbandFECRequest, value ? 1 : 0); if (result < 0) - throw new Exception("Encoder error: " + ((Error)result).ToString()); + throw new Exception("Encoder error: " + ((Opus.Error)result).ToString()); } + #region IDisposable private bool disposed; public void Dispose() { @@ -94,8 +95,8 @@ namespace Discord.Opus GC.SuppressFinalize(this); - if (_encoder != IntPtr.Zero) - API.opus_encoder_destroy(_encoder); + if (_encoderPtr != IntPtr.Zero) + Opus.DestroyEncoder(_encoderPtr); disposed = true; } @@ -103,5 +104,6 @@ namespace Discord.Opus { Dispose(); } + #endregion } } \ No newline at end of file diff --git a/src/Discord.Net/Collections/AsyncCollection.cs b/src/Discord.Net/Collections/AsyncCollection.cs new file mode 100644 index 000000000..21c5be04d --- /dev/null +++ b/src/Discord.Net/Collections/AsyncCollection.cs @@ -0,0 +1,118 @@ +using System; +using System.Collections; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; + +namespace Discord.Collections +{ + public abstract class AsyncCollection : IEnumerable + where TValue : class + { + private static readonly object _writerLock = new object(); + + internal class CollectionItemEventArgs : EventArgs + { + public TValue Item { get; } + public CollectionItemEventArgs(TValue item) { Item = item; } + } + + internal EventHandler ItemCreated; + private void RaiseItemCreated(TValue item) + { + if (ItemCreated != null) + ItemCreated(this, new CollectionItemEventArgs(item)); + } + internal EventHandler ItemUpdated; + protected void RaiseItemUpdated(TValue item) + { + if (ItemUpdated != null) + ItemUpdated(this, new CollectionItemEventArgs(item)); + } + internal EventHandler ItemDestroyed; + private void RaiseItemDestroyed(TValue item) + { + if (ItemDestroyed != null) + ItemDestroyed(this, new CollectionItemEventArgs(item)); + } + + protected readonly DiscordClient _client; + protected readonly ConcurrentDictionary _dictionary; + + protected AsyncCollection(DiscordClient client) + { + _client = client; + _dictionary = new ConcurrentDictionary(); + } + + protected TValue Get(string key) + { + TValue result; + if (!_dictionary.TryGetValue(key, out result)) + return null; + return result; + } + protected TValue GetOrAdd(string key, Func createFunc) + { + TValue result; + if (_dictionary.TryGetValue(key, out result)) + return result; + + lock (_writerLock) + { + TValue newItem = createFunc(); + result = _dictionary.GetOrAdd(key, newItem); + if (result == newItem) + { + OnCreated(newItem); + RaiseItemCreated(result); + } + } + return result; + } + protected TValue TryRemove(string key) + { + if (_dictionary.ContainsKey(key)) + { + lock (_writerLock) + { + TValue result; + if (_dictionary.TryRemove(key, out result)) + return result; + } + } + return null; + } + protected TValue Remap(string oldKey, string newKey) + { + if (_dictionary.ContainsKey(oldKey)) + { + lock (_writerLock) + { + TValue result; + if (_dictionary.TryRemove(oldKey, out result)) + _dictionary[newKey] = result; + return result; + } + } + return null; + } + protected internal void Clear() + { + lock (_writerLock) + _dictionary.Clear(); + } + + protected abstract void OnCreated(TValue item); + protected abstract void OnRemoved(TValue item); + + public IEnumerator GetEnumerator() + { + return _dictionary.Select(x => x.Value).GetEnumerator(); + } + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} diff --git a/src/Discord.Net/Collections/Channels.cs b/src/Discord.Net/Collections/Channels.cs new file mode 100644 index 000000000..959a12087 --- /dev/null +++ b/src/Discord.Net/Collections/Channels.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Discord.Collections +{ + public sealed class Channels : AsyncCollection + { + internal Channels(DiscordClient client) + : base(client) { } + + internal Channel GetOrAdd(string id, string serverId, string recipientId = null) => GetOrAdd(id, () => new Channel(_client, id, serverId, recipientId)); + internal new Channel TryRemove(string id) => base.TryRemove(id); + + protected override void OnCreated(Channel item) + { + item.Server.AddChannel(item.Id); + if (item.RecipientId != null) + { + var user = item.Recipient; + if (user.PrivateChannelId != null) + throw new Exception("User already has a private channel."); + user.PrivateChannelId = item.Id; + item.Recipient.AddRef(); + } + } + protected override void OnRemoved(Channel item) + { + item.Server.RemoveChannel(item.Id); + + if (item.RecipientId != null) + { + var user = item.Recipient; + if (user.PrivateChannelId != item.Id) + throw new Exception("User has a different private channel."); + user.PrivateChannelId = null; + user.RemoveRef(); + } + } + + internal Channel this[string id] => Get(id); + + internal IEnumerable Find(string serverId, string name) + { + if (serverId == null) throw new ArgumentNullException(nameof(serverId)); + + if (name.StartsWith("#")) + { + string name2 = name.Substring(1); + return this.Where(x => x.ServerId == serverId && + string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase) || string.Equals(x.Name, name2, StringComparison.OrdinalIgnoreCase)); + } + else + { + return this.Where(x => x.ServerId == serverId && + string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase)); + } + } + } +} diff --git a/src/Discord.Net/Collections/Members.cs b/src/Discord.Net/Collections/Members.cs new file mode 100644 index 000000000..da9d81c0d --- /dev/null +++ b/src/Discord.Net/Collections/Members.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Discord.Collections +{ + public sealed class Members : AsyncCollection + { + internal Members(DiscordClient client) + : base(client) { } + + private string GetKey(string userId, string serverId) => serverId + '_' + userId; + + internal Member GetOrAdd(string userId, string serverId) => GetOrAdd(GetKey(userId, serverId), () => new Member(_client, userId, serverId)); + internal Member TryRemove(string userId, string serverId) => base.TryRemove(GetKey(userId, serverId)); + + protected override void OnCreated(Member item) + { + item.Server.AddMember(item.UserId); + item.User.AddRef(); + } + protected override void OnRemoved(Member item) + { + item.Server.RemoveMember(item.UserId); + item.User.RemoveRef(); + } + + internal Member this[string userId, string serverId] + { + get + { + if (serverId == null) throw new ArgumentNullException(nameof(serverId)); + if (userId == null) throw new ArgumentNullException(nameof(userId)); + + return Get(GetKey(userId, serverId)); + } + } + + internal IEnumerable Find(Server server, string name) + { + if (server == null) throw new ArgumentNullException(nameof(server)); + if (name == null) throw new ArgumentNullException(nameof(name)); + + if (name.StartsWith("@")) + { + string name2 = name.Substring(1); + return server.Members.Where(x => + { + var user = x.User; + if (user == null) + return false; + return string.Equals(user.Name, name, StringComparison.OrdinalIgnoreCase) || string.Equals(user.Name, name2, StringComparison.OrdinalIgnoreCase); + }); + } + else + { + return server.Members.Where(x => + { + var user = x.User; + if (user == null) + return false; + return string.Equals(x.User.Name, name, StringComparison.OrdinalIgnoreCase); + }); + } + } + } +} diff --git a/src/Discord.Net/Collections/Messages.cs b/src/Discord.Net/Collections/Messages.cs new file mode 100644 index 000000000..5d17cfd72 --- /dev/null +++ b/src/Discord.Net/Collections/Messages.cs @@ -0,0 +1,33 @@ +using Discord.Helpers; + +namespace Discord.Collections +{ + public sealed class Messages : AsyncCollection + { + private readonly MessageCleaner _msgCleaner; + internal Messages(DiscordClient client) + : base(client) + { + _msgCleaner = new MessageCleaner(client); + } + + internal Message GetOrAdd(string id, string channelId) => GetOrAdd(id, () => new Message(_client, id, channelId)); + internal new Message TryRemove(string id) => base.TryRemove(id); + internal new Message Remap(string oldKey, string newKey) => base.Remap(oldKey, newKey); + + protected override void OnCreated(Message item) + { + item.Channel.AddMessage(item.UserId); + item.User.AddRef(); + } + protected override void OnRemoved(Message item) + { + item.Channel.RemoveMessage(item.UserId); + item.User.RemoveRef(); + } + + internal Message this[string id] => Get(id); + + internal string CleanText(string text) => _msgCleaner.Clean(text); + } +} diff --git a/src/Discord.Net/Collections/Roles.cs b/src/Discord.Net/Collections/Roles.cs new file mode 100644 index 000000000..8c63a2200 --- /dev/null +++ b/src/Discord.Net/Collections/Roles.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Discord.Collections +{ + public sealed class Roles : AsyncCollection + { + internal Roles(DiscordClient client) + : base(client) { } + + internal Role GetOrAdd(string id, string serverId) => GetOrAdd(id, () => new Role(_client, id, serverId)); + internal new Role TryRemove(string id) => base.TryRemove(id); + + protected override void OnCreated(Role item) + { + item.Server.AddRole(item.Id); + } + protected override void OnRemoved(Role item) + { + item.Server.RemoveRole(item.Id); + } + + internal Role this[string id] => Get(id); + + internal IEnumerable Find(string serverId, string name) + { + if (serverId == null) throw new ArgumentNullException(nameof(serverId)); + if (name == null) throw new ArgumentNullException(nameof(name)); + + if (name.StartsWith("@")) + { + string name2 = name.Substring(1); + return this.Where(x => x.ServerId == serverId && + string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase) || string.Equals(x.Name, name2, StringComparison.OrdinalIgnoreCase)); + } + else + { + return this.Where(x => x.ServerId == serverId && + string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase)); + } + } + } +} diff --git a/src/Discord.Net/Collections/Servers.cs b/src/Discord.Net/Collections/Servers.cs new file mode 100644 index 000000000..85e243c6e --- /dev/null +++ b/src/Discord.Net/Collections/Servers.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Discord.Collections +{ + public sealed class Servers : AsyncCollection + { + internal Servers(DiscordClient client) + : base(client) { } + + internal Server GetOrAdd(string id) => base.GetOrAdd(id, () => new Server(_client, id)); + internal new Server TryRemove(string id) => base.TryRemove(id); + + protected override void OnCreated(Server item) { } + protected override void OnRemoved(Server item) + { + var channels = _client.Channels; + foreach (var channelId in item.ChannelIds) + channels.TryRemove(channelId); + + var members = _client.Members; + foreach (var userId in item.UserIds) + members.TryRemove(userId, item.Id); + + var roles = _client.Roles; + foreach (var roleId in item.RoleIds) + roles.TryRemove(roleId); + } + + internal Server this[string id] => Get(id); + + internal IEnumerable Find(string name) + { + if (name == null) throw new ArgumentNullException(nameof(name)); + + return this.Where(x => string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase)); + } + } +} diff --git a/src/Discord.Net/Collections/Users.cs b/src/Discord.Net/Collections/Users.cs new file mode 100644 index 000000000..14da56c6b --- /dev/null +++ b/src/Discord.Net/Collections/Users.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Discord.Collections +{ + public sealed class Users : AsyncCollection + { + internal Users(DiscordClient client) + : base(client) { } + + internal User GetOrAdd(string id) => GetOrAdd(id, () => new User(_client, id)); + internal new User TryRemove(string id) => base.TryRemove(id); + + protected override void OnCreated(User item) { } + protected override void OnRemoved(User item) { } + + public User this[string id] => Get(id); + public User this[string name, string discriminator] + { + get + { + if (name == null) throw new ArgumentNullException(nameof(name)); + if (discriminator == null) throw new ArgumentNullException(nameof(discriminator)); + + if (name.StartsWith("@")) + name = name.Substring(1); + + return this.Where(x => + string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase) && + x.Discriminator == discriminator + ) + .FirstOrDefault(); + } + } + + public IEnumerable Find(string name) + { + if (name == null) throw new ArgumentNullException(nameof(name)); + + if (name.StartsWith("@")) + { + string name2 = name.Substring(1); + return this.Where(x => + string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase) || string.Equals(x.Name, name2, StringComparison.OrdinalIgnoreCase)); + } + else + { + return this.Where(x => + string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase)); + } + } + } +} diff --git a/src/Discord.Net/DiscordClient.API.cs b/src/Discord.Net/DiscordClient.API.cs index e160d6215..9a3a5b8d4 100644 --- a/src/Discord.Net/DiscordClient.API.cs +++ b/src/Discord.Net/DiscordClient.API.cs @@ -1,8 +1,7 @@ -using Discord.API; -using Discord.API.Models; +using Discord.Net; +using Discord.Net.API; using System; using System.Collections.Generic; -using System.IO; using System.Linq; using System.Net; using System.Threading.Tasks; @@ -14,8 +13,11 @@ namespace Discord Jpeg, Png } + public partial class DiscordClient { + //TODO: Move all these functions into their respective collections object + //Servers /// Creates a new server with the provided name and region (see Regions). public async Task CreateServer(string name, string region) @@ -25,7 +27,9 @@ namespace Discord if (region == null) throw new ArgumentNullException(nameof(region)); var response = await _api.CreateServer(name, region).ConfigureAwait(false); - return _servers.Update(response.Id, response); + var server = _servers.GetOrAdd(response.Id); + server.Update(response); + return server; } /// Leaves the provided server, destroying it if you are the owner. @@ -39,7 +43,7 @@ namespace Discord try { await _api.LeaveServer(serverId).ConfigureAwait(false); } catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } - return _servers.Remove(serverId); + return _servers.TryRemove(serverId); } //Channels @@ -55,7 +59,29 @@ namespace Discord if (type == null) throw new ArgumentNullException(nameof(type)); var response = await _api.CreateChannel(serverId, name, type).ConfigureAwait(false); - return _channels.Update(response.Id, serverId, response); + var channel = _channels.GetOrAdd(response.Id, response.GuildId, response.Recipient?.Id); + channel.Update(response); + return channel; + } + /// Returns the private channel with the provided user, creating one if it does not currently exist. + public Task CreatePMChannel(string userId) => CreatePMChannel(_users[userId], userId); + /// Returns the private channel with the provided user, creating one if it does not currently exist. + public Task CreatePMChannel(User user) => CreatePMChannel(user, user?.Id); + private async Task CreatePMChannel(User user, string userId) + { + CheckReady(); + if (userId == null) throw new ArgumentNullException(nameof(userId)); + + Channel channel = null; + if (user != null) + channel = user.PrivateChannel; + if (channel == null) + { + var response = await _api.CreatePMChannel(_currentUserId, userId).ConfigureAwait(false); + channel = _channels.GetOrAdd(response.Id, response.GuildId, response.Recipient?.Id); + channel.Update(response); + } + return channel; } /// Destroys the provided channel. @@ -69,7 +95,7 @@ namespace Discord try { await _api.DestroyChannel(channelId).ConfigureAwait(false); } catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } - return _channels.Remove(channelId); + return _channels.TryRemove(channelId); } //Bans @@ -140,36 +166,22 @@ namespace Discord if (maxUses <= 0) throw new ArgumentOutOfRangeException(nameof(maxUses)); var response = await _api.CreateInvite(channelId, maxAge, maxUses, isTemporary, hasXkcdPass).ConfigureAwait(false); - _channels.Update(response.Channel.Id, response.Server.Id, response.Channel); - _servers.Update(response.Server.Id, response.Server); - _users.Update(response.Inviter.Id, response.Inviter); - return new Invite(response.Code, response.XkcdPass, this) - { - ChannelId = response.Channel.Id, - InviterId = response.Inviter.Id, - ServerId = response.Server.Id, - IsRevoked = response.IsRevoked, - IsTemporary = response.IsTemporary, - MaxAge = response.MaxAge, - MaxUses = response.MaxUses, - Uses = response.Uses - }; + var invite = new Invite(this, response.Code, response.XkcdPass, response.Guild.Id); + invite.Update(response); + return invite; } - /// Gets more info about the provided invite. + /// Gets more info about the provided invite code. /// Supported formats: inviteCode, xkcdCode, https://discord.gg/inviteCode, https://discord.gg/xkcdCode public async Task GetInvite(string id) { CheckReady(); if (id == null) throw new ArgumentNullException(nameof(id)); - + var response = await _api.GetInvite(id).ConfigureAwait(false); - return new Invite(response.Code, response.XkcdPass, this) - { - ChannelId = response.Channel.Id, - InviterId = response.Inviter.Id, - ServerId = response.Server.Id - }; + var invite = new Invite(this, response.Code, response.XkcdPass, response.Guild.Id); + invite.Update(response); + return invite; } /// Accepts the provided invite. @@ -181,34 +193,33 @@ namespace Discord return _api.AcceptInvite(invite.Id); } /// Accepts the provided invite. - public async Task AcceptInvite(string id) + public async Task AcceptInvite(string code) { CheckReady(); - if (id == null) throw new ArgumentNullException(nameof(id)); + if (code == null) throw new ArgumentNullException(nameof(code)); - //Remove Url Parts - if (id.StartsWith(Endpoints.BaseShortHttps)) - id = id.Substring(Endpoints.BaseShortHttps.Length); - if (id.Length > 0 && id[0] == '/') - id = id.Substring(1); - if (id.Length > 0 && id[id.Length - 1] == '/') - id = id.Substring(0, id.Length - 1); + //Remove trailing slash and any non-code url parts + if (code.Length > 0 && code[code.Length - 1] == '/') + code = code.Substring(0, code.Length - 1); + int index = code.LastIndexOf('/'); + if (index >= 0) + code = code.Substring(index + 1); //Check if this is a human-readable link and get its ID - var response = await _api.GetInvite(id).ConfigureAwait(false); - await _api.AcceptInvite(response.Code).ConfigureAwait(false); + var invite = await GetInvite(code).ConfigureAwait(false); + await _api.AcceptInvite(invite.Id).ConfigureAwait(false); } /// Deletes the provided invite. - public async Task DeleteInvite(string id) + public async Task DeleteInvite(string code) { CheckReady(); - if (id == null) throw new ArgumentNullException(nameof(id)); + if (code == null) throw new ArgumentNullException(nameof(code)); try { //Check if this is a human-readable link and get its ID - var response = await _api.GetInvite(id).ConfigureAwait(false); + var response = await _api.GetInvite(code).ConfigureAwait(false); await _api.DeleteInvite(response.Code).ConfigureAwait(false); } catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } @@ -234,20 +245,21 @@ namespace Discord if (text == null) throw new ArgumentNullException(nameof(text)); if (mentions == null) throw new ArgumentNullException(nameof(mentions)); - int blockCount = (int)Math.Ceiling(text.Length / (double)DiscordAPI.MaxMessageSize); + int blockCount = (int)Math.Ceiling(text.Length / (double)DiscordAPIClient.MaxMessageSize); Message[] result = new Message[blockCount]; for (int i = 0; i < blockCount; i++) { - int index = i * DiscordAPI.MaxMessageSize; + int index = i * DiscordAPIClient.MaxMessageSize; string blockText = text.Substring(index, Math.Min(2000, text.Length - index)); var nonce = GenerateNonce(); if (_config.UseMessageQueue) { - var msg = _messages.Update("nonce_" + nonce, channelId, new API.Models.Message + var msg = _messages.GetOrAdd("nonce_" + nonce, channelId); + msg.Update(new Net.API.Message { Content = blockText, Timestamp = DateTime.UtcNow, - Author = new UserReference { Avatar = User.AvatarId, Discriminator = User.Discriminator, Id = User.Id, Username = User.Name }, + Author = new UserReference { Avatar = _currentUser.AvatarId, Discriminator = _currentUser.Discriminator, Id = _currentUser.Id, Username = _currentUser.Name }, ChannelId = channelId }); msg.IsQueued = true; @@ -257,9 +269,9 @@ namespace Discord } else { - var msg = await _api.SendMessage(channelId, blockText, mentions, nonce).ConfigureAwait(false); - result[i] = _messages.Update(msg.Id, channelId, msg); - result[i].Nonce = nonce; + var response = await _api.SendMessage(channelId, blockText, mentions, nonce).ConfigureAwait(false); + var msg = _messages.GetOrAdd(response.Id, channelId); + msg.Update(response); try { RaiseMessageSent(result[i]); } catch { } } await Task.Delay(1000).ConfigureAwait(false); @@ -268,15 +280,12 @@ namespace Discord } /// Sends a private message to the provided channel. - public async Task SendPrivateMessage(User user, string text) - { - var channel = await GetPMChannel(user).ConfigureAwait(false); - return await SendMessage(channel, text, new string[0]).ConfigureAwait(false); - } + public Task SendPrivateMessage(User user, string text) + => SendPrivateMessage(user?.Id, text); /// Sends a private message to the provided channel. public async Task SendPrivateMessage(string userId, string text) { - var channel = await GetPMChannel(userId).ConfigureAwait(false); + var channel = await CreatePMChannel(userId).ConfigureAwait(false); return await SendMessage(channel, text, new string[0]).ConfigureAwait(false); } @@ -307,11 +316,12 @@ namespace Discord if (text == null) throw new ArgumentNullException(nameof(text)); if (mentions == null) throw new ArgumentNullException(nameof(mentions)); - if (text.Length > DiscordAPI.MaxMessageSize) - text = text.Substring(0, DiscordAPI.MaxMessageSize); + if (text.Length > DiscordAPIClient.MaxMessageSize) + text = text.Substring(0, DiscordAPIClient.MaxMessageSize); - var msg = await _api.EditMessage(channelId, messageId, text, mentions).ConfigureAwait(false); - _messages.Update(msg.Id, channelId, msg); + var response = await _api.EditMessage(channelId, messageId, text, mentions).ConfigureAwait(false); + var msg = _messages.GetOrAdd(messageId, channelId); + msg.Update(response); } /// Deletes the provided message. @@ -327,7 +337,7 @@ namespace Discord try { await _api.DeleteMessage(channelId, msgId).ConfigureAwait(false); - _messages.Remove(msgId); + _messages.TryRemove(msgId); } catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } } @@ -338,7 +348,7 @@ namespace Discord foreach (var msg in msgs) { - try + try { await _api.DeleteMessage(msg.ChannelId, msg.Id).ConfigureAwait(false); } @@ -352,37 +362,25 @@ namespace Discord foreach (var msgId in msgIds) { - try + try { await _api.DeleteMessage(channelId, msgId).ConfigureAwait(false); } catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } } } - + /// Sends a file to the provided channel. - public Task SendFile(Channel channel, string path) - => SendFile(channel?.Id, path); + public Task SendFile(Channel channel, string filePath) + => SendFile(channel?.Id, filePath); /// Sends a file to the provided channel. - public Task SendFile(string channelId, string path) - { - if (path == null) throw new ArgumentNullException(nameof(path)); - return SendFile(channelId, File.OpenRead(path), Path.GetFileName(path)); - } - /// Reads a stream and sends it to the provided channel as a file. - /// It is highly recommended that this stream be cached in memory or on disk, or the request may time out. - public Task SendFile(Channel channel, Stream stream, string filename = null) - => SendFile(channel?.Id, stream, filename); - /// Reads a stream and sends it to the provided channel as a file. - /// It is highly recommended that this stream be cached in memory or on disk, or the request may time out. - public Task SendFile(string channelId, Stream stream, string filename = null) + public Task SendFile(string channelId, string filePath) { CheckReady(); if (channelId == null) throw new ArgumentNullException(nameof(channelId)); - if (stream == null) throw new ArgumentNullException(nameof(stream)); - if (filename == null) throw new ArgumentNullException(nameof(filename)); + if (filePath == null) throw new ArgumentNullException(nameof(filePath)); - return _api.SendFile(channelId, stream, filename); + return _api.SendFile(channelId, filePath); } /// Downloads last count messages from the server, starting at beforeMessageId if it's provided. @@ -396,16 +394,16 @@ namespace Discord if (count < 0) throw new ArgumentOutOfRangeException(nameof(count)); if (count == 0) return new Message[0]; - Channel channel = GetChannel(channelId); + Channel channel = _channels[channelId]; if (channel != null && channel.Type == ChannelTypes.Text) { try { var msgs = await _api.GetMessages(channel.Id, count).ConfigureAwait(false); - return msgs.OrderBy(x => x.Timestamp) - .Select(x => + return msgs.Select(x => { - var msg = _messages.Update(x.Id, x.ChannelId, x); + var msg = _messages.GetOrAdd(x.Id, x.ChannelId); + msg.Update(x); var user = msg.User; if (user != null) user.UpdateActivity(x.Timestamp); @@ -501,21 +499,21 @@ namespace Discord { CheckReady(); var response = await _api.ChangeUsername(newName, currentEmail, currentPassword).ConfigureAwait(false); - _users.Update(response.Id, response); + _currentUser.Update(response); } /// Changes your email to newEmail. public async Task ChangeEmail(string newEmail, string currentPassword) { CheckReady(); var response = await _api.ChangeEmail(newEmail, currentPassword).ConfigureAwait(false); - _users.Update(response.Id, response); + _currentUser.Update(response); } /// Changes your password to newPassword. public async Task ChangePassword(string newPassword, string currentEmail, string currentPassword) { CheckReady(); var response = await _api.ChangePassword(newPassword, currentEmail, currentPassword).ConfigureAwait(false); - _users.Update(response.Id, response); + _currentUser.Update(response); } /// Changes your avatar. @@ -524,7 +522,7 @@ namespace Discord { CheckReady(); var response = await _api.ChangeAvatar(imageType, bytes, currentEmail, currentPassword).ConfigureAwait(false); - _users.Update(response.Id, response); + _currentUser.Update(response); } } } diff --git a/src/Discord.Net/DiscordClient.Cache.cs b/src/Discord.Net/DiscordClient.Cache.cs index 515eb18ed..1416de4cb 100644 --- a/src/Discord.Net/DiscordClient.Cache.cs +++ b/src/Discord.Net/DiscordClient.Cache.cs @@ -1,472 +1,59 @@ -using Discord.API.Models; -using Discord.Helpers; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; +using System.Collections.Generic; namespace Discord { public partial class DiscordClient { - /// Returns a collection of all users the client can see across all servers. - /// This collection does not guarantee any ordering. - public IEnumerable Users => _users; - internal AsyncCache _users; - - /// Returns a collection of all servers the client is a member of. - /// This collection does not guarantee any ordering. - public IEnumerable Servers => _servers; - internal AsyncCache _servers; - - /// Returns a collection of all channels the client can see across all servers. - /// This collection does not guarantee any ordering. - public IEnumerable Channels => _channels; - internal AsyncCache _channels; - - /// Returns a collection of all messages the client has in cache. - /// This collection does not guarantee any ordering. - public IEnumerable Messages => _messages; - internal AsyncCache _messages; - - /// Returns a collection of all roles the client can see across all servers. - /// This collection does not guarantee any ordering. - public IEnumerable Roles => _roles; - internal AsyncCache _roles; - - private void CreateCaches() - { - _servers = new AsyncCache( - (key, parentKey) => - { - if (_isDebugMode) - RaiseOnDebugMessage(DebugMessageType.Cache, $"Created server {key}."); - return new Server(key, this); - }, - (server, model) => - { - server.Name = model.Name; - _channels.Update(server.DefaultChannelId, server.Id, null); - if (model is ExtendedServerInfo) - { - var extendedModel = model as ExtendedServerInfo; - server.AFKChannelId = extendedModel.AFKChannelId; - server.AFKTimeout = extendedModel.AFKTimeout; - server.JoinedAt = extendedModel.JoinedAt ?? DateTime.MinValue; - server.OwnerId = extendedModel.OwnerId; - server.Region = extendedModel.Region; - - foreach (var role in extendedModel.Roles) - _roles.Update(role.Id, model.Id, role); - foreach (var channel in extendedModel.Channels) - _channels.Update(channel.Id, model.Id, channel); - foreach (var membership in extendedModel.Members) - { - _users.Update(membership.User.Id, membership.User); - server.UpdateMember(membership); - } - foreach (var membership in extendedModel.VoiceStates) - server.UpdateMember(membership); - foreach (var membership in extendedModel.Presences) - server.UpdateMember(membership); - } - if (_isDebugMode) - RaiseOnDebugMessage(DebugMessageType.Cache, $"Updated server {server.Name} ({server.Id})."); - }, - server => - { - if (_isDebugMode) - RaiseOnDebugMessage(DebugMessageType.Cache, $"Destroyed server {server.Name} ({server.Id})."); - } - ); - - _channels = new AsyncCache( - (key, parentKey) => - { - if (_isDebugMode) - { - if (parentKey != null) - RaiseOnDebugMessage(DebugMessageType.Cache, $"Created channel {key} in server {parentKey}."); - else - RaiseOnDebugMessage(DebugMessageType.Cache, $"Created private channel {key}."); - } - return new Channel(key, parentKey, this); - }, - (channel, model) => - { - channel.Name = model.Name; - channel.Type = model.Type; - if (model is ChannelInfo) - { - var extendedModel = model as ChannelInfo; - channel.Position = extendedModel.Position; - - if (extendedModel.IsPrivate) - { - var user = _users.Update(extendedModel.Recipient.Id, extendedModel.Recipient); - channel.RecipientId = user.Id; - user.PrivateChannelId = channel.Id; - } - - if (extendedModel.PermissionOverwrites != null) - { - channel.PermissionOverwrites = extendedModel.PermissionOverwrites.Select(x => new Channel.PermissionOverwrite - { - Type = x.Type, - Id = x.Id, - Deny = new PackedPermissions(x.Deny), - Allow = new PackedPermissions(x.Allow) - }).ToArray(); - } - else - channel.PermissionOverwrites = null; - } - if (_isDebugMode) - { - if (channel.IsPrivate) - RaiseOnDebugMessage(DebugMessageType.Cache, $"Updated private channel {channel.Name} ({channel.Id})."); - else - RaiseOnDebugMessage(DebugMessageType.Cache, $"Updated channel {channel.Name} ({channel.Id}) in server {channel.Server?.Name} ({channel.ServerId})."); - } - }, - channel => - { - if (channel.IsPrivate) - { - var user = channel.Recipient; - if (user.PrivateChannelId == channel.Id) - user.PrivateChannelId = null; - if (_isDebugMode) - RaiseOnDebugMessage(DebugMessageType.Cache, $"Destroyed private channel {channel.Name} ({channel.Id})."); - } - else - { - if (_isDebugMode) - RaiseOnDebugMessage(DebugMessageType.Cache, $"Destroyed channel {channel.Name} ({channel.Id}) in server {channel.Server?.Name} ({channel.ServerId})."); - } - }); - - _messages = new AsyncCache( - (key, parentKey) => - { - if (_isDebugMode) - RaiseOnDebugMessage(DebugMessageType.Cache, $"Created message {key} in channel {parentKey}."); - return new Message(key, parentKey, this); - }, - (message, model) => - { - if (model is API.Models.Message) - { - var extendedModel = model as API.Models.Message; - if (extendedModel.Attachments != null) - { - message.Attachments = extendedModel.Attachments.Select(x => new Message.Attachment - { - Id = x.Id, - Url = x.Url, - ProxyUrl = x.ProxyUrl, - Size = x.Size, - Filename = x.Filename, - Width = x.Width, - Height = x.Height - }).ToArray(); - } - else - message.Attachments = new Message.Attachment[0]; - if (extendedModel.Embeds != null) - { - message.Embeds = extendedModel.Embeds.Select(x => - { - var embed = new Message.Embed - { - Url = x.Url, - Type = x.Type, - Description = x.Description, - Title = x.Title - }; - if (x.Provider != null) - { - embed.Provider = new Message.EmbedReference - { - Url = x.Provider.Url, - Name = x.Provider.Name - }; - } - if (x.Author != null) - { - embed.Author = new Message.EmbedReference - { - Url = x.Author.Url, - Name = x.Author.Name - }; - } - if (x.Thumbnail != null) - { - embed.Thumbnail = new Message.File - { - Url = x.Thumbnail.Url, - ProxyUrl = x.Thumbnail.ProxyUrl, - Width = x.Thumbnail.Width, - Height = x.Thumbnail.Height - }; - } - return embed; - }).ToArray(); - } - else - message.Embeds = new Message.Embed[0]; - message.IsMentioningEveryone = extendedModel.IsMentioningEveryone; - message.IsTTS = extendedModel.IsTextToSpeech; - message.MentionIds = extendedModel.Mentions?.Select(x => x.Id)?.ToArray() ?? new string[0]; - message.IsMentioningMe = message.MentionIds.Contains(_myId); - message.RawText = extendedModel.Content; - message.Timestamp = extendedModel.Timestamp; - message.EditedTimestamp = extendedModel.EditedTimestamp; - if (extendedModel.Author != null) - message.UserId = extendedModel.Author.Id; - } - if (_isDebugMode) - RaiseOnDebugMessage(DebugMessageType.Cache, $"Updated message {message.Id} in channel {message.Channel?.Name} ({message.ChannelId})."); - }, - message => - { - if (_isDebugMode) - RaiseOnDebugMessage(DebugMessageType.Cache, $"Destroyed message {message.Id} in channel {message.Channel?.Name} ({message.ChannelId})."); - } - ); - - _roles = new AsyncCache( - (key, parentKey) => - { - if (_isDebugMode) - RaiseOnDebugMessage(DebugMessageType.Cache, $"Created role {key} in server {parentKey}."); - return new Role(key, parentKey, this); - }, - (role, model) => - { - role.Name = model.Name; - role.Permissions.RawValue = (uint)model.Permissions; - if (_isDebugMode) - RaiseOnDebugMessage(DebugMessageType.Cache, $"Updated role {role.Name} ({role.Id}) in server {role.Server?.Name} ({role.ServerId})."); - }, - role => - { - if (_isDebugMode) - RaiseOnDebugMessage(DebugMessageType.Cache, $"Destroyed role {role.Name} ({role.Id}) in server {role.Server?.Name} ({role.ServerId})."); - } - ); - - _users = new AsyncCache( - (key, parentKey) => - { - if (_isDebugMode) - RaiseOnDebugMessage(DebugMessageType.Cache, $"Created user {key}."); - return new User(key, this); - }, - (user, model) => - { - user.AvatarId = model.Avatar; - user.Discriminator = model.Discriminator; - user.Name = model.Username; - if (model is SelfUserInfo) - { - var extendedModel = model as SelfUserInfo; - user.Email = extendedModel.Email; - user.IsVerified = extendedModel.IsVerified; - } - if (_isDebugMode) - RaiseOnDebugMessage(DebugMessageType.Cache, $"Updated user {user?.Name} ({user.Id})."); - }, - user => - { - if (_isDebugMode) - RaiseOnDebugMessage(DebugMessageType.Cache, $"Destroyed user {user?.Name} ({user.Id})."); - } - ); - } - - /// Returns the user with the specified id, or null if none was found. - public User GetUser(string id) - => _users[id]; - /// Returns the user with the specified name and discriminator, or null if none was found. - /// Name formats supported: Name and @Name. Search is case-insensitive. - public User GetUser(string name, string discriminator) - { - if (name == null || discriminator == null) - return null; - - if (name.StartsWith("@")) - name = name.Substring(1); - - return _users - .Where(x => - string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase) && - x.Discriminator == discriminator - ) - .FirstOrDefault(); - } - /// Returns all users with the specified name across all servers. - /// Name formats supported: Name and @Name. Search is case-insensitive. - public IEnumerable FindUsers(string name) - { - if (name == null) - return new User[0]; - - if (name.StartsWith("@")) - { - string name2 = name.Substring(1); - return _users.Where(x => - string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase) || string.Equals(x.Name, name2, StringComparison.OrdinalIgnoreCase)); - } - else - { - return _users.Where(x => - string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase)); - } - } + /// Returns the channel with the specified id, or null if none was found. + public Channel GetChannel(string id) => _channels[id]; + /// Returns all channels with the specified server and name. + /// Name formats supported: Name and #Name. Search is case-insensitive. + public IEnumerable FindChannels(Server server, string name) => _channels.Find(server?.Id, name); + /// Returns all channels with the specified server and name. + /// Name formats supported: Name and #Name. Search is case-insensitive. + public IEnumerable FindChannels(string serverId, string name) => _channels.Find(serverId, name); /// Returns the user with the specified id, along with their server-specific data, or null if none was found. - public Member GetMember(string serverId, User user) - => GetMember(_servers[serverId], user?.Id); + public Member GetMember(string serverId, User user) => _members[user?.Id, serverId]; /// Returns the user with the specified id, along with their server-specific data, or null if none was found. - public Member GetMember(string serverId, string userId) - => GetMember(_servers[serverId], userId); + public Member GetMember(Server server, User user) => _members[user?.Id, server?.Id]; /// Returns the user with the specified id, along with their server-specific data, or null if none was found. - public Member GetMember(Server server, User user) - => GetMember(server, user?.Id); + public Member GetMember(Server server, string userId) => _members[userId, server?.Id]; /// Returns the user with the specified id, along with their server-specific data, or null if none was found. - public Member GetMember(Server server, string userId) - { - if (server == null || userId == null) - return null; - return server.GetMember(userId); - } - + public Member GetMember(string serverId, string userId) => _members[userId, serverId]; /// Returns all users in with the specified server and name, along with their server-specific data. /// Name formats supported: Name and @Name. Search is case-insensitive. - public IEnumerable FindMembers(string serverId, string name) - => FindMembers(GetServer(serverId), name); + public IEnumerable FindMembers(Server server, string name) => _members.Find(server, name); /// Returns all users in with the specified server and name, along with their server-specific data. /// Name formats supported: Name and @Name. Search is case-insensitive. - public IEnumerable FindMembers(Server server, string name) - { - if (server == null || name == null) - return new Member[0]; - - if (name.StartsWith("@")) - { - string name2 = name.Substring(1); - return server.Members.Where(x => - { - var user = x.User; - if (user == null) - return false; - return string.Equals(user.Name, name, StringComparison.OrdinalIgnoreCase) || string.Equals(user.Name, name2, StringComparison.OrdinalIgnoreCase); - }); - } - else - { - return server.Members.Where(x => - { - var user = x.User; - if (user == null) - return false; - return string.Equals(x.User.Name, name, StringComparison.OrdinalIgnoreCase); - }); - } - } - - /// Returns the server with the specified id, or null if none was found. - public Server GetServer(string id) - => _servers[id]; - /// Returns all servers with the specified name. - /// Search is case-insensitive. - public IEnumerable FindServers(string name) - { - if (name == null) - return new Server[0]; - return _servers.Where(x => - string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase)); - } - - /// Returns the channel with the specified id, or null if none was found. - public Channel GetChannel(string id) => _channels[id]; - /// Returns the private channel with the provided user, creating one if it does not currently exist. - public Task GetPMChannel(string userId) - => GetPMChannel(_users[userId]); - /// Returns the private channel with the provided user, creating one if it does not currently exist. - public async Task GetPMChannel(User user) - { - CheckReady(); - if (user == null) throw new ArgumentNullException(nameof(user)); - - var channel = user.PrivateChannel; - if (channel != null) - return channel; - return await CreatePMChannel(user?.Id).ConfigureAwait(false); - } - private async Task CreatePMChannel(string userId) - { - CheckReady(); - if (userId == null) throw new ArgumentNullException(nameof(userId)); - - var response = await _api.CreatePMChannel(_myId, userId).ConfigureAwait(false); - return _channels.Update(response.Id, response); - } + public IEnumerable FindMembers(string serverId, string name) => _members.Find(_servers[serverId], name); - /// Returns all channels with the specified server and name. - /// Name formats supported: Name and #Name. Search is case-insensitive. - public IEnumerable FindChannels(Server server, string name) - => FindChannels(server?.Id, name); - /// Returns all channels with the specified server and name. - /// Name formats supported: Name and #Name. Search is case-insensitive. - public IEnumerable FindChannels(string serverId, string name) - { - if (serverId == null || name == null) - return new Channel[0]; - - if (name.StartsWith("#")) - { - string name2 = name.Substring(1); - return _channels.Where(x => x.ServerId == serverId && - string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase) || string.Equals(x.Name, name2, StringComparison.OrdinalIgnoreCase)); - } - else - { - return _channels.Where(x => x.ServerId == serverId && - string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase)); - } - } + /// Returns the message with the specified id, or null if none was found. + public Message GetMessage(string id) => _messages[id]; /// Returns the role with the specified id, or null if none was found. - public Role GetRole(string id) - => _roles[id]; + public Role GetRole(string id) => _roles[id]; /// Returns all roles with the specified server and name. /// Name formats supported: Name and @Name. Search is case-insensitive. - public IEnumerable FindRoles(Server server, string name) - => FindRoles(server?.Id, name); + public IEnumerable FindRoles(Server server, string name) => _roles.Find(server?.Id, name); /// Returns all roles with the specified server and name. /// Name formats supported: Name and @Name. Search is case-insensitive. - public IEnumerable FindRoles(string serverId, string name) - { - if (serverId == null || name == null) - return new Role[0]; + public IEnumerable FindRoles(string serverId, string name) => _roles.Find(serverId, name); - if (name.StartsWith("@")) - { - string name2 = name.Substring(1); - return _roles.Where(x => x.ServerId == serverId && - string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase) || string.Equals(x.Name, name2, StringComparison.OrdinalIgnoreCase)); - } - else - { - return _roles.Where(x => x.ServerId == serverId && - string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase)); - } - } + /// Returns the server with the specified id, or null if none was found. + public Server GetServer(string id) => _servers[id]; + /// Returns all servers with the specified name. + /// Search is case-insensitive. + public IEnumerable FindServers(string name) => _servers.Find(name); + + /// Returns the user with the specified id, or null if none was found. + public User GetUser(string id) => _users[id]; + /// Returns the user with the specified name and discriminator, or null if none was found. + /// Name formats supported: Name and @Name. Search is case-insensitive. + public User GetUser(string name, string discriminator) => _users[name, discriminator]; + /// Returns all users with the specified name across all servers. + /// Name formats supported: Name and @Name. Search is case-insensitive. + public IEnumerable FindUsers(string name) => _users.Find(name); - /// Returns the message with the specified id, or null if none was found. - public Message GetMessage(string id) - => _messages[id]; } } diff --git a/src/Discord.Net/DiscordClient.Events.cs b/src/Discord.Net/DiscordClient.Events.cs index 18e586b21..c3b9fa965 100644 --- a/src/Discord.Net/DiscordClient.Events.cs +++ b/src/Discord.Net/DiscordClient.Events.cs @@ -2,24 +2,30 @@ namespace Discord { - public enum DebugMessageType : byte + public enum LogMessageSeverity : byte { - Connection, - Event, + Error, + Warning, + Info, + Verbose, + Debug + } + public enum LogMessageSource : byte + { + Unknown, + Authentication, Cache, - WebSocketRawInput, //TODO: Make Http instanced and add a rawoutput event - WebSocketUnknownOpCode, - WebSocketUnknownEvent, - XHRRawOutput, - XHRTiming, - VoiceInput, - VoiceOutput, + DataWebSocket, + MessageQueue, + VoiceWebSocket, } + public sealed class LogMessageEventArgs : EventArgs { - public readonly DebugMessageType Type; + public readonly LogMessageSeverity Severity; + public readonly LogMessageSource Source; public readonly string Message; - internal LogMessageEventArgs(DebugMessageType type, string msg) { Type = type; Message = msg; } + internal LogMessageEventArgs(LogMessageSeverity severity, LogMessageSource source, string msg) { Severity = severity; Message = msg; } } public sealed class ServerEventArgs : EventArgs { @@ -82,57 +88,44 @@ namespace Discord } } - public partial class DiscordClient { - //Debug - public event EventHandler DebugMessage; - internal void RaiseOnDebugMessage(DebugMessageType type, string message) - { - if (DebugMessage != null) - DebugMessage(this, new LogMessageEventArgs(type, message)); - } - //General public event EventHandler Connected; private void RaiseConnected() { - if (_config.EnableDebug) - RaiseOnDebugMessage(DebugMessageType.Event, $"Connected"); if (Connected != null) Connected(this, EventArgs.Empty); } public event EventHandler Disconnected; private void RaiseDisconnected() { - if (_config.EnableDebug) - RaiseOnDebugMessage(DebugMessageType.Event, $"Disconnected"); if (Disconnected != null) Disconnected(this, EventArgs.Empty); } + public event EventHandler LogMessage; + internal void RaiseOnLog(LogMessageSeverity severity, LogMessageSource source, string message) + { + if (LogMessage != null) + LogMessage(this, new LogMessageEventArgs(severity, source, message)); + } //Server public event EventHandler ServerCreated; private void RaiseServerCreated(Server server) { - if (_config.EnableDebug) - RaiseOnDebugMessage(DebugMessageType.Event, $"ServerCreated {server.Name} ({server.Id})"); if (ServerCreated != null) ServerCreated(this, new ServerEventArgs(server)); } public event EventHandler ServerDestroyed; private void RaiseServerDestroyed(Server server) { - if (_config.EnableDebug) - RaiseOnDebugMessage(DebugMessageType.Event, $"ServerDestroyed {server.Name} ({server.Id})"); if (ServerDestroyed != null) ServerDestroyed(this, new ServerEventArgs(server)); } public event EventHandler ServerUpdated; private void RaiseServerUpdated(Server server) { - if (_config.EnableDebug) - RaiseOnDebugMessage(DebugMessageType.Event, $"ServerUpdated {server.Name} ({server.Id})"); if (ServerUpdated != null) ServerUpdated(this, new ServerEventArgs(server)); } @@ -141,8 +134,6 @@ namespace Discord public event EventHandler UserUpdated; private void RaiseUserUpdated(User user) { - if (_config.EnableDebug) - RaiseOnDebugMessage(DebugMessageType.Event, $"UserUpdated {user.Name} ({user.Id})"); if (UserUpdated != null) UserUpdated(this, new UserEventArgs(user)); } @@ -151,24 +142,18 @@ namespace Discord public event EventHandler ChannelCreated; private void RaiseChannelCreated(Channel channel) { - if (_config.EnableDebug) - RaiseOnDebugMessage(DebugMessageType.Event, $"ChannelCreated {channel.Name} ({channel.Id}) in {channel.Server?.Name} ({channel.ServerId})"); if (ChannelCreated != null) ChannelCreated(this, new ChannelEventArgs(channel)); } public event EventHandler ChannelDestroyed; private void RaiseChannelDestroyed(Channel channel) { - if (_config.EnableDebug) - RaiseOnDebugMessage(DebugMessageType.Event, $"ChannelDestroyed {channel.Name} ({channel.Id}) in {channel.Server?.Name} ({channel.ServerId})"); if (ChannelDestroyed != null) ChannelDestroyed(this, new ChannelEventArgs(channel)); } public event EventHandler ChannelUpdated; private void RaiseChannelUpdated(Channel channel) { - if (_config.EnableDebug) - RaiseOnDebugMessage(DebugMessageType.Event, $"ChannelUpdated {channel.Name} ({channel.Id}) in {channel.Server?.Name} ({channel.ServerId})"); if (ChannelUpdated != null) ChannelUpdated(this, new ChannelEventArgs(channel)); } @@ -177,40 +162,30 @@ namespace Discord public event EventHandler MessageCreated; private void RaiseMessageCreated(Message msg) { - if (_config.EnableDebug) - RaiseOnDebugMessage(DebugMessageType.Event, $"MessageCreated {msg.Id} in {msg.Channel?.Name} ({msg.ChannelId})"); if (MessageCreated != null) MessageCreated(this, new MessageEventArgs(msg)); } public event EventHandler MessageDeleted; private void RaiseMessageDeleted(Message msg) { - if (_config.EnableDebug) - RaiseOnDebugMessage(DebugMessageType.Event, $"MessageDeleted {msg.Id} in {msg.Channel?.Name} ({msg.ChannelId})"); if (MessageDeleted != null) MessageDeleted(this, new MessageEventArgs(msg)); } public event EventHandler MessageUpdated; private void RaiseMessageUpdated(Message msg) { - if (_config.EnableDebug) - RaiseOnDebugMessage(DebugMessageType.Event, $"MessageUpdated {msg.Id} in {msg.Channel?.Name} ({msg.ChannelId})"); if (MessageUpdated != null) MessageUpdated(this, new MessageEventArgs(msg)); } public event EventHandler MessageRead; private void RaiseMessageRead(Message msg) { - if (_config.EnableDebug) - RaiseOnDebugMessage(DebugMessageType.Event, $"MessageRead {msg.Id} in {msg.Channel?.Name} ({msg.ChannelId})"); if (MessageRead != null) MessageRead(this, new MessageEventArgs(msg)); } public event EventHandler MessageSent; private void RaiseMessageSent(Message msg) { - if (_config.EnableDebug) - RaiseOnDebugMessage(DebugMessageType.Event, $"MessageSent {msg.Id} in {msg.Channel?.Name} ({msg.ChannelId})"); if (MessageSent != null) MessageSent(this, new MessageEventArgs(msg)); } @@ -219,24 +194,18 @@ namespace Discord public event EventHandler RoleCreated; private void RaiseRoleCreated(Role role) { - if (_config.EnableDebug) - RaiseOnDebugMessage(DebugMessageType.Event, $"RoleCreated {role.Name} ({role.Id}) in {role.Server?.Name} ({role.ServerId})"); if (RoleCreated != null) RoleCreated(this, new RoleEventArgs(role)); } public event EventHandler RoleUpdated; private void RaiseRoleDeleted(Role role) { - if (_config.EnableDebug) - RaiseOnDebugMessage(DebugMessageType.Event, $"RoleDeleted {role.Name} ({role.Id}) in {role.Server?.Name} ({role.ServerId})"); if (RoleDeleted != null) RoleDeleted(this, new RoleEventArgs(role)); } public event EventHandler RoleDeleted; private void RaiseRoleUpdated(Role role) { - if (_config.EnableDebug) - RaiseOnDebugMessage(DebugMessageType.Event, $"RoleUpdated {role.Name} ({role.Id}) in {role.Server?.Name} ({role.ServerId})"); if (RoleUpdated != null) RoleUpdated(this, new RoleEventArgs(role)); } @@ -245,16 +214,12 @@ namespace Discord public event EventHandler BanAdded; private void RaiseBanAdded(User user, Server server) { - if (_config.EnableDebug) - RaiseOnDebugMessage(DebugMessageType.Event, $"BanAdded {user.Name} ({user.Id}) in {server.Name} ({server.Id})"); if (BanAdded != null) BanAdded(this, new BanEventArgs(user, server)); } public event EventHandler BanRemoved; private void RaiseBanRemoved(User user, Server server) { - if (_config.EnableDebug) - RaiseOnDebugMessage(DebugMessageType.Event, $"BanRemoved {user.Name} ({user.Id}) in {server.Name} ({server.Id})"); if (BanRemoved != null) BanRemoved(this, new BanEventArgs(user, server)); } @@ -263,24 +228,18 @@ namespace Discord public event EventHandler MemberAdded; private void RaiseMemberAdded(Member member) { - if (_config.EnableDebug) - RaiseOnDebugMessage(DebugMessageType.Event, $"MemberAdded {member.User?.Name} ({member.UserId}) in {member.Server?.Name} ({member.ServerId})"); if (MemberAdded != null) MemberAdded(this, new MemberEventArgs(member)); } public event EventHandler MemberRemoved; private void RaiseMemberRemoved(Member member) { - if (_config.EnableDebug) - RaiseOnDebugMessage(DebugMessageType.Event, $"MemberRemoved {member.User?.Name} ({member.UserId}) in {member.Server?.Name} ({member.ServerId})"); if (MemberRemoved != null) MemberRemoved(this, new MemberEventArgs(member)); } public event EventHandler MemberUpdated; private void RaiseMemberUpdated(Member member) { - if (_config.EnableDebug) - RaiseOnDebugMessage(DebugMessageType.Event, $"MemberUpdated {member.User?.Name} ({member.UserId}) in {member.Server?.Name} ({member.ServerId})"); if (MemberUpdated != null) MemberUpdated(this, new MemberEventArgs(member)); } @@ -289,24 +248,18 @@ namespace Discord public event EventHandler PresenceUpdated; private void RaisePresenceUpdated(Member member) { - if (_config.EnableDebug) - RaiseOnDebugMessage(DebugMessageType.Event, $"PresenceUpdated {member.User?.Name} ({member.UserId}) in {member.Server?.Name} ({member.ServerId})"); if (PresenceUpdated != null) PresenceUpdated(this, new MemberEventArgs(member)); } public event EventHandler VoiceStateUpdated; private void RaiseVoiceStateUpdated(Member member) { - if (_config.EnableDebug) - RaiseOnDebugMessage(DebugMessageType.Event, $"VoiceStateUpdated {member.User?.Name} ({member.UserId}) in {member.Server?.Name} ({member.ServerId})"); if (VoiceStateUpdated != null) VoiceStateUpdated(this, new MemberEventArgs(member)); } public event EventHandler UserTyping; private void RaiseUserTyping(User user, Channel channel) { - if (_config.EnableDebug) - RaiseOnDebugMessage(DebugMessageType.Event, $"VoiceStateUpdated {user.Name} ({user.Id}) in {channel.Name} ({channel.Id})"); if (UserTyping != null) UserTyping(this, new UserTypingEventArgs(user, channel)); } @@ -315,24 +268,18 @@ namespace Discord public event EventHandler VoiceConnected; private void RaiseVoiceConnected() { - if (_config.EnableDebug) - RaiseOnDebugMessage(DebugMessageType.Event, $"VoiceConnected"); if (VoiceConnected != null) VoiceConnected(this, EventArgs.Empty); } public event EventHandler VoiceDisconnected; private void RaiseVoiceDisconnected() { - if (_config.EnableDebug) - RaiseOnDebugMessage(DebugMessageType.Event, $"VoiceDisconnected"); if (VoiceDisconnected != null) VoiceDisconnected(this, EventArgs.Empty); } public event EventHandler VoiceServerUpdated; private void RaiseVoiceServerUpdated(Server server, string endpoint) { - if (_config.EnableDebug) - RaiseOnDebugMessage(DebugMessageType.Event, $"VoiceServerUpdated {server.Name} ({server.Id})"); if (VoiceServerUpdated != null) VoiceServerUpdated(this, new VoiceServerUpdatedEventArgs(server, endpoint)); } diff --git a/src/Discord.Net/DiscordClient.Voice.cs b/src/Discord.Net/DiscordClient.Voice.cs new file mode 100644 index 000000000..62e59cb74 --- /dev/null +++ b/src/Discord.Net/DiscordClient.Voice.cs @@ -0,0 +1,57 @@ +using Discord.Helpers; +using System; +using System.Threading.Tasks; + +namespace Discord +{ + public partial class DiscordClient + { + public Task JoinVoiceServer(string channelId) + => JoinVoiceServer(_channels[channelId]); + public async Task JoinVoiceServer(Channel channel) + { + CheckReady(checkVoice: true); + if (channel == null) throw new ArgumentNullException(nameof(channel)); + + await LeaveVoiceServer().ConfigureAwait(false); + _dataSocket.SendJoinVoice(channel); + //await _voiceSocket.WaitForConnection().ConfigureAwait(false); + //TODO: Add another ManualResetSlim to wait on here, base it off of DiscordClient's setup + } + + public async Task LeaveVoiceServer() + { + CheckReady(checkVoice: true); + + await _voiceSocket.Disconnect().ConfigureAwait(false); + await TaskHelper.CompletedTask.ConfigureAwait(false); + _dataSocket.SendLeaveVoice(); + } + + /// Sends a PCM frame to the voice server. + /// PCM frame to send. This must be a single or collection of uncompressed 48Kz monochannel 20ms PCM frames. + /// Number of bytes in this frame. + /// Will block until + public void SendVoicePCM(byte[] data, int count) + { + CheckReady(checkVoice: true); + if (count == 0) return; + _voiceSocket.SendPCMFrames(data, count); + } + + /// Clears the PCM buffer. + public void ClearVoicePCM() + { + CheckReady(checkVoice: true); + _voiceSocket.ClearPCMFrames(); + } + + /// Returns a task that completes once the voice output buffer is empty. + public async Task WaitVoice() + { + CheckReady(checkVoice: true); + _voiceSocket.Wait(); + await TaskHelper.CompletedTask.ConfigureAwait(false); + } + } +} diff --git a/src/Discord.Net/DiscordClient.cs b/src/Discord.Net/DiscordClient.cs index 2229d35bc..0c1baf1a4 100644 --- a/src/Discord.Net/DiscordClient.cs +++ b/src/Discord.Net/DiscordClient.cs @@ -1,498 +1,137 @@ -using Discord.API; -using Discord.API.Models; +using Discord.Collections; using Discord.Helpers; -using Newtonsoft.Json; +using Discord.Net; +using Discord.Net.API; +using Discord.Net.WebSockets; using System; using System.Collections.Concurrent; using System.Net; -using System.Text.RegularExpressions; +using System.Runtime.ExceptionServices; using System.Threading; using System.Threading.Tasks; namespace Discord { + public enum DiscordClientState : byte + { + Disconnected, + Connecting, + Connected, + Disconnecting + } + /// Provides a connection to the DiscordApp service. public partial class DiscordClient { - private readonly JsonHttpClient _http; - private readonly DiscordAPI _api; - private readonly DiscordDataSocket _webSocket; -#if !DNXCORE50 - private readonly DiscordVoiceSocket _voiceWebSocket; -#endif - private readonly JsonSerializer _serializer; - private readonly Regex _userRegex, _channelRegex; - private readonly MatchEvaluator _userRegexEvaluator, _channelRegexEvaluator; - private readonly ManualResetEvent _disconnectEvent; private readonly Random _rand; + private readonly DiscordAPIClient _api; + private readonly DataWebSocket _dataSocket; + private readonly VoiceWebSocket _voiceSocket; private readonly ConcurrentQueue _pendingMessages; + private readonly ManualResetEvent _disconnectedEvent; + private readonly ManualResetEventSlim _connectedEvent; + private Task _runTask; + protected ExceptionDispatchInfo _disconnectReason; + private bool _wasDisconnectUnexpected; + + /// Returns the id of the current logged-in user. + public string CurrentUserId => _currentUserId; + private string _currentUserId; + /// Returns the current logged-in user. + public User CurrentUser => _currentUser; + private User _currentUser; + + public DiscordClientState State => (DiscordClientState)_state; + private int _state; + + public DiscordClientConfig Config => _config; private readonly DiscordClientConfig _config; + + public Channels Channels => _channels; + private readonly Channels _channels; + public Members Members => _members; + private readonly Members _members; + public Messages Messages => _messages; + private readonly Messages _messages; + public Roles Roles => _roles; + private readonly Roles _roles; + public Servers Servers => _servers; + private readonly Servers _servers; + public Users Users => _users; + private readonly Users _users; + + public CancellationToken CancelToken => _cancelToken.Token; + private CancellationTokenSource _cancelToken; - private volatile Task _runTask; - protected volatile string _myId, _sessionId; - - /// Returns the User object for the current logged in user. - public User User => _user; - private User _user; - - /// Returns true if the user has successfully logged in and the websocket connection has been established. - public bool IsConnected => _isConnected; - private bool _isConnected, _isDisconnecting; - - /// Returns true if this client was requested to disconnect. - public bool IsClosing => _disconnectToken.IsCancellationRequested; - /// Returns a cancel token that is triggered when a disconnect is requested. - public CancellationToken CloseToken => _disconnectToken.Token; - private volatile CancellationTokenSource _disconnectToken; - - internal bool IsDebugMode => _isDebugMode; - private bool _isDebugMode; - -#if !DNXCORE50 - public Server CurrentVoiceServer => GetServer(_voiceWebSocket.CurrentVoiceServerId); -#endif - - //Constructor /// Initializes a new instance of the DiscordClient class. public DiscordClient(DiscordClientConfig config = null) { - _disconnectEvent = new ManualResetEvent(true); _config = config ?? new DiscordClientConfig(); - _isDebugMode = _config.EnableDebug; - _rand = new Random(); - - _serializer = new JsonSerializer(); -#if TEST_RESPONSES - _serializer.CheckAdditionalContent = true; - _serializer.MissingMemberHandling = MissingMemberHandling.Error; -#endif + _config.Lock(); - _userRegex = new Regex(@"<@\d+?>", RegexOptions.Compiled); - _channelRegex = new Regex(@"<#\d+?>", RegexOptions.Compiled); - _userRegexEvaluator = new MatchEvaluator(e => - { - string id = e.Value.Substring(2, e.Value.Length - 3); - var user = _users[id]; - if (user != null) - return '@' + user.Name; - else //User not found - return e.Value; - }); - _channelRegexEvaluator = new MatchEvaluator(e => - { - string id = e.Value.Substring(2, e.Value.Length - 3); - var channel = _channels[id]; - if (channel != null) - return channel.Name; - else //Channel not found - return e.Value; - }); - - if (_config.UseMessageQueue) - _pendingMessages = new ConcurrentQueue(); - - _http = new JsonHttpClient(_config.EnableDebug); - _api = new DiscordAPI(_http); - if (_isDebugMode) - _http.OnDebugMessage += (s, e) => RaiseOnDebugMessage(e.Type, e.Message); - - CreateCaches(); + _state = (int)DiscordClientState.Disconnected; + _disconnectedEvent = new ManualResetEvent(true); + _connectedEvent = new ManualResetEventSlim(false); + _rand = new Random(); - _webSocket = new DiscordDataSocket(this, _config.ConnectionTimeout, _config.WebSocketInterval, _config.EnableDebug); - _webSocket.Connected += (s, e) => RaiseConnected(); - _webSocket.Disconnected += async (s, e) => + _api = new DiscordAPIClient(_config.LogLevel); + _dataSocket = new DataWebSocket(this); + _dataSocket.Connected += (s, e) => { if (_state == (int)DiscordClientState.Connecting) CompleteConnect(); }; + _voiceSocket = new VoiceWebSocket(this); + + _channels = new Channels(this); + _members = new Members(this); + _messages = new Messages(this); + _roles = new Roles(this); + _servers = new Servers(this); + _users = new Users(this); + + _dataSocket.LogMessage += (s, e) => RaiseOnLog(e.Severity, LogMessageSource.DataWebSocket, e.Message); + _voiceSocket.LogMessage += (s, e) => RaiseOnLog(e.Severity, LogMessageSource.DataWebSocket, e.Message); + if (_config.LogLevel >= LogMessageSeverity.Info) { - RaiseDisconnected(); - - //Reconnect if we didn't cause the disconnect - if (e.WasUnexpected) - { - await Task.Delay(_config.ReconnectDelay).ConfigureAwait(false); - while (!_disconnectToken.IsCancellationRequested) - { - try - { - await _webSocket.ReconnectAsync().ConfigureAwait(false); - if (_http.Token != null) - await _webSocket.Login(_http.Token).ConfigureAwait(false); - break; - } - catch (Exception ex) - { - RaiseOnDebugMessage(DebugMessageType.Connection, $"DataSocket reconnect failed: {ex.Message}"); - //Net is down? We can keep trying to reconnect until the user runs Disconnect() - await Task.Delay(_config.FailedReconnectDelay).ConfigureAwait(false); - } - } - } - }; - if (_isDebugMode) - _webSocket.OnDebugMessage += (s, e) => RaiseOnDebugMessage(e.Type, $"DataSocket: {e.Message}"); - -#if !DNXCORE50 - if (_config.EnableVoice) + _dataSocket.Connected += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.DataWebSocket, "Connected"); + _dataSocket.Disconnected += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.DataWebSocket, "Disconnected"); + _voiceSocket.Connected += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.VoiceWebSocket, "Connected"); + _voiceSocket.Disconnected += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.VoiceWebSocket, "Disconnected"); + } + if (_config.LogLevel >= LogMessageSeverity.Verbose) { - _voiceWebSocket = new DiscordVoiceSocket(this, _config.VoiceConnectionTimeout, _config.WebSocketInterval, _config.VoiceBufferLength, _config.EnableDebug); - _voiceWebSocket.Connected += (s, e) => RaiseVoiceConnected(); - _voiceWebSocket.Disconnected += async (s, e) => + _channels.ItemCreated += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Cache, $"Created Channel {e.Item.ServerId}/{e.Item.Id}"); + _channels.ItemUpdated += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Cache, $"Updated Channel {e.Item.ServerId}/{e.Item.Id}"); + _channels.ItemDestroyed += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Cache, $"Destroyed Channel {e.Item.ServerId}/{e.Item.Id}"); + _members.ItemCreated += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Cache, $"Created Member {e.Item.ServerId}/{e.Item.UserId}"); + _members.ItemUpdated += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Cache, $"Updated Member {e.Item.ServerId}/{e.Item.UserId}"); + _members.ItemDestroyed += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Cache, $"Destroyed Member {e.Item.ServerId}/{e.Item.UserId}"); + _messages.ItemCreated += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Cache, $"Created Message {e.Item.ServerId}/{e.Item.ChannelId}/{e.Item.Id}"); + _messages.ItemUpdated += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Cache, $"Updated Message {e.Item.ServerId}/{e.Item.ChannelId}/{e.Item.Id}"); + _messages.ItemDestroyed += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Cache, $"Destroyed Message {e.Item.ServerId}/{e.Item.ChannelId}/{e.Item.Id}"); + _roles.ItemCreated += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Cache, $"Created Role {e.Item.ServerId}/{e.Item.Id}"); + _roles.ItemUpdated += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Cache, $"Updated Role {e.Item.ServerId}/{e.Item.Id}"); + _roles.ItemDestroyed += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Cache, $"Destroyed Role {e.Item.ServerId}/{e.Item.Id}"); + _servers.ItemCreated += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Cache, $"Created Server {e.Item.Id}"); + _servers.ItemUpdated += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Cache, $"Updated Server {e.Item.Id}"); + _servers.ItemDestroyed += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Cache, $"Destroyed Server {e.Item.Id}"); + _users.ItemCreated += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Cache, $"Created User {e.Item.Id}"); + _users.ItemUpdated += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Cache, $"Updated User {e.Item.Id}"); + _users.ItemDestroyed += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Cache, $"Destroyed User {e.Item.Id}"); + + _api.RestClient.OnRequest += (s, e) => { - RaiseVoiceDisconnected(); - - //Reconnect if we didn't cause the disconnect - if (e.WasUnexpected) - { - await Task.Delay(_config.ReconnectDelay).ConfigureAwait(false); - while (!_disconnectToken.IsCancellationRequested) - { - try - { - await _voiceWebSocket.ReconnectAsync().ConfigureAwait(false); - break; - } - catch (Exception ex) - { - if (_isDebugMode) - RaiseOnDebugMessage(DebugMessageType.Connection, $"VoiceSocket reconnect failed: {ex.Message}"); - //Net is down? We can keep trying to reconnect until the user runs Disconnect() - await Task.Delay(_config.FailedReconnectDelay).ConfigureAwait(false); - } - } - } + if (e.Payload != null) + RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Cache, $"{e.Method.Method} {e.Path}: {Math.Round(e.ElapsedMilliseconds, 2)} ({e.Payload})"); + else + RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Cache, $"{e.Method.Method} {e.Path}: {Math.Round(e.ElapsedMilliseconds, 2)}"); }; - if (_isDebugMode) - _voiceWebSocket.OnDebugMessage += (s, e) => RaiseOnDebugMessage(e.Type, $"VoiceSocket: {e.Message}"); } -#endif -#if !DNXCORE50 - _webSocket.GotEvent += async (s, e) => -#else - _webSocket.GotEvent += (s, e) => -#endif - { - switch (e.Type) - { - //Global - case "READY": //Resync - { - var data = e.Event.ToObject(_serializer); - - _servers.Clear(); - _channels.Clear(); - _users.Clear(); - - _myId = data.User.Id; -#if !DNXCORE50 - _sessionId = data.SessionId; -#endif - _user = _users.Update(data.User.Id, data.User); - foreach (var server in data.Guilds) - _servers.Update(server.Id, server); - foreach (var channel in data.PrivateChannels) - _channels.Update(channel.Id, null, channel); - } - break; - - //Servers - case "GUILD_CREATE": - { - var data = e.Event.ToObject(_serializer); - var server = _servers.Update(data.Id, data); - try { RaiseServerCreated(server); } catch { } - } - break; - case "GUILD_UPDATE": - { - var data = e.Event.ToObject(_serializer); - var server = _servers.Update(data.Id, data); - try { RaiseServerUpdated(server); } catch { } - } - break; - case "GUILD_DELETE": - { - var data = e.Event.ToObject(_serializer); - var server = _servers.Remove(data.Id); - if (server != null) - try { RaiseServerDestroyed(server); } catch { } - } - break; - - //Channels - case "CHANNEL_CREATE": - { - var data = e.Event.ToObject(_serializer); - var channel = _channels.Update(data.Id, data.GuildId, data); - try { RaiseChannelCreated(channel); } catch { } - } - break; - case "CHANNEL_UPDATE": - { - var data = e.Event.ToObject(_serializer); - var channel = _channels.Update(data.Id, data.GuildId, data); - try { RaiseChannelUpdated(channel); } catch { } - } - break; - case "CHANNEL_DELETE": - { - var data = e.Event.ToObject(_serializer); - var channel = _channels.Remove(data.Id); - if (channel != null) - try { RaiseChannelDestroyed(channel); } catch { } - } - break; - - //Members - case "GUILD_MEMBER_ADD": - { - var data = e.Event.ToObject(_serializer); - var user = _users.Update(data.User.Id, data.User); - var server = _servers[data.ServerId]; - if (server != null) - { - var member = server.UpdateMember(data); - try { RaiseMemberAdded(member); } catch { } - } - } - break; - case "GUILD_MEMBER_UPDATE": - { - var data = e.Event.ToObject(_serializer); - var user = _users.Update(data.User.Id, data.User); - var server = _servers[data.ServerId]; - if (server != null) - { - var member = server.UpdateMember(data); - try { RaiseMemberUpdated(member); } catch { } - } - } - break; - case "GUILD_MEMBER_REMOVE": - { - var data = e.Event.ToObject(_serializer); - var server = _servers[data.ServerId]; - if (server != null) - { - var member = server.RemoveMember(data.User.Id); - if (member != null) - try { RaiseMemberRemoved(member); } catch { } - } - } - break; - - //Roles - case "GUILD_ROLE_CREATE": - { - var data = e.Event.ToObject(_serializer); - var role = _roles.Update(data.Role.Id, data.ServerId, data.Role); - try { RaiseRoleCreated(role); } catch { } - } - break; - case "GUILD_ROLE_UPDATE": - { - var data = e.Event.ToObject(_serializer); - var role = _roles.Update(data.Role.Id, data.ServerId, data.Role); - try { RaiseRoleUpdated(role); } catch { } - } - break; - case "GUILD_ROLE_DELETE": - { - var data = e.Event.ToObject(_serializer); - var role = _roles.Remove(data.RoleId); - if (role != null) - try { RaiseRoleDeleted(role); } catch { } - } - break; - - //Bans - case "GUILD_BAN_ADD": - { - var data = e.Event.ToObject(_serializer); - var user = _users.Update(data.User.Id, data.User); - var server = _servers[data.ServerId]; - try { RaiseBanAdded(user, server); } catch { } - } - break; - case "GUILD_BAN_REMOVE": - { - var data = e.Event.ToObject(_serializer); - var user = _users.Update(data.User.Id, data.User); - var server = _servers[data.ServerId]; - if (server != null && server.RemoveBan(user.Id)) - { - try { RaiseBanRemoved(user, server); } catch { } - } - } - break; - - //Messages - case "MESSAGE_CREATE": - { - var data = e.Event.ToObject(_serializer); - Message msg = null; - bool wasLocal = _config.UseMessageQueue && data.Author.Id == _myId && data.Nonce != null; - if (wasLocal) - { - msg = _messages.Remap("nonce" + data.Nonce, data.Id); - if (msg != null) - { - msg.IsQueued = false; - msg.Id = data.Id; - } - } - msg = _messages.Update(data.Id, data.ChannelId, data); - msg.User.UpdateActivity(data.Timestamp); - if (wasLocal) - { - try { RaiseMessageSent(msg); } catch { } - } - try { RaiseMessageCreated(msg); } catch { } - } - break; - case "MESSAGE_UPDATE": - { - var data = e.Event.ToObject(_serializer); - var msg = _messages.Update(data.Id, data.ChannelId, data); - try { RaiseMessageUpdated(msg); } catch { } - } - break; - case "MESSAGE_DELETE": - { - var data = e.Event.ToObject(_serializer); - var msg = GetMessage(data.MessageId); - if (msg != null) - { - _messages.Remove(msg.Id); - try { RaiseMessageDeleted(msg); } catch { } - } - } - break; - case "MESSAGE_ACK": - { - var data = e.Event.ToObject(_serializer); - var msg = GetMessage(data.MessageId); - if (msg != null) - try { RaiseMessageRead(msg); } catch { } - } - break; - - //Statuses - case "PRESENCE_UPDATE": - { - var data = e.Event.ToObject(_serializer); - var user = _users.Update(data.User.Id, data.User); - var server = _servers[data.ServerId]; - if (server != null) - { - var member = server.UpdateMember(data); - try { RaisePresenceUpdated(member); } catch { } - } - } - break; - case "VOICE_STATE_UPDATE": - { - var data = e.Event.ToObject(_serializer); - var server = _servers[data.ServerId]; - if (server != null) - { - var member = server.UpdateMember(data); - if (member != null) - try { RaiseVoiceStateUpdated(member); } catch { } - } - } - break; - case "TYPING_START": - { - var data = e.Event.ToObject(_serializer); - var channel = _channels[data.ChannelId]; - var user = _users[data.UserId]; - if (user != null) - { - user.UpdateActivity(DateTime.UtcNow); - if (channel != null) - try { RaiseUserTyping(user, channel); } catch { } - } - } - break; - - //Voice - case "VOICE_SERVER_UPDATE": - { - var data = e.Event.ToObject(_serializer); - var server = _servers[data.ServerId]; - server.VoiceServer = data.Endpoint; - try { RaiseVoiceServerUpdated(server, data.Endpoint); } catch { } - -#if !DNXCORE50 - if (_config.EnableVoice) - { - _voiceWebSocket.SetSessionData(data.ServerId, _myId, _sessionId, data.Token); - await _voiceWebSocket.ConnectAsync("wss://" + data.Endpoint.Split(':')[0]).ConfigureAwait(false); - } -#endif - } - break; - - //Settings - case "USER_UPDATE": - { - var data = e.Event.ToObject(_serializer); - var user = _users.Update(data.Id, data); - try { RaiseUserUpdated(user); } catch { } - } - break; - case "USER_SETTINGS_UPDATE": - { - //TODO: Process this - } - break; - - //Others - default: - RaiseOnDebugMessage(DebugMessageType.WebSocketUnknownEvent, "Unknown WebSocket message type: " + e.Type); - break; - } - }; - } - - //Async - private async Task MessageQueueLoop() - { - var cancelToken = _disconnectToken.Token; - try - { - Message msg; - while (!cancelToken.IsCancellationRequested) - { - while (_pendingMessages.TryDequeue(out msg)) - { - bool hasFailed = false; - APIResponses.SendMessage apiMsg = null; - try - { - apiMsg = await _api.SendMessage(msg.ChannelId, msg.RawText, msg.MentionIds, msg.Nonce).ConfigureAwait(false); - } - catch (WebException) { break; } - catch (HttpException) { hasFailed = true; } + if (_config.UseMessageQueue) + _pendingMessages = new ConcurrentQueue(); + } - if (!hasFailed) - { - _messages.Remap("nonce_", apiMsg.Id); - _messages.Update(msg.Id, msg.ChannelId, apiMsg); - } - msg.IsQueued = false; - msg.HasFailed = hasFailed; - try { RaiseMessageSent(msg); } catch { } - } - await Task.Delay(_config.MessageQueueInterval).ConfigureAwait(false); - } - } - catch { } - finally { _disconnectToken.Cancel(); } - } - private string GenerateNonce() + private void _dataSocket_Connected(object sender, EventArgs e) { - lock (_rand) - return _rand.Next().ToString(); + throw new NotImplementedException(); } //Connection @@ -501,184 +140,213 @@ namespace Discord { await Disconnect().ConfigureAwait(false); - if (_isDebugMode) - RaiseOnDebugMessage(DebugMessageType.Connection, $"DataSocket is using cached token."); + if (_config.LogLevel >= LogMessageSeverity.Verbose) + RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Authentication, $"Using cached token."); - await ConnectInternal(token).ConfigureAwait(false); - } + await ConnectInternal(token).ConfigureAwait(false); + } /// Connects to the Discord server with the provided email and password. /// Returns a token for future connections. public async Task Connect(string email, string password) { await Disconnect().ConfigureAwait(false); - + var response = await _api.Login(email, password).ConfigureAwait(false); - if (_isDebugMode) - RaiseOnDebugMessage(DebugMessageType.Connection, $"DataSocket got token."); - + if (_config.LogLevel >= LogMessageSeverity.Verbose) + RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Authentication, "Login successful, got token."); + return await ConnectInternal(response.Token).ConfigureAwait(false); } - private async Task ConnectInternal(string token) { - _http.Token = token; - string url = (await _api.GetWebSocketEndpoint().ConfigureAwait(false)).Url; - if (_isDebugMode) - RaiseOnDebugMessage(DebugMessageType.Connection, $"DataSocket got endpoint."); + if (_state != (int)DiscordClientState.Disconnected) + throw new InvalidOperationException("Client is already connected or connecting to the server."); - await _webSocket.ConnectAsync(url).ConfigureAwait(false); - await _webSocket.Login(token).ConfigureAwait(false); + try + { + _disconnectedEvent.Reset(); + _cancelToken = new CancellationTokenSource(); + _state = (int)DiscordClientState.Connecting; + + _api.Token = token; + string url = (await _api.GetWebSocketEndpoint().ConfigureAwait(false)).Url; + if (_config.LogLevel >= LogMessageSeverity.Verbose) + RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Authentication, $"Websocket endpoint: {url}"); + + await _dataSocket.Login(url, token).ConfigureAwait(false); + + _runTask = RunTasks(); + + try + { + if (!_connectedEvent.Wait(_config.ConnectionTimeout, CancellationTokenSource.CreateLinkedTokenSource(_cancelToken.Token, _dataSocket.CancelToken).Token)) + throw new Exception("Operation timed out."); + } + catch (OperationCanceledException) + { + _dataSocket.ThrowError(); + throw; + } - _runTask = Run(); - return token; + //_state = (int)DiscordClientState.Connected; + return token; + } + catch + { + await Disconnect().ConfigureAwait(false); + throw; + } + } + protected void CompleteConnect() + { + _state = (int)WebSocketState.Connected; + _connectedEvent.Set(); } + /// Disconnects from the Discord server, canceling any pending requests. - public async Task Disconnect() + public Task Disconnect() => DisconnectInternal(new Exception("Disconnect was requested by user.")); + protected async Task DisconnectInternal(Exception ex, bool isUnexpected = true) { - Task task = _runTask; - if (task != null) + int oldState; + bool hasWriterLock; + + //If in either connecting or connected state, get a lock by being the first to switch to disconnecting + oldState = Interlocked.CompareExchange(ref _state, (int)DiscordClientState.Disconnecting, (int)DiscordClientState.Connecting); + if (oldState == (int)DiscordClientState.Disconnected) return; //Already disconnected + hasWriterLock = oldState == (int)DiscordClientState.Connecting; //Caused state change + if (!hasWriterLock) { - try { _disconnectToken.Cancel(); } catch (NullReferenceException) { } - try { await task.ConfigureAwait(false); } catch (NullReferenceException) { } + oldState = Interlocked.CompareExchange(ref _state, (int)DiscordClientState.Disconnecting, (int)DiscordClientState.Connected); + if (oldState == (int)DiscordClientState.Disconnected) return; //Already disconnected + hasWriterLock = oldState == (int)DiscordClientState.Connected; //Caused state change } + + if (hasWriterLock) + { + _wasDisconnectUnexpected = isUnexpected; + _disconnectReason = ExceptionDispatchInfo.Capture(ex); + _cancelToken.Cancel(); + } + + Task task = _runTask; + if (task != null) + await task.ConfigureAwait(false); + + if (hasWriterLock) + { + _state = (int)DiscordClientState.Disconnected; + _disconnectedEvent.Set(); + _connectedEvent.Reset(); + } } - private async Task Run() + private async Task RunTasks() { - _disconnectEvent.Reset(); - _disconnectToken = new CancellationTokenSource(); - - //Run Loops Task task; if (_config.UseMessageQueue) task = MessageQueueLoop(); else - task = _disconnectToken.Wait(); + task = _cancelToken.Wait(); - _isConnected = true; + try + { + await task.ConfigureAwait(false); + } + catch (Exception ex) { await DisconnectInternal(ex).ConfigureAwait(false); } - await task.ConfigureAwait(false); - await Cleanup(); + await Cleanup().ConfigureAwait(false); + _runTask = null; } - //TODO: What happens if a reconnect occurs and caches havent been cleared yet? Compare to DiscordWebSocket.Cleanup() private async Task Cleanup() { - _disconnectEvent.Set(); + _disconnectedEvent.Set(); - await _webSocket.DisconnectAsync().ConfigureAwait(false); + await _dataSocket.Disconnect().ConfigureAwait(false); #if !DNXCORE50 if (_config.EnableVoice) - await _voiceWebSocket.DisconnectAsync().ConfigureAwait(false); + await _voiceSocket.Disconnect().ConfigureAwait(false); #endif Message ignored; while (_pendingMessages.TryDequeue(out ignored)) { } - + _channels.Clear(); + _members.Clear(); _messages.Clear(); _roles.Clear(); _servers.Clear(); _users.Clear(); - _runTask = null; - _isConnected = false; - _isDisconnecting = false; - } - - //Voice - public Task JoinVoiceServer(string channelId) - => JoinVoiceServer(_channels[channelId]); - public async Task JoinVoiceServer(Channel channel) - { - CheckReady(checkVoice: true); - if (channel == null) throw new ArgumentNullException(nameof(channel)); - - await LeaveVoiceServer().ConfigureAwait(false); - _webSocket.JoinVoice(channel); -#if !DNXCORE50 - await _voiceWebSocket.BeginConnect().ConfigureAwait(false); -#else - await Task.CompletedTask.ConfigureAwait(false); -#endif + _currentUser = null; + _currentUserId = null; } - public async Task LeaveVoiceServer() + //Helpers + private void CheckReady(bool checkVoice = false) { - CheckReady(checkVoice: true); + switch (_state) + { + case (int)DiscordClientState.Disconnecting: + throw new InvalidOperationException("The client is disconnecting."); + case (int)DiscordClientState.Disconnected: + throw new InvalidOperationException("The client is not connected to Discord"); + case (int)DiscordClientState.Connecting: + throw new InvalidOperationException("The client is connecting."); + } #if !DNXCORE50 - await _voiceWebSocket.DisconnectAsync().ConfigureAwait(false); + if (checkVoice && !_config.EnableVoice) #else - await Task.CompletedTask.ConfigureAwait(false); + if (checkVoice) //Always fail on DNXCORE50 #endif - _webSocket.LeaveVoice(); + throw new InvalidOperationException("Voice is not enabled for this client."); } - - /// Sends a PCM frame to the voice server. - /// PCM frame to send. This must be a single or collection of uncompressed 48Kz monochannel 20ms PCM frames. - /// Number of bytes in this frame. - /// Will block until - public void SendVoicePCM(byte[] data, int count) + /// Blocking call that will not return until client has been stopped. This is mainly intended for use in console applications. + public void Block() { - CheckReady(checkVoice: true); - if (count == 0) return; - - if (_isDebugMode) - RaiseOnDebugMessage(DebugMessageType.VoiceOutput, $"Queued {count} bytes for voice output."); -#if !DNXCORE50 - _voiceWebSocket.SendPCMFrames(data, count); -#endif + _disconnectedEvent.WaitOne(); } - /// Clears the PCM buffer. - public void ClearVoicePCM() + //Experimental + private Task MessageQueueLoop() { - CheckReady(checkVoice: true); - - if (_isDebugMode) - RaiseOnDebugMessage(DebugMessageType.VoiceOutput, $"Cleared the voice buffer."); -#if !DNXCORE50 - _voiceWebSocket.ClearPCMFrames(); -#endif - } + var cancelToken = _cancelToken.Token; + int interval = _config.MessageQueueInterval; - /// Returns a task that completes once the voice output buffer is empty. - public async Task WaitVoice() - { - CheckReady(checkVoice: true); + return Task.Run(async () => + { + Message msg; + while (!cancelToken.IsCancellationRequested) + { + while (_pendingMessages.TryDequeue(out msg)) + { + bool hasFailed = false; + Responses.SendMessage response = null; + try + { + response = await _api.SendMessage(msg.ChannelId, msg.RawText, msg.MentionIds, msg.Nonce).ConfigureAwait(false); + } + catch (WebException) { break; } + catch (HttpException) { hasFailed = true; } -#if !DNXCORE50 - _voiceWebSocket.Wait(); -#endif - await TaskHelper.CompletedTask.ConfigureAwait(false); + if (!hasFailed) + { + _messages.Remap(msg.Id, response.Id); + msg.Id = response.Id; + msg.Update(response); + } + msg.IsQueued = false; + msg.HasFailed = hasFailed; + try { RaiseMessageSent(msg); } catch { } + } + await Task.Delay(interval).ConfigureAwait(false); + } + }); } - - //Helpers - private void CheckReady(bool checkVoice = false) + private string GenerateNonce() { - if (_isDisconnecting) - throw new InvalidOperationException("The client is currently disconnecting."); - else if (!_isConnected) - throw new InvalidOperationException("The client is not currently connected to Discord"); -#if !DNXCORE50 - else if (checkVoice && !_config.EnableVoice) -#else - else if (checkVoice) //Always fail on DNXCORE50 -#endif - throw new InvalidOperationException("Voice is not enabled for this client."); + lock (_rand) + return _rand.Next().ToString(); } - internal string CleanMessageText(string text) - { - text = _userRegex.Replace(text, _userRegexEvaluator); - text = _channelRegex.Replace(text, _channelRegexEvaluator); - return text; - } - - /// Blocking call that will not return until client has been stopped. This is mainly intended for use in console applications. - public void Block() - { - if (_isConnected) - _disconnectEvent.WaitOne(); - } } } diff --git a/src/Discord.Net/DiscordClientConfig.cs b/src/Discord.Net/DiscordClientConfig.cs index bf6ff8715..900ee4b4a 100644 --- a/src/Discord.Net/DiscordClientConfig.cs +++ b/src/Discord.Net/DiscordClientConfig.cs @@ -1,31 +1,51 @@ -namespace Discord +using System; + +namespace Discord { - public class DiscordClientConfig + public class DiscordClientConfig { -#if !DNXCORE50 - /// Enables the voice websocket and UDP client (Experimental!). This option requires the opus .dll or .so be in the local lib/ folder. - public bool EnableVoice { get; set; } = false; -#endif - /// Enables the verbose DebugMessage event handler. May hinder performance but should help debug any issues. - public bool EnableDebug { get; set; } = false; + /// Specifies the minimum log level severity that will be sent to the LogMessage event. Warning: setting this to verbose will hinder performance but should help investigate any internal issues. + public LogMessageSeverity LogLevel { get { return _logLevel; } set { SetValue(ref _logLevel, value); } } + private LogMessageSeverity _logLevel = LogMessageSeverity.Info; - /// Max time in milliseconds to wait for the web socket to connect. - public int ConnectionTimeout { get; set; } = 10000; - /// Max time in milliseconds to wait for the voice web socket to connect. - public int VoiceConnectionTimeout { get; set; } = 10000; + /// Max time in milliseconds to wait for DiscordClient to connect and initialize. + public int ConnectionTimeout { get { return _connectionTimeout; } set { SetValue(ref _connectionTimeout, value); } } + private int _connectionTimeout = 10000; /// Gets or sets the time (in milliseconds) to wait after an unexpected disconnect before reconnecting. - public int ReconnectDelay { get; set; } = 1000; + public int ReconnectDelay { get { return _reconnectDelay; } set { SetValue(ref _reconnectDelay, value); } } + private int _reconnectDelay = 1000; /// Gets or sets the time (in milliseconds) to wait after an reconnect fails before retrying. - public int FailedReconnectDelay { get; set; } = 10000; + public int FailedReconnectDelay { get { return _failedReconnectDelay; } set { SetValue(ref _failedReconnectDelay, value); } } + private int _failedReconnectDelay = 10000; + /// Gets or sets the time (in milliseconds) to wait when the websocket's message queue is empty before checking again. - public int WebSocketInterval { get; set; } = 100; - /// Enables or disables the internal message queue. This will allow SendMessage to return immediately and handle messages internally. Messages will set the IsQueued and HasFailed properties to show their progress. - public bool UseMessageQueue { get; set; } = false; + public int WebSocketInterval { get { return _webSocketInterval; } set { SetValue(ref _webSocketInterval, value); } } + private int _webSocketInterval = 100; /// Gets or sets the time (in milliseconds) to wait when the message queue is empty before checking again. - public int MessageQueueInterval { get; set; } = 100; + public int MessageQueueInterval { get { return _messageQueueInterval; } set { SetValue(ref _messageQueueInterval, value); } } + private int _messageQueueInterval = 100; /// Gets or sets the max buffer length (in milliseconds) for outgoing voice packets. This value is the target maximum but is not guaranteed, the buffer will often go slightly above this value. - public int VoiceBufferLength { get; set; } = 3000; + public int VoiceBufferLength { get { return _voiceBufferLength; } set { SetValue(ref _voiceBufferLength, value); } } + private int _voiceBufferLength = 3000; + + //Experimental Features +#if !DNXCORE50 + /// (Experimental) Enables the voice websocket and UDP client (Experimental!). This option requires the opus .dll or .so be in the local lib/ folder. + public bool EnableVoice { get { return _enableVoice; } set { SetValue(ref _enableVoice, value); } } + private bool _enableVoice = false; +#endif + /// (Experimental) Enables or disables the internal message queue. This will allow SendMessage to return immediately and handle messages internally. Messages will set the IsQueued and HasFailed properties to show their progress. + public bool UseMessageQueue { get { return _useMessageQueue; } set { SetValue(ref _useMessageQueue, value); } } + private bool _useMessageQueue = false; - public DiscordClientConfig() { } + //Lock + private bool _isLocked; + internal void Lock() { _isLocked = true; } + private void SetValue(ref T storage, T value) + { + if (_isLocked) + throw new InvalidOperationException("Unable to modify a discord client's configuration after it has been created."); + storage = value; + } } } diff --git a/src/Discord.Net/DiscordDataSocket.Events.cs b/src/Discord.Net/DiscordDataSocket.Events.cs deleted file mode 100644 index f0d8188c2..000000000 --- a/src/Discord.Net/DiscordDataSocket.Events.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Newtonsoft.Json.Linq; -using System; - -namespace Discord -{ - internal partial class DiscordDataSocket - { - public event EventHandler GotEvent; - public sealed class MessageEventArgs : EventArgs - { - public readonly string Type; - public readonly JToken Event; - internal MessageEventArgs(string type, JToken data) - { - Type = type; - Event = data; - } - } - private void RaiseGotEvent(string type, JToken payload) - { - if (GotEvent != null) - GotEvent(this, new MessageEventArgs(type, payload)); - } - } -} diff --git a/src/Discord.Net/DiscordDataSocket.cs b/src/Discord.Net/DiscordDataSocket.cs deleted file mode 100644 index ec9331d15..000000000 --- a/src/Discord.Net/DiscordDataSocket.cs +++ /dev/null @@ -1,140 +0,0 @@ -using Discord.API.Models; -using Discord.Helpers; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using System; -using System.Threading; -using System.Threading.Tasks; -using WebSocketMessage = Discord.API.Models.TextWebSocketCommands.WebSocketMessage; - -namespace Discord -{ - internal sealed partial class DiscordDataSocket : DiscordWebSocket - { - private readonly ManualResetEventSlim _connectWaitOnLogin, _connectWaitOnLogin2; - private string _lastSession, _redirectServer; - private int _lastSeq; - - public DiscordDataSocket(DiscordClient client, int timeout, int interval, bool isDebug) - : base(client, timeout, interval, isDebug) - { - _connectWaitOnLogin = new ManualResetEventSlim(false); - _connectWaitOnLogin2 = new ManualResetEventSlim(false); - } - - public override async Task ConnectAsync(string url) - { - _lastSeq = 0; - _lastSession = null; - _redirectServer = null; - await BeginConnect().ConfigureAwait(false); - await base.ConnectAsync(url).ConfigureAwait(false); - } - public async Task Login(string token) - { - var cancelToken = _disconnectToken.Token; - - _connectWaitOnLogin.Reset(); - _connectWaitOnLogin2.Reset(); - - TextWebSocketCommands.Login msg = new TextWebSocketCommands.Login(); - msg.Payload.Token = token; - msg.Payload.Properties["$os"] = ""; - msg.Payload.Properties["$browser"] = ""; - msg.Payload.Properties["$device"] = "Discord.Net"; - msg.Payload.Properties["$referrer"] = ""; - msg.Payload.Properties["$referring_domain"] = ""; - await SendMessage(msg, cancelToken).ConfigureAwait(false); - - try - { - if (!_connectWaitOnLogin.Wait(_timeout, cancelToken)) //Waiting on READY message - throw new Exception("No reply from Discord server"); - } - catch (OperationCanceledException) - { - if (_disconnectReason == null) - throw new Exception("An unknown websocket error occurred."); - else - _disconnectReason.Throw(); - } - try { _connectWaitOnLogin2.Wait(cancelToken); } //Waiting on READY handler - catch (OperationCanceledException) { return; } - - if (_isDebug) - RaiseOnDebugMessage(DebugMessageType.Connection, $"Logged in."); - - SetConnected(); - } - - protected override Task ProcessMessage(string json) - { - var msg = JsonConvert.DeserializeObject(json); - if (msg.Sequence.HasValue) - _lastSeq = msg.Sequence.Value; - switch (msg.Operation) - { - case 0: - { - if (msg.Type == "READY") - { - var payload = (msg.Payload as JToken).ToObject(); - _lastSession = payload.SessionId; - _heartbeatInterval = payload.HeartbeatInterval; - QueueMessage(new TextWebSocketCommands.UpdateStatus()); - //QueueMessage(GetKeepAlive()); - _connectWaitOnLogin.Set(); //Pre-Event - } - RaiseGotEvent(msg.Type, msg.Payload as JToken); - if (msg.Type == "READY") - _connectWaitOnLogin2.Set(); //Post-Event - } - break; - case 7: - { - var payload = (msg.Payload as JToken).ToObject(); - if (_isDebug) - RaiseOnDebugMessage(DebugMessageType.Connection, $"Redirected to {payload.Url}."); - _host = payload.Url; - DisconnectInternal(new Exception("Server is redirecting."), true); - } - break; - default: - if (_isDebug) - RaiseOnDebugMessage(DebugMessageType.WebSocketUnknownOpCode, "Unknown Opcode: " + msg.Operation); - break; - } - return TaskHelper.CompletedTask; - } - - protected override object GetKeepAlive() - { - return new TextWebSocketCommands.KeepAlive(); - } - - public void JoinVoice(Channel channel) - { - var joinVoice = new TextWebSocketCommands.JoinVoice(); - joinVoice.Payload.ServerId = channel.ServerId; - joinVoice.Payload.ChannelId = channel.Id; - QueueMessage(joinVoice); - } - public void LeaveVoice() - { - var joinVoice = new TextWebSocketCommands.JoinVoice(); - QueueMessage(joinVoice); - } - - protected override void OnConnect() - { - if (_redirectServer != null) - { - var resumeMsg = new TextWebSocketCommands.Resume(); - resumeMsg.Payload.SessionId = _lastSession; - resumeMsg.Payload.Sequence = _lastSeq; - SendMessage(resumeMsg, _disconnectToken.Token); - } - _redirectServer = null; - } - } -} diff --git a/src/Discord.Net/DiscordWebSocket.Events.cs b/src/Discord.Net/DiscordWebSocket.Events.cs deleted file mode 100644 index 607820e7d..000000000 --- a/src/Discord.Net/DiscordWebSocket.Events.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; - -namespace Discord -{ - public class DisconnectedEventArgs : EventArgs - { - public readonly bool WasUnexpected; - internal DisconnectedEventArgs(bool wasUnexpected) { WasUnexpected = wasUnexpected; } - } - - internal abstract partial class DiscordWebSocket - { - //Debug - public event EventHandler OnDebugMessage; - protected void RaiseOnDebugMessage(DebugMessageType type, string message) - { - if (OnDebugMessage != null) - OnDebugMessage(this, new LogMessageEventArgs(type, message)); - } - - //Connection - public event EventHandler Connected; - private void RaiseConnected() - { - if (Connected != null) - Connected(this, EventArgs.Empty); - } - public event EventHandler Disconnected; - private void RaiseDisconnected(bool wasUnexpected) - { - if (Disconnected != null) - Disconnected(this, new DisconnectedEventArgs(wasUnexpected)); - } - } -} diff --git a/src/Discord.Net/DiscordWebSocket.cs b/src/Discord.Net/DiscordWebSocket.cs deleted file mode 100644 index 0c577ba13..000000000 --- a/src/Discord.Net/DiscordWebSocket.cs +++ /dev/null @@ -1,286 +0,0 @@ -using Newtonsoft.Json; -using System; -using System.Collections.Concurrent; -using System.ComponentModel; -using System.Net.WebSockets; -using System.Runtime.ExceptionServices; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -namespace Discord -{ - internal abstract partial class DiscordWebSocket : IDisposable - { - private const int ReceiveChunkSize = 4096; - private const int SendChunkSize = 4096; - private const int HR_TIMEOUT = -2147012894; - - protected readonly DiscordClient _client; - protected readonly int _sendInterval; - protected readonly bool _isDebug; - private readonly ConcurrentQueue _sendQueue; - - protected CancellationTokenSource _disconnectToken; - protected string _host; - protected int _timeout, _heartbeatInterval; - protected ExceptionDispatchInfo _disconnectReason; - private ClientWebSocket _webSocket; - private DateTime _lastHeartbeat; - private Task _runTask; - private bool _isConnected, _wasDisconnectUnexpected; - - public DiscordWebSocket(DiscordClient client, int timeout, int interval, bool isDebug) - { - _client = client; - _timeout = timeout; - _sendInterval = interval; - _isDebug = isDebug; - - _sendQueue = new ConcurrentQueue(); - } - - protected virtual async Task BeginConnect() - { - await DisconnectAsync().ConfigureAwait(false); - _disconnectToken = new CancellationTokenSource(); - _disconnectReason = null; - } - public virtual async Task ConnectAsync(string url) - { - var cancelToken = _disconnectToken.Token; - - _webSocket = new ClientWebSocket(); - _webSocket.Options.KeepAliveInterval = TimeSpan.Zero; - await _webSocket.ConnectAsync(new Uri(url), cancelToken).ConfigureAwait(false); - _host = url; - - if (_isDebug) - RaiseOnDebugMessage(DebugMessageType.Connection, $"Connected."); - - OnConnect(); - - _runTask = Run(); - } - public Task ReconnectAsync() - => ConnectAsync(_host); - public async Task DisconnectAsync() - { - Task task = _runTask; - if (task != null) - { - try { DisconnectInternal(new Exception("Disconnect requested by user."), false); } catch (NullReferenceException) { } - try { await task.ConfigureAwait(false); } catch (NullReferenceException) { } - } - } - - protected void DisconnectInternal(Exception ex, bool isUnexpected = true) - { - if (_disconnectReason == null) - { - _wasDisconnectUnexpected = isUnexpected; - _disconnectReason = ExceptionDispatchInfo.Capture(ex); - _disconnectToken.Cancel(); - } - } - - protected virtual void OnConnect() { } - protected virtual void OnDisconnect() { } - - protected void SetConnected() - { - _isConnected = true; - RaiseConnected(); - } - - private async Task Run() - { - _lastHeartbeat = DateTime.UtcNow; - - await Task.WhenAll(CreateTasks()); - Cleanup(); - } - private void Cleanup() - { - if (_isDebug) - RaiseOnDebugMessage(DebugMessageType.Connection, $"Disconnected."); - OnDisconnect(); - - bool wasUnexpected = _wasDisconnectUnexpected; - _disconnectToken.Dispose(); - _disconnectToken = null; - _wasDisconnectUnexpected = false; - - _heartbeatInterval = 0; - _lastHeartbeat = DateTime.MinValue; - _webSocket.Dispose(); - _webSocket = null; - byte[] ignored; - while (_sendQueue.TryDequeue(out ignored)) { } - - _runTask = null; - if (_isConnected) - { - _isConnected = false; - RaiseDisconnected(wasUnexpected); - } - } - - protected virtual Task[] CreateTasks() - { - return new Task[] - { - ReceiveAsync(), - SendAsync() - }; - } - - private Task ReceiveAsync() - { - var cancelSource = _disconnectToken; - var cancelToken = cancelSource.Token; - - return Task.Run(async () => - { - var buffer = new byte[ReceiveChunkSize]; - var builder = new StringBuilder(); - - try - { - while (_webSocket.State == WebSocketState.Open && !cancelToken.IsCancellationRequested) - { - WebSocketReceiveResult result = null; - do - { - if (_webSocket.State != WebSocketState.Open || cancelToken.IsCancellationRequested) - return; - - try - { - result = await _webSocket.ReceiveAsync(new ArraySegment(buffer), cancelToken).ConfigureAwait(false); - } - catch (Win32Exception ex) - when (ex.HResult == HR_TIMEOUT) - { - string msg = $"Connection timed out."; - RaiseOnDebugMessage(DebugMessageType.Connection, msg); - DisconnectInternal(new Exception(msg)); - return; - } - - if (result.MessageType == WebSocketMessageType.Close) - { - string msg = $"Got Close Message ({result.CloseStatus?.ToString() ?? "Unexpected"}, {result.CloseStatusDescription ?? "No Reason"})"; - RaiseOnDebugMessage(DebugMessageType.Connection, msg); - DisconnectInternal(new Exception(msg)); - await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).ConfigureAwait(false); - return; - } - else - builder.Append(Encoding.UTF8.GetString(buffer, 0, result.Count)); - - } - while (result == null || !result.EndOfMessage); - -#if DEBUG - System.Diagnostics.Debug.WriteLine(">>> " + builder.ToString()); -#endif - await ProcessMessage(builder.ToString()).ConfigureAwait(false); - - builder.Clear(); - } - } - catch (OperationCanceledException) { } - catch (Exception ex) { DisconnectInternal(ex); } - }); - } - private Task SendAsync() - { - var cancelSource = _disconnectToken; - var cancelToken = cancelSource.Token; - - return Task.Run(async () => - { - try - { - byte[] bytes; - while (_webSocket.State == WebSocketState.Open && !cancelToken.IsCancellationRequested) - { - if (_heartbeatInterval > 0) - { - DateTime now = DateTime.UtcNow; - if ((now - _lastHeartbeat).TotalMilliseconds > _heartbeatInterval) - { - await SendMessage(GetKeepAlive(), cancelToken).ConfigureAwait(false); - _lastHeartbeat = now; - } - } - while (_sendQueue.TryDequeue(out bytes)) - await SendMessage(bytes, cancelToken).ConfigureAwait(false); - await Task.Delay(_sendInterval, cancelToken).ConfigureAwait(false); - } - } - catch (OperationCanceledException) { } - catch (Exception ex) { DisconnectInternal(ex); } - }); - } - - protected abstract Task ProcessMessage(string json); - protected abstract object GetKeepAlive(); - - protected void QueueMessage(object message) - { -#if DEBUG - System.Diagnostics.Debug.WriteLine("<<< " + JsonConvert.SerializeObject(message)); -#endif - var bytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(message)); - _sendQueue.Enqueue(bytes); - } - protected Task SendMessage(object message, CancellationToken cancelToken) - { -#if DEBUG - System.Diagnostics.Debug.WriteLine("<<< " + JsonConvert.SerializeObject(message)); -#endif - return SendMessage(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(message)), cancelToken); - } - protected async Task SendMessage(byte[] message, CancellationToken cancelToken) - { - var frameCount = (int)Math.Ceiling((double)message.Length / SendChunkSize); - - int offset = 0; - for (var i = 0; i < frameCount; i++, offset += SendChunkSize) - { - bool isLast = i == (frameCount - 1); - - int count; - if (isLast) - count = message.Length - (i * SendChunkSize); - else - count = SendChunkSize; - - try - { - await _webSocket.SendAsync(new ArraySegment(message, offset, count), WebSocketMessageType.Text, isLast, cancelToken).ConfigureAwait(false); - } - catch (Win32Exception ex) - when (ex.HResult == HR_TIMEOUT) - { - return; - } - } - } - -#region IDisposable Support - private bool _isDisposed = false; - - public void Dispose() - { - if (!_isDisposed) - { - DisconnectAsync().Wait(); - _isDisposed = true; - } - } -#endregion - } -} diff --git a/src/Discord.Net/Enums/Regions.cs b/src/Discord.Net/Enums/Regions.cs index 087b2c552..ea38a6bd7 100644 --- a/src/Discord.Net/Enums/Regions.cs +++ b/src/Discord.Net/Enums/Regions.cs @@ -4,9 +4,9 @@ { public const string US_West = "us-west"; public const string US_East = "us-east"; - public const string Singapore = "singapore"; - public const string London = "london"; - public const string Sydney = "sydney"; + public const string Singapore = "singapore"; + public const string London = "london"; + public const string Sydney = "sydney"; public const string Amsterdam = "amsterdam"; - } + } } diff --git a/src/Discord.Net/Enums/UserStatus.cs b/src/Discord.Net/Enums/UserStatus.cs index 843345b38..76bf7f163 100644 --- a/src/Discord.Net/Enums/UserStatus.cs +++ b/src/Discord.Net/Enums/UserStatus.cs @@ -1,6 +1,6 @@ namespace Discord { - public static class UserStatus + public static class UserStatus { /// User is currently online and active. public const string Online = "online"; diff --git a/src/Discord.Net/Format.cs b/src/Discord.Net/Format.cs index 9b8730696..cdee8e473 100644 --- a/src/Discord.Net/Format.cs +++ b/src/Discord.Net/Format.cs @@ -4,7 +4,7 @@ namespace Discord { public static class Format { - private static char[] specialChars = new char[] {'_', '*', '~', '\\' }; //Backslash must always be last! + private static char[] specialChars = new char[] { '_', '*', '~', '\\' }; //Backslash must always be last! /// Removes all special formatting characters from the provided text. private static string Escape(string text) @@ -21,27 +21,13 @@ namespace Discord { builder.Insert(i, '\\'); length++; - } - } + } + } } return builder.ToString(); } return text; - } - - /// Returns the string used to create a user mention. - public static string User(User user) - => $"<@{user.Id}>"; - /// Returns the string used to create a user mention. - public static string User(string userId) - => $"<@{userId}>"; - - /// Returns the string used to create a channel mention. - public static string Channel(Channel channel) - => $"<#{channel.Id}>"; - /// Returns the string used to create a channel mention. - public static string Channel(string channelId) - => $"<#{channelId}>"; + } /// Returns a markdown-formatted string with no formatting, optionally escaping the contents. public static string Normal(string text, bool escape = true) diff --git a/src/Discord.Net/Helpers/AsyncCache.cs b/src/Discord.Net/Helpers/AsyncCache.cs deleted file mode 100644 index 46a4fa3b1..000000000 --- a/src/Discord.Net/Helpers/AsyncCache.cs +++ /dev/null @@ -1,107 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Concurrent; -using System.Collections.Generic; - -namespace Discord.Helpers -{ - public class AsyncCache : IEnumerable - where TValue : class - where TModel : class - { - protected readonly ConcurrentDictionary _dictionary; - private readonly Func _onCreate; - private readonly Action _onUpdate; - private readonly Action _onRemove; - - public AsyncCache(Func onCreate, Action onUpdate, Action onRemove = null) - { - _dictionary = new ConcurrentDictionary(); - _onCreate = onCreate; - _onUpdate = onUpdate; - _onRemove = onRemove; - } - - public TValue this[string key] - { - get - { - if (key == null) - return null; - TValue value = null; - _dictionary.TryGetValue(key, out value); - return value; - } - } - - public TValue Add(string key, TValue obj) - { - _dictionary[key] = obj; - return obj; - } - public TValue Remap(string oldKey, string newKey) - { - var obj = Remove(oldKey); - if (obj != null) - Add(newKey, obj); - return obj; - } - public TValue Update(string key, TModel model) - { - return Update(key, null, model); - } - public TValue Update(string key, string parentKey, TModel model) - { - if (key == null) - return null; - while (true) - { - bool isNew; - TValue value; - isNew = !_dictionary.TryGetValue(key, out value); - if (isNew) - value = _onCreate(key, parentKey); - if (model != null) - _onUpdate(value, model); - if (isNew) - { - //If this fails, repeat as an update instead of an add - if (_dictionary.TryAdd(key, value)) - return value; - } - else - { - _dictionary[key] = value; - return value; - } - } - } - - public TValue Remove(string key) - { - TValue value = null; - if (_dictionary.TryRemove(key, out value)) - { - if (_onRemove != null) - _onRemove(value); - return value; - } - else - return null; - } - - public void Clear() - { - _dictionary.Clear(); - } - - public IEnumerator GetEnumerator() - { - return _dictionary.Values.GetEnumerator(); - } - IEnumerator IEnumerable.GetEnumerator() - { - return _dictionary.Values.GetEnumerator(); - } - } -} diff --git a/src/Discord.Net/Helpers/Extensions.cs b/src/Discord.Net/Helpers/Extensions.cs index cdbe47284..74af2c3f2 100644 --- a/src/Discord.Net/Helpers/Extensions.cs +++ b/src/Discord.Net/Helpers/Extensions.cs @@ -5,17 +5,17 @@ using System.Threading.Tasks; namespace Discord.Helpers { internal static class Extensions - { + { public static async Task Wait(this CancellationTokenSource tokenSource) { var token = tokenSource.Token; try { await Task.Delay(-1, token).ConfigureAwait(false); } - catch (OperationCanceledException) { } + catch (OperationCanceledException) { } //Expected } public static async Task Wait(this CancellationToken token) { try { await Task.Delay(-1, token).ConfigureAwait(false); } - catch (OperationCanceledException) { } + catch (OperationCanceledException) { } //Expected } } } diff --git a/src/Discord.Net/Helpers/JsonHttpClient.Events.cs b/src/Discord.Net/Helpers/JsonHttpClient.Events.cs deleted file mode 100644 index 02c9afd99..000000000 --- a/src/Discord.Net/Helpers/JsonHttpClient.Events.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; - -namespace Discord.Helpers -{ - internal partial class JsonHttpClient - { - public event EventHandler OnDebugMessage; - protected void RaiseOnDebugMessage(DebugMessageType type, string message) - { - if (OnDebugMessage != null) - OnDebugMessage(this, new LogMessageEventArgs(type, message)); - } - } -} diff --git a/src/Discord.Net/Helpers/JsonHttpClient.cs b/src/Discord.Net/Helpers/JsonHttpClient.cs deleted file mode 100644 index 95772d82a..000000000 --- a/src/Discord.Net/Helpers/JsonHttpClient.cs +++ /dev/null @@ -1,207 +0,0 @@ -using Discord.API; -using Newtonsoft.Json; -using System; -using System.IO; -using System.Globalization; -using System.Net.Http; -using System.Net; -using System.Reflection; -using System.Text; -using System.Threading.Tasks; -using System.Diagnostics; - -namespace Discord.Helpers -{ - internal partial class JsonHttpClient - { - private bool _isDebug; - private readonly HttpClient _client; - private readonly HttpMethod _patch; -#if TEST_RESPONSES - private readonly JsonSerializerSettings _settings; -#endif - - public JsonHttpClient(bool isDebug) - { - _isDebug = isDebug; - _patch = new HttpMethod("PATCH"); //Not sure why this isn't a default... - - _client = new HttpClient(new HttpClientHandler - { - AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip, - UseCookies = false, - PreAuthenticate = false //We do auth ourselves - }); - _client.DefaultRequestHeaders.Add("accept", "*/*"); - _client.DefaultRequestHeaders.Add("accept-encoding", "gzip, deflate"); - - string version = typeof(JsonHttpClient).GetTypeInfo().Assembly.GetName().Version.ToString(2); - _client.DefaultRequestHeaders.Add("user-agent", $"Discord.Net/{version} (https://github.com/RogueException/Discord.Net)"); - -#if TEST_RESPONSES - _settings = new JsonSerializerSettings(); - _settings.CheckAdditionalContent = true; - _settings.MissingMemberHandling = MissingMemberHandling.Error; -#endif - } - - private string _token; - public string Token - { - get { return _token; } - set - { - _token = value; - _client.DefaultRequestHeaders.Remove("authorization"); - if (_token != null) - _client.DefaultRequestHeaders.Add("authorization", _token); - } - } - - internal Task Get(string path) - where ResponseT : class - => Send(HttpMethod.Get, path, null); - internal Task Get(string path) - => Send(HttpMethod.Get, path, null); - - internal Task Post(string path, object data) - where ResponseT : class - => Send(HttpMethod.Post, path, AsJson(data)); - internal Task Post(string path, object data) - => Send(HttpMethod.Post, path, AsJson(data)); - internal Task Post(string path) - where ResponseT : class - => Send(HttpMethod.Post, path, null); - internal Task Post(string path) - => Send(HttpMethod.Post, path, null); - - internal Task Put(string path, object data) - where ResponseT : class - => Send(HttpMethod.Put, path, AsJson(data)); - internal Task Put(string path, object data) - => Send(HttpMethod.Put, path, AsJson(data)); - internal Task Put(string path) - where ResponseT : class - => Send(HttpMethod.Put, path, null); - internal Task Put(string path) - => Send(HttpMethod.Put, path, null); - - internal Task Patch(string path, object data) - where ResponseT : class - => Send(_patch, path, AsJson(data)); - internal Task Patch(string path, object data) - => Send(_patch, path, AsJson(data)); - internal Task Patch(string path) - where ResponseT : class - => Send(_patch, path, null); - internal Task Patch(string path) - => Send(_patch, path, null); - - internal Task Delete(string path, object data) - where ResponseT : class - => Send(HttpMethod.Delete, path, AsJson(data)); - internal Task Delete(string path, object data) - => Send(HttpMethod.Delete, path, AsJson(data)); - internal Task Delete(string path) - where ResponseT : class - => Send(HttpMethod.Delete, path, null); - internal Task Delete(string path) - => Send(HttpMethod.Delete, path, null); - - internal Task File(string path, Stream stream, string filename = null) - where ResponseT : class - => Send(HttpMethod.Post, path, AsFormData(stream, filename)); - internal Task File(string path, Stream stream, string filename = null) - => Send(HttpMethod.Post, path, AsFormData(stream, filename)); - - private async Task Send(HttpMethod method, string path, HttpContent content) - where ResponseT : class - { - string responseJson = await SendRequest(method, path, content, true).ConfigureAwait(false); -#if TEST_RESPONSES - if (path.StartsWith(Endpoints.BaseApi)) - return JsonConvert.DeserializeObject(responseJson, _settings); -#endif - return JsonConvert.DeserializeObject(responseJson); - } -#if TEST_RESPONSES - private async Task Send(HttpMethod method, string path, HttpContent content) - { - string responseJson = await SendRequest(method, path, content, true).ConfigureAwait(false); - if (path.StartsWith(Endpoints.BaseApi) && !string.IsNullOrEmpty(responseJson)) - throw new Exception("API check failed: Response is not empty."); - return responseJson; - } -#else - private Task Send(HttpMethod method, string path, HttpContent content) - => SendRequest(method, path, content, false); -#endif - - private async Task SendRequest(HttpMethod method, string path, HttpContent content, bool hasResponse) - { - Stopwatch stopwatch = null; - if (_isDebug) - { - if (content != null) - { - if (content is StringContent) - { - string json = await (content as StringContent).ReadAsStringAsync().ConfigureAwait(false); - RaiseOnDebugMessage(DebugMessageType.XHRRawOutput, $"{method} {path}: {json}"); - } - else - { - byte[] bytes = await content.ReadAsByteArrayAsync().ConfigureAwait(false); - RaiseOnDebugMessage(DebugMessageType.XHRRawOutput, $"{method} {path}: {bytes.Length} bytes"); - } - } - stopwatch = Stopwatch.StartNew(); - } - - string result; - using (HttpRequestMessage msg = new HttpRequestMessage(method, path)) - { - if (content != null) - msg.Content = content; - - HttpResponseMessage response; - if (hasResponse) - { - response = await _client.SendAsync(msg, HttpCompletionOption.ResponseContentRead).ConfigureAwait(false); - if (!response.IsSuccessStatusCode) - throw new HttpException(response.StatusCode); - result = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - } - else - { -#if !NET45 - response = await _client.SendAsync(msg, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false); -#else - response = await _client.SendAsync(msg, HttpCompletionOption.ResponseContentRead).ConfigureAwait(false); -#endif - if (!response.IsSuccessStatusCode) - throw new HttpException(response.StatusCode); - result = null; - } - } - - if (_isDebug) - { - stopwatch.Stop(); - RaiseOnDebugMessage(DebugMessageType.XHRTiming, $"{method} {path}: {Math.Round(stopwatch.ElapsedTicks / (double)TimeSpan.TicksPerMillisecond, 2)}ms"); - } - return result; - } - - private StringContent AsJson(object obj) - { - return new StringContent(JsonConvert.SerializeObject(obj), Encoding.UTF8, "application/json"); - } - private MultipartFormDataContent AsFormData(Stream stream, string filename) - { - var content = new MultipartFormDataContent("Upload----" + DateTime.Now.ToString(CultureInfo.InvariantCulture)); - content.Add(new StreamContent(stream), "file", filename); - return content; - } - } -} diff --git a/src/Discord.Net/Helpers/MessageCleaner.cs b/src/Discord.Net/Helpers/MessageCleaner.cs new file mode 100644 index 000000000..624760bfc --- /dev/null +++ b/src/Discord.Net/Helpers/MessageCleaner.cs @@ -0,0 +1,43 @@ +using System.Text.RegularExpressions; + +namespace Discord.Helpers +{ + //TODO: Better name please? + internal class MessageCleaner + { + private readonly Regex _userRegex, _channelRegex; + private readonly MatchEvaluator _userRegexEvaluator, _channelRegexEvaluator; + + public MessageCleaner(DiscordClient client) + { + _userRegex = new Regex(@"<@\d+?>", RegexOptions.Compiled); + _userRegexEvaluator = new MatchEvaluator(e => + { + string id = e.Value.Substring(2, e.Value.Length - 3); + var user = client.Users[id]; + if (user != null) + return '@' + user.Name; + else //User not found + return e.Value; + }); + + _channelRegex = new Regex(@"<#\d+?>", RegexOptions.Compiled); + _channelRegexEvaluator = new MatchEvaluator(e => + { + string id = e.Value.Substring(2, e.Value.Length - 3); + var channel = client.Channels[id]; + if (channel != null) + return channel.Name; + else //Channel not found + return e.Value; + }); + } + + public string Clean(string text) + { + text = _userRegex.Replace(text, _userRegexEvaluator); + text = _channelRegex.Replace(text, _channelRegexEvaluator); + return text; + } + } +} diff --git a/src/Discord.Net/Helpers/TaskHelper.cs b/src/Discord.Net/Helpers/TaskHelper.cs index 31d902465..9c2fd2c77 100644 --- a/src/Discord.Net/Helpers/TaskHelper.cs +++ b/src/Discord.Net/Helpers/TaskHelper.cs @@ -2,8 +2,8 @@ namespace Discord.Helpers { - internal static class TaskHelper - { + internal static class TaskHelper + { public static Task CompletedTask { get; } static TaskHelper() { diff --git a/src/Discord.Net/Mention.cs b/src/Discord.Net/Mention.cs new file mode 100644 index 000000000..f80284b6c --- /dev/null +++ b/src/Discord.Net/Mention.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Discord +{ + public static class Mention + { + /// Returns the string used to create a user mention. + public static string User(User user) + => $"<@{user.Id}>"; + /// Returns the string used to create a user mention. + public static string User(string userId) + => $"<@{userId}>"; + + /// Returns the string used to create a channel mention. + public static string Channel(Channel channel) + => $"<#{channel.Id}>"; + /// Returns the string used to create a channel mention. + public static string Channel(string channelId) + => $"<#{channelId}>"; + } +} diff --git a/src/Discord.Net/Models/Channel.cs b/src/Discord.Net/Models/Channel.cs index 880a35ca3..f732ef584 100644 --- a/src/Discord.Net/Models/Channel.cs +++ b/src/Discord.Net/Models/Channel.cs @@ -1,10 +1,12 @@ -using Newtonsoft.Json; +using Discord.Net.API; +using Newtonsoft.Json; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; namespace Discord { - public sealed class Channel + public sealed class Channel { public sealed class PermissionOverwrite { @@ -15,18 +17,19 @@ namespace Discord } private readonly DiscordClient _client; + private ConcurrentDictionary _messages; /// Returns the unique identifier for this channel. public string Id { get; } private string _name; /// Returns the name of this channel. - public string Name { get { return !IsPrivate ? $"{_name}" : $"@{Recipient.Name}"; } internal set { _name = value; } } + public string Name { get { return !IsPrivate ? $"{_name}" : $"@{Recipient.Name}"; } internal set { _name = value; } } /// Returns the position of this channel in the channel list for this server. public int Position { get; internal set; } /// Returns false is this is a public chat and true if this is a private chat with another user (see Recipient). - public bool IsPrivate { get; } + public bool IsPrivate => ServerId == null; /// Returns the type of this channel (see ChannelTypes). public string Type { get; internal set; } @@ -34,33 +37,70 @@ namespace Discord public string ServerId { get; } /// Returns the server containing this channel. [JsonIgnore] - public Server Server => ServerId != null ? _client.GetServer(ServerId) : null; + public Server Server => _client.Servers[ServerId]; /// For private chats, returns the Id of the target user, otherwise null. - [JsonIgnore] public string RecipientId { get; internal set; } /// For private chats, returns the target user, otherwise null. - public User Recipient => _client.GetUser(RecipientId); + [JsonIgnore] + public User Recipient => _client.Users[RecipientId]; - /// Returns a collection of all messages the client has in cache. + /// Returns a collection of the ids of all messages the client has seen posted in this channel. + /// This collection does not guarantee any ordering. + [JsonIgnore] + public IEnumerable MessageIds => _messages.Select(x => x.Key); + /// Returns a collection of all messages the client has seen posted in this channel. /// This collection does not guarantee any ordering. [JsonIgnore] - public IEnumerable Messages => _client.Messages.Where(x => x.ChannelId == Id); + public IEnumerable Messages => _messages.Select(x => _client.Messages[x.Key]); /// Returns a collection of all custom permissions used for this channel. public PermissionOverwrite[] PermissionOverwrites { get; internal set; } - internal Channel(string id, string serverId, DiscordClient client) + internal Channel(DiscordClient client, string id, string serverId, string recipientId) { + _client = client; Id = id; ServerId = serverId; - IsPrivate = serverId == null; - _client = client; + RecipientId = recipientId; + _messages = new ConcurrentDictionary(); } - public override string ToString() + internal void Update(ChannelReference model) + { + Name = model.Name; + Type = model.Type; + } + internal void Update(ChannelInfo model) + { + Update(model as ChannelReference); + + Position = model.Position; + + if (model.PermissionOverwrites != null) + { + PermissionOverwrites = model.PermissionOverwrites.Select(x => new PermissionOverwrite + { + Type = x.Type, + Id = x.Id, + Deny = new PackedPermissions(true, x.Deny), + Allow = new PackedPermissions(true, x.Allow) + }).ToArray(); + } + else + PermissionOverwrites = null; + } + + public override string ToString() => Name; + + internal void AddMessage(string messageId) + { + _messages.TryAdd(messageId, true); + } + internal bool RemoveMessage(string messageId) { - return Name; + bool ignored; + return _messages.TryRemove(messageId, out ignored); } } } diff --git a/src/Discord.Net/Models/Invite.cs b/src/Discord.Net/Models/Invite.cs index ba5170aea..f01838f9a 100644 --- a/src/Discord.Net/Models/Invite.cs +++ b/src/Discord.Net/Models/Invite.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json; +using Discord.Net.API; +using Newtonsoft.Json; namespace Discord { @@ -8,7 +9,7 @@ namespace Discord /// Returns the unique identifier for this invite. public string Id { get; } - + /// Time (in seconds) until the invite expires. Set to 0 to never expire. public int MaxAge { get; internal set; } /// The amount of times this invite has been used. @@ -23,31 +24,51 @@ namespace Discord public string XkcdPass { get; } /// Returns a URL for this invite using XkcdPass if available or Id if not. - public string Url => API.Endpoints.InviteUrl(XkcdPass ?? Id); + public string Url => Endpoints.InviteUrl(XkcdPass ?? Id); /// Returns the id of the user that created this invite. public string InviterId { get; internal set; } /// Returns the user that created this invite. [JsonIgnore] - public User Inviter => _client.GetUser(InviterId); + public User Inviter => _client.Users[InviterId]; /// Returns the id of the server this invite is to. public string ServerId { get; internal set; } /// Returns the server this invite is to. [JsonIgnore] - public Server Server => _client.GetServer(ServerId); + public Server Server => _client.Servers[ServerId]; /// Returns the id of the channel this invite is to. public string ChannelId { get; internal set; } /// Returns the channel this invite is to. [JsonIgnore] - public Channel Channel => _client.GetChannel(ChannelId); + public Channel Channel => _client.Channels[ChannelId]; - internal Invite(string code, string xkcdPass, DiscordClient client) + internal Invite(DiscordClient client, string code, string xkcdPass, string serverId) { + _client = client; Id = code; XkcdPass = xkcdPass; - _client = client; - } + ServerId = serverId; + } + + public override string ToString() => XkcdPass ?? Id; + + internal void Update(Net.API.Invite model) + { + ChannelId = model.Channel.Id; + InviterId = model.Inviter.Id; + ServerId = model.Guild.Id; + } + + internal void Update(Net.API.ExtendedInvite model) + { + Update(model as Net.API.Invite); + IsRevoked = model.IsRevoked; + IsTemporary = model.IsTemporary; + MaxAge = model.MaxAge; + MaxUses = model.MaxUses; + Uses = model.Uses; + } } } diff --git a/src/Discord.Net/Models/Member.cs b/src/Discord.Net/Models/Member.cs index 686d1b88f..ce1777915 100644 --- a/src/Discord.Net/Models/Member.cs +++ b/src/Discord.Net/Models/Member.cs @@ -1,4 +1,4 @@ -using Discord.API.Models; +using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Linq; @@ -24,23 +24,65 @@ namespace Discord /// Returns the current status for this user. public string Status { get; internal set; } - public string ServerId { get; } - public Server Server => _client.GetServer(ServerId); - public string UserId { get; } - public User User => _client.GetUser(UserId); + [JsonIgnore] + public User User => _client.Users[UserId]; + + public string ServerId { get; } + [JsonIgnore] + public Server Server => _client.Servers[ServerId]; public string VoiceChannelId { get; internal set; } - public Channel VoiceChannel => _client.GetChannel(VoiceChannelId); + [JsonIgnore] + public Channel VoiceChannel => _client.Channels[VoiceChannelId]; public string[] RoleIds { get; internal set; } - public IEnumerable Roles => RoleIds.Select(x => _client.GetRole(x)); + [JsonIgnore] + public IEnumerable Roles => RoleIds.Select(x => _client.Roles[x]); + + /// Returns a collection of all messages this user has sent on this server that are still in cache. + public IEnumerable Messages => _client.Messages.Where(x => x.UserId == UserId && x.ServerId == ServerId); - public Member(string serverId, string userId, DiscordClient client) + internal Member(DiscordClient client, string userId, string serverId) { - ServerId = serverId; - UserId = userId; _client = client; + UserId = userId; + ServerId = serverId; + } + + public override string ToString() => UserId; + + internal void Update(Net.API.MemberInfo model) + { + RoleIds = model.Roles; + if (model.JoinedAt.HasValue) + JoinedAt = model.JoinedAt.Value; + } + internal void Update(Net.API.ExtendedMemberInfo model) + { + Update(model as Net.API.MemberInfo); + IsDeafened = model.IsDeafened; + IsMuted = model.IsMuted; + } + internal void Update(Net.API.PresenceMemberInfo model) + { + Status = model.Status; + GameId = model.GameId; + } + internal void Update(Net.API.VoiceMemberInfo model) + { + IsDeafened = model.IsDeafened; + IsMuted = model.IsMuted; + SessionId = model.SessionId; + Token = model.Token; + + VoiceChannelId = model.ChannelId; + if (model.IsSelfDeafened.HasValue) + IsSelfDeafened = model.IsSelfDeafened.Value; + if (model.IsSelfMuted.HasValue) + IsSelfMuted = model.IsSelfMuted.Value; + if (model.IsSuppressed.HasValue) + IsSuppressed = model.IsSuppressed.Value; } - } + } } diff --git a/src/Discord.Net/Models/Message.cs b/src/Discord.Net/Models/Message.cs index b66078b8b..3760b85f5 100644 --- a/src/Discord.Net/Models/Message.cs +++ b/src/Discord.Net/Models/Message.cs @@ -54,7 +54,7 @@ namespace Discord private readonly DiscordClient _client; private string _cleanText; - + /// Returns the global unique identifier for this message. public string Id { get; internal set; } /// Returns the local unique identifier for this message. @@ -75,7 +75,7 @@ namespace Discord public string RawText { get; internal set; } /// Returns the content of this message with any special references such as mentions converted. /// This value is lazy loaded and only processed on first request. Each subsequent request will pull from cache. - public string Text => _cleanText != null ? _cleanText : (_cleanText = _client.CleanMessageText(RawText)); + public string Text => _cleanText != null ? _cleanText : (_cleanText = _client.Messages.CleanText(RawText)); /// Returns the timestamp for when this message was sent. public DateTime Timestamp { get; internal set; } /// Returns the timestamp for when this message was last edited. @@ -89,40 +89,108 @@ namespace Discord public string[] MentionIds { get; internal set; } /// Returns a collection of all users mentioned in this message. [JsonIgnore] - public IEnumerable Mentions => MentionIds.Select(x => _client.GetUser(x)).Where(x => x != null); + public IEnumerable Mentions => MentionIds.Select(x => _client.Users[x]).Where(x => x != null); /// Returns the id of the server containing the channel this message was sent to. public string ServerId => Channel.ServerId; /// Returns the server containing the channel this message was sent to. [JsonIgnore] - public Server Server => _client.GetServer(Channel.ServerId); + public Server Server => _client.Servers[Channel.ServerId]; /// Returns the id of the channel this message was sent to. public string ChannelId { get; } /// Returns the channel this message was sent to. [JsonIgnore] - public Channel Channel => _client.GetChannel(ChannelId); + public Channel Channel => _client.Channels[ChannelId]; + /// Returns true if the current user created this message. + public bool IsAuthor => _client.CurrentUserId == UserId; /// Returns the id of the author of this message. public string UserId { get; internal set; } /// Returns the author of this message. [JsonIgnore] - public User User => _client.GetUser(UserId); + public User User => _client.Users[UserId]; + /// Returns the author of this message. [JsonIgnore] - public Member Member => _client.GetMember(ServerId, UserId); - /// Returns true if the current user created this message. - public bool IsAuthor => _client.User?.Id == UserId; + public Member Member => _client.Members[ServerId, UserId]; - internal Message(string id, string channelId, DiscordClient client) + internal Message(DiscordClient client, string id, string channelId) { + _client = client; Id = id; ChannelId = channelId; - _client = client; - } + } - public override string ToString() + internal void Update(Net.API.Message model) { - return User.ToString() + ": " + RawText; + if (model.Attachments != null) + { + Attachments = model.Attachments.Select(x => new Attachment + { + Id = x.Id, + Url = x.Url, + ProxyUrl = x.ProxyUrl, + Size = x.Size, + Filename = x.Filename, + Width = x.Width, + Height = x.Height + }).ToArray(); + } + else + Attachments = new Attachment[0]; + if (model.Embeds != null) + { + Embeds = model.Embeds.Select(x => + { + var embed = new Embed + { + Url = x.Url, + Type = x.Type, + Description = x.Description, + Title = x.Title + }; + if (x.Provider != null) + { + embed.Provider = new EmbedReference + { + Url = x.Provider.Url, + Name = x.Provider.Name + }; + } + if (x.Author != null) + { + embed.Author = new EmbedReference + { + Url = x.Author.Url, + Name = x.Author.Name + }; + } + if (x.Thumbnail != null) + { + embed.Thumbnail = new File + { + Url = x.Thumbnail.Url, + ProxyUrl = x.Thumbnail.ProxyUrl, + Width = x.Thumbnail.Width, + Height = x.Thumbnail.Height + }; + } + return embed; + }).ToArray(); + } + else + Embeds = new Embed[0]; + IsMentioningEveryone = model.IsMentioningEveryone; + IsTTS = model.IsTextToSpeech; + MentionIds = model.Mentions?.Select(x => x.Id)?.ToArray() ?? new string[0]; + IsMentioningMe = MentionIds.Contains(_client.CurrentUserId); + RawText = model.Content; + Timestamp = model.Timestamp; + EditedTimestamp = model.EditedTimestamp; + if (model.Author != null) + UserId = model.Author.Id; } - } + + public override string ToString() => User.ToString() + ": " + RawText; + } } diff --git a/src/Discord.Net/Models/PackedPermissions.cs b/src/Discord.Net/Models/PackedPermissions.cs index 1d6b7ba02..f16a49e4a 100644 --- a/src/Discord.Net/Models/PackedPermissions.cs +++ b/src/Discord.Net/Models/PackedPermissions.cs @@ -2,65 +2,72 @@ { public sealed class PackedPermissions { + private bool _isLocked; private uint _rawValue; - internal uint RawValue { get { return _rawValue; } set { _rawValue = value; } } + public uint RawValue { get { return _rawValue; } internal set { _rawValue = value; } } //Internal set bypasses isLocked for API changes. - internal PackedPermissions() { } - internal PackedPermissions(uint rawValue) { _rawValue = rawValue; } + internal PackedPermissions(bool isLocked) { _isLocked = isLocked; } + internal PackedPermissions(bool isLocked, uint rawValue) { _isLocked = isLocked; _rawValue = rawValue; } /// If True, a user may create invites. - public bool General_CreateInstantInvite => ((_rawValue >> 0) & 0x1) == 1; + public bool General_CreateInstantInvite { get { return GetBit(1); } set { SetBit(1, value); } } /// If True, a user may ban users from the server. - public bool General_BanMembers => ((_rawValue >> 1) & 0x1) == 1; + public bool General_BanMembers { get { return GetBit(2); } set { SetBit(2, value); } } /// If True, a user may kick users from the server. - public bool General_KickMembers => ((_rawValue >> 2) & 0x1) == 1; - /// If True, a user may adjust roles. This also bypasses all other permissions, granting all the others. - public bool General_ManageRoles => ((_rawValue >> 3) & 0x1) == 1; + public bool General_KickMembers { get { return GetBit(3); } set { SetBit(3, value); } } + /// If True, a user may adjust roles. This also implictly grants all other permissions. + public bool General_ManageRoles { get { return GetBit(4); } set { SetBit(4, value); } } /// If True, a user may create, delete and modify channels. - public bool General_ManageChannels => ((_rawValue >> 4) & 0x1) == 1; + public bool General_ManageChannels { get { return GetBit(5); } set { SetBit(5, value); } } /// If True, a user may adjust server properties. - public bool General_ManageServer => ((_rawValue >> 5) & 0x1) == 1; + public bool General_ManageServer { get { return GetBit(6); } set { SetBit(6, value); } } //4 Unused /// If True, a user may join channels. - /// Note that without this permission, a channel is not sent by the server. - public bool Text_ReadMessages => ((_rawValue >> 10) & 0x1) == 1; + public bool Text_ReadMessages { get { return GetBit(11); } set { SetBit(11, value); } } /// If True, a user may send messages. - public bool Text_SendMessages => ((_rawValue >> 11) & 0x1) == 1; + public bool Text_SendMessages { get { return GetBit(12); } set { SetBit(12, value); } } /// If True, a user may send text-to-speech messages. - public bool Text_SendTTSMessages => ((_rawValue >> 12) & 0x1) == 1; + public bool Text_SendTTSMessages { get { return GetBit(13); } set { SetBit(13, value); } } /// If True, a user may delete messages. - public bool Text_ManageMessages => ((_rawValue >> 13) & 0x1) == 1; + public bool Text_ManageMessages { get { return GetBit(14); } set { SetBit(14, value); } } /// If True, Discord will auto-embed links sent by this user. - public bool Text_EmbedLinks => ((_rawValue >> 14) & 0x1) == 1; + public bool Text_EmbedLinks { get { return GetBit(15); } set { SetBit(15, value); } } /// If True, a user may send files. - public bool Text_AttachFiles => ((_rawValue >> 15) & 0x1) == 1; + public bool Text_AttachFiles { get { return GetBit(16); } set { SetBit(16, value); } } /// If True, a user may read previous messages. - public bool Text_ReadMessageHistory => ((_rawValue >> 16) & 0x1) == 1; + public bool Text_ReadMessageHistory { get { return GetBit(17); } set { SetBit(17, value); } } /// If True, a user may mention @everyone. - public bool Text_MentionEveryone => ((_rawValue >> 17) & 0x1) == 1; + public bool Text_MentionEveryone { get { return GetBit(18); } set { SetBit(18, value); } } //2 Unused /// If True, a user may connect to a voice channel. - public bool Voice_Connect => ((_rawValue >> 20) & 0x1) == 1; + public bool Voice_Connect { get { return GetBit(21); } set { SetBit(21, value); } } /// If True, a user may speak in a voice channel. - public bool Voice_Speak => ((_rawValue >> 21) & 0x1) == 1; + public bool Voice_Speak { get { return GetBit(22); } set { SetBit(22, value); } } /// If True, a user may mute users. - public bool Voice_MuteMembers => ((_rawValue >> 22) & 0x1) == 1; + public bool Voice_MuteMembers { get { return GetBit(23); } set { SetBit(23, value); } } /// If True, a user may deafen users. - public bool Voice_DeafenMembers => ((_rawValue >> 23) & 0x1) == 1; + public bool Voice_DeafenMembers { get { return GetBit(24); } set { SetBit(24, value); } } /// If True, a user may move other users between voice channels. - public bool Voice_MoveMembers => ((_rawValue >> 24) & 0x1) == 1; + public bool Voice_MoveMembers { get { return GetBit(25); } set { SetBit(25, value); } } /// If True, a user may use voice activation rather than push-to-talk. - public bool Voice_UseVoiceActivation => ((_rawValue >> 25) & 0x1) == 1; + public bool Voice_UseVoiceActivation { get { return GetBit(26); } set { SetBit(26, value); } } //6 Unused - public static implicit operator uint (PackedPermissions perms) + private bool GetBit(int pos) => ((_rawValue >> (pos - 1)) & 1U) == 1; + private void SetBit(int pos, bool value) { - return perms._rawValue; + if (value) + _rawValue &= (1U << (pos - 1)); + else + _rawValue |= ~(1U << (pos - 1)); } + + public static implicit operator uint (PackedPermissions perms) => perms._rawValue; + public PackedPermissions Edit() => new PackedPermissions(false, _rawValue); } } diff --git a/src/Discord.Net/Models/Role.cs b/src/Discord.Net/Models/Role.cs index 515274710..4eb1a1f25 100644 --- a/src/Discord.Net/Models/Role.cs +++ b/src/Discord.Net/Models/Role.cs @@ -1,4 +1,5 @@ using Newtonsoft.Json; +using System.Threading; namespace Discord { @@ -18,19 +19,22 @@ namespace Discord public string ServerId { get; } /// Returns the server this role is a member of. [JsonIgnore] - public Server Server => _client.GetServer(ServerId); + public Server Server => _client.Servers[ServerId]; - internal Role(string id, string serverId, DiscordClient client) + internal Role(DiscordClient client, string id, string serverId) { - Permissions = new PackedPermissions(); + _client = client; Id = id; ServerId = serverId; - _client = client; + Permissions = new PackedPermissions(true); } - public override string ToString() + internal void Update(Net.API.RoleInfo model) { - return Name; + Name = model.Name; + Permissions.RawValue = (uint)model.Permissions; } + + public override string ToString() => Name; } } diff --git a/src/Discord.Net/Models/Server.cs b/src/Discord.Net/Models/Server.cs index d8e60fdbd..16c1b3671 100644 --- a/src/Discord.Net/Models/Server.cs +++ b/src/Discord.Net/Models/Server.cs @@ -1,4 +1,5 @@ -using Discord.Helpers; +using Discord.Net.API; +using Newtonsoft.Json; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -9,6 +10,7 @@ namespace Discord public sealed class Server { private readonly DiscordClient _client; + private ConcurrentDictionary _bans, _channels, _invites, _members, _roles; /// Returns the unique identifier for this server. public string Id { get; } @@ -24,116 +26,128 @@ namespace Discord /// Returns the endpoint for this server's voice server. internal string VoiceServer { get; set; } + /// Returns true if the current user created this server. + public bool IsOwner => _client.CurrentUserId == OwnerId; /// Returns the id of the user that first created this server. public string OwnerId { get; internal set; } /// Returns the user that first created this server. - public User Owner => _client.GetUser(OwnerId); - /// Returns true if the current user created this server. - public bool IsOwner => _client.User?.Id == OwnerId; + [JsonIgnore] + public User Owner => _client.Users[OwnerId]; /// Returns the id of the AFK voice channel for this server (see AFKTimeout). public string AFKChannelId { get; internal set; } /// Returns the AFK voice channel for this server (see AFKTimeout). - public Channel AFKChannel => _client.GetChannel(AFKChannelId); + [JsonIgnore] + public Channel AFKChannel => _client.Channels[AFKChannelId]; /// Returns the id of the default channel for this server. public string DefaultChannelId => Id; /// Returns the default channel for this server. - public Channel DefaultChannel =>_client.GetChannel(DefaultChannelId); - - internal AsyncCache _members; - /// Returns a collection of all channels within this server. - public IEnumerable Members => _members; - - internal ConcurrentDictionary _bans; - /// Returns a collection of all users banned on this server. - /// Only users seen in other servers will be returned. To get a list of all users, use BanIds. - public IEnumerable Bans => _bans.Keys.Select(x => _client.GetUser(x)); + [JsonIgnore] + public Channel DefaultChannel => _client.Channels[DefaultChannelId]; + /// Returns a collection of the ids of all users banned on this server. - public IEnumerable BanIds => _bans.Keys; + [JsonIgnore] + public IEnumerable BanIds => _bans.Select(x => x.Key); + /// Returns a collection of the ids of all channels within this server. + [JsonIgnore] + public IEnumerable ChannelIds => _channels.Select(x => x.Key); /// Returns a collection of all channels within this server. - public IEnumerable Channels => _client.Channels.Where(x => x.ServerId == Id); + [JsonIgnore] + public IEnumerable Channels => _channels.Select(x => _client.Channels[x.Key]); /// Returns a collection of all channels within this server. - public IEnumerable TextChannels => _client.Channels.Where(x => x.ServerId == Id && x.Type == ChannelTypes.Text); + [JsonIgnore] + public IEnumerable TextChannels => _channels.Select(x => _client.Channels[x.Key]).Where(x => x.Type == ChannelTypes.Text); /// Returns a collection of all channels within this server. - public IEnumerable VoiceChannels => _client.Channels.Where(x => x.ServerId == Id && x.Type == ChannelTypes.Voice); + [JsonIgnore] + public IEnumerable VoiceChannels => _channels.Select(x => _client.Channels[x.Key]).Where(x => x.Type == ChannelTypes.Voice); + + /// Returns a collection of all invite codes to this server. + [JsonIgnore] + public IEnumerable InviteCodes => _invites.Select(x => x.Key); + /*/// Returns a collection of all invites to this server. + [JsonIgnore] + public IEnumerable Invites => _invites.Select(x => _client.Invites[x.Key]);*/ + + /// Returns a collection of all users within this server with their server-specific data. + [JsonIgnore] + public IEnumerable Members => _members.Select(x => _client.Members[x.Key, Id]); + /// Returns a collection of the ids of all users within this server. + [JsonIgnore] + public IEnumerable UserIds => _members.Select(x => x.Key); + /// Returns a collection of all users within this server. + [JsonIgnore] + public IEnumerable Users => _members.Select(x => _client.Users[x.Key]); + + /// Returns a collection of the ids of all roles within this server. + [JsonIgnore] + public IEnumerable RoleIds => _roles.Select(x => x.Key); /// Returns a collection of all roles within this server. - public IEnumerable Roles => _client.Roles.Where(x => x.ServerId == Id); + [JsonIgnore] + public IEnumerable Roles => _roles.Select(x => _client.Roles[x.Key]); - internal Server(string id, DiscordClient client) + internal Server(DiscordClient client, string id) { - Id = id; _client = client; + Id = id; _bans = new ConcurrentDictionary(); - _members = new AsyncCache( - (key, parentKey) => - { - if (_client.IsDebugMode) - _client.RaiseOnDebugMessage(DebugMessageType.Cache, $"Created user {key} in server {parentKey}."); - return new Member(parentKey, key, _client); - }, - (member, model) => - { - if (model is API.Models.PresenceMemberInfo) - { - var extendedModel = model as API.Models.PresenceMemberInfo; - member.Status = extendedModel.Status; - member.GameId = extendedModel.GameId; - } - if (model is API.Models.VoiceMemberInfo) - { - var extendedModel = model as API.Models.VoiceMemberInfo; - member.VoiceChannelId = extendedModel.ChannelId; - member.IsDeafened = extendedModel.IsDeafened; - member.IsMuted = extendedModel.IsMuted; - if (extendedModel.IsSelfDeafened.HasValue) - member.IsSelfDeafened = extendedModel.IsSelfDeafened.Value; - if (extendedModel.IsSelfMuted.HasValue) - member.IsSelfMuted = extendedModel.IsSelfMuted.Value; - member.IsSuppressed = extendedModel.IsSuppressed; - member.SessionId = extendedModel.SessionId; - member.Token = extendedModel.Token; - } - if (model is API.Models.RoleMemberInfo) - { - var extendedModel = model as API.Models.RoleMemberInfo; - member.RoleIds = extendedModel.Roles; - if (extendedModel.JoinedAt.HasValue) - member.JoinedAt = extendedModel.JoinedAt.Value; - } - if (model is API.Models.InitialMemberInfo) - { - var extendedModel = model as API.Models.InitialMemberInfo; - member.IsDeafened = extendedModel.IsDeafened; - member.IsMuted = extendedModel.IsMuted; - } - if (_client.IsDebugMode) - _client.RaiseOnDebugMessage(DebugMessageType.Cache, $"Updated user {member.User?.Name} ({member.UserId}) in server {member.Server?.Name} ({member.ServerId})."); - }, - (member) => - { - if (_client.IsDebugMode) - _client.RaiseOnDebugMessage(DebugMessageType.Cache, $"Destroyed user {member.User?.Name} ({member.UserId}) in server {member.Server?.Name} ({member.ServerId})."); - } - ); + _channels = new ConcurrentDictionary(); + _invites = new ConcurrentDictionary(); + _members = new ConcurrentDictionary(); + _roles = new ConcurrentDictionary(); } - internal Member UpdateMember(API.Models.MemberInfo membership) + internal void Update(GuildInfo model) { - return _members.Update(membership.User?.Id ?? membership.UserId, Id, membership); - } - internal Member RemoveMember(string userId) - { - return _members.Remove(userId); + AFKChannelId = model.AFKChannelId; + AFKTimeout = model.AFKTimeout; + if (model.JoinedAt.HasValue) + JoinedAt = model.JoinedAt.Value; + OwnerId = model.OwnerId; + Region = model.Region; + + var roles = _client.Roles; + foreach (var subModel in model.Roles) + { + var role = roles.GetOrAdd(subModel.Id, Id); + role.Update(subModel); + } } - public Member GetMembership(User user) - => GetMember(user.Id); - public Member GetMember(string userId) + internal void Update(ExtendedGuildInfo model) { - return _members[userId]; + Update(model as GuildInfo); + + var channels = _client.Channels; + foreach (var subModel in model.Channels) + { + var channel = channels.GetOrAdd(subModel.Id, Id); + channel.Update(subModel); + } + + var users = _client.Users; + var members = _client.Members; + foreach (var subModel in model.Members) + { + var user = users.GetOrAdd(subModel.UserId); + var member = members.GetOrAdd(subModel.UserId, Id); + user.Update(subModel.User); + member.Update(subModel); + } + foreach (var subModel in model.VoiceStates) + { + var member = members.GetOrAdd(subModel.UserId, Id); + member.Update(subModel); + } + foreach (var subModel in model.Presences) + { + var member = members.GetOrAdd(subModel.UserId, Id); + member.Update(subModel); + } } + public override string ToString() => Name; + internal void AddBan(string banId) { _bans.TryAdd(banId, true); @@ -144,9 +158,48 @@ namespace Discord return _bans.TryRemove(banId, out ignored); } - public override string ToString() + internal void AddChannel(string channelId) + { + _channels.TryAdd(channelId, true); + } + internal bool RemoveChannel(string channelId) + { + bool ignored; + return _channels.TryRemove(channelId, out ignored); + } + + internal void AddInvite(string inviteId) + { + _invites.TryAdd(inviteId, true); + } + internal bool RemoveInvite(string inviteId) + { + bool ignored; + return _invites.TryRemove(inviteId, out ignored); + } + + internal void AddMember(string userId) + { + _members.TryAdd(userId, true); + } + internal bool RemoveMember(string userId) { - return Name; + bool ignored; + return _members.TryRemove(userId, out ignored); + } + internal bool HasMember(string userId) + { + return _members.ContainsKey(userId); + } + + internal void AddRole(string roleId) + { + _roles.TryAdd(roleId, true); + } + internal bool RemoveRole(string roleId) + { + bool ignored; + return _roles.TryRemove(roleId, out ignored); } } } diff --git a/src/Discord.Net/Models/User.cs b/src/Discord.Net/Models/User.cs index ad55a300a..caab18b27 100644 --- a/src/Discord.Net/Models/User.cs +++ b/src/Discord.Net/Models/User.cs @@ -1,14 +1,16 @@ -using Discord.API; +using Discord.Net.API; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Linq; +using System.Threading; namespace Discord { public sealed class User { private readonly DiscordClient _client; + private int _refs; /// Returns the unique identifier for this user. public string Id { get; } @@ -21,37 +23,52 @@ namespace Discord public string AvatarUrl => Endpoints.UserAvatar(Id, AvatarId); /// Returns a by-name unique identifier separating this user from others with the same name. public string Discriminator { get; internal set; } - [JsonIgnore] /// Returns the email for this user. /// This field is only ever populated for the current logged in user. - public string Email { get; internal set; } [JsonIgnore] + public string Email { get; internal set; } /// Returns if the email for this user has been verified. /// This field is only ever populated for the current logged in user. - public bool IsVerified { get; internal set; } + [JsonIgnore] + public bool? IsVerified { get; internal set; } /// Returns the Id of the private messaging channel with this user, if one exists. public string PrivateChannelId { get; set; } /// Returns the private messaging channel with this user, if one exists. - public Channel PrivateChannel => _client.GetChannel(PrivateChannelId); + [JsonIgnore] + public Channel PrivateChannel => _client.Channels[PrivateChannelId]; /// Returns a collection of all server-specific data for every server this user is a member of. - public IEnumerable Memberships => _client._servers.Select(x => x._members[Id]).Where(x => x != null); + public IEnumerable Memberships => _client.Servers.Where(x => x.HasMember(Id)).Select(x => _client.Members[Id, x?.Id]); /// Returns a collection of all servers this user is a member of. - public IEnumerable Servers => _client._servers.Where(x => x._members[Id] != null); + public IEnumerable Servers => _client.Servers.Where(x => x.HasMember(Id)); + /// Returns a collection of all messages this user has sent that are still in cache. + public IEnumerable Messages => _client.Messages.Where(x => x.UserId == Id); - //TODO: Add voice triggering lastactivity + //TODO: Add voice triggering LastActivity /// Returns the time this user last sent a message. /// Is not currently affected by voice activity public DateTime LastActivity { get; private set; } - internal User(string id, DiscordClient client) + internal User(DiscordClient client, string id) { - Id = id; _client = client; + Id = id; LastActivity = DateTime.UtcNow; - IsVerified = true; - } + } + + internal void Update(UserReference model) + { + AvatarId = model.Avatar; + Discriminator = model.Discriminator; + Name = model.Username; + } + internal void Update(SelfUserInfo model) + { + Update(model as UserReference); + Email = model.Email; + IsVerified = model.IsVerified; + } internal void UpdateActivity(DateTime activity) { @@ -59,9 +76,16 @@ namespace Discord LastActivity = activity; } - public override string ToString() + public override string ToString() => Name; + + public void AddRef() + { + Interlocked.Increment(ref _refs); + } + public void RemoveRef() { - return Name; + if (Interlocked.Decrement(ref _refs) == 0) + _client.Users.TryRemove(Id); } } } diff --git a/src/Discord.Net/API/Models/Common.cs b/src/Discord.Net/Net/API/Common.cs similarity index 81% rename from src/Discord.Net/API/Models/Common.cs rename to src/Discord.Net/Net/API/Common.cs index cfc7a3580..230e286dd 100644 --- a/src/Discord.Net/API/Models/Common.cs +++ b/src/Discord.Net/Net/API/Common.cs @@ -5,9 +5,9 @@ using Newtonsoft.Json; using System; -namespace Discord.API.Models +namespace Discord.Net.API { - //Users + //User internal class UserReference { [JsonProperty(PropertyName = "username")] @@ -26,35 +26,44 @@ namespace Discord.API.Models [JsonProperty(PropertyName = "verified")] public bool IsVerified; } - internal class MemberInfo + + //Members + internal class MemberReference { [JsonProperty(PropertyName = "user_id")] public string UserId; [JsonProperty(PropertyName = "user")] public UserReference User; [JsonProperty(PropertyName = "guild_id")] - public string ServerId; + public string GuildId; + } + internal class MemberInfo : MemberReference + { + [JsonProperty(PropertyName = "joined_at")] + public DateTime? JoinedAt; + [JsonProperty(PropertyName = "roles")] + public string[] Roles; } - internal class InitialMemberInfo : RoleMemberInfo + internal class ExtendedMemberInfo : MemberInfo { [JsonProperty(PropertyName = "mute")] public bool IsMuted; [JsonProperty(PropertyName = "deaf")] public bool IsDeafened; } - internal class PresenceMemberInfo : MemberInfo + internal class PresenceMemberInfo : MemberReference { [JsonProperty(PropertyName = "game_id")] public string GameId; [JsonProperty(PropertyName = "status")] public string Status; } - internal class VoiceMemberInfo : MemberInfo + internal class VoiceMemberInfo : MemberReference { [JsonProperty(PropertyName = "channel_id")] public string ChannelId; [JsonProperty(PropertyName = "suppress")] - public bool IsSuppressed; + public bool? IsSuppressed; [JsonProperty(PropertyName = "session_id")] public string SessionId; [JsonProperty(PropertyName = "self_mute")] @@ -68,13 +77,6 @@ namespace Discord.API.Models [JsonProperty(PropertyName = "token")] public string Token; } - internal class RoleMemberInfo : MemberInfo - { - [JsonProperty(PropertyName = "joined_at")] - public DateTime? JoinedAt; - [JsonProperty(PropertyName = "roles")] - public string[] Roles; - } //Channels internal class ChannelReference @@ -114,15 +116,15 @@ namespace Discord.API.Models public UserReference Recipient; } - //Servers - internal class ServerReference + //Guilds (Servers) + internal class GuildReference { [JsonProperty(PropertyName = "id")] public string Id; [JsonProperty(PropertyName = "name")] public string Name; } - internal class ServerInfo : ServerReference + internal class GuildInfo : GuildReference { [JsonProperty(PropertyName = "afk_channel_id")] public string AFKChannelId; @@ -141,14 +143,14 @@ namespace Discord.API.Models [JsonProperty(PropertyName = "region")] public string Region; [JsonProperty(PropertyName = "roles")] - public Role[] Roles; + public RoleInfo[] Roles; } - internal class ExtendedServerInfo : ServerInfo + internal class ExtendedGuildInfo : GuildInfo { [JsonProperty(PropertyName = "channels")] public ChannelInfo[] Channels; [JsonProperty(PropertyName = "members")] - public InitialMemberInfo[] Members; + public ExtendedMemberInfo[] Members; [JsonProperty(PropertyName = "presences")] public PresenceMemberInfo[] Presences; [JsonProperty(PropertyName = "voice_states")] @@ -244,7 +246,14 @@ namespace Discord.API.Models } //Roles - internal class Role + internal class RoleReference + { + [JsonProperty(PropertyName = "guild_id")] + public string GuildId; + [JsonProperty(PropertyName = "role_id")] + public string RoleId; + } + internal class RoleInfo { [JsonProperty(PropertyName = "permissions")] public int Permissions; @@ -253,4 +262,34 @@ namespace Discord.API.Models [JsonProperty(PropertyName = "id")] public string Id; } + + //Invites + internal class Invite + { + [JsonProperty(PropertyName = "inviter")] + public UserReference Inviter; + [JsonProperty(PropertyName = "guild")] + public GuildReference Guild; + [JsonProperty(PropertyName = "channel")] + public ChannelReference Channel; + [JsonProperty(PropertyName = "code")] + public string Code; + [JsonProperty(PropertyName = "xkcdpass")] + public string XkcdPass; + } + internal class ExtendedInvite : Invite + { + [JsonProperty(PropertyName = "max_age")] + public int MaxAge; + [JsonProperty(PropertyName = "max_uses")] + public int MaxUses; + [JsonProperty(PropertyName = "revoked")] + public bool IsRevoked; + [JsonProperty(PropertyName = "temporary")] + public bool IsTemporary; + [JsonProperty(PropertyName = "uses")] + public int Uses; + [JsonProperty(PropertyName = "created_at")] + public DateTime CreatedAt; + } } diff --git a/src/Discord.Net/Net/API/DiscordAPIClient.cs b/src/Discord.Net/Net/API/DiscordAPIClient.cs new file mode 100644 index 000000000..3e73ce40e --- /dev/null +++ b/src/Discord.Net/Net/API/DiscordAPIClient.cs @@ -0,0 +1,158 @@ +using System; +using System.Threading.Tasks; + +namespace Discord.Net.API +{ + internal class DiscordAPIClient + { + public const int MaxMessageSize = 2000; + + public RestClient RestClient => _rest; + private readonly RestClient _rest; + + public DiscordAPIClient(LogMessageSeverity logLevel) + { + _rest = new RestClient(logLevel); + } + + private string _token; + public string Token + { + get { return _token; } + set { _token = value; _rest.SetToken(value); } + } + + //Auth + public Task GetWebSocketEndpoint() + => _rest.Get(Endpoints.Gateway); + public async Task LoginAnonymous(string username) + { + var fingerprintResponse = await _rest.Post(Endpoints.AuthFingerprint).ConfigureAwait(false); + var registerRequest = new Requests.AuthRegister { Fingerprint = fingerprintResponse.Fingerprint, Username = username }; + var registerResponse = await _rest.Post(Endpoints.AuthRegister, registerRequest).ConfigureAwait(false); + return registerResponse; + } + public async Task Login(string email, string password) + { + var request = new Requests.AuthLogin { Email = email, Password = password }; + var response = await _rest.Post(Endpoints.AuthLogin, request).ConfigureAwait(false); + return response; + } + public Task Logout() + => _rest.Post(Endpoints.AuthLogout); + + //Servers + public Task CreateServer(string name, string region) + { + var request = new Requests.CreateServer { Name = name, Region = region }; + return _rest.Post(Endpoints.Servers, request); + } + public Task LeaveServer(string id) + => _rest.Delete(Endpoints.Server(id)); + + //Channels + public Task CreateChannel(string serverId, string name, string channelType) + { + var request = new Requests.CreateChannel { Name = name, Type = channelType }; + return _rest.Post(Endpoints.ServerChannels(serverId), request); + } + public Task CreatePMChannel(string myId, string recipientId) + { + var request = new Requests.CreatePMChannel { RecipientId = recipientId }; + return _rest.Post(Endpoints.UserChannels(myId), request); + } + public Task DestroyChannel(string channelId) + => _rest.Delete(Endpoints.Channel(channelId)); + public Task GetMessages(string channelId, int count) + => _rest.Get(Endpoints.ChannelMessages(channelId, count)); + + //Members + public Task Kick(string serverId, string memberId) + => _rest.Delete(Endpoints.ServerMember(serverId, memberId)); + public Task Ban(string serverId, string memberId) + => _rest.Put(Endpoints.ServerBan(serverId, memberId)); + public Task Unban(string serverId, string memberId) + => _rest.Delete(Endpoints.ServerBan(serverId, memberId)); + + //Invites + public Task CreateInvite(string channelId, int maxAge, int maxUses, bool isTemporary, bool withXkcdPass) + { + var request = new Requests.CreateInvite { MaxAge = maxAge, MaxUses = maxUses, IsTemporary = isTemporary, WithXkcdPass = withXkcdPass }; + return _rest.Post(Endpoints.ChannelInvites(channelId), request); + } + public Task GetInvite(string id) + => _rest.Get(Endpoints.Invite(id)); + public Task AcceptInvite(string id) + => _rest.Post(Endpoints.Invite(id)); + public Task DeleteInvite(string id) + => _rest.Delete(Endpoints.Invite(id)); + + //Chat + public Task SendMessage(string channelId, string message, string[] mentions, string nonce) + { + var request = new Requests.SendMessage { Content = message, Mentions = mentions, Nonce = nonce }; + return _rest.Post(Endpoints.ChannelMessages(channelId), request); + } + public Task EditMessage(string messageId, string channelId, string message, string[] mentions) + { + var request = new Requests.EditMessage { Content = message, Mentions = mentions }; + return _rest.Patch(Endpoints.ChannelMessage(channelId, messageId), request); + } + public Task SendIsTyping(string channelId) + => _rest.Post(Endpoints.ChannelTyping(channelId)); + public Task DeleteMessage(string channelId, string msgId) + => _rest.Delete(Endpoints.ChannelMessage(channelId, msgId)); + public Task SendFile(string channelId, string filePath) + => _rest.PostFile(Endpoints.ChannelMessages(channelId), filePath); + + //Voice + public Task GetVoiceRegions() + => _rest.Get(Endpoints.VoiceRegions); + public Task GetVoiceIce() + => _rest.Get(Endpoints.VoiceIce); + public Task Mute(string serverId, string memberId) + { + var request = new Requests.SetMemberMute { Value = true }; + return _rest.Patch(Endpoints.ServerMember(serverId, memberId)); + } + public Task Unmute(string serverId, string memberId) + { + var request = new Requests.SetMemberMute { Value = false }; + return _rest.Patch(Endpoints.ServerMember(serverId, memberId)); + } + public Task Deafen(string serverId, string memberId) + { + var request = new Requests.SetMemberDeaf { Value = true }; + return _rest.Patch(Endpoints.ServerMember(serverId, memberId)); + } + public Task Undeafen(string serverId, string memberId) + { + var request = new Requests.SetMemberDeaf { Value = false }; + return _rest.Patch(Endpoints.ServerMember(serverId, memberId)); + } + + //Profile + public Task ChangeUsername(string newUsername, string currentEmail, string currentPassword) + { + var request = new Requests.ChangeUsername { Username = newUsername, CurrentEmail = currentEmail, CurrentPassword = currentPassword }; + return _rest.Patch(Endpoints.UserMe, request); + } + public Task ChangeEmail(string newEmail, string currentPassword) + { + var request = new Requests.ChangeEmail { NewEmail = newEmail, CurrentPassword = currentPassword }; + return _rest.Patch(Endpoints.UserMe, request); + } + public Task ChangePassword(string newPassword, string currentEmail, string currentPassword) + { + var request = new Requests.ChangePassword { NewPassword = newPassword, CurrentEmail = currentEmail, CurrentPassword = currentPassword }; + return _rest.Patch(Endpoints.UserMe, request); + } + public Task ChangeAvatar(AvatarImageType imageType, byte[] bytes, string currentEmail, string currentPassword) + { + string base64 = Convert.ToBase64String(bytes); + string type = imageType == AvatarImageType.Jpeg ? "image/jpeg;base64" : "image/png;base64"; + var request = new Requests.ChangeAvatar { Avatar = $"data:{type},/9j/{base64}", CurrentEmail = currentEmail, CurrentPassword = currentPassword }; + return _rest.Patch(Endpoints.UserMe, request); + } + } +} diff --git a/src/Discord.Net/Net/API/Endpoints.cs b/src/Discord.Net/Net/API/Endpoints.cs new file mode 100644 index 000000000..eab668d0d --- /dev/null +++ b/src/Discord.Net/Net/API/Endpoints.cs @@ -0,0 +1,42 @@ +namespace Discord.Net.API +{ + internal static class Endpoints + { + public const string BaseApi = "https://discordapp.com/api/"; + //public const string Track = "track"; + public const string Gateway = "gateway"; + + public const string Auth = "auth"; + public const string AuthFingerprint = "auth/fingerprint"; + public const string AuthRegister = "auth/register"; + public const string AuthLogin = "auth/login"; + public const string AuthLogout = "auth/logout"; + + public const string Channels = "channels"; + public static string Channel(string channelId) => $"channels/{channelId}"; + public static string ChannelTyping(string channelId) => $"channels/{channelId}/typing"; + public static string ChannelMessages(string channelId) => $"channels/{channelId}/messages"; + public static string ChannelMessages(string channelId, int limit) => $"channels/{channelId}/messages?limit={limit}"; + public static string ChannelMessage(string channelId, string msgId) => $"channels/{channelId}/messages/{msgId}"; + public static string ChannelInvites(string channelId) => $"channels/{channelId}/invites"; + + public const string Servers = "guilds"; + public static string Server(string serverId) => $"guilds/{serverId}"; + public static string ServerChannels(string serverId) => $"guilds/{serverId}/channels"; + public static string ServerMember(string serverId, string userId) => $"guilds/{serverId}/members/{userId}"; + public static string ServerBan(string serverId, string userId) => $"guilds/{serverId}/bans/{userId}"; + + public const string Invites = "invite"; + public static string Invite(string inviteId) => $"invite/{inviteId}"; + public static string InviteUrl(string inviteId) => $"https://discord.gg/{inviteId}"; + + public const string Users = "users"; + public static string UserMe => $"users/@me"; + public static string UserChannels(string userId) => $"users/{userId}/channels"; + public static string UserAvatar(string userId, string avatarId) => $"users/{userId}/avatars/{avatarId}.jpg"; + + public const string Voice = "voice"; + public const string VoiceRegions = "voice/regions"; + public const string VoiceIce = "voice/ice"; + } +} diff --git a/src/Discord.Net/API/Models/APIRequests.cs b/src/Discord.Net/Net/API/Requests.cs similarity index 62% rename from src/Discord.Net/API/Models/APIRequests.cs rename to src/Discord.Net/Net/API/Requests.cs index 39017e9e3..47d798084 100644 --- a/src/Discord.Net/API/Models/APIRequests.cs +++ b/src/Discord.Net/Net/API/Requests.cs @@ -4,18 +4,19 @@ using Newtonsoft.Json; -namespace Discord.API.Models +namespace Discord.Net.API { - internal static class APIRequests + internal static class Requests { - public class AuthRegisterRequest + //Auth + public sealed class AuthRegister { [JsonProperty(PropertyName = "fingerprint")] public string Fingerprint; [JsonProperty(PropertyName = "username")] public string Username; } - public class AuthLogin + public sealed class AuthLogin { [JsonProperty(PropertyName = "email")] public string Email; @@ -23,7 +24,8 @@ namespace Discord.API.Models public string Password; } - public class CreateServer + //Servers + public sealed class CreateServer { [JsonProperty(PropertyName = "name")] public string Name; @@ -31,20 +33,22 @@ namespace Discord.API.Models public string Region; } - public class CreateChannel + //Channels + public sealed class CreateChannel { [JsonProperty(PropertyName = "name")] public string Name; [JsonProperty(PropertyName = "type")] public string Type; } - public class CreatePMChannel + public sealed class CreatePMChannel { [JsonProperty(PropertyName = "recipient_id")] public string RecipientId; } - public class CreateInvite + //Invites + public sealed class CreateInvite { [JsonProperty(PropertyName = "max_age")] public int MaxAge; @@ -53,10 +57,11 @@ namespace Discord.API.Models [JsonProperty(PropertyName = "temporary")] public bool IsTemporary; [JsonProperty(PropertyName = "xkcdpass")] - public bool HasXkcdPass; + public bool WithXkcdPass; } - public class SendMessage + //Messages + public sealed class SendMessage { [JsonProperty(PropertyName = "content")] public string Content; @@ -65,45 +70,58 @@ namespace Discord.API.Models [JsonProperty(PropertyName = "nonce")] public string Nonce; } - public class EditMessage : SendMessage { } + public sealed class EditMessage + { + [JsonProperty(PropertyName = "content")] + public string Content; + [JsonProperty(PropertyName = "mentions")] + public string[] Mentions; + } - public class SetMemberMute + //Members + public sealed class SetMemberMute { [JsonProperty(PropertyName = "mute")] - public bool Mute; + public bool Value; } - public class SetMemberDeaf + public sealed class SetMemberDeaf { [JsonProperty(PropertyName = "deaf")] - public bool Deaf; + public bool Value; } - - public abstract class ChangeProfile + + //Profile + public sealed class ChangeUsername { [JsonProperty(PropertyName = "email")] public string CurrentEmail; [JsonProperty(PropertyName = "password")] public string CurrentPassword; - } - public class ChangeUsername : ChangeProfile - { [JsonProperty(PropertyName = "username")] public string Username; } - public class ChangeEmail + public sealed class ChangeEmail { [JsonProperty(PropertyName = "email")] public string NewEmail; [JsonProperty(PropertyName = "password")] public string CurrentPassword; } - public class ChangePassword : ChangeProfile + public sealed class ChangePassword { + [JsonProperty(PropertyName = "email")] + public string CurrentEmail; + [JsonProperty(PropertyName = "password")] + public string CurrentPassword; [JsonProperty(PropertyName = "new_password")] public string NewPassword; } - public class ChangeAvatar : ChangeProfile + public sealed class ChangeAvatar { + [JsonProperty(PropertyName = "email")] + public string CurrentEmail; + [JsonProperty(PropertyName = "password")] + public string CurrentPassword; [JsonProperty(PropertyName = "avatar")] public string Avatar; } diff --git a/src/Discord.Net/Net/API/Responses.cs b/src/Discord.Net/Net/API/Responses.cs new file mode 100644 index 000000000..b4d07dc73 --- /dev/null +++ b/src/Discord.Net/Net/API/Responses.cs @@ -0,0 +1,85 @@ +//Ignore unused/unassigned variable warnings +#pragma warning disable CS0649 +#pragma warning disable CS0169 + +using Newtonsoft.Json; +using System; + +namespace Discord.Net.API +{ + internal static class Responses + { + //Auth + public sealed class Gateway + { + [JsonProperty(PropertyName = "url")] + public string Url; + } + public sealed class AuthFingerprint + { + [JsonProperty(PropertyName = "fingerprint")] + public string Fingerprint; + } + public sealed class AuthRegister + { + [JsonProperty(PropertyName = "token")] + public string Token; + } + public sealed class AuthLogin + { + [JsonProperty(PropertyName = "token")] + public string Token; + } + + //Users + public sealed class ChangeProfile : SelfUserInfo { } + + //Servers + public sealed class CreateServer : GuildInfo { } + public sealed class DeleteServer : GuildInfo { } + + //Channels + public sealed class CreateChannel : ChannelInfo { } + public sealed class DestroyChannel : ChannelInfo { } + + //Invites + public sealed class CreateInvite : ExtendedInvite { } + public sealed class GetInvite : Invite { } + public sealed class AcceptInvite : Invite { } + + //Messages + public sealed class SendMessage : Message { } + public sealed class EditMessage : Message { } + public sealed class GetMessages : Message { } + + //Voice + public sealed class GetRegions + { + [JsonProperty(PropertyName = "sample_hostname")] + public string Hostname; + [JsonProperty(PropertyName = "sample_port")] + public int Port; + [JsonProperty(PropertyName = "id")] + public string Id; + [JsonProperty(PropertyName = "name")] + public string Name; + } + public sealed class GetIce + { + [JsonProperty(PropertyName = "ttl")] + public string TTL; + [JsonProperty(PropertyName = "servers")] + public Server[] Servers; + + public sealed class Server + { + [JsonProperty(PropertyName = "url")] + public string URL; + [JsonProperty(PropertyName = "username")] + public string Username; + [JsonProperty(PropertyName = "credential")] + public string Credential; + } + } + } +} diff --git a/src/Discord.Net/HttpException.cs b/src/Discord.Net/Net/HttpException.cs similarity index 87% rename from src/Discord.Net/HttpException.cs rename to src/Discord.Net/Net/HttpException.cs index 3f30fff95..88bbcee3d 100644 --- a/src/Discord.Net/HttpException.cs +++ b/src/Discord.Net/Net/HttpException.cs @@ -1,7 +1,7 @@ using System; using System.Net; -namespace Discord +namespace Discord.Net { public class HttpException : Exception { @@ -9,8 +9,8 @@ namespace Discord public HttpException(HttpStatusCode statusCode) : base($"The server responded with error {statusCode}") - { + { StatusCode = statusCode; - } + } } } diff --git a/src/Discord.Net/Net/RestClient.BuiltIn.cs b/src/Discord.Net/Net/RestClient.BuiltIn.cs new file mode 100644 index 000000000..03eff9f7a --- /dev/null +++ b/src/Discord.Net/Net/RestClient.BuiltIn.cs @@ -0,0 +1,65 @@ +#if DNXCORE50 +using System; +using System.Globalization; +using System.IO; +using System.Net; +using System.Net.Http; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Discord.Net +{ + internal class BuiltInRestEngine : IRestEngine + { + private readonly HttpClient _client; + + public BuiltInRestEngine(string userAgent) + { + _client = new HttpClient(new HttpClientHandler + { + AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip, + UseCookies = false, + PreAuthenticate = false //We do auth ourselves + }); + _client.DefaultRequestHeaders.Add("accept", "*/*"); + _client.DefaultRequestHeaders.Add("accept-encoding", "gzip,deflate"); + _client.DefaultRequestHeaders.Add("user-agent", userAgent); + } + + public void SetToken(string token) + { + _client.DefaultRequestHeaders.Remove("authorization"); + if (token != null) + _client.DefaultRequestHeaders.Add("authorization", token); + } + + public Task Send(HttpMethod method, string path, string json, CancellationToken cancelToken) + { + using (var request = new HttpRequestMessage(method, path)) + { + if (json != null) + request.Content = new StringContent(json, Encoding.UTF8, "application/json"); + return Send(request, cancelToken); + } + } + public Task SendFile(HttpMethod method, string path, string filePath, CancellationToken cancelToken) + { + using (var request = new HttpRequestMessage(method, path)) + { + var content = new MultipartFormDataContent("Upload----" + DateTime.Now.ToString(CultureInfo.InvariantCulture)); + content.Add(new StreamContent(File.OpenRead(filePath)), "file", Path.GetFileName(filePath)); + request.Content = content; + return Send(request, cancelToken); + } + } + private async Task Send(HttpRequestMessage request, CancellationToken cancelToken) + { + var response = await _client.SendAsync(request, cancelToken).ConfigureAwait(false); + if (!response.IsSuccessStatusCode) + throw new HttpException(response.StatusCode); + return await response.Content.ReadAsStringAsync().ConfigureAwait(false); + } + } +} +#endif \ No newline at end of file diff --git a/src/Discord.Net/Net/RestClient.Events.cs b/src/Discord.Net/Net/RestClient.Events.cs new file mode 100644 index 000000000..85c4d02a1 --- /dev/null +++ b/src/Discord.Net/Net/RestClient.Events.cs @@ -0,0 +1,30 @@ +using System; +using System.Net.Http; + +namespace Discord.Net +{ + internal partial class RestClient + { + public class RequestEventArgs : EventArgs + { + public HttpMethod Method { get; } + public string Path { get; } + public string Payload { get; } + public double ElapsedMilliseconds { get; } + public RequestEventArgs(HttpMethod method, string path, string payload, double milliseconds) + { + Method = method; + Path = path; + Payload = payload; + ElapsedMilliseconds = milliseconds; + } + } + + public event EventHandler OnRequest; + protected void RaiseOnRequest(HttpMethod method, string path, string payload, double milliseconds) + { + if (OnRequest != null) + OnRequest(this, new RequestEventArgs(method, path, payload, milliseconds)); + } + } +} diff --git a/src/Discord.Net/Net/RestClient.SharpRest.cs b/src/Discord.Net/Net/RestClient.SharpRest.cs new file mode 100644 index 000000000..678325d42 --- /dev/null +++ b/src/Discord.Net/Net/RestClient.SharpRest.cs @@ -0,0 +1,68 @@ +#if !DNXCORE50 +using RestSharp; +using System; +using System.IO; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +namespace Discord.Net +{ + internal class SharpRestEngine : IRestEngine + { + private readonly RestSharp.RestClient _client; + + public SharpRestEngine(string userAgent) + { + _client = new RestSharp.RestClient() + { + PreAuthenticate = false + }; + _client.AddDefaultHeader("accept", "*/*"); + _client.AddDefaultHeader("accept-encoding", "gzip,deflate"); + _client.UserAgent = userAgent; + } + + public void SetToken(string token) + { + _client.RemoveDefaultParameter("authorization"); + if (token != null) + _client.AddDefaultHeader("authorization", token); + } + + public Task Send(HttpMethod method, string path, string json, CancellationToken cancelToken) + { + var request = new RestRequest(path, GetMethod(method)) { RequestFormat = DataFormat.Json }; + request.AddBody(json); + return Send(request, cancelToken); + } + public Task SendFile(HttpMethod method, string path, string filePath, CancellationToken cancelToken) + { + var request = new RestRequest(path, Method.POST); + request.AddFile(Path.GetFileName(filePath), filePath); + return Send(request, cancelToken); + } + private async Task Send(RestRequest request, CancellationToken cancelToken) + { + var response = await _client.ExecuteTaskAsync(request, cancelToken).ConfigureAwait(false); + int statusCode = (int)response.StatusCode; + if (statusCode < 200 || statusCode >= 300) //2xx = Success + throw new HttpException(response.StatusCode); + return response.Content; + } + + private Method GetMethod(HttpMethod method) + { + switch (method.Method) + { + case "DELETE": return Method.DELETE; + case "GET": return Method.GET; + case "PATCH": return Method.PATCH; + case "POST": return Method.POST; + case "PUT": return Method.PUT; + default: throw new InvalidOperationException($"Unknown HttpMethod: {method}"); + } + } + } +} +#endif \ No newline at end of file diff --git a/src/Discord.Net/Net/RestClient.cs b/src/Discord.Net/Net/RestClient.cs new file mode 100644 index 000000000..f7dafa985 --- /dev/null +++ b/src/Discord.Net/Net/RestClient.cs @@ -0,0 +1,184 @@ +using Discord.Net.API; +using Newtonsoft.Json; +using System; +using System.Diagnostics; +using System.Net.Http; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; + +namespace Discord.Net +{ + internal interface IRestEngine + { + void SetToken(string token); + Task Send(HttpMethod method, string path, string json, CancellationToken cancelToken); + Task SendFile(HttpMethod method, string path, string filePath, CancellationToken cancelToken); + } + + internal partial class RestClient + { + private readonly IRestEngine _engine; + private readonly LogMessageSeverity _logLevel; + private CancellationToken _cancelToken; + + public RestClient(LogMessageSeverity logLevel) + { + _logLevel = logLevel; + + string version = typeof(RestClient).GetTypeInfo().Assembly.GetName().Version.ToString(2); + string userAgent = $"Discord.Net/{version} (https://github.com/RogueException/Discord.Net)"; +#if DNXCORE50 + _engine = new BuiltInRestEngine(userAgent); +#else + _engine = new SharpRestEngine(userAgent); +#endif + } + + private static readonly HttpMethod _delete = HttpMethod.Delete; + internal Task Delete(string path, object data) where ResponseT : class + => Send(_delete, path, data); + internal Task Delete(string path) where ResponseT : class + => Send(_delete, path); + internal Task Delete(string path, object data) + => Send(_delete, path, data); + internal Task Delete(string path) + => Send(_delete, path); + + private static readonly HttpMethod _get = HttpMethod.Get; + internal Task Get(string path) where ResponseT : class + => Send(_get, path); + internal Task Get(string path) + => Send(_get, path); + + private static readonly HttpMethod _patch = new HttpMethod("PATCH"); + internal Task Patch(string path, object data) where ResponseT : class + => Send(_patch, path, data); + internal Task Patch(string path, object data) + => Send(_patch, path, data); + internal Task Patch(string path) where ResponseT : class + => Send(_patch, path); + internal Task Patch(string path) + => Send(_patch, path); + + private static readonly HttpMethod _post = HttpMethod.Post; + internal Task Post(string path, object data) where ResponseT : class + => Send(_post, path, data); + internal Task Post(string path) where ResponseT : class + => Send(_post, path); + internal Task Post(string path, object data) + => Send(_post, path, data); + internal Task Post(string path) + => Send(_post, path); + + private static readonly HttpMethod _put = HttpMethod.Put; + internal Task Put(string path, object data) where ResponseT : class + => Send(_put, path, data); + internal Task Put(string path) where ResponseT : class + => Send(_put, path); + internal Task Put(string path, object data) + => Send(_put, path, data); + internal Task Put(string path) + => Send(_put, path); + + internal Task PostFile(string path, string filePath) where ResponseT : class + => SendFile(_post, path, filePath); + internal Task PostFile(string path, string filePath) + => SendFile(_post, path, filePath); + + internal Task PutFile(string path, string filePath) where ResponseT : class + => SendFile(_put, path, filePath); + internal Task PutFile(string path, string filePath) + => SendFile(_put, path, filePath); + + private async Task Send(HttpMethod method, string path, object content = null) + where ResponseT : class + { + string responseJson = await Send(method, path, content, true).ConfigureAwait(false); + return DeserializeResponse(responseJson); + } + private Task Send(HttpMethod method, string path, object content = null) + => Send(method, path, content, false); + private async Task Send(HttpMethod method, string path, object content, bool hasResponse) + { + Stopwatch stopwatch = null; + string requestJson = null; + if (content != null) + requestJson = JsonConvert.SerializeObject(content); + + if (_logLevel >= LogMessageSeverity.Verbose) + stopwatch = Stopwatch.StartNew(); + + string responseJson = await _engine.Send(method, path, requestJson, _cancelToken).ConfigureAwait(false); +#if TEST_RESPONSES + if (!hasResponse && !string.IsNullOrEmpty(responseJson)) + throw new Exception("API check failed: Response is not empty."); +#endif + + if (_logLevel >= LogMessageSeverity.Verbose) + { + stopwatch.Stop(); + if (content != null && _logLevel >= LogMessageSeverity.Debug) + RaiseOnRequest(method, path, requestJson, stopwatch.ElapsedTicks / (double)TimeSpan.TicksPerMillisecond); + else + RaiseOnRequest(method, path, null, stopwatch.ElapsedTicks / (double)TimeSpan.TicksPerMillisecond); + } + + return responseJson; + } + + private async Task SendFile(HttpMethod method, string path, string filePath) + where ResponseT : class + { + string responseJson = await SendFile(method, path, filePath, true).ConfigureAwait(false); + return DeserializeResponse(responseJson); + } + private Task SendFile(HttpMethod method, string path, string filePath) + => SendFile(method, path, filePath, false); + private async Task SendFile(HttpMethod method, string path, string filePath, bool hasResponse) + { + Stopwatch stopwatch = null; + + if (_logLevel >= LogMessageSeverity.Verbose) + stopwatch = Stopwatch.StartNew(); + + string responseJson = await _engine.SendFile(method, path, filePath, _cancelToken).ConfigureAwait(false); +#if TEST_RESPONSES + if (!hasResponse && !string.IsNullOrEmpty(responseJson)) + throw new Exception("API check failed: Response is not empty."); +#endif + + if (_logLevel >= LogMessageSeverity.Verbose) + { + stopwatch.Stop(); + if (_logLevel >= LogMessageSeverity.Debug) + RaiseOnRequest(method, path, filePath, stopwatch.ElapsedTicks / (double)TimeSpan.TicksPerMillisecond); + else + RaiseOnRequest(method, path, null, stopwatch.ElapsedTicks / (double)TimeSpan.TicksPerMillisecond); + } + + return responseJson; + } + + private JsonSerializerSettings _deserializeSettings = new JsonSerializerSettings(); + private T DeserializeResponse(string json) + { +#if TEST_RESPONSES + if (_deserializeSettings == null) + { + _deserializeSettings = new JsonSerializerSettings(); + _deserializeSettings.CheckAdditionalContent = true; + _deserializeSettings.MissingMemberHandling = MissingMemberHandling.Error; + } + if (string.IsNullOrEmpty(json)) + throw new Exception("API check failed: Response is empty."); + return JsonConvert.DeserializeObject(json, _deserializeSettings); +#else + return JsonConvert.DeserializeObject(json); +#endif + } + + internal void SetToken(string token) => _engine.SetToken(token); + internal void SetCancelToken(CancellationToken token) => _cancelToken = token; + } +} diff --git a/src/Discord.Net/API/Models/TextWebSocketCommands.cs b/src/Discord.Net/Net/WebSockets/Commands.cs similarity index 61% rename from src/Discord.Net/API/Models/TextWebSocketCommands.cs rename to src/Discord.Net/Net/WebSockets/Commands.cs index 552f1a38e..2e1cce653 100644 --- a/src/Discord.Net/API/Models/TextWebSocketCommands.cs +++ b/src/Discord.Net/Net/WebSockets/Commands.cs @@ -7,39 +7,16 @@ using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; -namespace Discord.API.Models +namespace Discord.Net.WebSockets { - internal static class TextWebSocketCommands + internal static class Commands { - public class WebSocketMessage - { - [JsonProperty(PropertyName = "op")] - public int Operation; - [JsonProperty(PropertyName = "t", NullValueHandling = NullValueHandling.Ignore)] - public string Type; - [JsonProperty(PropertyName = "s", NullValueHandling = NullValueHandling.Ignore)] - public int? Sequence; - [JsonProperty(PropertyName = "d", NullValueHandling = NullValueHandling.Ignore)] - public object Payload; - } - internal abstract class WebSocketMessage : WebSocketMessage - where T : new() - { - public WebSocketMessage() { Payload = new T(); } - public WebSocketMessage(int op) { Operation = op; Payload = new T(); } - public WebSocketMessage(int op, T payload) { Operation = op; Payload = payload; } - - [JsonIgnore] - public new T Payload - { - get { if (base.Payload is JToken) { base.Payload = (base.Payload as JToken).ToObject(); } return (T)base.Payload; } - set { base.Payload = value; } - } - } public sealed class KeepAlive : WebSocketMessage { + public KeepAlive() : base(1, GetTimestamp()) { } private static DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); - public KeepAlive() : base(1, (int)(DateTime.UtcNow - epoch).TotalMilliseconds) { } + private static int GetTimestamp() + => (int)(DateTime.UtcNow - epoch).TotalMilliseconds; } public sealed class Login : WebSocketMessage { diff --git a/src/Discord.Net/Net/WebSockets/DataWebSocket.cs b/src/Discord.Net/Net/WebSockets/DataWebSocket.cs new file mode 100644 index 000000000..4cac9842a --- /dev/null +++ b/src/Discord.Net/Net/WebSockets/DataWebSocket.cs @@ -0,0 +1,102 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.Threading.Tasks; + +namespace Discord.Net.WebSockets +{ + internal partial class DataWebSocket : WebSocket + { + private string _lastSession, _redirectServer; + private int _lastSeq; + + public DataWebSocket(DiscordClient client) + : base(client) + { + } + + public async Task Login(string host, string token) + { + await base.Connect(host); + + Commands.Login msg = new Commands.Login(); + msg.Payload.Token = token; + //msg.Payload.Properties["$os"] = ""; + //msg.Payload.Properties["$browser"] = ""; + msg.Payload.Properties["$device"] = "Discord.Net"; + //msg.Payload.Properties["$referrer"] = ""; + //msg.Payload.Properties["$referring_domain"] = ""; + QueueMessage(msg); + } + + protected override Task[] Run() + { + //Send resume session if we were transferred + if (_redirectServer != null) + { + var resumeMsg = new Commands.Resume(); + resumeMsg.Payload.SessionId = _lastSession; + resumeMsg.Payload.Sequence = _lastSeq; + QueueMessage(resumeMsg); + _redirectServer = null; + } + return base.Run(); + } + + protected override async Task ProcessMessage(string json) + { + var msg = JsonConvert.DeserializeObject(json); + if (msg.Sequence.HasValue) + _lastSeq = msg.Sequence.Value; + + switch (msg.Operation) + { + case 0: + { + if (msg.Type == "READY") + { + var payload = (msg.Payload as JToken).ToObject(); + _lastSession = payload.SessionId; + _heartbeatInterval = payload.HeartbeatInterval; + QueueMessage(new Commands.UpdateStatus()); + CompleteConnect(); + } + if (_logLevel >= LogMessageSeverity.Info) + RaiseOnLog(LogMessageSeverity.Info, "Got Event: " + msg.Type); + } + break; + case 7: //Redirect + { + var payload = (msg.Payload as JToken).ToObject(); + _host = payload.Url; + if (_logLevel >= LogMessageSeverity.Info) + RaiseOnLog(LogMessageSeverity.Info, "Redirected to " + payload.Url); + await DisconnectInternal(new Exception("Server is redirecting."), true); + } + break; + default: + if (_logLevel >= LogMessageSeverity.Warning) + RaiseOnLog(LogMessageSeverity.Warning, $"Unknown Opcode: {msg.Operation}"); + break; + } + } + + protected override object GetKeepAlive() + { + return new Commands.KeepAlive(); + } + + public void SendJoinVoice(Channel channel) + { + var joinVoice = new Commands.JoinVoice(); + joinVoice.Payload.ServerId = channel.ServerId; + joinVoice.Payload.ChannelId = channel.Id; + QueueMessage(joinVoice); + } + public void SendLeaveVoice() + { + var leaveVoice = new Commands.JoinVoice(); + QueueMessage(leaveVoice); + } + } +} diff --git a/src/Discord.Net/API/Models/TextWebSocketEvents.cs b/src/Discord.Net/Net/WebSockets/Events.cs similarity index 66% rename from src/Discord.Net/API/Models/TextWebSocketEvents.cs rename to src/Discord.Net/Net/WebSockets/Events.cs index b5497fb5d..98f52334d 100644 --- a/src/Discord.Net/API/Models/TextWebSocketEvents.cs +++ b/src/Discord.Net/Net/WebSockets/Events.cs @@ -2,11 +2,12 @@ #pragma warning disable CS0649 #pragma warning disable CS0169 +using Discord.Net.API; using Newtonsoft.Json; -namespace Discord.API.Models +namespace Discord.Net.WebSockets { - internal static class TextWebSocketEvents + internal static class Events { public sealed class Ready { @@ -29,7 +30,7 @@ namespace Discord.API.Models [JsonProperty(PropertyName = "read_state")] public ReadStateInfo[] ReadState; [JsonProperty(PropertyName = "guilds")] - public ExtendedServerInfo[] Guilds; + public ExtendedGuildInfo[] Guilds; [JsonProperty(PropertyName = "private_channels")] public ChannelInfo[] PrivateChannels; [JsonProperty(PropertyName = "heartbeat_interval")] @@ -43,9 +44,9 @@ namespace Discord.API.Models } //Servers - public sealed class GuildCreate : ExtendedServerInfo { } - public sealed class GuildUpdate : ServerInfo { } - public sealed class GuildDelete : ExtendedServerInfo { } + public sealed class GuildCreate : ExtendedGuildInfo { } + public sealed class GuildUpdate : GuildInfo { } + public sealed class GuildDelete : ExtendedGuildInfo { } //Channels public sealed class ChannelCreate : ChannelInfo { } @@ -53,43 +54,30 @@ namespace Discord.API.Models public sealed class ChannelUpdate : ChannelInfo { } //Memberships - public sealed class GuildMemberAdd : RoleMemberInfo { } - public sealed class GuildMemberUpdate : RoleMemberInfo { } + public sealed class GuildMemberAdd : MemberInfo { } + public sealed class GuildMemberUpdate : MemberInfo { } public sealed class GuildMemberRemove : MemberInfo { } //Roles - public abstract class GuildRoleEvent + public sealed class GuildRoleCreate { [JsonProperty(PropertyName = "guild_id")] - public string ServerId; - } - public sealed class GuildRoleCreateUpdate : GuildRoleEvent - { + public string GuildId; [JsonProperty(PropertyName = "role")] - public Role Role; + public RoleInfo Data; } - public sealed class GuildRoleDelete : GuildRoleEvent + public sealed class GuildRoleUpdate { - [JsonProperty(PropertyName = "role_id")] - public string RoleId; + [JsonProperty(PropertyName = "guild_id")] + public string GuildId; + [JsonProperty(PropertyName = "role")] + public RoleInfo Data; } + public sealed class GuildRoleDelete : RoleReference { } //Bans - public abstract class GuildBanEvent - { - [JsonProperty(PropertyName = "guild_id")] - public string ServerId; - } - public sealed class GuildBanAddRemove : GuildBanEvent - { - [JsonProperty(PropertyName = "user")] - public UserReference User; - } - public sealed class GuildBanRemove : GuildBanEvent - { - [JsonProperty(PropertyName = "user_id")] - public string UserId; - } + public sealed class GuildBanAdd : MemberReference { } + public sealed class GuildBanRemove : MemberReference { } //User public sealed class UserUpdate : SelfUserInfo { } @@ -97,8 +85,8 @@ namespace Discord.API.Models public sealed class VoiceStateUpdate : VoiceMemberInfo { } //Chat - public sealed class MessageCreate : Message { } - public sealed class MessageUpdate : Message { } + public sealed class MessageCreate : API.Message { } + public sealed class MessageUpdate : API.Message { } public sealed class MessageDelete : MessageReference { } public sealed class MessageAck : MessageReference { } public sealed class TypingStart @@ -115,7 +103,7 @@ namespace Discord.API.Models public sealed class VoiceServerUpdate { [JsonProperty(PropertyName = "guild_id")] - public string ServerId; + public string GuildId; [JsonProperty(PropertyName = "endpoint")] public string Endpoint; [JsonProperty(PropertyName = "token")] diff --git a/src/Discord.Net/API/Models/VoiceWebSocketCommands.cs b/src/Discord.Net/Net/WebSockets/VoiceCommands.cs similarity index 66% rename from src/Discord.Net/API/Models/VoiceWebSocketCommands.cs rename to src/Discord.Net/Net/WebSockets/VoiceCommands.cs index 329eae732..9b7ac1a14 100644 --- a/src/Discord.Net/API/Models/VoiceWebSocketCommands.cs +++ b/src/Discord.Net/Net/WebSockets/VoiceCommands.cs @@ -3,38 +3,11 @@ #pragma warning disable CS0169 using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -namespace Discord.API.Models +namespace Discord.Net.WebSockets { - internal static class VoiceWebSocketCommands + internal static class VoiceCommands { - public class WebSocketMessage - { - [JsonProperty(PropertyName = "op")] - public int Operation; - [JsonProperty(PropertyName = "d")] - public object Payload; - } - internal abstract class WebSocketMessage : WebSocketMessage - where T : new() - { - public WebSocketMessage() { Payload = new T(); } - public WebSocketMessage(int op) { Operation = op; Payload = new T(); } - public WebSocketMessage(int op, T payload) { Operation = op; Payload = payload; } - - [JsonIgnore] - public new T Payload - { - get { if (base.Payload is JToken) { base.Payload = (base.Payload as JToken).ToObject(); } return (T)base.Payload; } - set { base.Payload = value; } - } - } - - public sealed class KeepAlive : WebSocketMessage - { - public KeepAlive() : base(3, null) { } - } public sealed class Login : WebSocketMessage { public Login() : base(0) { } @@ -70,6 +43,10 @@ namespace Discord.API.Models public SocketInfo SocketData = new SocketInfo(); } } + public sealed class KeepAlive : WebSocketMessage + { + public KeepAlive() : base(3, null) { } + } public sealed class IsTalking : WebSocketMessage { public IsTalking() : base(5) { } diff --git a/src/Discord.Net/API/Models/VoiceWebSocketEvents.cs b/src/Discord.Net/Net/WebSockets/VoiceEvents.cs similarity index 92% rename from src/Discord.Net/API/Models/VoiceWebSocketEvents.cs rename to src/Discord.Net/Net/WebSockets/VoiceEvents.cs index d5e4f33fb..8f17a7b11 100644 --- a/src/Discord.Net/API/Models/VoiceWebSocketEvents.cs +++ b/src/Discord.Net/Net/WebSockets/VoiceEvents.cs @@ -4,9 +4,9 @@ using Newtonsoft.Json; -namespace Discord.API.Models +namespace Discord.Net.WebSockets { - internal static class VoiceWebSocketEvents + internal static class VoiceEvents { public sealed class Ready { diff --git a/src/Discord.Net/DiscordVoiceSocket.cs b/src/Discord.Net/Net/WebSockets/VoiceWebSocket.cs similarity index 56% rename from src/Discord.Net/DiscordVoiceSocket.cs rename to src/Discord.Net/Net/WebSockets/VoiceWebSocket.cs index 6c3954066..68a81c14d 100644 --- a/src/Discord.Net/DiscordVoiceSocket.cs +++ b/src/Discord.Net/Net/WebSockets/VoiceWebSocket.cs @@ -1,7 +1,5 @@ -#define USE_THREAD -#if !DNXCORE50 -using Discord.API.Models; -using Discord.Opus; +using Discord.Audio; +using Discord.Helpers; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; @@ -10,21 +8,19 @@ using System.Diagnostics; using System.Linq; using System.Net; using System.Net.Sockets; +using System.Text; using System.Threading; using System.Threading.Tasks; -using System.Text; -using WebSocketMessage = Discord.API.Models.VoiceWebSocketCommands.WebSocketMessage; -using Discord.Helpers; -namespace Discord +namespace Discord.Net.WebSockets { - internal sealed partial class DiscordVoiceSocket : DiscordWebSocket + internal partial class VoiceWebSocket : WebSocket { private readonly int _targetAudioBufferLength; - private ManualResetEventSlim _connectWaitOnLogin; + private ManualResetEventSlim _connectWaitOnLogin; private uint _ssrc; - private readonly Random _rand = new Random(); - + private readonly Random _rand = new Random(); + private OpusEncoder _encoder; private ConcurrentQueue _sendQueue; private ManualResetEventSlim _sendQueueWait, _sendQueueEmptyWait; @@ -44,19 +40,19 @@ namespace Discord public string CurrentVoiceServerId => _serverId; - public DiscordVoiceSocket(DiscordClient client, int timeout, int interval, int audioBufferLength, bool isDebug) - : base(client, timeout, interval, isDebug) + public VoiceWebSocket(DiscordClient client) + : base(client) { _connectWaitOnLogin = new ManualResetEventSlim(false); _sendQueue = new ConcurrentQueue(); _sendQueueWait = new ManualResetEventSlim(true); _sendQueueEmptyWait = new ManualResetEventSlim(true); - _encoder = new OpusEncoder(48000, 1, 20, Application.Audio); + _encoder = new OpusEncoder(48000, 1, 20, Opus.Application.Audio); _encodingBuffer = new byte[4000]; - _targetAudioBufferLength = audioBufferLength / 20; - } - - protected override void OnConnect() + _targetAudioBufferLength = client.Config.VoiceBufferLength / 20; //20 ms frames + } + + protected override Task[] Run() { _udp = new UdpClient(new IPEndPoint(IPAddress.Any, 0)); #if !DNX451 @@ -64,31 +60,32 @@ namespace Discord #endif _isReady = false; _isClearing = false; + + VoiceCommands.Login msg = new VoiceCommands.Login(); + msg.Payload.ServerId = _serverId; + msg.Payload.SessionId = _sessionId; + msg.Payload.Token = _token; + msg.Payload.UserId = _userId; + QueueMessage(msg); - var cancelToken = _disconnectToken.Token; - Task.Run(async () => +#if USE_THREAD + _sendThread = new Thread(new ThreadStart(() => SendVoiceAsync(_disconnectToken))); + _sendThread.Start(); +#endif + return new Task[] { - try - { - _connectWaitOnLogin.Reset(); - - VoiceWebSocketCommands.Login msg = new VoiceWebSocketCommands.Login(); - msg.Payload.ServerId = _serverId; - msg.Payload.SessionId = _sessionId; - msg.Payload.Token = _token; - msg.Payload.UserId = _userId; - await SendMessage(msg, cancelToken).ConfigureAwait(false); - - if (!_connectWaitOnLogin.Wait(_timeout, cancelToken)) - return; - - SetConnected(); - } - catch (OperationCanceledException) { } - }, _disconnectToken.Token); + ReceiveVoiceAsync(), +#if !USE_THREAD + SendVoiceAsync(), +#endif +#if !DNXCORE50 + WatcherAsync() +#endif + }.Concat(base.Run()).ToArray(); } - protected override void OnDisconnect() + protected override Task Cleanup() { + ClearPCMFrames(); _udp = null; _serverId = null; _userId = null; @@ -98,22 +95,7 @@ namespace Discord _sendThread.Join(); _sendThread = null; #endif - } - - protected override Task[] CreateTasks() - { -#if USE_THREAD - _sendThread = new Thread(new ThreadStart(() => SendVoiceAsync(_disconnectToken))); - _sendThread.Start(); -#endif - return new Task[] - { - ReceiveVoiceAsync(), -#if !USE_THREAD - SendVoiceAsync(), -#endif - WatcherAsync() - }.Concat(base.CreateTasks()).ToArray(); + return base.Cleanup(); } public void SetSessionData(string serverId, string userId, string sessionId, string token) @@ -124,31 +106,9 @@ namespace Discord _token = token; } - public new async Task BeginConnect() - { - await base.BeginConnect().ConfigureAwait(false); - var cancelToken = _disconnectToken.Token; - - await Task.Run(() => - { - try - { - if (!_connectWaitOnLogin.Wait(_timeout, cancelToken)) //Waiting on JoinServer message - throw new Exception("No reply from Discord server"); - } - catch (OperationCanceledException) - { - if (_disconnectReason == null) - throw new Exception("An unknown websocket error occurred."); - else - _disconnectReason.Throw(); - } - }).ConfigureAwait(false); - } - private async Task ReceiveVoiceAsync() { - var cancelSource = _disconnectToken; + var cancelSource = _cancelToken; var cancelToken = cancelSource.Token; await Task.Run(async () => @@ -157,13 +117,22 @@ namespace Discord { while (!cancelToken.IsCancellationRequested) { - var result = await _udp.ReceiveAsync().ConfigureAwait(false); - ProcessUdpMessage(result); +#if DNXCORE50 + if (_udp.Available > 0) + { +#endif + var result = await _udp.ReceiveAsync().ConfigureAwait(false); + ProcessUdpMessage(result); +#if DNXCORE50 + } + else + await Task.Delay(1).ConfigureAwait(false); +#endif } } catch (OperationCanceledException) { } catch (InvalidOperationException) { } //Includes ObjectDisposedException - catch (Exception ex) { DisconnectInternal(ex); } + catch (Exception ex) { await DisconnectInternal(ex); } }).ConfigureAwait(false); } @@ -174,101 +143,106 @@ namespace Discord #else private Task SendVoiceAsync() { - var cancelSource = _disconnectToken; + var cancelSource = _cancelToken; var cancelToken = cancelSource.Token; - return Task.Run(() => + return Task.Run(async () => { #endif - byte[] packet; - try - { - while (!cancelToken.IsCancellationRequested && !_isReady) - Thread.Sleep(1); - - if (cancelToken.IsCancellationRequested) - return; - - uint timestamp = 0; - double nextTicks = 0.0; - double ticksPerMillisecond = Stopwatch.Frequency / 1000.0; - double ticksPerFrame = ticksPerMillisecond * _encoder.FrameLength; - double spinLockThreshold = 1.5 * ticksPerMillisecond; - uint samplesPerFrame = (uint)_encoder.SamplesPerFrame; - Stopwatch sw = Stopwatch.StartNew(); - - byte[] rtpPacket = new byte[_encodingBuffer.Length + 12]; - rtpPacket[0] = 0x80; //Flags; - rtpPacket[1] = 0x78; //Payload Type - rtpPacket[8] = (byte)((_ssrc >> 24) & 0xFF); - rtpPacket[9] = (byte)((_ssrc >> 16) & 0xFF); - rtpPacket[10] = (byte)((_ssrc >> 8) & 0xFF); - rtpPacket[11] = (byte)((_ssrc >> 0) & 0xFF); - - while (!cancelToken.IsCancellationRequested) + byte[] packet; + try { - double ticksToNextFrame = nextTicks - sw.ElapsedTicks; - if (ticksToNextFrame <= 0.0) + while (!cancelToken.IsCancellationRequested && !_isReady) +#if USE_THREAD + Thread.Sleep(1); +#else + await Task.Delay(1); +#endif + + if (cancelToken.IsCancellationRequested) + return; + + uint timestamp = 0; + double nextTicks = 0.0; + double ticksPerMillisecond = Stopwatch.Frequency / 1000.0; + double ticksPerFrame = ticksPerMillisecond * _encoder.FrameLength; + double spinLockThreshold = 1.5 * ticksPerMillisecond; + uint samplesPerFrame = (uint)_encoder.SamplesPerFrame; + Stopwatch sw = Stopwatch.StartNew(); + + byte[] rtpPacket = new byte[_encodingBuffer.Length + 12]; + rtpPacket[0] = 0x80; //Flags; + rtpPacket[1] = 0x78; //Payload Type + rtpPacket[8] = (byte)((_ssrc >> 24) & 0xFF); + rtpPacket[9] = (byte)((_ssrc >> 16) & 0xFF); + rtpPacket[10] = (byte)((_ssrc >> 8) & 0xFF); + rtpPacket[11] = (byte)((_ssrc >> 0) & 0xFF); + + while (!cancelToken.IsCancellationRequested) { - while (sw.ElapsedTicks > nextTicks) + double ticksToNextFrame = nextTicks - sw.ElapsedTicks; + if (ticksToNextFrame <= 0.0) { - if (!_isClearing) + while (sw.ElapsedTicks > nextTicks) { - if (_sendQueue.TryDequeue(out packet)) + if (!_isClearing) { - ushort sequence = unchecked(_sequence++); - rtpPacket[2] = (byte)((sequence >> 8) & 0xFF); - rtpPacket[3] = (byte)((sequence >> 0) & 0xFF); - rtpPacket[4] = (byte)((timestamp >> 24) & 0xFF); - rtpPacket[5] = (byte)((timestamp >> 16) & 0xFF); - rtpPacket[6] = (byte)((timestamp >> 8) & 0xFF); - rtpPacket[7] = (byte)((timestamp >> 0) & 0xFF); - Buffer.BlockCopy(packet, 0, rtpPacket, 12, packet.Length); + if (_sendQueue.TryDequeue(out packet)) + { + ushort sequence = unchecked(_sequence++); + rtpPacket[2] = (byte)((sequence >> 8) & 0xFF); + rtpPacket[3] = (byte)((sequence >> 0) & 0xFF); + rtpPacket[4] = (byte)((timestamp >> 24) & 0xFF); + rtpPacket[5] = (byte)((timestamp >> 16) & 0xFF); + rtpPacket[6] = (byte)((timestamp >> 8) & 0xFF); + rtpPacket[7] = (byte)((timestamp >> 0) & 0xFF); + Buffer.BlockCopy(packet, 0, rtpPacket, 12, packet.Length); #if USE_THREAD - _udp.Send(rtpPacket, packet.Length + 12); + _udp.Send(rtpPacket, packet.Length + 12); #else - await _udp.SendAsync(rtpPacket, packet.Length + 12).ConfigureAwait(false); + await _udp.SendAsync(rtpPacket, packet.Length + 12).ConfigureAwait(false); #endif + } + timestamp = unchecked(timestamp + samplesPerFrame); + nextTicks += ticksPerFrame; + + //If we have less than our target data buffered, request more + int count = _sendQueue.Count; + if (count == 0) + { + _sendQueueWait.Set(); + _sendQueueEmptyWait.Set(); + } + else if (count < _targetAudioBufferLength) + _sendQueueWait.Set(); } - timestamp = unchecked(timestamp + samplesPerFrame); - nextTicks += ticksPerFrame; - - //If we have less than our target data buffered, request more - int count = _sendQueue.Count; - if (count == 0) - { - _sendQueueWait.Set(); - _sendQueueEmptyWait.Set(); - } - else if (count < _targetAudioBufferLength) - _sendQueueWait.Set(); } } - } - //Dont sleep for 1 millisecond if we need to output audio in the next 1.5 - else if (_sendQueue.Count == 0 || ticksToNextFrame >= spinLockThreshold) + //Dont sleep for 1 millisecond if we need to output audio in the next 1.5 + else if (_sendQueue.Count == 0 || ticksToNextFrame >= spinLockThreshold) #if USE_THREAD Thread.Sleep(1); #else - await Task.Delay(1).ConfigureAwait(false); + await Task.Delay(1).ConfigureAwait(false); #endif + } } - } - catch (OperationCanceledException) { } - catch (InvalidOperationException) { } //Includes ObjectDisposedException - catch (Exception ex) { DisconnectInternal(ex); } + catch (OperationCanceledException) { } + catch (InvalidOperationException) { } //Includes ObjectDisposedException #if !USE_THREAD - }).ConfigureAwait(false); + }); #endif } +#if !DNXCORE50 //Closes the UDP socket when _disconnectToken is triggered, since UDPClient doesn't allow passing a canceltoken private Task WatcherAsync() { - var cancelToken = _disconnectToken.Token; + var cancelToken = _cancelToken.Token; return cancelToken.Wait() .ContinueWith(_ => _udp.Close()); - } - + } +#endif + protected override async Task ProcessMessage(string json) { var msg = JsonConvert.DeserializeObject(json); @@ -278,7 +252,7 @@ namespace Discord { if (!_isReady) { - var payload = (msg.Payload as JToken).ToObject(); + var payload = (msg.Payload as JToken).ToObject(); _heartbeatInterval = payload.HeartbeatInterval; _ssrc = payload.SSRC; _endpoint = new IPEndPoint((await Dns.GetHostAddressesAsync(_host.Replace("wss://", "")).ConfigureAwait(false)).FirstOrDefault(), payload.Port); @@ -303,22 +277,22 @@ namespace Discord break; case 4: //SESSION_DESCRIPTION { - var payload = (msg.Payload as JToken).ToObject(); + var payload = (msg.Payload as JToken).ToObject(); _secretKey = payload.SecretKey; SendIsTalking(true); _connectWaitOnLogin.Set(); } break; default: - if (_isDebug) - RaiseOnDebugMessage(DebugMessageType.WebSocketUnknownOpCode, "Unknown Opcode: " + msg.Operation); + if (_logLevel >= LogMessageSeverity.Warning) + RaiseOnLog(LogMessageSeverity.Warning, $"Unknown Opcode: {msg.Operation}"); break; } } private void ProcessUdpMessage(UdpReceiveResult msg) { - if (msg.Buffer.Length > 0 && msg.RemoteEndPoint.Equals(_endpoint)) + if (msg.Buffer.Length > 0 && msg.RemoteEndPoint.Equals(_endpoint)) { byte[] buffer = msg.Buffer; int length = msg.Buffer.Length; @@ -327,8 +301,8 @@ namespace Discord _isReady = true; if (length != 70) { - if (_isDebug) - RaiseOnDebugMessage(DebugMessageType.VoiceInput, $"Unexpected message length. Expected >= 70, got {length}."); + if (_logLevel >= LogMessageSeverity.Warning) + RaiseOnLog(LogMessageSeverity.Warning, $"Unexpected message length. Expected 70, got {length}."); return; } @@ -337,7 +311,7 @@ namespace Discord _myIp = Encoding.ASCII.GetString(buffer, 4, 70 - 6).TrimEnd('\0'); _isReady = true; - var login2 = new VoiceWebSocketCommands.Login2(); + var login2 = new VoiceCommands.Login2(); login2.Payload.Protocol = "udp"; login2.Payload.SocketData.Address = _myIp; login2.Payload.SocketData.Mode = _mode; @@ -349,36 +323,36 @@ namespace Discord //Parse RTP Data if (length < 12) { - if (_isDebug) - RaiseOnDebugMessage(DebugMessageType.VoiceInput, $"Unexpected message length. Expected >= 12, got {length}."); + if (_logLevel >= LogMessageSeverity.Warning) + RaiseOnLog(LogMessageSeverity.Warning, $"Unexpected message length. Expected >= 12, got {length}."); return; } byte flags = buffer[0]; if (flags != 0x80) { - if (_isDebug) - RaiseOnDebugMessage(DebugMessageType.VoiceInput, $"Unexpected Flags: {flags}"); + if (_logLevel >= LogMessageSeverity.Warning) + RaiseOnLog(LogMessageSeverity.Warning, $"Unexpected Flags: {flags}"); return; } byte payloadType = buffer[1]; if (payloadType != 0x78) { - if (_isDebug) - RaiseOnDebugMessage(DebugMessageType.VoiceInput, $"Unexpected Payload Type: {flags}"); + if (_logLevel >= LogMessageSeverity.Warning) + RaiseOnLog(LogMessageSeverity.Warning, $"Unexpected Payload Type: {payloadType}"); return; } - ushort sequenceNumber = (ushort)((buffer[2] << 8) | + ushort sequenceNumber = (ushort)((buffer[2] << 8) | buffer[3] << 0); - uint timestamp = (uint)((buffer[4] << 24) | + uint timestamp = (uint)((buffer[4] << 24) | (buffer[5] << 16) | - (buffer[6] << 8) | + (buffer[6] << 8) | (buffer[7] << 0)); - uint ssrc = (uint)((buffer[8] << 24) | + uint ssrc = (uint)((buffer[8] << 24) | (buffer[9] << 16) | - (buffer[10] << 8) | + (buffer[10] << 8) | (buffer[11] << 0)); //Decrypt @@ -402,24 +376,24 @@ namespace Discord buffer = newBuffer; }*/ - if (_isDebug) - RaiseOnDebugMessage(DebugMessageType.VoiceInput, $"Received {buffer.Length - 12} bytes."); + if (_logLevel >= LogMessageSeverity.Verbose) + RaiseOnLog(LogMessageSeverity.Verbose, $"Received {buffer.Length - 12} bytes."); //TODO: Use Voice Data } } - } + } public void SendPCMFrames(byte[] data, int bytes) { - var cancelToken = _disconnectToken.Token; - if (!_isReady || cancelToken == null) + var cancelToken = _cancelToken.Token; + if (!_isReady || cancelToken == null) throw new InvalidOperationException("Not connected to a voice server."); if (bytes == 0) return; int frameSize = _encoder.FrameSize; int frames = bytes / frameSize; - int expectedBytes = frames * frameSize; + int expectedBytes = frames * frameSize; int lastFrameSize = expectedBytes - bytes; //If this only consists of a partial frame and the buffer is too small to pad the end, make a new one @@ -432,7 +406,7 @@ namespace Discord byte[] payload; //Opus encoder requires packets be queued in the same order they were generated, so all of this must still be locked. - lock (_encoder) + lock (_encoder) { for (int i = 0, pos = 0; i <= frames; i++, pos += frameSize) { @@ -465,39 +439,43 @@ namespace Discord Buffer.BlockCopy(_encodingBuffer, 0, payload, 0, encodedLength); //Wait until the queue has a spot open - _sendQueueWait.Wait(_disconnectToken.Token); + _sendQueueWait.Wait(_cancelToken.Token); _sendQueue.Enqueue(payload); if (_sendQueue.Count >= _targetAudioBufferLength) _sendQueueWait.Reset(); _sendQueueEmptyWait.Reset(); } } + + if (_logLevel >= LogMessageSeverity.Verbose) + RaiseOnLog(LogMessageSeverity.Verbose, $"Queued {bytes} bytes for voice output."); } public void ClearPCMFrames() { _isClearing = true; byte[] ignored; - while (_sendQueue.TryDequeue(out ignored)) { } + while (_sendQueue.TryDequeue(out ignored)) { } + if (_logLevel >= LogMessageSeverity.Verbose) + RaiseOnLog(LogMessageSeverity.Verbose, "Cleared the voice buffer."); _isClearing = false; } private void SendIsTalking(bool value) { - var isTalking = new VoiceWebSocketCommands.IsTalking(); + var isTalking = new VoiceCommands.IsTalking(); isTalking.Payload.IsSpeaking = value; isTalking.Payload.Delay = 0; - QueueMessage(isTalking); + QueueMessage(isTalking); } protected override object GetKeepAlive() { - return new VoiceWebSocketCommands.KeepAlive(); + return new VoiceCommands.KeepAlive(); } public void Wait() { _sendQueueEmptyWait.Wait(); - } + } } } -#endif \ No newline at end of file diff --git a/src/Discord.Net/Net/WebSockets/WebSocket.BuiltIn.cs b/src/Discord.Net/Net/WebSockets/WebSocket.BuiltIn.cs new file mode 100644 index 000000000..e5751588c --- /dev/null +++ b/src/Discord.Net/Net/WebSockets/WebSocket.BuiltIn.cs @@ -0,0 +1,153 @@ +using Discord.Helpers; +using System; +using System.Collections.Concurrent; +using System.ComponentModel; +using System.Net.WebSockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using State = System.Net.WebSockets.WebSocketState; + +namespace Discord.Net.WebSockets +{ + internal class BuiltInWebSocketEngine : IWebSocketEngine + { + private const int ReceiveChunkSize = 4096; + private const int SendChunkSize = 4096; + private const int HR_TIMEOUT = -2147012894; + + private readonly ConcurrentQueue _sendQueue; + private readonly ClientWebSocket _webSocket; + private readonly int _sendInterval; + + public event EventHandler ProcessMessage; + private void RaiseProcessMessage(string msg) + { + if (ProcessMessage != null) + ProcessMessage(this, new WebSocketMessageEventArgs(msg)); + } + + public BuiltInWebSocketEngine(int sendInterval) + { + _sendInterval = sendInterval; + _sendQueue = new ConcurrentQueue(); + _webSocket = new ClientWebSocket(); + _webSocket.Options.KeepAliveInterval = TimeSpan.Zero; + } + + public Task Connect(string host, CancellationToken cancelToken) + { + return _webSocket.ConnectAsync(new Uri(host), cancelToken); + } + + public Task Disconnect() + { + byte[] ignored; + while (_sendQueue.TryDequeue(out ignored)) { } + return TaskHelper.CompletedTask; + } + + public Task[] RunTasks(CancellationToken cancelToken) + { + return new Task[] + { + ReceiveAsync(cancelToken), + SendAsync(cancelToken) + }; + } + + private Task ReceiveAsync(CancellationToken cancelToken) + { + return Task.Run(async () => + { + var buffer = new ArraySegment(new byte[ReceiveChunkSize]); + var builder = new StringBuilder(); + + try + { + while (_webSocket.State == State.Open && !cancelToken.IsCancellationRequested) + { + WebSocketReceiveResult result = null; + do + { + if (_webSocket.State != State.Open || cancelToken.IsCancellationRequested) + return; + + try + { + result = await _webSocket.ReceiveAsync(buffer, cancelToken).ConfigureAwait(false); + } + catch (Win32Exception ex) when (ex.HResult == HR_TIMEOUT) + { + throw new Exception($"Connection timed out."); + } + + if (result.MessageType == WebSocketMessageType.Close) + throw new Exception($"Got Close Message ({result.CloseStatus?.ToString() ?? "Unexpected"}, {result.CloseStatusDescription ?? "No Reason"})"); + else + builder.Append(Encoding.UTF8.GetString(buffer.Array, buffer.Offset, result.Count)); + + } + while (result == null || !result.EndOfMessage); + +#if DEBUG + System.Diagnostics.Debug.WriteLine(">>> " + builder.ToString()); +#endif + RaiseProcessMessage(builder.ToString()); + + builder.Clear(); + } + } + catch (OperationCanceledException) { } + }); + } + private Task SendAsync(CancellationToken cancelToken) + { + return Task.Run(async () => + { + try + { + byte[] bytes; + while (_webSocket.State == State.Open && !cancelToken.IsCancellationRequested) + { + while (_sendQueue.TryDequeue(out bytes)) + QueueMessage(bytes); + await Task.Delay(_sendInterval, cancelToken).ConfigureAwait(false); + } + } + catch (OperationCanceledException) { } + }); + } + + public void QueueMessage(byte[] message) + { + _sendQueue.Enqueue(message); + } + + private async Task SendMessageInternal(byte[] message, CancellationToken cancelToken) + { + var frameCount = (int)Math.Ceiling((double)message.Length / SendChunkSize); + + int offset = 0; + for (var i = 0; i < frameCount; i++, offset += SendChunkSize) + { + bool isLast = i == (frameCount - 1); + + int count; + if (isLast) + count = message.Length - (i * SendChunkSize); + else + count = SendChunkSize; + + try + { + await _webSocket.SendAsync(new ArraySegment(message, offset, count), WebSocketMessageType.Text, isLast, cancelToken).ConfigureAwait(false); + } + catch (Win32Exception ex) when (ex.HResult == HR_TIMEOUT) + { + return; + } + } + } + } +} \ No newline at end of file diff --git a/src/Discord.Net/Net/WebSockets/WebSocket.Events.cs b/src/Discord.Net/Net/WebSockets/WebSocket.Events.cs new file mode 100644 index 000000000..dd91f81ce --- /dev/null +++ b/src/Discord.Net/Net/WebSockets/WebSocket.Events.cs @@ -0,0 +1,34 @@ +using System; + +namespace Discord.Net.WebSockets +{ + public class DisconnectedEventArgs : EventArgs + { + public readonly bool WasUnexpected; + public readonly Exception Error; + internal DisconnectedEventArgs(bool wasUnexpected, Exception error) { WasUnexpected = wasUnexpected; Error = error; } + } + + internal partial class WebSocket + { + public event EventHandler Connected; + private void RaiseConnected() + { + if (Connected != null) + Connected(this, EventArgs.Empty); + } + public event EventHandler Disconnected; + private void RaiseDisconnected(bool wasUnexpected, Exception error) + { + if (Disconnected != null) + Disconnected(this, new DisconnectedEventArgs(wasUnexpected, error)); + } + + public event EventHandler LogMessage; + internal void RaiseOnLog(LogMessageSeverity severity, string message) + { + if (LogMessage != null) + LogMessage(this, new LogMessageEventArgs(severity, LogMessageSource.Unknown, message)); + } + } +} diff --git a/src/Discord.Net/Net/WebSockets/WebSocket.cs b/src/Discord.Net/Net/WebSockets/WebSocket.cs new file mode 100644 index 000000000..451f2286e --- /dev/null +++ b/src/Discord.Net/Net/WebSockets/WebSocket.cs @@ -0,0 +1,192 @@ +using Discord.Helpers; +using Newtonsoft.Json; +using System; +using System.Runtime.ExceptionServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Discord.Net.WebSockets +{ + public enum WebSocketState : byte + { + Disconnected, + Connecting, + Connected, + Disconnecting + } + + public class WebSocketMessageEventArgs : EventArgs + { + public readonly string Message; + public WebSocketMessageEventArgs(string msg) { Message = msg; } + } + internal interface IWebSocketEngine + { + event EventHandler ProcessMessage; + + Task Connect(string host, CancellationToken cancelToken); + Task Disconnect(); + void QueueMessage(byte[] message); + Task[] RunTasks(CancellationToken cancelToken); + } + + internal abstract partial class WebSocket + { + protected readonly IWebSocketEngine _engine; + protected readonly DiscordClient _client; + protected readonly LogMessageSeverity _logLevel; + + protected int _state; + protected string _host; + protected int _loginTimeout, _heartbeatInterval; + private DateTime _lastHeartbeat; + private Task _runTask; + + protected ExceptionDispatchInfo _disconnectReason; + private bool _wasDisconnectUnexpected; + + public CancellationToken CancelToken => _cancelToken.Token; + protected CancellationTokenSource _cancelToken; + + public WebSocket(DiscordClient client) + { + _client = client; + _logLevel = client.Config.LogLevel; + _loginTimeout = client.Config.ConnectionTimeout; + _engine = new BuiltInWebSocketEngine(client.Config.WebSocketInterval); + _engine.ProcessMessage += (s, e) => + { + if (_logLevel >= LogMessageSeverity.Debug) + RaiseOnLog(LogMessageSeverity.Debug, $"In: " + e.Message); + ProcessMessage(e.Message); + }; + } + + protected virtual async Task Connect(string host) + { + if (_state != (int)WebSocketState.Disconnected) + throw new InvalidOperationException("Client is already connected or connecting to the server."); + + try + { + await Disconnect().ConfigureAwait(false); + + _state = (int)WebSocketState.Connecting; + + _cancelToken = new CancellationTokenSource(); + + await _engine.Connect(host, _cancelToken.Token).ConfigureAwait(false); + _host = host; + _lastHeartbeat = DateTime.UtcNow; + + _runTask = RunTasks(); + } + catch + { + await Disconnect().ConfigureAwait(false); + throw; + } + } + protected void CompleteConnect() + { + _state = (int)WebSocketState.Connected; + RaiseConnected(); + } + public Task Reconnect() + => Connect(_host); + + public Task Disconnect() => DisconnectInternal(new Exception("Disconnect was requested by user.")); + protected async Task DisconnectInternal(Exception ex, bool isUnexpected = true) + { + int oldState; + bool hasWriterLock; + + //If in either connecting or connected state, get a lock by being the first to switch to disconnecting + oldState = Interlocked.CompareExchange(ref _state, (int)WebSocketState.Disconnecting, (int)WebSocketState.Connecting); + if (oldState == (int)WebSocketState.Disconnected) return; //Already disconnected + hasWriterLock = oldState == (int)WebSocketState.Connecting; //Caused state change + if (!hasWriterLock) + { + oldState = Interlocked.CompareExchange(ref _state, (int)WebSocketState.Disconnecting, (int)WebSocketState.Connected); + if (oldState == (int)WebSocketState.Disconnected) return; //Already disconnected + hasWriterLock = oldState == (int)WebSocketState.Connected; //Caused state change + } + + if (hasWriterLock) + { + _wasDisconnectUnexpected = isUnexpected; + _disconnectReason = ExceptionDispatchInfo.Capture(ex); + _cancelToken.Cancel(); + } + + Task task = _runTask; + if (task != null) + await task.ConfigureAwait(false); + + if (hasWriterLock) + { + _state = (int)WebSocketState.Disconnected; + RaiseDisconnected(isUnexpected, ex); + } + } + + protected virtual async Task RunTasks() + { + Task[] tasks = Run(); + try + { + await Task.WhenAll(tasks).ConfigureAwait(false); + } + catch (Exception ex) { await DisconnectInternal(ex).ConfigureAwait(false); } + + bool wasUnexpected = _wasDisconnectUnexpected; + _wasDisconnectUnexpected = false; + + await _engine.Disconnect().ConfigureAwait(false); + await Cleanup().ConfigureAwait(false); + _runTask = null; + } + protected virtual Task[] Run() { return _engine.RunTasks(_cancelToken.Token); } + protected virtual Task Cleanup() { return TaskHelper.CompletedTask; } + + protected abstract Task ProcessMessage(string json); + protected abstract object GetKeepAlive(); + + protected void QueueMessage(object message) + { + string json = JsonConvert.SerializeObject(message); + if (_logLevel >= LogMessageSeverity.Debug) + RaiseOnLog(LogMessageSeverity.Debug, $"Out: " + json); + var bytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(message)); + _engine.QueueMessage(bytes); + } + + private Task HeartbeatAsync(CancellationToken cancelToken) + { + return Task.Run(async () => + { + try + { + while (!cancelToken.IsCancellationRequested) + { + if (_heartbeatInterval > 0) + { + QueueMessage(GetKeepAlive()); + await Task.Delay(_heartbeatInterval, cancelToken).ConfigureAwait(false); + } + else + await Task.Delay(100, cancelToken); + } + } + catch (OperationCanceledException) { } + }); + } + + internal void ThrowError() + { + if (_wasDisconnectUnexpected) + _disconnectReason.Throw(); + } + } +} diff --git a/src/Discord.Net/Net/WebSockets/WebSocketMessage.cs b/src/Discord.Net/Net/WebSockets/WebSocketMessage.cs new file mode 100644 index 000000000..bbaaf9efe --- /dev/null +++ b/src/Discord.Net/Net/WebSockets/WebSocketMessage.cs @@ -0,0 +1,31 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Discord.Net.WebSockets +{ + public class WebSocketMessage + { + [JsonProperty(PropertyName = "op")] + public int Operation; + [JsonProperty(PropertyName = "d")] + public object Payload; + [JsonProperty(PropertyName = "t", NullValueHandling = NullValueHandling.Ignore)] + public string Type; + [JsonProperty(PropertyName = "s", NullValueHandling = NullValueHandling.Ignore)] + public int? Sequence; + } + internal abstract class WebSocketMessage : WebSocketMessage + where T : new() + { + public WebSocketMessage() { Payload = new T(); } + public WebSocketMessage(int op) { Operation = op; Payload = new T(); } + public WebSocketMessage(int op, T payload) { Operation = op; Payload = payload; } + + [JsonIgnore] + public new T Payload + { + get { if (base.Payload is JToken) { base.Payload = (base.Payload as JToken).ToObject(); } return (T)base.Payload; } + set { base.Payload = value; } + } + } +} diff --git a/src/Discord.Net/lib/Opus/API.cs b/src/Discord.Net/lib/Opus/API.cs deleted file mode 100644 index afc5060ea..000000000 --- a/src/Discord.Net/lib/Opus/API.cs +++ /dev/null @@ -1,78 +0,0 @@ -using System; -using System.Runtime.InteropServices; - -namespace Discord.Opus -{ - internal unsafe class API - { - [DllImport("lib/opus", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr opus_encoder_create(int Fs, int channels, int application, out Error error); - - [DllImport("lib/opus", CallingConvention = CallingConvention.Cdecl)] - public static extern void opus_encoder_destroy(IntPtr encoder); - - [DllImport("lib/opus", CallingConvention = CallingConvention.Cdecl)] - public static extern int opus_encode(IntPtr st, byte* pcm, int frame_size, byte* data, int max_data_bytes); - - /*[DllImport("lib/opus", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr opus_decoder_create(int Fs, int channels, out Errors error); - - [DllImport("lib/opus", CallingConvention = CallingConvention.Cdecl)] - public static extern void opus_decoder_destroy(IntPtr decoder); - - [DllImport("lib/opus", CallingConvention = CallingConvention.Cdecl)] - public static extern int opus_decode(IntPtr st, byte[] data, int len, IntPtr pcm, int frame_size, int decode_fec);*/ - - [DllImport("lib/opus", CallingConvention = CallingConvention.Cdecl)] - public static extern int opus_encoder_ctl(IntPtr st, Ctl request, int value); - - [DllImport("lib/opus", CallingConvention = CallingConvention.Cdecl)] - public static extern int opus_encoder_ctl(IntPtr st, Ctl request, out int value); - } - - public enum Ctl : int - { - SetBitrateRequest = 4002, - GetBitrateRequest = 4003, - SetInbandFECRequest = 4012, - GetInbandFECRequest = 4013 - } - - /// Supported coding modes. - public enum Application : int - { - /// - /// Gives best quality at a given bitrate for voice signals. It enhances the input signal by high-pass filtering and emphasizing formants and harmonics. - /// Optionally it includes in-band forward error correction to protect against packet loss. Use this mode for typical VoIP applications. - /// Because of the enhancement, even at high bitrates the output may sound different from the input. - /// - Voip = 2048, - /// - /// Gives best quality at a given bitrate for most non-voice signals like music. - /// Use this mode for music and mixed (music/voice) content, broadcast, and applications requiring less than 15 ms of coding delay. - /// - Audio = 2049, - /// Low-delay mode that disables the speech-optimized mode in exchange for slightly reduced delay. - Restricted_LowLatency = 2051 - } - - public enum Error : int - { - /// No error. - OK = 0, - /// One or more invalid/out of range arguments. - BadArg = -1, - /// The mode struct passed is invalid. - BufferToSmall = -2, - /// An internal error was detected. - InternalError = -3, - /// The compressed data passed is corrupted. - InvalidPacket = -4, - /// Invalid/unsupported request number. - Unimplemented = -5, - /// An encoder or decoder structure is invalid or already freed. - InvalidState = -6, - /// Memory allocation has failed. - AllocFail = -7 - } -} diff --git a/src/Discord.Net/project.json b/src/Discord.Net/project.json index 20bcde093..edbc886fb 100644 --- a/src/Discord.Net/project.json +++ b/src/Discord.Net/project.json @@ -1,5 +1,5 @@ { - "version": "0.6.1-beta2", + "version": "0.7.0-beta1", "description": "An unofficial .Net API wrapper for the Discord client.", "authors": [ "RogueException" ], "tags": [ "discord", "discordapp" ], @@ -27,12 +27,14 @@ "frameworks": { "net45": { "dependencies": { - "Microsoft.Net.Http": "2.2.29" + "Microsoft.Net.Http": "2.2.29", + "RestSharp": "105.2.3" } }, "dnx451": { "dependencies": { - "Microsoft.Net.Http": "2.2.29" + "Microsoft.Net.Http": "2.2.29", + "RestSharp": "105.2.3" } }, "dnxcore50": { diff --git a/test/Discord.Net.Tests/Discord.Net.Tests.csproj b/test/Discord.Net.Tests/Discord.Net.Tests.csproj index 82dacfe9c..536d853bd 100644 --- a/test/Discord.Net.Tests/Discord.Net.Tests.csproj +++ b/test/Discord.Net.Tests/Discord.Net.Tests.csproj @@ -59,12 +59,6 @@ - - - {8d71a857-879a-4a10-859e-5ff824ed6688} - Discord.Net - -