From cc08046acd4b87b102b5ce2e7e8323582c4d5003 Mon Sep 17 00:00:00 2001 From: RogueException Date: Sun, 18 Oct 2015 02:46:23 -0300 Subject: [PATCH 001/109] 0.7.4 --- src/Discord.Net.Commands/project.json | 4 ++-- src/Discord.Net/project.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Discord.Net.Commands/project.json b/src/Discord.Net.Commands/project.json index 668ba485b..8b8d7fbef 100644 --- a/src/Discord.Net.Commands/project.json +++ b/src/Discord.Net.Commands/project.json @@ -1,5 +1,5 @@ { - "version": "0.7.3-beta4", + "version": "0.7.4", "description": "A Discord.Net extension adding basic command support.", "authors": [ "RogueException" ], "tags": [ "discord", "discordapp" ], @@ -14,7 +14,7 @@ "warningsAsErrors": true }, "dependencies": { - "Discord.Net": "0.7.3-beta4" + "Discord.Net": "0.7.4" }, "frameworks": { "net45": { }, diff --git a/src/Discord.Net/project.json b/src/Discord.Net/project.json index c5d75563c..a1f041cf4 100644 --- a/src/Discord.Net/project.json +++ b/src/Discord.Net/project.json @@ -1,5 +1,5 @@ { - "version": "0.7.3-beta4", + "version": "0.7.4", "description": "An unofficial .Net API wrapper for the Discord client.", "authors": [ "RogueException" ], "tags": [ "discord", "discordapp" ], From d0722b457c006bcd5e64cd0a51360e62fa344f30 Mon Sep 17 00:00:00 2001 From: RogueException Date: Sun, 18 Oct 2015 03:10:45 -0300 Subject: [PATCH 002/109] Renamed Mute/Deaf to ServerMute/ServerDeaf --- src/Discord.Net/API/Common.cs | 31 +++++++++++++++++++++---------- src/Discord.Net/Models/Member.cs | 26 +++++++++++++------------- 2 files changed, 34 insertions(+), 23 deletions(-) diff --git a/src/Discord.Net/API/Common.cs b/src/Discord.Net/API/Common.cs index 508bb1df4..9885b3cd5 100644 --- a/src/Discord.Net/API/Common.cs +++ b/src/Discord.Net/API/Common.cs @@ -32,10 +32,20 @@ namespace Discord.API { [JsonProperty("user_id")] public string UserId; - [JsonProperty("user")] - public UserReference User; [JsonProperty("guild_id")] public string GuildId; + + [JsonProperty("user")] + private UserReference _user; + public UserReference User + { + get { return _user; } + set + { + _user = value; + UserId = User.Id; + } + } } public class MemberInfo : MemberReference { @@ -47,9 +57,9 @@ namespace Discord.API public class ExtendedMemberInfo : MemberInfo { [JsonProperty("mute")] - public bool? IsMuted; + public bool? IsServerMuted; [JsonProperty("deaf")] - public bool? IsDeafened; + public bool? IsServerDeafened; } public class PresenceMemberInfo : MemberReference { @@ -62,20 +72,21 @@ namespace Discord.API { [JsonProperty("channel_id")] public string ChannelId; - [JsonProperty("suppress")] - public bool? IsSuppressed; [JsonProperty("session_id")] public string SessionId; + [JsonProperty("token")] + public string Token; + [JsonProperty("self_mute")] public bool? IsSelfMuted; [JsonProperty("self_deaf")] public bool? IsSelfDeafened; [JsonProperty("mute")] - public bool? IsMuted; + public bool? IsServerMuted; [JsonProperty("deaf")] - public bool? IsDeafened; - [JsonProperty("token")] - public string Token; + public bool? IsServerDeafened; + [JsonProperty("suppress")] + public bool? IsServerSuppressed; } //Channels diff --git a/src/Discord.Net/Models/Member.cs b/src/Discord.Net/Models/Member.cs index c45adfcc5..c784fbb47 100644 --- a/src/Discord.Net/Models/Member.cs +++ b/src/Discord.Net/Models/Member.cs @@ -22,11 +22,11 @@ namespace Discord /// Returns the datetime that this user joined this server. public DateTime JoinedAt { get; private set; } - public bool IsMuted { get; private set; } - public bool IsDeafened { get; private set; } public bool IsSelfMuted { get; private set; } public bool IsSelfDeafened { get; private set; } - public bool IsSuppressed { get; private set; } + public bool IsServerMuted { get; private set; } + public bool IsServerDeafened { get; private set; } + public bool IsServerSuppressed { get; private set; } public bool IsSpeaking { get; internal set; } public string SessionId { get; private set; } @@ -105,10 +105,10 @@ namespace Discord internal void Update(API.ExtendedMemberInfo model) { Update(model as API.MemberInfo); - if (model.IsDeafened != null) - IsDeafened = model.IsDeafened.Value; - if (model.IsMuted != null) - IsMuted = model.IsMuted.Value; + if (model.IsServerDeafened != null) + IsServerDeafened = model.IsServerDeafened.Value; + if (model.IsServerMuted != null) + IsServerMuted = model.IsServerMuted.Value; } internal void Update(API.PresenceMemberInfo model) { @@ -123,10 +123,10 @@ namespace Discord } internal void Update(API.VoiceMemberInfo model) { - if (model.IsDeafened != null) - IsDeafened = model.IsDeafened.Value; - if (model.IsMuted != null) - IsMuted = model.IsMuted.Value; + if (model.IsServerDeafened != null) + IsServerDeafened = model.IsServerDeafened.Value; + if (model.IsServerMuted != null) + IsServerMuted = model.IsServerMuted.Value; if (model.SessionId != null) SessionId = model.SessionId; if (model.Token != null) @@ -138,8 +138,8 @@ namespace Discord IsSelfDeafened = model.IsSelfDeafened.Value; if (model.IsSelfMuted != null) IsSelfMuted = model.IsSelfMuted.Value; - if (model.IsSuppressed != null) - IsSuppressed = model.IsSuppressed.Value; + if (model.IsServerSuppressed != null) + IsServerSuppressed = model.IsServerSuppressed.Value; } internal void UpdateActivity(DateTime? activity = null) From d7235b62c16c3265d31c8958c183f3ea4940fc55 Mon Sep 17 00:00:00 2001 From: RogueException Date: Sun, 18 Oct 2015 03:12:23 -0300 Subject: [PATCH 003/109] Removed leftovers from anonymous logins and registration. --- src/Discord.Net/API/Endpoints.cs | 3 --- src/Discord.Net/API/Responses.cs | 10 ---------- src/Discord.Net/DiscordAPIClient.cs | 10 ---------- 3 files changed, 23 deletions(-) diff --git a/src/Discord.Net/API/Endpoints.cs b/src/Discord.Net/API/Endpoints.cs index 95dedca52..706607eca 100644 --- a/src/Discord.Net/API/Endpoints.cs +++ b/src/Discord.Net/API/Endpoints.cs @@ -4,12 +4,9 @@ { public const string BaseStatusApi = "https://status.discordapp.com/api/v2/"; 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"; diff --git a/src/Discord.Net/API/Responses.cs b/src/Discord.Net/API/Responses.cs index 7936679e8..c26dc701f 100644 --- a/src/Discord.Net/API/Responses.cs +++ b/src/Discord.Net/API/Responses.cs @@ -14,16 +14,6 @@ namespace Discord.API [JsonProperty("url")] public string Url; } - public sealed class FingerprintResponse - { - [JsonProperty("fingerprint")] - public string Fingerprint; - } - public sealed class RegisterResponse - { - [JsonProperty("token")] - public string Token; - } public sealed class LoginResponse { [JsonProperty("token")] diff --git a/src/Discord.Net/DiscordAPIClient.cs b/src/Discord.Net/DiscordAPIClient.cs index ea75e0459..44c5b6dc4 100644 --- a/src/Discord.Net/DiscordAPIClient.cs +++ b/src/Discord.Net/DiscordAPIClient.cs @@ -34,16 +34,6 @@ namespace Discord //Auth public Task Gateway() => _rest.Get(Endpoints.Gateway); - public Task Fingerprint() - => _rest.Post(Endpoints.AuthFingerprint); - public async Task LoginAnonymous(string username, string fingerprint) - { - if (username == null) throw new ArgumentNullException(nameof(username)); - if (fingerprint == null) throw new ArgumentNullException(nameof(fingerprint)); - - var request = new RegisterRequest { Fingerprint = fingerprint, Username = username }; - return await _rest.Post(Endpoints.AuthRegister, request).ConfigureAwait(false); - } public async Task Login(string email, string password) { if (email == null) throw new ArgumentNullException(nameof(email)); From cfac08188418408c7dad33072cc38b2810c4a044 Mon Sep 17 00:00:00 2001 From: RogueException Date: Sun, 18 Oct 2015 08:30:19 -0300 Subject: [PATCH 004/109] Renamed UserStatus.Away to Idle --- src/Discord.Net/Enums/UserStatus.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Discord.Net/Enums/UserStatus.cs b/src/Discord.Net/Enums/UserStatus.cs index 76bf7f163..4a4a5d8c3 100644 --- a/src/Discord.Net/Enums/UserStatus.cs +++ b/src/Discord.Net/Enums/UserStatus.cs @@ -5,7 +5,7 @@ /// User is currently online and active. public const string Online = "online"; /// User is currently online but inactive. - public const string Away = "away"; + public const string Idle = "idle"; /// User is offline. public const string Offline = "offline"; } From a0b1237d3309b69accf8260d89a81661a013b721 Mon Sep 17 00:00:00 2001 From: RogueException Date: Sun, 18 Oct 2015 08:44:25 -0300 Subject: [PATCH 005/109] Starting reorganization - grouped all API serialization definitions together, moved Audio to Interop and grouped Net classes together. --- .../Properties/AssemblyInfo.cs | 4 +- src/Discord.Net.Net45/Discord.Net.csproj | 169 ++++++----- src/Discord.Net.Net45/lib/libopus.so | Bin 260748 -> 0 bytes src/Discord.Net.Net45/lib/libsodium.dll | Bin 492032 -> 0 bytes src/Discord.Net.Net45/lib/opus.dll | Bin 271872 -> 0 bytes src/Discord.Net/API/Auth.cs | 29 ++ src/Discord.Net/API/Bans.cs | 6 + src/Discord.Net/API/Channels.cs | 100 +++++++ src/Discord.Net/API/Common.cs | 318 --------------------- src/Discord.Net/API/Endpoints.cs | 5 +- src/Discord.Net/API/Invites.cs | 59 ++++ src/Discord.Net/API/Maintenance.cs | 33 +++ src/Discord.Net/API/Members.cs | 88 ++++++ src/Discord.Net/API/Messages.cs | 133 +++++++++ src/Discord.Net/API/Permissions.cs | 21 ++ src/Discord.Net/API/Presence.cs | 33 +++ src/Discord.Net/API/Requests.cs | 178 ------------ src/Discord.Net/API/Responses.cs | 106 ------- src/Discord.Net/API/RestClient.BuiltIn.cs | 67 ----- src/Discord.Net/API/Roles.cs | 87 ++++++ src/Discord.Net/API/Servers.cs | 80 ++++++ src/Discord.Net/API/Users.cs | 47 +++ src/Discord.Net/API/Voice.cs | 153 ++++++++++ src/Discord.Net/API/WebSockets.cs | 112 ++++++++ src/Discord.Net/DiscordClient.API.cs | 21 +- src/Discord.Net/DiscordClient.cs | 22 +- src/Discord.Net/DiscordSimpleClient.cs | 3 +- src/Discord.Net/Helpers/Extensions.cs | 47 +++ src/Discord.Net/{ => Helpers}/Format.cs | 0 src/Discord.Net/{ => Helpers}/Mention.cs | 0 .../Helpers/{ => Shared}/CollectionHelper.cs | 0 src/Discord.Net/{ => Helpers}/Shared/TaskHelper.cs | 0 src/Discord.Net/Helpers/TaskExtensions.cs | 56 ---- src/Discord.Net/{ => Helpers}/TimeoutException.cs | 0 src/Discord.Net/{Audio => Interop}/Opus.cs | 2 +- src/Discord.Net/{Audio => Interop}/OpusDecoder.cs | 2 +- src/Discord.Net/{Audio => Interop}/OpusEncoder.cs | 2 +- src/Discord.Net/{Audio => Interop}/Sodium.cs | 2 +- src/Discord.Net/Models/Channel.cs | 9 +- src/Discord.Net/Models/Invite.cs | 9 +- src/Discord.Net/Models/Member.cs | 13 +- src/Discord.Net/Models/Message.cs | 5 +- src/Discord.Net/Models/Role.cs | 5 +- src/Discord.Net/Models/Server.cs | 9 +- src/Discord.Net/Models/User.cs | 16 +- .../{WebSockets/Data => Net}/DataWebSocket.cs | 5 +- .../Data => Net}/DataWebSockets.Events.cs | 2 +- src/Discord.Net/{ => Net}/DiscordAPIClient.cs | 46 +-- src/Discord.Net/{API => Net}/HttpException.cs | 4 +- src/Discord.Net/{API => Net}/RestClient.Events.cs | 2 +- .../{API => Net}/RestClient.SharpRest.cs | 18 +- src/Discord.Net/{API => Net}/RestClient.cs | 24 +- .../{WebSockets/Voice => Net}/VoiceBuffer.cs | 14 +- .../Voice => Net}/VoiceWebSocket.Events.cs | 2 +- .../{WebSockets/Voice => Net}/VoiceWebSocket.cs | 13 +- .../WebSocket.BuiltIn.cs.old} | 6 +- .../{WebSockets => Net}/WebSocket.Events.cs | 2 +- .../WebSocket.WebSocketSharp.cs | 2 +- src/Discord.Net/{WebSockets => Net}/WebSocket.cs | 2 +- src/Discord.Net/WebSockets/Data/Commands.cs | 64 ----- src/Discord.Net/WebSockets/Data/Events.cs | 115 -------- src/Discord.Net/WebSockets/Voice/Commands.cs | 59 ---- src/Discord.Net/WebSockets/Voice/Events.cs | 38 --- src/Discord.Net/WebSockets/WebSocketMessage.cs | 31 -- src/Discord.Net/packages.config | 4 - 65 files changed, 1258 insertions(+), 1246 deletions(-) delete mode 100644 src/Discord.Net.Net45/lib/libopus.so delete mode 100644 src/Discord.Net.Net45/lib/libsodium.dll delete mode 100644 src/Discord.Net.Net45/lib/opus.dll create mode 100644 src/Discord.Net/API/Auth.cs create mode 100644 src/Discord.Net/API/Bans.cs create mode 100644 src/Discord.Net/API/Channels.cs delete mode 100644 src/Discord.Net/API/Common.cs create mode 100644 src/Discord.Net/API/Invites.cs create mode 100644 src/Discord.Net/API/Maintenance.cs create mode 100644 src/Discord.Net/API/Members.cs create mode 100644 src/Discord.Net/API/Messages.cs create mode 100644 src/Discord.Net/API/Permissions.cs create mode 100644 src/Discord.Net/API/Presence.cs delete mode 100644 src/Discord.Net/API/Requests.cs delete mode 100644 src/Discord.Net/API/Responses.cs delete mode 100644 src/Discord.Net/API/RestClient.BuiltIn.cs create mode 100644 src/Discord.Net/API/Roles.cs create mode 100644 src/Discord.Net/API/Servers.cs create mode 100644 src/Discord.Net/API/Users.cs create mode 100644 src/Discord.Net/API/Voice.cs create mode 100644 src/Discord.Net/API/WebSockets.cs rename src/Discord.Net/{ => Helpers}/Format.cs (100%) rename src/Discord.Net/{ => Helpers}/Mention.cs (100%) rename src/Discord.Net/Helpers/{ => Shared}/CollectionHelper.cs (100%) rename src/Discord.Net/{ => Helpers}/Shared/TaskHelper.cs (100%) delete mode 100644 src/Discord.Net/Helpers/TaskExtensions.cs rename src/Discord.Net/{ => Helpers}/TimeoutException.cs (100%) rename src/Discord.Net/{Audio => Interop}/Opus.cs (99%) rename src/Discord.Net/{Audio => Interop}/OpusDecoder.cs (99%) rename src/Discord.Net/{Audio => Interop}/OpusEncoder.cs (99%) rename src/Discord.Net/{Audio => Interop}/Sodium.cs (97%) rename src/Discord.Net/{WebSockets/Data => Net}/DataWebSocket.cs (98%) rename src/Discord.Net/{WebSockets/Data => Net}/DataWebSockets.Events.cs (94%) rename src/Discord.Net/{ => Net}/DiscordAPIClient.cs (96%) rename src/Discord.Net/{API => Net}/HttpException.cs (68%) rename src/Discord.Net/{API => Net}/RestClient.Events.cs (97%) rename src/Discord.Net/{API => Net}/RestClient.SharpRest.cs (80%) rename src/Discord.Net/{API => Net}/RestClient.cs (90%) rename src/Discord.Net/{WebSockets/Voice => Net}/VoiceBuffer.cs (92%) rename src/Discord.Net/{WebSockets/Voice => Net}/VoiceWebSocket.Events.cs (95%) rename src/Discord.Net/{WebSockets/Voice => Net}/VoiceWebSocket.cs (98%) rename src/Discord.Net/{WebSockets/WebSocket.BuiltIn.cs => Net/WebSocket.BuiltIn.cs.old} (99%) rename src/Discord.Net/{WebSockets => Net}/WebSocket.Events.cs (96%) rename src/Discord.Net/{WebSockets => Net}/WebSocket.WebSocketSharp.cs (98%) rename src/Discord.Net/{WebSockets => Net}/WebSocket.cs (99%) delete mode 100644 src/Discord.Net/WebSockets/Data/Commands.cs delete mode 100644 src/Discord.Net/WebSockets/Data/Events.cs delete mode 100644 src/Discord.Net/WebSockets/Voice/Commands.cs delete mode 100644 src/Discord.Net/WebSockets/Voice/Events.cs delete mode 100644 src/Discord.Net/WebSockets/WebSocketMessage.cs delete mode 100644 src/Discord.Net/packages.config diff --git a/src/Discord.Net.Commands.Net45/Properties/AssemblyInfo.cs b/src/Discord.Net.Commands.Net45/Properties/AssemblyInfo.cs index cb9c99e42..e242053d8 100644 --- a/src/Discord.Net.Commands.Net45/Properties/AssemblyInfo.cs +++ b/src/Discord.Net.Commands.Net45/Properties/AssemblyInfo.cs @@ -13,6 +13,6 @@ using System.Runtime.InteropServices; [assembly: ComVisible(false)] [assembly: Guid("76ea00e6-ea24-41e1-acb2-639c0313fa80")] -[assembly: AssemblyVersion("0.7.3.0")] -[assembly: AssemblyFileVersion("0.7.3.0")] +[assembly: AssemblyVersion("0.8.0.0")] +[assembly: AssemblyFileVersion("0.8.0.0")] diff --git a/src/Discord.Net.Net45/Discord.Net.csproj b/src/Discord.Net.Net45/Discord.Net.csproj index aa9491683..b9c24516b 100644 --- a/src/Discord.Net.Net45/Discord.Net.csproj +++ b/src/Discord.Net.Net45/Discord.Net.csproj @@ -67,49 +67,50 @@ - - - - - - - API\Common.cs + + API\Auth.cs + + + API\Bans.cs + + + API\Channels.cs API\Endpoints.cs - - API\HttpException.cs + + API\Invites.cs - - API\Requests.cs + + API\Maintenance.cs - - API\Responses.cs + + API\Members.cs - - API\RestClient.BuiltIn.cs + + API\Messages.cs - - API\RestClient.cs + + API\Permissions.cs - - API\RestClient.Events.cs + + API\Presence.cs - - API\RestClient.SharpRest.cs + + API\Roles.cs - - Audio\Opus.cs + + API\Servers.cs - - Audio\OpusDecoder.cs + + API\Users.cs - - Audio\OpusEncoder.cs + + API\Voice.cs - - Audio\Sodium.cs + + API\WebSockets.cs Collections\AsyncCollection.cs @@ -132,9 +133,6 @@ Collections\Users.cs - - DiscordAPIClient.cs - DiscordClient.API.cs @@ -174,26 +172,41 @@ Enums\UserStatus.cs - - Format.cs - - - Helpers\CollectionHelper.cs - Helpers\EpochTime.cs Helpers\Extensions.cs + + Helpers\Format.cs + + + Helpers\Mention.cs + Helpers\MessageCleaner.cs - - Helpers\TaskExtensions.cs + + Helpers\Shared\CollectionHelper.cs + + + Helpers\Shared\TaskHelper.cs - - Mention.cs + + Helpers\TimeoutException.cs + + + Interop\Opus.cs + + + Interop\OpusDecoder.cs + + + Interop\OpusEncoder.cs + + + Interop\Sodium.cs Models\Channel.cs @@ -222,59 +235,61 @@ Models\User.cs - - Shared\TaskHelper.cs - - - TimeoutException.cs + + Net\DataWebSocket.cs - - WebSockets\Data\Commands.cs + + Net\DataWebSockets.Events.cs - - WebSockets\Data\DataWebSocket.cs + + Net\DiscordAPIClient.cs - - WebSockets\Data\DataWebSockets.Events.cs + + Net\HttpException.cs - - WebSockets\Data\Events.cs + + Net\RestClient.cs - - WebSockets\Voice\Commands.cs + + Net\RestClient.Events.cs - - WebSockets\Voice\Events.cs + + Net\RestClient.SharpRest.cs - - WebSockets\Voice\VoiceBuffer.cs + + Net\VoiceBuffer.cs - - WebSockets\Voice\VoiceWebSocket.cs + + Net\VoiceWebSocket.cs - - WebSockets\Voice\VoiceWebSocket.Events.cs + + Net\VoiceWebSocket.Events.cs - - WebSockets\WebSocket.BuiltIn.cs + + Net\WebSocket.cs - - WebSockets\WebSocket.cs + + Net\WebSocket.Events.cs - - WebSockets\WebSocket.Events.cs - - - WebSockets\WebSocket.WebSocketSharp.cs - - - WebSockets\WebSocketMessage.cs + + Net\WebSocket.WebSocketSharp.cs + + lib\libopus.so + + + + lib\libsodium.dll + + + lib\opus.dll + + i_?VL?j4{h2-l0|A|B-1dD|j>;M0W z1SIG=@Bjbl;P?Oki=F)H|Nm>lJel{s@aq5nfxi?1005E`004~)1B+h-4~5A1|No1H z{ObSzx*vt>Q31~P>WhW^>i_@2`UJuFA1T5)mGJ8S{|`mZ>7Drh|BGJ)x!{uo0O^4E z|NprE|NsB#kD|LNNP|Nk+->C^uI|BZAe0FAsS00000S24g=Q-kafi(Dii zi*zI;h1-5TIo^Lch4y$g#{?t-0000f^XLYp|NqAXBmn>b0Ex%w_@w{;jaLJM;`|S< z)`P$UgW&uRuh@-*8Y}?-0E5I3TV7wokd3q^00000Q-kani&P{ei(Diih0=8qgZlw= zTQR_c`vP=ygTxqH!!f{v`T=wdf%^Xd004FDiQ4NMgZl`7?}gib-;G26!QcdokRa_- zVvB$z?NefjL?i@>6eQ?H|NsAk`vQKvY1`xh004u;7y;N@!;N+T$3!F~00000$3!F` z0RR91gTxqH=@G%Hs|BbXJ00000 zS5{Mtd?YA~TqGQebR-~)L?jT2d?Xx;d?X}`R3sFOOe7e`L?j>p0001qd?Xl&bR-ms zTqF=%USH{f>i_?cRaaJ1gX}DeL?lFuOe9E)Y$QO7bR^dH;)4Bv66> z{r~^~i(DjBi9{q2i$o+`i9{qA4~ISh004#d$At_500960|NnJxgZTjugbD!w0E77g z4}~rP004_bBoM~}0RR91#snk)#{&TX0071S0LDN80gE&wF~EcQ0uP_SA;ybDBp8Ww zBv6S&ButA$BoK*2BuI+`i9{qsivo*OBov86BtV12EQ#n_USD15#OeS4jdUgegZ=>z zlmsM$`3Mi5z#;@Bh(shGbXvjZ1dBu@5R11U?Nefj6eNiC(ls72t zQ(}k|FoVVcInH(Ni9{q254J=kAA|V?ixeb?1SAiRVgLXCivo>s|B1B>0RRB$QUCw{ zjdUgef&Tvh004DZi+m&yW-<1O3W@N?0{{R30EzyPjdUgehy(_MzyUePb@qvLBoOHu z|NsAsL?jT$0s#O30LBC)0LKFW0002S008O!{{R1wxc~qE|AWLVTV7vX!;N+TxcmSA z|Lg9F_KmzJ00000S5{Ml>>!JDBs7a$BuI@$|2_Ty004Iii$o+iIYcBpeiMb;bOy%* z|NsC0F~EbwAX{Ev!;3^DJOe}|I6eOW004j9i$o+ig}{E+i$o+m!RQ}^z;(-uL?k?k zL?i@@L?k$g1SIIC{Qv)n1SIG!RR92mz=_9nM~g%xM2Yc>R3tFRL?jRZ0001qL?jH0 zL?l3oL?j4{L?k?kL?i@@L?k$g1SE<0=rid5|AoMHB!$~_G>HR=_~~%}|Nmx?h1(B= zWdHyF#{>EQ|Nk+-=~@5(|A{;#G5?7~Bpm3?Q~&^rL?j&R$cgyrKL7v!S5{Ml>@bUT zBt(l`BwUR~|2_Ty004Fvh1+xn#{>WW|Nk+-gTydfUSGqIjdUgei$o+yi8LgML?i_0 zK>Yvzi3B9*XjA|Ig}{l&bY_c0Bv^@bBnXK-B#A^M9E(IGREb0+7>h(CP>Dn&6pKV8 zOo>D!5Q{`4NQp!w42wi0K#4>o1nB1c|Nn)+brOZ!bUuj#gTye2_*-6I!;N+Ti994R z|A|B-Ea(zc004_bBrNONh1(B=X8-^H#{>EQ|Nk+->0AH*|BbXJ00000iTLSF|NsAq z_KmzJ00000R#S^qBovEWBp8cyBpi!GBn*p8BoK*IBn-On2UB9lL?j&Gyi#JvL?jp( z0001qbR-msTqF=%Ug>i1|Nmx?R#S^qBovEWBp8cyBpi!GBn*p8BoK*IBn-Mh2vcIm zL?j$AzEWbxL?jpx0001qbR-msTqF=%Uga0ssJhZ-M{64*&oU zggOHN0F8D4500<^004pi$`1el4}`7)000krUSD0q zf&Zxw000k!U;_XE503=`004piqYnT84}|#u003r?507jE004pip$`B64}>}c000k; z_yYg{i$o+?JsAA||8xn1`2P=#JOcm#iG~~j001$->!N}GxDNmT4}|^!000k;_yPa` zf&aS?000k!_yPa`5032v004piybk~X4}}T@004`I7y$qP$A%~Y000000075^Bmn>b z00000$A%yQ00000004!+4}~!U004_d1cks4j^F?Q0E!T^gW~*+G!w@}Bm}$< z004;`B=h71Scp z|NsAsL?je3(ur&&9EsI~zypayBov9*iSvVGBozGX0*Ua8Oe7RJYY2@r2!0KVR3sdO z!UKci{Eajai%cXKiF_mki3B8xOe6^C(ANL|g}@JmF#Z4kgT(5TsW|BF;49E~gxi%cXK$3!FqzYhQai3B8xOe6^CVAlWtg}@Jm!2JLJi$o+WgU10e z2042K4~|p*|Nn{TIeQ3yz>7>I6pd*EgXR1I#p}6{f&aD-000k!^#1?rUSD0qxc~qE|LJD@|NnvirVjuB4}`-0|Njq;vH$=8f&Zru z004Brf&Zuv000k!sQ&-|>5BgU|BZG4500_`004pi`YHebjdUge4}^;T|Nnvi3o8Hs zbdKp{{{R1r28}$000000iSXzh;s5`Md?X0RL?i?N0001q1SIImP5=Oj@M%8a0RRB# zY~la^$3!Fq00000iF_moi3B9*s7?R?>4p6N|BZG4f&ZWn000k#3;zHAjdUge=^Ot4 z|AGI+4*&oUgb)7z|LO7l|Nnviqz?c94}|{x|NrUW{r~@g|G5tU01t%u{{R2!&;9@Z zf&a@7000k!=>Grz>AwB{|BbXJ00000f&aY^000k!(Ek7bjdUge>7xDr|BZAe0FAsS z00000>HAFp0FaQ7kdTm&kdTm&S5{MtOe7qOTqGog$#g-5+kP^OL?j@KR3sRUJcR%N z004_bBovJ_g#Z8m0ES{0r_T$Dgp92*mObJ1<37FV%Q1W>UYWA*xA7A&xPB5FN;JZD2sF?ER75Ri$o+O zjTCS(z>Tyf00000G4_MN0cMFZ0m?bpb@J;3-PqmOgTxS9USD0qjdUgeS24g=Q-kaf zi(Diii*zI;h1-5TIo^Lch4y$g#{?t-0000f^XNu||NqAXBmn>b0Ex%wIE4TIjaLJM z;`|S<)`P$UgW&uRuh@-*8Vmsd0E5I3TV7wokd3q^00000RaaJ1gX|oOOe8pqTqH1y zR3to$d?Y}PY5$9KBt(Jz{r~^~4~O#r0024H50C2r0050F7lHl%0000FkK6zN0EN;I zj?@4E0EzvRi3>5niBklLdk7D<6b6V?0}O+}0XfKZ@rfKH=(pSd z|AoL0gq#2X0LKI*0ssI2G5_d+ga7}HvkUk0wxgU10mdkB6ci$o+e$3!Fu0ssI2i8LgML?i_0ScCung}`*$gTx$L zUSD0qi)>!JD zBs7a$Bt(ry|2_Ty004Iii$o+iIYcBpejbI}4}_)w0074W|NsC0F~EbwAX{Ev!;Q2i z00000i$o+m14JY^J^ugz0DseqL?k$cz<$e%L?k@H=pTf@b-;^6Bs_^kBm|2@Bshr# zB|Nn^uB)z*}Bl z!;Q2i00000h1+!O#{>EQ|Nk+-=|TVh|LYBqkdTm&kdTm&kdTm&RaaJ1gX}!V1SA3g z004`0Bv6Z7Bvk0Nf&c%<1SA0f0044*&oUglho+0D=1g6aWAZg--zh0E=8C zSh)ZH|Nn*CcnUGVJ6;42oxu0li%cY3g~^FbBpeTf(gBp8F`{DZ~=gXa8@jdUgejRZ0Q>N(F3Ptb$G0Xf-q?~6nv6pKtG9ElNw z#5`MGUtPn2`@9bT01t)10001sY$O;l|BFThgT(`b<@|-fe-r8H0001k#{oHh1P_kg z0001q+Btp*jT|a}42wi06pdH|gX8=G)5k<41iTLb0ErYN=(mFZ|AoMG%ISju004pe z!Vdrdb#{wHBv^?=Bpi!I1cks4j*|cY0E=8C6fysU!vllk{Ob>pjdUgegU10meguAa ziRw9i2#qWnehrI6Bp8iI1B2oGjVua_L?j%?L?i^l4*&p(6eNj6Bnaq9g8%=8z;wmy z7>#xSf&0A=0049tf%~Zs0049lxcmSA|AWLlTV7vX!;N+Ti$o+?iCiQEi4-J>L?j64 z41)jvgTy>rUSD0qjdUgegTy>Bz*}BlUBivG00000i+m(lh5vMQi$o+CG400(00000 zgTMocL?jrCMg)UoBpCdKz<$YzbR|L8jX|No0kfVv-r>rsiw_v*pdl0{{Sn=m9awi@j+7|Nn`FT>t<7i-lzW z|Nn`#O#lD?iOz|gNdN!;$AxVF|Nj60004=@>mp{5i&cb!#sLA-Dc3u-VE_OB4}=2) z004#metwCCZ2$lNjYWisL?j%IMTm(+BovK>fB*mgi9{p}F~B*sT>t<7$3!F;00000 z$3!F$0000050$`)L?j4_d?W;k93<%dYXAR*zz>Lw0ssJug;f9l|A`zVi9{p_i$#Qq zL?i_0jEMjLJB4ij|Njq!aR2}Rh4v4GX#fBJjb-$>{Qv*|TV7vX!?^(f004vW0gFQf zG5>?%0f>!M|NsAsjZpvp|AWB;iH%(U|Nn!fT>t<7{Dr^|j>iE206BXI4~|^{004`X zT>t<7jSLoxm1zI}|A~!Y|NsAm(hrUR0ssJm=mCpO7&-V4mB0_Sz==d87>h*|ja7(= zOe7$QR3sdUG$e^cBovEe7>|A|B-42y+m|NsAqL?j4{ zg=GK#|A|B-1nB00|Nn`>iG_6k|No0chyjIU|Ns9fg=qi(|Aom9j(7n806BXIzW@LK z|9>>;_W%F@i-mOm|NqBCBoG1s004<}BnXK_BovE3)-|LZW2i-mOm|NqBC zBoG1s004<}BnXK_BovEi$w^Dd?W~i#{tJgBoF`q004{r~^}TV7vX!-c?$wM_s2 z|BIbS|NsAZ&O3#4|NsAg4l&8;a{d4Rxc~qE|LeH8{{R2~>#;Gw>)(yG00000S5{Mt zTqGQebR-~)d?X}`L?jT4R3sFOOe7e`L?j>q0001qd?Xx+bR-zZL?jeoDN|yJTqF=% zUSH{9`~UxtS5{MtTqGQebR-~)d?X}`L?jT4R3sFOOe7e`L?j>p0001qd?Xx+bR-zZ zL?jf@C{tpITqF=%USH`U`~Uxt>5D=D0FaQ7kdTm&kdTm&Rac46R#SuQJcY>*j`_g}@KC0uO|R0000o1E~BDg_r;U0Ev7g1c@{x=(mLb|AoMb#Se~x0001s zO9YJ+0S~W}C;rUSD0qhy%w& zBm_Vo004;;B1hA||AWLlxcvYB|65*PUBivECIA2c0FAsS00000 zxGVtx0K<)T0FAsS00000i$o*@$3O%C0002PjdUgegX|o~1SBj0008J=K>z@Sz;p-4 zKm-5)004u;9K&Xi>03bn0F87e0FAsS00000i$o*@i%cX8i&P{C$3!Fq00000>9_v> z|BbXJ00000i$o*@i$Da!kd3q^00000RaaJ1gX}DeL?lFuR3u0_&<~Ay0{{St!NKVt zgvk$uYy$uQi%cXyJ5dA=k6!}-0F8KHDZ-6hc?gYkfe4FCBvgr1BoxO)Bp3hy004!_ z4~4n`004u0BoqM;gwO#10E2ua6ao)~wE_SD4~}>P004_bBvdiUg}`(Zi$o+8G0A3u zd_(^G01uzRgTVodY$Oze{Q(b-IRgLyi$o+q55H3aW;;}P2oHq;0ssJwLj;XHYwHbz zzyfADQ2q~v>;V7(Irw$&53f`t6pcg?0y#t^P>Dn&7!QoQ0ssJuL?l2l|BFl{Ob@mI zjcEdd$ODK054J=k6o^3qi$o+?g}`)Oi$o+)D?}t1i9{qEbWe*+Buu)2fPjF2NdZ8O zNdJk*_v!)4!TJZm`5!6Dh0+g>s{#N3jYJgyjdUgegT(Dn&7={0IFpESa6ocpig}{C#iF70!G5?9iiEJbU55EL^gU11h1SIHX zWdHvG#X0wN=ZkbC9E*G;RE7T!h3^0W0Ei$o+$i%cX; zjc5UjL?l2Dwg7|R{D=XIL?jqHL?loTjUfO3|BFN>D&GP|BFl{Ko7rD z0*wqo54VBH0001HIl^>oWDvDP|BZG4 zxc~qE|LJZ1|NprD|NsB#WB&jDi$o+CjYLoZIYcB-i9{qAeB_HnButA;ButHH0gFT= zKo7P6gW&vw$N`7}>5~5c|BFNko|No0@Buwda{{R1twg3PC z0FAsS00000gX}DeL?l4RL?jph0002TL?jdd0001qL?jT4L?k?kL?jH0L?k$gL?j4{ zL?kqcL?i@@L?keY1SIIe`~Uxg#4N*&wg3PC0FAsS00000gX}DeR3tcyL?kfBL?jph z0002TL?jdd0001qR3s3KR3tQsR3r?GPz1+BBm@8e004;uB#Bfc2}0i<~560RRAtv?O2w004#CeqF}|Bme*a0EyX&@{L3! zB*#P~C;$Ke0O}0TVBJs|NsC0>+oifRaaJ1f$XdR004`eB*Xv! z0E@IFzyJUMh1(B~?EnA(i-aV|0000vge1fO000k!%m4rY50BFT004`GB+LK+0E?U? z!~g&QgXjT;z==d8EPib<|BY-UJnI^ejdUgei$o+mgU0~@#wo))d?YM%PlelldyPaS zK#4>o6pchAJBe&09LGc?7ytkO0LMfm5C8xG0Et8-497$y2mt^90Et{A1c@{x=!0MX z|AoMLuYts@00016USD0qjdUgei-aV|0000hge1fO004&uXk zRaaJ1i%cXGi+m&;i(Diai;*-T004`DI3WN4iPkB`h4ziE$N&HU4@mWa|KI@t0Dc%b z&<}(s0RRAlzyZAg0000z$N&HU4@AW|&<}(U0RRAnz`Dl(000k2!fU`Bjf4R}Apii2 z5Q!6uPy~rn1dC7ziBkxRPz;Gv42w_@iBk}ZP!x$%6pKI@x`~L0h>1fOiTL;G0s6uE z2EqRyDf$DEJRtx8gZcxBkvt&)01u9{0001sfjA)m0E6lTiPypJ0*yom0r%gH6uOC# zJRtx8iGesF002G50001f3oXb1004=BI3WN4gZ>JCV7kZv001$-+3tXJ;(q60CW%N2N0qN=f|No1D zI3WN4>1F@_|BZAe0FAsS00000Rk$oC00383Q|KZn001e(i>xF-CjbD8j3h`W004`; zBv2;+0E>mt8UO%;h0GcN00D#e6AzEx1ONbyPyvJZ1Bp~5c-urIc-TE)^iyKlR3uc{ zTqIBrkE0U+0ENJdSxCo)xEcTe0000#4~5AS007xUBv82E0RRBH@Bjb+$Ay3v008g+ z007xsBwUNMBw!~106F*%PsfQwBnXX!l|Tys0Et8-1dD_uOeX*UiF70wi?k$MCjbD6 z1SE-cBovFZBv>Z^0Eu)Y5R0@VR3`uciF70k|CA(XCjbD8g@6_S00H$8oXeR&wjf5m17ytl?y?_<~0FAsP7$*P#iF70cz5xIL0ErwVjh%!C004jY+y+05DCp=|V1V%W>s%GeFK^iyKq!QRQ<^4ad+>D|~5M+Io~Q(}okBqZ5HBqZL! z*bTh&Q)1bJfcXFa*y-6+Bq-RUK>Yvz*<2(f+3wlG*~#DQ4~J$0007zU*~Hn&-|Y{F zOalM_-oe=gsPt1}-|5}hb_&@Atn^c2+{)g|*}>S#*~8e(+vyL6fC2yj*#)rlQ)1Z4 z*umKawDePA*vr_$-pSeS*~#1KdJ+$W69WJM-Pzg9>k5sw00000-Pzg9+2EmU^iyKk z&DqM>4Y>4EV&1{t$=>qW?%(U(+7CwsX!KKJi9{qU*+e8P-oe-ny!2CI+~L_=Brw@j zBrMrPBq-kL*<2(j-33tfQ)1XeBqZG&BQ)1W+!1PmM+34KC-tpKa#Pm~Q*_^bX0RRBm$%DoL*_(X$|Nq%UBrMsR zJpBLv*<1X9{_p?*01t$g0ssKng!CX7007ws*}>Ss*@W~Y7ytm-M+Diy*x}jA-oe?+ z-sIWt*}~b$-|G*Dg8={l+3wlG*~#DQ4~K36007>>*#)TdQ)1ug-Pm>t*#)fhQ)1l8 z-pSd)*vi?#*vi}K4~L5V|Nq$qu=G=6*vZ(!*#)%pQ)1Z4*u&n*+3wlN+v(ledI}GO zvjG4A+05$#+059>*~!=qxb#zE-oo6;*~#AX+3wi|X!KKJ*#&I$Q)1c5+w9%k*}>b< z-o@F=-|F4j+tJ3#kG|AY835017D002Gb0{{RIj^GXe0NDkg^iyKl zTqIoC1*G&-V%c0IP}v1!^iyKlTqIPBm4Fri0JzWs004_wNGZ_iF8lxgEyn-=0EvY( zApijBtNZ`|*#)rlQ)1Z3*uvQbwDePA*vi<$-pSeS*~#1KdJ+$Wx&QzG-Pzg9>k5sw z00000-Pzg9*v;9=*bTV!Q)1r2+{xL=-t*b+*#&6yQ)1Z#Z1huN*~;7Q-QL;3+tJ>| z+0EbT-PzmI-P_s8-s0KG*bTh&Q)20D{r~@vjdUge+2HAm{{R2k;OTb$|Nmx?+2HBd z{r~^j!P(&1TqHE|FH|Nq_C-P+yQ*#&6yQ)1Z#Z1huN*~;7P*}~h>+0Ne0 z-|XGn+tS_I*~;GG*bTh&Q)21W{Qv*m*xlIO*x3bW^iyKl1#I+FV%hH7>)FBE)7i}4 z&EM?Z+uG9I+TP*V4ZQSIV(H-f|Nq_C-PqmO-Pzd%aP(7R+3w%!-Pzd%bo5hV+R@$F z>Am~^|BJQI8UO%`L?l>+_QyaF00000bs#ao$ApCt82|wJ0u@llgoRic008*{70`pg z0fYDwb>i9XG5^_ABruB{y+rO)Vz>eT007xkBsAGnBuLp*BskerBrG|@*;FJbjkORN z007xsBqR@xX#xNM+3vdF0RRBmz$wDn!HtZ>AQ%7u*#(I7Q(}#j)F2oD0FAUJ00000 z*#U#<2!p{0*+K}}LHyZ54B5-r$l1!+$lk)($lk%($=Jx=!Q8^xgoL00007v@-o)6z z*@T2(1pokpzya9mIoRIG-o$md-PqX$eDqUd*umWU*#&s?Q)1Y{*~*KB#2NqqjRe4r zgh#Xm007xsg#g*h*hC~O*<2(n*}&QQ+vpF9vkL$K*~{5nBuv@K*<2(**~!^lBz)OK zBz)LJBrw@rBrx3^BDfdiK-okjOm-dF1&H)hV%)>o%h^^)+05SK*~-}# z*~{6=+05$#+05AE*~*QE!2SRK*adv_Q)1rZ*~#AW*~!>MBsAGvBsAIX*}&@p*~!@e z*}&QQ+vpF9Xbb=V-Pze(Bz%LwNZCXreBQy?$=E=+Iq-GZ*bUJ1Q)1l(*z{9k-oe?x z+4|YQ+vpF9?+X9`*<2)i*+e9K*!$T9fb>&h*#+SAQ)1c5-|F4j4~ED9007DtlV*@MRcgT@5eTqG#lL?n3F$=F0BIN4kzIN6?X{r~@i{}OajiP(!Ay+rO)VmZSP zkC*-b|Jen2^iyKl?%4%=^iyKl!P&yu$=S=<%-PD>$?5$4|Nq_C+vpF9&I$kk-Pze( zBz)OKBz)fa*vQz(=^y|9|AV|FWG4Us2iXOL^iyKlTqHn#CyRt6WG4UsjY#;o0RR91 z)8L6jBzW6IBzV|8==4)!*}&e(*<2(**+e8tVDwXB$3!F~00000iL4|*CjbE1 z?%BYQjdUgejb8zaG`-;MQ(}uOo#5?LVma;)jX4Pb0J`7-001fSjfC_d7ytl`l++*? z0051Q>>wBb0NL)@0E55?gX##`LI~MF{MkYb*~>Y|*vQ$+*vQ_(*vQ_&*~!?*-of6% zb=BF*i-aUVCjbE3>e|xTgqWNe007v`+v|34*_4=^82|v;%iHVP)7{(I$=D5K^iyKn z>e|xT!rcY%^iyKk%Gt@^>D}0NE!hS5^iyKk%irpDCn@!bY$SNwL?n3FOe8?r%irtW z+S$nuMtmeBiF_m^>k8f4*~#k!-P+m7gZmY9ABo@T6#xJK-Pqa6i-aUVCjbE1l$e|u z007y_*~#mP-PqmO-Pnt)BtRzs0JszY007xUBrt=$&>8>$0^J-W4^KoSB#A^MBVDwXB-pby=*DtlV z*@M6Vnc0=h761T)!2#JsBsjt99oP*J^;2R2(1YLyDbO*}*F0Ey^{jnEnZ z0NGyz*##8!Q)1bKl#BoX0E55;*nm(P007v@*?{mF007v4Fd6^=*bNxu-PnWq1%59vz}ZA3RM`ZB*@T6Z8UO&;%h|%%%HGP}!P%UJlo|j4gTMiU z_z890-PqmO>jK@_jg%ye0001q=-GfY8UO%;!UTiB1lgES8UO&E9bBv{!%1lgPS9smH@L?kTP zgd~Un007v@*umM9B!~b20NF$&DA@%#^;2Tl$==D>1<3SMV%f{tTqJziL?nFJ4LtQz zV&3cA%GpFDFxkM^;@->I%ihA-$=Q@7ga7~l*~!=qK=o5%*<2(j*#(UBQ)1c4+vyL7 ziBrMs<*i0la*~!>UBq-j& z*<2(**+e8r-pk&}-of7a*agV+Q)1mzBna7DBuLprBskq1B2^90D=0)0001W-PuGWC^5j?$=S)? z%h}4=od6yH0N%;o$=RJCApii`1(fttVvV#W00000*@T7U8UO&;goOki004u)0X@h7 z007>>b>`STVDwXBG185NiBu$bf%^0T007%WBzO-_!q`1v^iyJqg)kui0NI5oApii`z}T!L zga7~l*#(&NQ)1ic+R@$F-pby=*_|jM007+u@bpt+-|5}hc2bLlEFk~@jZgu&^Z@_> ziBu$bf%^0T007%WBzO-_!q`1v^iyJqg)AWe0NI5kApii`z}T!Lga7~l*#(^RQ)1c4 z+v(fU-PzvC-oe?OBq0C**#(I7Q)1c4F~HyH-PjM8z&VA;8UO%v3de=a8UO$Q0001s zlq5ta004=F$Ql3ui+m&`i$o+?*+e8Xh0=+57};11#{dBU004A4i@YR6CjbD8v?N3) z0074a0RR91iN}jT1c^oji$@5FMhJ^X42ebzi$@TNMi7ff6p2O@i$@rVMi_y_EGGZ} zTV7vX!`Z;tEnxIhV%c0IEZGH+^iyKl#MtB6?%(MThLQaL|Jlje%h}1<;Mfgh^iyKl z&e_S?%h}24e*FLc-s;%}kn~ex=_UOC|A~cw761TINO09#&PUBio=Fd+Z{h1+!s z$Au^%003rYWEr(Y|JenI^iyKl!09dh z|Nq(U*~;k&_y7Oh*xAb2TqJPWTqI206eQhTBtYoB6aWC(L?m$8L?lezL?l4j$=S;3 zBJ}_NjZguD_ydVlBzW6IBzV|8VDwXB*<2)0*#%_uQ)1a%BvcQMUF`q=*+e8z*<2)C z=|1uQ|AW8*i9{rL+e9RI*gatMQ)1a%BwX1Ar1Vo_*<2)0*#%_uQ)1a%Bvk1T@&Ese zm4Fri0JzWs007xUBv9F0BwQ)b={fBG|J~Tz=?{mh{{R2o+1bF@EnxIhV%bzAEZM=? z$=S-;$>}Wn|Nq_C+vyL7!2bXL-Pzpf*}>`O`v3pk*xAY1TqG#n93<%B6953&L?kHL z$?359|Nq_C*~{5nByibWBuv>{BtYF9BvBtY59*~{q%_5c6f z*xP(0VA;#rTqJbaTqJPWTqI206eQhTBtYni6953)Y$RaWL?m?CL?m$8L?lezL?l4j z%Gt}=&DqJ>%-PB5%=7>M-Pqa8*<2)G*~!^lBy`zaByibWBtYF9B#A^MOz1)r004_b zBuv>vBw*P@By`zCByib8BtY5A+05x%^Z);ljdUgeRaaJ1gX~<3L?m>JbR>9-TqJOd zY$SY%L?kSWgd~6f004#di;N_Y0001qL?lFugd~Ij004e2J zi~s-ti9{qgi-aVU0001qL?k?mgd~^%004rsol;2Hn`iOBcr!TBG-`UNSWOS5Oey`4i$o+qf&JhC006n*0RR9G zPR)Vs-~j*tiF_m!fydwh004Jqc9E(IGFo{GY7>h(CEQv%U2Acg-Ajtc<*0E;kUjQCSxiA*FM*aeXIQ)1m5B3}%T$BrMr% zBrMxpBq-TzBrc0YBq)hQBq-ZNBq-Pol=xF(*+e81-r?Bf3 z0001sv?S;O0050Fy#N3J0NDkE_)}ty42EWqh5y+DjZ*{w007wqi1<@t*~;7M-P#X^?EnA(+3w%!4~Odj007zO z*<2(H*<2(P*}&OcBoNpQjQCSxiA*FI*aeXIQ)1m5B z3}%T$BrMr%BrMxpBq-TzBrc0YBq)hQBq-ZNBq-Pol=xF(*+e7|-r?BCFEB|BbXJ00000-Pzgb+2HBA{{R2k$%)hH2LAv5*~!`9+34Be z*<2(L*~#f0{{R2o+1cppq}|!s=-J@woY~3Q;MwTe;MrUx4B5%)2><{8*~y91>DB)K z|B#T7kdTm&kdTm&kdTm&Q-ka%p{z4hVu=JK=&u0)0E77di9{qA4}^jM0074XBnSWi z0O;Nw005z^GgM-U1SIH*0RRArL?jrYs54Yzi3B9*cmV(ai9{qEjYK3Ii9{p_jYK3E zi9{p>i$o+ai3B9*s2uw2hVuQpeTf>dCCIA2c0FAsS00000 zgX|oOL?j@I1SIG&|NsAmzz>wbgTx#Uw!rt;!;p}WgX|oOL?k4Iz;qRZ_ycqhxB&nF z0E5IF!VCbAjdUgei&P{ii9{p>i$o+KiBu#Ai3B9**a-jtxB&nF0E5IF!VCb7wg3PC z0FAsS00000Q-kangFOs@Q(^*(L?k413daQufKy@u0001k_yTkhgZKe-JA=d+xB&nF z09(Qg0J|t)RAPbqC}31#bmwM}ivxwgbOQg-gTn-Y`zT;kVs-6<#2B~%00016!VCbA zi$o+S$3!Fq0RR91i9{p_i$o+Ki3B9*L!)UrF~Gx+kdTm&kdTm&kdTm&i$o*@ z!;N$%0FAsS00000i$o*@!;p}WkdTm&kdTm&Q-kanp=eQ5V#h=z2pa$Z0F7KEB*#P~ z1OWg50LKI*WdBoQgTO$EL?jI85F7vii$o+KiCiQIi9{p>p=eQ5VuQdyi3B9*{2KrO z=>Ho40P78qkdTm&kdTm&kdTm&Rf*+SR*A+_gX~O+O~8u{957U3iB-gf$q$av0RRAt z6(BHFVlmr`LIgR+at@1n2m?n5ImQoy)BpeggTn!X!VEdfb>WFLB?2yo*)1gX|22zz>8#0RRAtMc{+@ z1P_JL0001k1spI`VgbiQBnTV;004&Tc>@i9{p_i$%nVL?i_0q#6JKjb-#(USD0qjdUgejaAHx9ZXSFV(YJvjdUgegZMyn z$%{qUjeW?A9Y|4BVu^et497$y2tWV;0Et8-1dBz?i3BA7(~CvGgX|EAL?j4{MZ}3T zB#A^M1n5Q@004_c;Dh);beeD+{{R2~6%0{SV#fp|)c;dri9{p>=(qj<|BbXJ00000G27`+ z{{R1>ATU&7i$n+ni9{p_i$@5@1SFvUQ(}okBn0S*{r~@obR-1F1SEj}Q)1|O{r~@o zbR-DJL?i?p0002T1SFvUQ)1|0{r~@vp%^e!VuiqT2g8lF00000Rf*+SR#SuQOveQn zFjQgz0002!SO@?BjRZiA1ce>|004vF@aPr_0074Y957U300000Da4ILBp-v|@S`9w zRARVXB~)T9TqRUugZKvzj+_7h0E74o4~}I4005y}B~)UCzz>Cv0001?U?o&yg}@Jm zfdBvii#=o|RARemB~)UC{||&c0RRBITqRUui$e&5_yG@v7XknPgToAg`&=bdVslE1 z6N`HUi;4gMP-2aT002;8iA}^0wo3?u`4A6;8UX+RgZUs2gqQ&U0E77m4}@0%004!*#j00000gTnzk9UL%JVttmOATU&7jWk=0JhcD-0005_ix7p{|~kZ54QP<{(;T_|NsAl$qz{Wi*>|- z?*IT$Vky^+P0RuQiA}_fRm`~o0001qbVsW>yL{KATU&70r`tT1c^i>2#Y}oi$Dm+1SFvUQ(}okBn0T4 z`v3olL?i^q1SI_bQ)1|i`v3oskdTm&kdTm&kdTm&p$LFeVv7KUz;qvj>MpjdUgeQ-kaniyc6CQ(}Yr|8yjU+jI~A6rDhL zQ(}Yb0gZM5b@9gpBv4>eV(0?-|Nn!;7+b?J+v@^}!;M1$ixh=GcvE79(sl6b#*MTl z00000iwz_&RAPn6bO!UojdUge#|0!XRAK=D008ThkdTm&Rf*+SR#SuQETJ%fQ)0(O z*Z=?k0LMk#00000Jx=C8yL1ot(4#bfQ(}!}^jltEUBit<*og!r{}mWfRAP&D*o%GK zG5&+>1pgHTQB-1y!~YcsQB-1yMb!Tl98pwaiAB(jMc|19B>xpKQB-0xb>K5~?n&x1dUY$i4_DfRAR>kATv~A2?PMZ#|0!aRAK=D005)-FjQirEHhMM zi$w^>1S9|e005)xFjQipFo08HiAC_CG=NiLiAD7P6*N&)VuS1i#{?u?090cC6+}@~ zVuS1i{}ljHRAR?OBm@Zr0Kka^B>xpWQB-1s>xpaQB-1s z>;#F!|JH@Tbqs~ubO?(_1c@9Z|Imwn2!;Q3*T)1Th%;1T{}n7zRAPhd1dV0%TV7vX z!|M%@kdTm&kdTm&kdTm&Rf*+RG4@k~>=2=ZGgM-Qz;z9cW%!BkTV7qmjdUge$3!Fq z3;+NC#{?t+0002!L=*r3g}{l!bV7?o2*(5@h%;1Ti35v842eSo{}udDRAQlkGgM-U z9e^`bVu?ctgX{#y1SE(vRAT=XEKyWqiSUE$1dV0*TV7qmzW@LK|Lc&Awg3PC0FAsS z00000Rf*+OgX|QcgfmoPi(LqXz;z8Vz>8h{$-|Ad00000#{?vZGgM;#75q?CVv7}k zGgM-O>;#3qd(RIoWg|iNcFQ2!+6P@y7%th%;1T{}n7zRAPhd1Tnyi zUHr+zi%1BGN(hMrB;&uCi$VyafHPELiO}oXjkW*) z0051@00000Rf*+;>==th41@RqbVhO&gZKk=3892DRAPm|b!4~!0002V!=Z#TRAPm| zbz-4}GgM-O_yKk1#|4BlRAK-C0074XB#1LqV*eHRP*h@r>;&t|p@cJ3VuiqT6UPOF zGgM*$0002E0RR91$-`!l#{?vZGgM;#6(CVmVuS1i>)Ytb{r~^#m5sC}00000=)3*@ z|Ld2KkdTm&kdTm&13)~1I8a3Z0CWX0z{6&cW`TGZ3jq(5zz?>-!;N+Ti$o*@W`Q_O zT66_5z{6&c>&}g|CIA2c0FAsS00000R#S^qBn*pOBoG5qJP)^W1`oGF6ovMUL;@Ie z8!^(5i%1MP*>?v6NC-L0atwpQ0fWFOIr4JoF~D11!;N+TRaaIq_EUrG9E*G;Fo`@Q z=o=CM0E74lcME2LJpceuVoh3f3xmWQiSS!qUtPnHxBvi9V(3Qy|Nn)+bmyTw08nC$ zkN^NsVh^`~6aY|S54UOtja&j4h3$0?>&Ir0gT?`a!zelKb0)_`BnSuq004=6Bm{{Z zB4%-9EskE@LOJAUBhOOgTx#$_FIYYUSD0qjdUgeQ!(3v>n002;8=%fAr|AoMGC5uEP z495f{002;8EdT&eVu?f~1n9>7|Nn)+bP|g|B=^w4=pTc`2*ZuG00000F~IBcjkW*) z0051@00000S26!qQ-kaXW`R8b08nC0T8msH7<37P#0ZJ_TV7woxBvi9V(2#g|Nn)+ zbm^fy08nC!uz&ziVuki}+KF@|1jhs<002;8=u`dw|AoMG#(~BF08nCy@^uUAx{Y=K zgX;lI00000gTx3i z|B3ipUSGqIkdTm&QBXYq5C8y;Oe73)6@lph5C8y!2>>ks5C8x@01yBGck3y^g9!jw zS;LT!W{_r(xB&nF0KyCakdTm&kdTm&xB&nF0KyCakdTm&kdTm&gX}!rL?l4jz}e-S z-{9TYiNb;607U=*f%*Ue004AA*}&Q5o8REw*!IK%buxka06+i$4}_cm004pd00jU5 z4}_Wk007zO*}&P#+v?rgdw$*6>jK@_gTy@9`om_B*}&Q5o8REw*!IK%byC>{Z~#RASv+Bp~aojdUgeb%fp7>)+kj+3xGv zjdUge-PqX$bO2Oh>&uXkkdTm&gX~P*L?lqz<(uE&B>+VL06hQ!000k!Tmb+8Jpcs& z01t$Y0000z06+i$4}?Ae007+ufB;lt*~#1K-PsR^+yDRo-35pMRAS%h4~G>2007zQ z*vU|X>Bm@t)L?i@&%nW9U1SHukB-#Ag!P)%TY$ODO#0Xf~!P_k$fKy`G!q~~) z;@QO5K*?c4fx&QfA^-pY-BctP$3!FqkN{L- z*<2(n+3ww3BoN(1Brx4vBnZa@Bm)2d0O)QD007xUBrNGq|NsAxkdTm&gX~P*L?lqz z<(uE&B>+VL06hQ!004AoJpcs&0CW`FJS6BP2LJ%wTqHQ%L?k$a#7x88TqH2)6bk?V z*#)EkRAR>fApigX-BctP$3!Fqpa4{2*<2(j+3ww3BoN(1Brx4vBnZa@Bmn>b0O;-t z007xUBq)Q#Ov8<~00000-CQIn=*J2G0Nq3+D8~RH0002pR3sSL?%iA@5ZzoP2**Su z1fT#^V#fp|0RR91=&K3<0Nq3+DCu$k|NoGX-9#h=*}zbP>Bm@t)L?i@&%nW9U z1SHukB-#AgY$ODO#0Xf0z}dib2HDBo80!Mv+1bI_<(uE&-PmR&07Yg!00ndi-s!`T zjdUge-PqmO+3v%M_KmzJ00000Q-kbi-9#jC-9#jK*}>W6o8REw*onc}z}e-S-{9TY ziNb;607U=*f%*VI000k^zz>8G0RRBm=-=uOo5K%8!-T*Og$Dru0D<8EMF0SS`Tzj{ z0Cj2E!P(`T-{9TY_QV1Yh5P^j0NDks090b#TqG#nTqHQ%TqH2)un7PF-9#iP#{eM! z007-wBpBU9Bskq%BoN(1Brx4vBnZbuBm}4cRAR>jBmn>b0O*bh007-YBq)Q#Xj{XO zjdUgef%yOd004B4f%yPH000k!!2tjOf%*Ui000k!lmGw#f%yOh000k#R0041*}>W6 zo8REw*!IK%ja(#H4}{(T007y^-CQIv-4rB^TqIcCTqG!o*yx7-|Nq(U*~#DO-Pqkk zBq-fPBrtjg4}`D)007y^+2xzx;N95v!~ze6GXnqs+2HFK-PqmO>jq|!-PqmO+34#C z-PqmP>jK@_gT!cC!`TI}090b#TqG#z|Nj5~f%yOh000k!FarPp*}>W6o8REw*!IK% zbOzbU+2Gm9+34B8*~{DO-P;d~yaNCL-Pr2_-Pqaa*~;JQ-PsR@7X$zR+2xzx;N95v z!~%5@>1+W20NvQ#*y{$}*y{q_*xByuhmEu*00000*~#5pBrx3+B;8yjD91!32><{8 z|LA4}004>D-5ez7oc;g*F~GV30002l?%(O%*bkb(-9#iX4@A(}z}e-S-{9TY_QV3+ zL?kG56WPGo<(uE&-PrcT0(1q1(sYWD*}>W6o8REw*!IK%4}~%U007y++2xzx;N95v z!~%2{*}&Q5o8REw*!IK%bqIyh4}`M-007zLo8REw*!IK%4~0qs004#34~2>V0077W1r0D<}d1pojKgr5Qc0NKIW<(uE&-PrcT0*zcGSPzAM0RRBm$=zHeFx?a+-CQIn z$3!Fu|NsC0=#K*c0EyV$93<$){Qv(kz`6kd007zU-|5}h51PQ;L?kE=M9|sgo8REw z*!IK%-9#iX4}{7A004#3bS2rq+2xzx;N95v!~%31+2xzx;N95v!~%2?>3IPF0NvQ# z*y{$}*y{q_*x3cR090b>ApZaV*~#5pBrx3+B#m4oSlwJCD2dqUNc{i*+3wlN-|5}h z-9#iP-9#iXdIk@KfdK#j*}&Q5o8REw*!IK%4~6{!007wqzyMTY*~;7M-P#X}+yMXp z*~#k;-Pqa5>j-9$*~#ky*~#5pBq-Uy-CQIv+2PwfB<{8|L9r*004>D-5ez7ko*7t+3wlN-|5}h-9#iP-9#iXdI}GOHvRwq-P!8`-PzgT z=_~#J|JljiTqH2v6eNvYBv{>CBq)j4=u`Xu|Jm-@$=~VS*xf`VDBVOPFnR_LgjD|j z|Jlje<(uE&-PrcT0uO{({r~^j;MhI1090b>-2DIl*}&Q5o8REw*!IK%4~4w_|Nn*3 z4}`S+|Nq_C-PqX$yZ}^U>9hR*|BZG4-PzgYo8REw*!IK%4}^36|Nq(U>6iTf|BbXJ z00000h0+g%`~3g^+2xzx;N95v!~ze5?fn1$+3wlk>2Li1|J_6+SltE4090b%=?{XH z{{R2k1<(LgV%f>x>D|~5f{XtD|Jljed?ZE>w?rgHv`~Uyh$=zHeINe+%FzBKL007wqumDtI#{eM!007-o zBpAm;Bm}4cRASj&Bq-fPBskq%BoN(1Brx4vBnZa@Bmn>b0O)!I007xUBq-^T`~Uyh z<(uE&-PrcT0uO~&{r~^j?&*5_|NoGXkdTm&kdTm&00000008hmkHYmRT#wQ}+3nkG zBm~*O+$8{WQ)1b`+2PzB2y;_n-{64cMRWz-+1dHS-Pqaj!;p}W+iWBR+5g|*sQ?53 z0CfSw+3VQA*~!`U-{7eL1ONba_1)Ro|HF`wW{_r(W{_r(W{_r(gX|pLL?j^Dz}e-S z-{9TY_QV0%z;!U)*xByc$=m7O*?I))E8W=JL?kHL$=Utg+1cfr-{AJd0d*hQ?%B!P z>D}3T6YB!q*@MI!!;N+T*}!$t-P!B$b?@CAB#m|e=xYN20Nn&6#{eP#004u;9K(>1 zkdTm&kdTm&kdTm&00000008hmkHYmRT#wQ}-39P-Q)1g}Bm~*z-{9H6o^l=7;@QC2 z;oKegb5mm9;DF>sbOqhn+4;lW*xB*Jdg0yK-PyyC+iWBR+2xzx;I>5|=79bMbTHY# zgX{>|d?W-9w?rfafXobLi3B9sEF{_d*=!^PgTx5j>DkHI@Y(3z!P))Y*~6&-0ssJX z1l`!#?!%CfW{_r(W{_r(Q-kbC-9#i%i(DjF+2xzx;MwT900000+vyLR!4E{ygwl0< zh1+y~B>+VL06hQ!004A-iQ0qW0X+af000k!+yDRoJpcs&01t!#0RRBm!G+t|;SY$o z0002p*@OE5bR+h|0olM0gl7Q&0NKfj*wOf?0ocfN0^G^b_;vK%*xAYJ0@=yg<(uE& z-PrcT0(2RL(sUKs;Oh~PjdUge-PqmO>jd4{-PqaagTzQ%!`;}~<(uE&_QV1Yg#-Zr z0NDlP090b#TqG#nTqH2)+yMXp-9#iP#{eM!007-wBp8WYBs|+hBs|?*BoN(1Brx4v zBnZbuBm~$1RAR>jBmn>b0O+;>007-YBq)Q#NL$0+`S;uDLI3~%jdUge-PncO4~W(P z004#3bQ0O+o8REw*!IK%bPVaC0001x-Pr2_-Pqag>0kf<|BZG4-PqaXo8RE}!~%2_ ziP*vDAB4bk3x(2j+l{m)00000+2xzx;P%7F56c z|JeoL090b#TqG#z_x}I?-Pqaa>5%^a|BZG4+3wlk>BIj2|BbXJ00000h1+!gs7kdTm&kdTm&|0Rr3RAP{j|0P^e zRAP{j|0RG?RAP{j|0QHmRAP{j|0R@BRAP{j|0R%7RAP{j|0S4FRAP{j|0SqVRAP{j z|0Re~RAP{j|0QfuRAP{j|0PsWRAP{j|0Q%$RAP{j|0RS`RAP{j|0S$ZRAP{j|0SeR zRAP{j|0Q@)RAP{j|0SGJRAP{j|0SSNRAP{j|0Q5iRAP{jW{_r(W{_r(gX}z^q%%}v z-9#iv-9#i%-9#iKBt(f+BovKQBoK)d zB>&Ls2Hn`**xlHJ#5}{0jdUgei$o*@qogxbV(DT4003r?gX}zuL?l3q5RFRzy6^y0 zVuSewcL$3UjSK=*V%01c_862**Su1ONe4Vu?f~ z454UIRAPg`K#2q-=o0_{05QOW#5}{0kdTm&|0P&aRAP{j|0R4;RAP{jRf*+;>=@{U zoc#aC1S9|zP-5tL;s5{1!;p}WkdTm&|NsC0Ab3+^00000|NsC0000000000000000 z00000000000000000000000000000000000000000000000000000000000000000 z00000000000000000000000000000000000000000000000000000000000000000 z00000000000000000000000000000000000000000000000000000000000000000 z00000000000000000000000000000000000000000000000000000000000000000 z00000000000000000000000000000000000000000000000000000000000000000 z00000000000000000000000000000000000000000000000000000000000000000 z00000000000000000000000000000000000000000000000000000000000000000 z00000000000000000000000000000000000000000000000000000000000000000 z00000|NsC0|NsC0P<{91^@s64FCWD0ssI23IG5A1poj5eI1Y{^8__N004ly004kH z0000u004kL(sZ5@00000?>YMZLN4Y%0002K0000#^8~d&004kK004kJ004kIQb`>; z00000b#7~JZ+C7WWpZ+FasX^;VsCJDb09G;F#vOQV`F7=a{y^>c42I3WFTR3XLW65 zZgc=*b!KK|av*eXZyN37a&BR4AZ2oLZ*l-*Z*p>VaCBv4AaitbWnpaq za%FLKWpi{OZf|rTX>D+9Wo>0{bY)}!X>N95Y-wa5b97;JWdLnuZEtdUAYp85Z)0I} zX>V>IW?^Y;Wn=&V00000000000000000000000000000000000T9H#?Vv$o~bCFYG zgppHXlaW(mq>)o%ypdC4%8^rI005mn06<X8000000KiN?007NB z00000000000000000000000000000000000000000RI600RI600RI5~0RRF20R8~| z0ssO30R8~|0{{X4004kL00000000000000000000000000000000000|NoqBRz^EB zEhr@+9~>GP7Zw#16A}>+5DyOy4Gjzo3=9hk3knJf2?+@a2?+=Y2nYxV2L}fS2L=WP z1_lNO1_lNO1_lNO1_cEL1qB5K1qB5K1q1{H1Ox;G1Ox;G1Ox;G1Ox;G1Ox;G1Ox*E z0|NsC0|NsC0|NsC0|NsB004kL004kL004kL004kL004kL004kL004kL0000$0000$ z0000$0000$0000$0000$0000$002Nh002Nh004kM005vs006*10000%0000%00000 z0000000000004kL0000$002Nh004kM005vs006*1007`X0000%004kN006*2000m| z001yT002lr003Y@004MG004$U0059e005ps006K;007iN000I<0s#OS7Xko&e0ObE zDH90<0ssI20000#004kL004l;004kT0000-&CE`}PAAB8JFUMb5xZxVw#Gcz+k&CJX_&CCox&CJZb z0000&006)}00000Gcz+YGc(gaDO``j^(gc|Na1=Eu1D!V004kJ004kO001aL001LH zWf|f-00000m~Ik-z{uIM-}%!002Ng004l%&CE`}NqnwgnD z0000um49Gg>O)SXjeh@ewqIHOZy<{93jhEB4FCWD4*&oF5dZ)H6951J6#xJL7XSbN82|tP8vpOVApigX zBLDyZB>(^bCjbBdDF6TfD*ylhEdT%jF8}}lF#rGnGXMYpH2?qrHvj+tIRF3vI{*Lx zJpcdzKL7v#K>z>%LjV8(MF0Q*M*si-NdN!O#lD@PXGV_Q2+n{Qvd(}RR910 zR{#J2SpWb4TL1t6T>t<8UjP6AVE_OCV*mgEWdHyGX8-^IX#fBKYXATMZ2$lOZvX%Q zaR2}Sa{vGUbpQYWcK`qYc>n+adjJ3ceEkg#Z8mhX4Qoi2wiq zivR!sjQ{`uj{pDwkpKVylK=n!l>h($mjD0&nE(I)n*aa+od5s;p8x;=p#T5?qW}N^ zr2qf`rvLx|sQ>@~s{jB1tpET3uK)l5u>b%7vj6}9wEzGBw*UYDxc~qFy8r+Hy#N3J zzW@LL!2kdN!vFvP#Q*>R#{d8T$p8QV%K!iX%>V!Z&j0`b(EtDd(*OVf)c^nh*8l(j z*#H0l+W-In-2eap-v9sr;Q#;t;{X5vPx#>i_@%?EnA(?*IS*@c;k- z^8f$<^#A|>_W%F@`2YX_`v3p{{Qv*}{{R300RaF20|5X41pxp62LS*82>}2A3jqKC z4FLcE4*>uG5di=I69E7K6#)PM7XbhO836zQ8vy_S9RUCU9{~UWAprmYBLM&aB>?~c zCjkHeDFFZgD**riEdc-kF984mF#!MoGXVeqH30wsHvs?uIRO9wI{^RyJplj!KLG#$ zK>+{&LjeE)MF9W+M*#o;NdW)=O921?O#uJ^PXPb`Q2_t|Qvm<~RRI71R{;P3Spfh5 zTLAz7T>$_9UjYCB4FCWD82|tPDF6TfJpcdzRR910aR2}SkpKVywEzGB-2eap2>}2A zIRO9wZ2^#cF^Q3L=0u>=4B z6$JnQeFXpj=>-4)Sq1F$@3z(F_0pbqxRj9S#5h$qoPjc@F>pEf4?zp%DN8VG;lUB@+Mu?Gpe1 zxfB2Zi4_0 zfgk_?g&_a{jUoU5nIixIsU!dZy(Itu)g}M{@h1QP5h(xwH7WoAT`K?pi7WsBxh((y z?JfWSB`^R0VKD#zp)vpfPEj0iDc{Tt5$u|H19XS91bvgh5(K`SDF+BhPl|BFf z{XYNzX+Z!0*+KvSO+)|y#YF%BK}P@p!AJlAMM?kw%}W3PSxo={=}rIueNX@Z6;c2I zu~Ps5QB?o{^;Q4?omc<>Nm>8^`C9-0tz7^BWnTaQAz=Uj;bH&)rDOmAZDs%fIcNX? z32FcU-D>~8F>Hz4SN6p1$_Vj0e=7h0f7Jj z1%m(p4TS&z8HWG>DTx38J&OPURgC}uagP81k&yrZwUYn<004mhf8CV;0121?06Cff z0BxKA0HvM)0O6nj03o9Q0A-~B0IjD00Qso^070G+M?0QIl{08z650I{_I02R0Z z0DZau0O`B{09n5P0L{Sw07b+A0Kvup071zB0L9Ay08P#S0NKz00BO?z0R7bf0F~GP z05RJD0MXq50CnI103G810LkS50D0&D04?hP0Ojof0HN>z06zc#0D%Ai0OtSz0C@ob z06zi%06_x)0EYws02u}V0RIO706_`>0Ot$<0QnC90Dltz0D%_(00$fZ02v_w0KX;x z00Auk0OvCR0C_tA0KY>30KrTE0Ebop02yHb0Do)%0Ks?w0Oy4O0Qr*u0RN%@00FZA z00+bX02$T*06*yf0D=7g0Ot|`0C_0^06#$i06|&-0Ect|02z=00ROB3071(E0O#rf z0Qn070Dmq50D)2h00(#i02!YG0Kdlq00Hs>0OuV80C`IT0Ka+z0KuyR0EgZK02vbm z0DnmY0KtI-0Oz{|0QvU>0RJ@w00DLd00+1Q02%xR06#(o0D+1I0O!;O0C^z?06%aC z071bA0EZL^02yis0RO=V06`oH0OxoK0QuJm0Dn0O0D+wf00#sM02yry0KeD^00Bn~ z0Oz+10C_770KcIP0Kpm#0Ed z02y)<06z;90D-3z0Ov*(0D0*Z06&iw06{nx0Egrk02!1R0RKiA073m30Oz(E0Qqwp z0Dm+b0D<@%00+Pw02zxO0KZuu00Ar@0OtWA0D0RX0Kd2*0Ku6f0Ed1g02yZ`0Dn^^ z0Kr2i0OvO-0QoN|0RJm000As300%HE02w$f06#=70D)950Ox2h0C|Bi06(5G071Vq z0Egr>02vN80RKBT06}gz0OzDR0QuiK0Dm7m0D)sY00*o-02%o|0KZB>00E&w0O$Kd z0C`(Q0KdOR0Kq6o0Ed=I02v8O0Dpi?0KxoC0Ox&A0Qmz^0RN6s00AFV00*>H02xkK z06*Mpi0tx?&0;#I10zp>E0)YqV0x84^0|}BV z0|9qV0|9w*0|}m&11api1A%Gf13?@P1gQ!*1PME11i`b91VMYf1PPez1gQ`m1;Oi1 z1pzRE1u5jV1ql!B1%bCD27%;V1__;*1}W&&1_8|y2fG;eA3;e8APKb1AgQ30A;B48A^|Ee zBPrYhBndm&B!N-0C6fjK07VV}01q4h01-0)09#i801b`+0C(L0069$o0AuC>0F|=> z03X2v03jU(0ISyq0Nnx!00*-R0LlIi05ks*0OijY056Xn05M)90Q+Dp0Ij|?0GFXa z09iLp00XgG05wBw06!*w06~_N087)X0DU3I04K@j0O=M90n=MA0fp^Z0Z%500a0bW z0hNCU>BMQX4&_; zYlFkBakUxacfTTCejOtK0C7zK0Fj&k0R1Kb0A&mU0O@xF0GXl%05#SK04;D00O8~l z03pL10PVLZ0Ckc$09i;)04e%s044Z|06oXF08u9D0I?G+0o~Gk0fqA30m)KT0=X;W z0u>aD15F521i^7X1wk%T2F;h02bGKo2|0JT3Q1R}3PT?AE}inB-xy8DfPKhFKx)?HKC?CJz+syMXjG;PPOh?SQ(C7V`&_IZ{>s4eE+=w z00F200FP?}0P&s%06!xP0Kq*K0FSmK0P*}e0RK^200H`o01tK10CB%E0Y93!0m0mD z0uRq$196U?1pj+I1_8EH2#=in3h`lB4nL=i62T?K7msB$9`S(`Cja}VF9Do7I}c(a zNpXh2SwD(`ZNXUMgaHl&0FfaH01+$`0733006|Yj0Fgt001@}y00BmG0RfU)0uceY z1Ccen1woIe2tfwU3=y)M5|Iy@8UY%1B>_waG?AWHM-iQ@U_qvlfg@2C0L7j%02jGy z0GamK0Beb$0S(0d0v`cT1tHlT2{UXk57nZi7^i0lCn^1XJbSh0TOD^DiLan7$*qoi z0Ey-q0Xddl0zDSk1hr##3E?Ql5g`LKAazKBIDJ=FV82_h0)f~k2KR(s4e9=^8-I)I zHi5V0Xcq&V!NCmw4moOtA{p+HOM!SRo-^SIQ~&?~KidyUKL7v#000000000000000 z00000000000D!vy0C)fZ02KfL02KfL005d@VqnZuVt@w#00RI30D!VnVnDJ}VgRyJV!*LeVj!whVu%3% z06@!AVxY-WV&KSAVgLXD000000000000000000000001hKc@eGKbii2KiK?#KV$lT zKRWk+KVI{HKhExdKa1&qKa}HtKk3|mKa16WKY7i6KeWVuKRdgBKM%2gKQE?#Kb)F> zKUR)^KU{%-Kd*CtKTc(TKRQ-_KXgWaKlwF(Kg=Y5KkO2JKUn~OKN9VJKN;D6KW)T* zKMt>cKmC|~KRAMZKf!8#KbTT}KfF19KQ17CKkWs6Kls8Kd6*^KVWfuKW9yRKfNe?KVbuWKULj(KbW?A zKPr!VKMZGkKRrHsKfV-uKac8qKd`@gKOvNQKjLS4Kl3_zKU58RKL+1Kk7zzKM@aiKa19O zKUkf1KX+qxKk6uUKd|j}KgqFmKQ?uBKlV3uKMVSFKXbcuKQMfBKS4ZnKd1Y1KViCa zKX7<+Kh-sJKacWqKc22~KLcuVKfWY#KhNHAKQ@?gKlD&>KL`nNKXbrvKQ(u7KTj=i zKfmH{KZuuaKd4J@KOp~ZKk>0{KN@6iKb#eAKX%4#KdN?ZKQ|_AKOWU=KVE}uKlw0h zKkeOXKRt?TKj$@TKkMLXKTC^hKL<5xKONp_KahrLKVL3sKZ@08KN5UsKldSMKRL%| zKiF$$KhzIrKQFUpKj&3uKmPY+KXjR8KS(-dKYQ9_KMs0iKLHtJKU243KL%A}KN<33 zKaGxJKVd3jKbOT}KQCZmKPdWOKZKKDKSC^EKV!sSKjvFsKh5%9KNpE!KgAwiKi9Ke zKTb$pKPlW?KWcAWKMw+2KN^%KVr68KcGfhKSkDAKTu^^Kg9D`Kc0bDKj98o zKa87KKcFX9KOM7HKLb8iKUBz7KMhhKfq&DKe_8uKOS>aKkxZcKP!MzKhyG>KjDo$Kac!7Ke=iL$KO?d^KS3TjKir8pKlk)EKagKHKdH$!KS47# zKU$nMKl21NKMid(Kd96*KgByUKV+gZKZgi1KP_%CKULE(KLI!}KQf#zKj{B1KO$o; zKjOtLKPf0JKlX_%KS}E=KO{^mKZvXKcs>hKX2C=Kg}x{Kfr$&KS$FSKV&8sKNxuyKVr)jKSCW9 zKe=ucKgqunKXDKfKeAvFKa8*uKL7v`KMhY1Ken9@Kl$qpKiN4BKUIefKW@|vKO`Xx zKXz>kKWMoMKlcO*KPFHKKlYjkKV;kKE}rNJ||A}KHSvu zKI>ZXK1|^6J_Tj&J{RfiK89@TK1}cFK5ud8KJD`6KF)LHJ__{XKB;r!KC1HIJ_&K( zKFjXiKJjbZK7r`kK67N*KFr_OK8{+}KEBk{K2%QAK32!kKFB}NKB%|OKJ+flKCh+A zKHnS4K7NtNK9B~-J{Eh$J{9l7K8Iq$K5^B+KG#RTKC`*KJ_;+mKHZzTJ~RxuJ_3BV zK2PhlJ{?-LK5xjfK0!9IK9-}eK64PRKGS@xKDy|tJ}6YFJ|w@VKBg(AKC_dgK2iOm zK73}OJ~PjsK6y8uK2x8ZKD`E;KDlj~K1$S=K6E;kJ}{t^K7<96K5=T1KK#y)J|8oV zKH-*(K0x}4J~~~AKGeU5J`W;>KJSBsK5pfLK9WX(KD17MKAI1FKFH5_K1PpqJ{eAO zK1mF3KGDf*KCFglKI=kdK7#&XK6|-dKG$SkCJ00jUP03!f0015yZ04M-A0096H03QG@00saR03-l201E&b04V@C z00ICJ03ZM_00#gT03`r401N;d04e}E00RIL03iS{00;mV044x601W^f04o4G00aON z03rY}00{sX04D%801f~h04xAI00000000000000U06YL%01p5y080RA00jUe06_p> z02BZ+08jvK015yo07d|002u%`0962U0096W06hR(01yB!089XC00sag073v@02Kf; z08s#M01E&q07n3202%-|09F8W00ICY06qX*01*H$08IdE00#gi07C#_02Tl=08#*O z01N;s07w9402=@~09OEY00RIa06zd-01^N&08RjG00;mk07L*{02cr?08;>Q01W^u z07(F602}~109XKa00aOc06+j<022T)08apI00{sm07U>}02lx^08{{S01f~w07?L8 z0385309gQc000000000000000000000000y0C)hj01p650EYm>03HBZ0G0sK04)G% z0Hpxo00jU+0DS0IC4y015y`0D=I%02u&P0FeOA z04M-t0H6Te05<@00IvY+0096!0C@nl01yC70Ehs@03QHb0G9yM04@M(0Hy%q00sa; z0Db_v02KgH0F40203-ll0Gt5W05bq@0ILA!01E&|0D}O(02%;R0FnUC04V@v0HFZg z05|}20I&e;00IC$0D1tn01*I90Eqy_03ZNd0GI&O051S*0H*-s00#g=0Dl0x02TmJ z0FD6403`rn0G$BY05kw_0IUG$01N;~0E7U*02=^T0FwaE04e}x0HOfi067440I>k= z00RI&0DAzp01^OB0Ez&{03iTf0GR;Q05AY-0H^@u00;m?0Du6z02csL0FMC6044xp z0GV0agIc07e160dxTD z0AvB&0RjMe0P+B80U!XF01yF)0X_h>04@Qh0bBso089bI0TBR+009Ac0WScj03QLD z0ZjnK06hV<0c!x`09ygm0S5qo0QUfI0VM#P02Kj^0Yd=005bur0bu~y08s(S0Tuv` z00sem0W<)t03-pN0a5_U073z}0dN550AK;w0So|y0Q>-S0V)8Z02%?30Z0JA05}1# z0cHT+09FCc0UH3501E+w0XYD%04V{X0apOe07n780d)ZF0A&H)0RsSg0P_HA0U-dH z01*L+0Y3n@051Wj0bKyq08IhK0TKX;00IGe0Wbil03ZRF0ZstM06qb>0c-%|09*mo z0SEwq0QdlK0VV*R02Tp`0Ym`205k!t0b&5!08#N+0R#Yi0Q3NC0U`jJ01^R;0YCt_05Acl0bT&s z08RnM0TTd=00RMg0Wkon03iXH0Z#zO06zh@0c`-~09^sq0SN$s0QmrM0Ve>T02cv| z0Yw1405t)v0b>B$08;_W0T=*~00;qq0X6`x044#R0aO6Y07L=20dfH90Ac~!0S*9$ z0R8}W0W1Kd02~370ZIVE06GD(0cZf=09XOg0UZF901W|!0XqP*04o8b0a*ai07(JC z0e1lJ0A~T;0RRAiKL7v#fMfc9KfiTWyo>37KMHZyym`%kKj+RMy)UMJKRRAky{~hB zKNIqWz4!z5SSeKLiujy}UVoKXbS6y;aYBKd7h$zROvCKfCV| zz9rdwKU^>%zGqE*KlN!XzL>UqKl7V9zP=QDKf=XFzT#(kKh5S;z6r2-KZ5>YzC7)C zKTQm8zJwomKb#MIzUoGIKkkEszIS7GKR)`3zBYArKXq!6zA$`rKTOn@zHxYSKYBNw zz5{ACbKgmDPz7l+BKayJ3zSIwAKl5wczI2&oKdN)$z5y9zKXGyBzKxDzKL=&* zz9{-&KPOJ~zUEtBKaec?zSpx|KL7v#zYhXjKO5u%zo151KX<|fzn+0uKlYjkza6ty zKX+{lzcJfXKiWACzwh}{KL7v`zibatKgz!ozr7nzKNxuyzxWm7KfOLazvId%Ka+SszdAxDKliCZzechnKhe@eza$$WKMM0kzwvw?KZpuRzslDe zKQtLjzs)NdKlmO^zZiKIKfN1IzskQ8KWqzjtj5KOM7H zzxJ94Kc0bDzjwj~KcGfhzZ>KNKMw+2zW@LLKi9KezmP2YKIU6rzb8)gJ}CNNzXxUR zK8=oIzj1NsJ^>kIzp8WMK6II7zw>L{KGY9qzmi(kJ`#LrzsWz)J|5m_zup_lKIb)S zzZLJpK3;=tzqGl%K6b`!zX5!=J|X{Zzd<&!K2I%hzr5(GKJ-v=zqga5J_BlUzj`;G zK5=++zf9DZJ}`WAzjbPnJ~nlAzdrhkK6hhwzwU#CKI%qyznl+zK7=25zfBBpK0NJs zzk>c@J_)dSzs=@UKH_J3zrw{wKE4!tzw?_qKA5(9zx8P?K4(pQzg#dNJ|)?Fzq{`e zKFe8szo@7MK2^_szjL?nJ-j)7zXTK3J^h$|zcXdKJreDHzbdbwJ^3|%zZ3F>J+E_r zzdBx4Jujwzzvs>%J$cQ4zY1~IJd5dnzrS@>JY)KQzW^|eBme+_zrS@>ykq)*zY1~I zyo>37zvs>%y?M=lzdBx4y)UMJzZ3F>y{~hBzbdbwz4Uqzrw{w zzP=QDzs=@UzT#(kzk>c@z6r2-zfBBpzC7)Cznl+zzJwomzwU#CzUoGIzdrhkzIS7G zzjbPnzBYArzf9DZzA$`rzj`;GzHxYSzqga5z5{ACbzmi(kz7l+Bzw>L{zSIwA zzp8WMzI2&ozj1Nsz5y9zzXxURzKxDzzb8)gz9{-&zmP2YzUEtBzW@LLzt^)}zZ>KN zzYhXjzjwj~zo151zxJ94zn+0uzjtj5za6tyzuGwtzcJfXzW@Lczwh}{zskQ8zibat zzZiKIzr7nzzs)NdzxWm7zr8*_zvIO%zmNPozX3Qfzap|Zzh$B_zmQ)yzw-n&zw-n& zzmQ)yzh$B_zap|ZzX3QfzmNPozvIO%zr8*_za>m7zms@CzvId%zxSy^zdAxDztPe} zzechnzY6n4za$$WzlaJ+zwvw?zcd+3zslDezxWKNzt^)} zzW@LLzvf$CzmP2Yz9{-&zb8)gzKxDzzXxURz5y9zzj1NszI2&ozp8WMzSIwAzw>L{ zz7l+Bzmi(kz8>CbzsWz)zUMV-zup_lzFvcDzZLJpzIMiKzqGl%z9Ii^zX5!=zE3T1 zzd<&!zVuLWzr5(Gz5{Ac@zT#(kzs=@UzP=QDzrw{wzL>Uq zzw?_qzGqE*zx8P?z9rdwzg#dNzROvCzq{`ezE#hCzo@7MzPvepzjL?nz5SSezXTK3 zy%OzyzcXdKz4y)UMJzdBx4y?M=lzvs>%y^HC8zY1~Iykq)* zzrS@>yZ``zzW^`|rDOVkzrS@>Jd5dnzY1~IJbBH3zvs>%JujwzzdBx4J+E_rzZ3F> zJ^3|%zbdbwJreDHzcXdKJ^h$|zXTK3J-j)7zjL?nJyp+rzo@7MKFe8szq{`eJ|)?F zzg#dNK4(pQzx8P?KA5(9zw?_qKE4!tzrw{wKH_J3zs=@UJ_)dSzk>c@K0NJszfBBp zK7=25znl+zKI%qyzwU#CK6hhwzdrhkJ~nlAzjbPnJ}`WAzf9DZK5=++zj`;GJ_BlU zzqga5KJ-v=zr5(GK2I%hzd<&!J|X{ZzX5!=K6b`!zqGl%K3;=tzZLJpKIb)Szup_l zJ|5m_zsWz)J`#Lrzmi(kKGY9qzw>L{K6II7zp8WMJ^>kIzj1NsK8=oIzXxURJ}CNN zzb8)gKIU6rzmP2YKG(BdzW@LLKMw+2zZ>KNKcGfhzjwj~Kc0bDzxJ94KOM7Hzjtj5 zKQY@>zuGwtKkxZczW@LcKWqm7Kac!7zvIO% zKO(X@zX3QfKagKHzh$B_Kl21Nzw-n&KV_mazmQ)yKLI!}zap|ZKjXzMzmNPoKP5~n zzr8*_KjX?Mzms@CKRQAtzxSy^KSr`6ztPe}KO`F=zY6n4KkCbKi(V4zUMV-KNau8 zzFvcDKeV~MzIMiKKLLEVz9Ii^KS4IJzE3T1KfLIwzVuLWKev;lz5{AY zz6r2-Kh5S;zT#(kKf=XFzP=QDKl7V9zL>UqKlN!XzGqE*KU^>%z9rdwKfCV|zROvC zKd7h$zE#hCKXbS6y}UVoKLiujz5SSeKQm>!y%OzyKPs=Fz437KfiTWykq)*KLE&6r2qhcKfiTWJY)KQKMHZyJd5dn zKj+RMJ$cQ4KRRAkJujwzKNIqWJ+E_rKPs=FJ^3|%KQm>!JreDHKLiujJ^h$|KXbS6 zJ-j)7Kd7h$K2^_sKfCV|KFe8sKU^>%J|)?FKlN!XK4(pQKl7V9KA5(9Kf=XFKE4!t zKh5S;KH_J3KZ5>YJ_)dSKTQm8K0NJsKb#MIK7=25KkkEsKI%qyKR)`3K6hhwKXq!6 zJ~nlAKTOn@J}`WAKYBNwK5=++Kev;lJ_BlUKfLIwKJ-v=KS4IJK2I%hKLLEVJ|X{Z zKeV~MK6b`!KNau8K3;=tKi(V4KIb)SKgmDPJ|5m_KayJ3J`#LrKl5wcKGY9qKdN)$ zK6II7KXGyBJ^>kIKL=&*K8=oIKPOJ~J}CNNKaec?KIU6rKL7v#Ki9KeKO5u%KMw+2 zKX<|fKcGfhKlYjkKc0bDKX+{lKOM7HKiWACKQY@>KL7v`KkxZcKgz!oKWqKiWACKOM7HKX+{lKc0bDKlYjkKcGfhKX<|fKMw+2KO5u%Ki9KeKL7v# zKjvFsKaec?J}CNNKPOJ~K8=oIKL=&*J^>kIKXGyBK6II7KdN)$KGY9qKl5wcJ`#Lr zKayJ3J|5m_KgmDPKIb)SKi(V4K3;=tKNau8K6b`!KeV~MJ|X{ZKLLEVK2I%hKS4IJ zKJ-v=KfLIwJ_BlUKev;lK5=++KYBNwJ}`WAKTOn@J~nlAKXq!6K6hhwKR)`3KI%qy zKkkEsK7=25Kb#MIK0NJsKTQm8J_)dSKZ5>YKH_J3Kh5S;KE4!tKf=XFKA5(9Kl7V9 zK4(pQKlN!XJ|)?FKU^>%KFe8sKfCV|K2^_sKd7h$KD;@8KXbS6J^h$|KLiujJreDH zKQm>!J^3|%KPs=FJ+E_rKNIqWJujwzKRRAkJ$cQ4Kj+RMJ&WmoKMHZyJY)KQKfiTW zJmBEq;Nalk;Gm$Opt-rZva+a#Jtg4a;Nalk;NbA^@bJ&i&&I~LkU}V+prD|jprD|+ zxw*N)!NIq;tcpM;@bK{P@bK{P&(F`#%*@Qe!L*M;D7m@0xw*NyxxvA~!NI}7x3{c{ zK_}18&(F`#&(F-v%*@Hj$-KOU3GyRVEwC;$Ke000000000000000000000000004N6s2L}fS2L}fS2L}fS z2L}fS2L}fS2L}fS2L}fS2L}fS2L}fS2L~t*7aSiVBqk>*Dl054E-x=JGBY$aHa9mp zIy*c)K0iM}LPJDFMn^{|6d@@vH90*%LPbYOOH58sQdL)3TU=gXVr6G(Yiw?Ca&>ok zdwhO?C>JL)JVQxOQ&(GFVPs}$Yj1OQd3=6>gNKWbk(8F1o1demsjRNCvm+caK~7m? zYjSvggourim6@NVtFW}Wy}-xK)7aeM<>>GA`xPNNPhoI+|~& zBR^JZdyAN*vAf02*52mr`WYWLOIvAmfQpiup{TC3xxUEI*WTsp@b&)?BtcnjfRLZ9 zxx~-e;_CAH3n@xxfS0Vl(c$s`2`f)-ile&EHq)$00000000000000000000000000RR90|NsC0|NsC0|NsC0 z{{R300000004V?|04V@c08#*Z0HgrO0Nwxq0000000000000000000f04V?|04V@_ z0DAy?0Hgq<0Pp|`0UH4)0Vx0}04V?|04V?|04V?|0DAy?0DAy?0Pq0t0Pp|`0SW;z z0X_jp0Z;*Z0DAy?0DAy?0DAy?0DA!N0Pq0t0Pp}Y0WkqF0X_jf0apQE0cHVg0q_9u z0Pq0t0Pq0t0Pq0t05JhE0WkqF0apQ60apQE0bc=f0eAs^0fPYm000000000000000 z0000000000000000000000;mG00;mG01yBW01yBb02Kfj0384#03-ka0000000000 z0000000000000000BRhKI8wVFItXdpI)d$2JBeemI|C{OJTTljJhVP{JRGB*Jkj#S zJpP99Je#W!Jq4{QJ%qwWJyYgnJrRk5J%^u@J7K9nC+KEFp|K90t4KCqX6K2lqUK41@zKA7c}K6%@o zK5*`)KG++tKKo#|J`uaWK1L_VKDVOKK6onGK9sxRJ`7XpKHlx}J_d>VKE4nFKOb5K zKO~yKbeC% zKQj|PKgEneKX>m$KYuz%KPPQVKhB0uKjnr|Kk;l-KaMz8KWXV%KSh6BKl=GzKXX}O zKd+r*Kf%UjKhM->KM}}jKhmIUKW<-bKTrg3KlG1qKjs2*KfPUZKOdoVKfld&KUd^- zKd|L@KbO*YKjW);KXY<=KPM-9Kj+GOKb>BDKQ-fgKb%>8KfS_zKZYNEKi+VDKdZNX zKhN}jKPD-EKZaF*KfZc@KbDz)Kg_a!KNQD*KOEP8KZ4_bKi%wqKeqFKKZ^K&Kgj!k zKi2(!KL`JRKc)YFKj{B|KmGrIKL7xLKL7v#00000000000000000000003H0OGQ68 zF(@J(6cP#m000000001PWLi{CM?pF>Ehiuv6bt|W00000c5Y`~R!~buK|3`sC?6LO z1ONa40DgCHXkS@VPDnyLHZLb284(Nt004%6cyec9TUAa-LOeDvDI*;h5DEd1iGqA_ zYh_n30Nvdv$MVVq8{GN<%$2F)Jh#0iv6fj)i}Db8Kc{T2xI( zK|3`r9s#VSo|un`gMD^zYGhtlQAmfIxsgfIxsifLu;oPh3t~Pjo4BC~_!-8iX2= z5upsQ3b6|qvoMM_hc<*YhB$>cgg1meazN>@i(NnJ`^N^K}|By}QnB99S~5|R_545bRP2fYTq2(bwlvNm%+W^ zTS;3^SyoV7LR~;+J7+v-JasgCEQ%wAA7LgQ(HnS zD3&3N9bzL*Dk^zkLToEz?iKmY&$J^%oKIR1hwWk(hFKL7v#000000000000000000OL5EB&_7#bTK z9UdPbAR!_mBP1jxB@X}q015yA00jU500000Kidzn+a0000000000000000Q&Xn%SSRQCL7leO*e|~>@g@T4NbdD6XzB>Z8Yepg_yPb;3kmoZEcyp1 z2^s?l`4naO1Oxag8UXPjL;@H7H!l7U1OF8eDF6Tf00000000000000000agY2L%8C z0ssIE94QU__YW!=4gvl?DfjjzLHq;y1VRV+5Dq5&ArRG5VgS`sV&K$MVo=pnVnEeX zVj$I1VuOH&jg5_wk&%|0o12@UprD|prmd~Ez_!4^z|FwT;6+_?c7uwIik6m}mYbla zpbvwJikq9Qtq?E(000000P*3|$GfkjnT~{ea%o^#P)I;IGAt$`9T*f43<(7S0LP_} zd~IM|GCF|NsC0|H4xlN8SJb|No*% zB0{47|Nqi{Nh>+6|NsBVeMc}lg8%>jrfx|&K4{Ya|NDDEH$-Hs{{R300000000000 z0000000000000000RR5|?hXS20RaF0{MsV}0s#R3{`$~61Ofp7|Ni#EM+yP~0RQ~# zw^9fK0RaE}>$F-C0s#R3_~5Hy9s~gZ|NiW)UnK_t0000000000000000000000000 z0000001yBG003h`BqSmvA|fIpQ$-{+B6Ma(L`1M=L_{J%RYX)Ibd8vcs)W0^*olPs zsJV-1W@JP}vf8=1s`S{qyQ=iNyW5Iqo4C1&bhn$Bh=kd=xT>VttDB4TskphSq}bou ziq?<3+Nku6h?}aYirm|aq}$5aiKx2vxr=nU+qsHlWQJsBB1A-FM5xQ%+Nh_Rh-gNn z_N$4DiJRKni`24|foxz^Qcq1iJ2^NrF)%GEDJUi+A|D|aQ9W6m>e3q-k;2tqcVR??Fy3yqm88k*+bdRRM+ZrN1OJR1Ar^4A8Avi`` zagU|M-xVS=NMUu6tHj0bCAvHi#b(pQp;Ts}BUu=P!uF~NY8c1VtgPO3&+afDHPF-@FvCZWd9X4Tnilej9 ziAO6t zi~s-s|ARs@LTkeR|E6?IH#L3#{{R3000000000000000000000000000RR5|^b7+6 z0RaF0{NN@70s#R3{`=861Ofp7|NivDMFau?0RQ{wxKsxM0RaF0@VZtG0s#R3{_nC| z9t8ma|M=svWD^1b00000000000000000000000000000000000000000c2)oL_{Pa zV5DoFxwN^9Wore;Y^XCwds000000Wbgn00000AVi0IcXww+Vnk<<+pGd)gRYJP~Ar@77H@Cp?qGCxmWZhnoO zt-a0N?GY3uGdoUMYkP*4q`1xC@CpwgGec2eaDk4Vt-Z|L><|+mBsfo7Y<7s6tGdkG?F$?$I!ak-c!!pyw8Ycm@dgkf zE<8(IYkP^Dskz72;R_QUEICS9X?cg3sk+MA>I@SdE;&b7WO#&>rMAb|=@AzrE;vnG zYI}y5r@74D?G6zwH9t#PYjcE`tGv$L@CgtdC^$!0XLpI3tG&(M?GO_rFgZ+DX?BE` zr@6}7>kbnnEj>(IYX(_XL5s;r@71A?GY9uDmqSGZhecJs=UtN@CXkgGDA~ibAytivcb{d?+p+sLP=I% zZ-9xGqqolC@evs%G(SsRW_N^;ps&Bw=Mx_>K~rRPg_EJKyU5k!@)933L|A8aeu|Z& zvB1yY?+_g?Jxp3^cZHLou))#b@DCLuGCfIFVQzPUjjzVq?MCx19xLNZ4j*QK7#u7e z3ff^v678Jy2JA2s3*(Tb1ns090t(-20{{R30000000000000000000000000019XF z4pzXW5849P2O_po1={+g z3L>^r1!O1?2Lk~B;J~1OU_c-M)vJJ2D*(-!W-|c5fItANRR93ZJOD5NAY=gW03ZNl z0Q23zwE*S-=$;AyfIt9v0PEdTV&vUZV%Ni_eQr_!%a#B@0QvX!_4Mk}%E`#Zu2MTV zH#RO73=0Yg2LJ#7000000000000000000000000009?wx*tXxA;+*Dq?0WRf{4@iG z3y2eB8D0N^eF0Hino07OUt0JL8K0IZ4t z0Dw3F0000006-rA0JtOo0PHLP0K7B+09-@>0H|RA0Dw3F0C7J6rFT$0FC!Zi4haMR zys(>!cVR;-8VUda000000N=^Cr+aMm{()EGHu$8yFQ55Dp9q2nGZL0s#Qh z(9X-p!MCW6Xi79C8x#%-1_S~C00000000000R8&(>D0ECgLrOYRY*KBC?FXV4-E?m z2nPlQ1OoyA0RAyUO;TNB#tRY{A0#dRE<#RMUTDw`6(A}zLjPW9ZE$mT_%%gPSYKuU z00IRG4ipIsoT_8UXaTObh_|)`BYr0Q~5!R~Q2T{qf6# zI1T~;{`TFcULyzr0RH*t!Gb{-1pxs6`|s1DV=WH^0RaE|^WVCIN+Ama0RaE}_2$Z+ zX*n1O0s#R3{rBtEvxHPE69)nT0RR5_@ZZFra782!1_A*9|Ni~;=h3i;T{jyG0|Eg6 z|Ni~_@7=_2LS;6_UFr=X*d@K0RaE_>(#S`RWB0-0RaF0 z^WDOqbw(u&0s#R3{`l(9tAJH776<|X0RR5_@Z7zXYeOUu1_A*9|Ni^)eaWCZAmK@3IqZH00000000000D%C{G63v>6ae+PND2V?)qyGo0Q%yo zR~rNX`{}}hJ`@B1`s~l3V=oZ{0RQmWwu4bA3jqNC{^`k|YdsVK0RaE`>CUQvRx%d{ z0RaE{?%BGOY(^&%1_1#7{PW_-rhr;F9t{Km0RQ~;=h3v1ZcHy33j_fG|NZ#w+rp!O zT|Olf2m=8C|Ni>%;?1#`tH-8QX>xq0Q%?4sDM~C83q9S?%27MY)2>(1OWZ>;m4+c zS~(vL0|5T^>D0HAZAmN_3IYKC`taYzrhr;HArJ-u0RQ{+=hC&3Zc8p137TYX(jdvkm7zOsZ@ zN+}Ki-@%=XYB?to0000003SJPpv}$y|NsC0|NsC0|Ns9*FGFvp&Hw-Z|NsC0|NsC0 z|58d%Uu}Sqpsmf#&EWt4;Q!!#N;^eijkf>)|NsC0|NsC0|E+UXNlaXGk*&?(;Q#;s z|NsA{hH++PW@~nRijkY9w!py6mf*IAWm8X8VR(_P&EWt4|NsC0;J}u6Wn5WZW_E_4 zw!q-v;Q!#@wws1_ZD?j{Z+3%`psj-d00000000000000000000000000000000000 z0DUtW5)TXu3JC}Y1_cBI0s#QVX)P8H3=0Yg2nPlQ1OoyA0M(*&Q#&gl7!nT*2?quR z0|Ef=yL3Ao5DN+k2nPlQ1OoyA0Q%*;hgLHl6AcOo1_cBI0s#Q|>(#lOfM-)eH7X$* z6%h>z0RH*w&ZKrtE*lUE2L%KJ0s#R3{rT_QznphfIVBel3I+rM0RR2__U+%xvzUBh zOE)MT6%PRa{{8pdqH04b91;u(1_T2F00;^U5C8xG0000005Ej`0B{!o03gx-{~*!- z{{R30000000000000000001EVANC*WAJ`wlAE+OQA7URm9||AX9-tm#9wHvx9grPD z9qt^K96B7>8*&>88=M*{8nhWY8N3)g7`7KK7oZjp7Je1z6iyVd6A%+_637uA5p)p7 z4;Bw(4z3OO3_uKo3&;uz3Q7rz2*w8j2R;UP1+oO{10n-j0+IpB0000s|8D-K{oec* z{7(Cb`oj7C_&WG@_qO)-^*Hr>^uY57^HTDh@#^q8@Qm-`?l|s}?eOeS?6T_^>w@a= z>0s&7=t}6s=S1hm=1k_+Sc^2><{9000000000000000 z000000000000092|Nj600RaF30096100001000000096100961000300s#O40RRC1 z|NRI42LJ#8{{jC3{sR63{Qv;~0sjL30{#R30{{U40003100000000000000000000 z00aO6000091P25F0}KZT1^@#92Lk{B3IGBI5(*3e0KEXH{b>SccXa?^|I+&ibTk0n z0I2qKZv6id0_yVRX3+ok0*vsbU9kU&19k5PQ=R|J1A^~nN0tA_1B>t{JDLB017Y&6 zE~Wnk1JCqcBDw!#0-^Vq7SjKz0ipV|3lsH~=2;T+DIW$@AOHXW000000000000000 z000000F-NI#1Q}t00;m90QUdt|Kk7P|LXq(04xAx0H^@-06hVH0j&YY0RR9100000 z000000000000000000000OVH%#RCBH|Lp(q{{jFR03`q!0Pz30|CRr-|1tn10dE4< z0|*7U1poj500000000000000000000000000Omf%#Pk0E02BZk00jWy|JDEe|3Ls+ z00#hL{|Nuo|4{-j1~dxC3}^xk$oBsy06GBD|E&Lb0Js3f{^R`w1q2m9BLDyZ00000 z00000000000000000000000000Efpt$v^;r0EYi005$;t{YU~cBt=CG0Du4z0CN8z z0gL~i{2LH^H~;_u000000000000000000000000004lH5$KmY(hKmY&$0B;h5I&Tt#JDQmpKh4a{JpcfJ zJDQmpJ^%m!H~;{EHwxDyJOBVdKmY&$Kmb4-MgRanKL7xLKDSa*VgXZ9V$)MnVgXZ9 zVm4G#VgXZ9Vz5+FVgXZ9VgXZ9VgXZ9VjxpeV$ICVJpcdzKmY)MH~;{Ez5oCKJOBUy zKL7v#H~;{kK>z@NJ^%oKJI&0@z5oEgKmY&$0Boaq2~U#>KmY)sK+Vj|J^%nfKW`F) zIx{nKKML0)JOBWIHwxDyz5oCKKo_@BI5RUdKc4S@K+Vj|J zKW1izKh4ZcJ^%nfKxSr!K4xa7Kbn~tKbo1DJ^%oKHvj;DKL7xLJ^=pzKF!QbJz@NJ^%m!KL7xLM*skTHvj+tIL*vVy_%Vs zKh4Zcz5oD#JI&0@y#N4!I?c>XKX=S4GOWd?HUI#CM*skTIRF5FL;wH)MgRbSMgRbS zMF0Q*K>z@NNB{tUIIP7dH(zjxFaQ7mz|6diD_?MlFaQ96KXUnhKXUnhzW@LL0001b z!~j4XNB{tUInB&WJ^%oKKL7v#0Boaq2~U#>KmY&$KL7v#0Q{-((oJGeKh4ZcJpcdz z0AFy4FaQ7mzyJUMKmY&$006O4VmGoe50MG{j05Ej`0MG{j0Qf2Z0O$h%0I)Uy0O$h% z00>e50MG{j0AOtZ0MG{j0N^eF0O$h%0GK)e0O$h%08mB%0MG{j0Ps}+0MG{j0Qf2Z z0O$h%0I)Uy0O$h%0BAx00O$h%00>e50MG{j0001hFOCaGUzSO5UXK^M1CIttgLJOBWoK>z^yMF0R$ zK>z@HMgRbaK>&a|MgRbCK>z@NJ^%m!zyJUM#{mBSMgRcFLI41IM*yGLKF!QbJ^%oKH~;_uH~;_u00000000000000000000KODf7KEKl>KGFcLJ+dD& zJ+aocJgq!8JWBGnJCRN}I{*Lx0000000000000000000000000000000000000000 z00000000000000000000000000000000961000000000000000009610096100031 z0000000IC200961000630000000IC200IC2000330{{R300RI300IC2000C50s{a5 z00aO400IC2000C50s{pA00aO400RI3000C50s{pG00jU500RI3000I70s{mE2LJ#7 zH~;_uK>z>%KmY&$KmLL%Wk(hFKL7v#0000000000#{mBSMgRZ+M*si-0000000000 z00000000000096100IC200aO400sa600;m8015yA01N;C01f~E01yBG02BZK02lxO z02}}S03ZMW04M+e05AXm05|{u07L))08jt`0AK(B0C)fZ0H6Q>0LTCU0000000000 z00IC200aO400sa600;m8015yA01N;C01f~E01yBG02BZK02lxO02}}S03ZMW04M+e z05AXm05|{u07L))08jt`0AK(B0C)fZ00000000000000000000000000OndWICoAn zIn&w6Ig*hxI?P~TJT(Uh zJrK+hJTJ>d6` zJ&NU)J$w0`J+U*TJt323KkDmEKY2b+ zKaY)2KP%Z$KOG@cKXYYNKMkf;KkMRFKl~e5KQB>PKXZawKgq9KKMvVkKSTaqKUg1M zKTAAdKm1f-KWb@WKZSW?KRktGKcI|dKYov9Kh}+BKe~r#KlXfSKb&o9KZ;pvKhZ#J zKTjiVKLi18Kk?aaKl80`KOB8wKeWtpKk$chKM+22KNIhCKl++=KeIu0 zKR@GkKaGEPKdlycKYplqKLIs)KQz2~KNvoGKb^XIKf*D4KX;>hKf4cnKc9AdKLXf% zKlC$wKV^!2KSbMoKeHx&KaXsFKkKZ1KcnYy)-b=J^Jm7KF}q!J_)6tKKQ9B zK3ee&z0Is;zLzK7Bo>KGKT{ zKGKT{zI{EXzI{EXzS4^dzS4^dK7Bo>J_)6tK3ee&J^Jm7zLzz0Is;KF}q! zKKQ9BKKQ9BzR)GKzRj#J(oGHKKkv8K3ee&y$PkCz8iyz>%02Wr~p05MaJfv`aKaaxoJPm9WIsgCw0GgSZ znVFfnKPYQ%P5=O)K>z^oK>z@dK>z>{LCwreKL7xLJTBtqmCKCB!{KHVj8J~XdKbyv8 zKX0IDKlfN_KY;RUKi_U`KMT-qKgt(yKj24kKjC3=KTT(IKbBc^KMXo`KmQ4KKZ?qB zKf8Q)Kfob)KVrIgKb=Q;KTFDaKO{qXKk~AAKRX(GKgfA|KOoY3Kgc6|KR0iDKk~GE zKVJ2GKj1KZKkR3HKew8FKeWkxKL+)EKM@^%KNv!OKTc$RKhuMLKmDM7Kkd7IKf~01 zKeOw8Kkop4KZg{5KaD1TKSMWvKdMH5KjTz?KM!JmKQC^7KV*4-KfZ&1KR=CsKNyyO zKRBL$KfKWxl@KU>g$KL*r)KX2E6KZDwT zKW5#3KNaAAKZ4?aKh5NSKj!9tKh5ZWKa%QyKSu0-KgjKWKPc~iKX~weKc?}2Kg05W zKfm*TKf3gPKa}-?lKmGWBKa}}@KMDGOKY#jvKlJ*4KUw>K zKdt+JKLPxIKUDmGKbZV~Kim9&KOX&mKT!RDKZ5;#KehdTKj-~_KMDSSKRf<^KVANR zKYspyKac)@Kd}CPKhpkxKjZ#?KlJ{8KNbIfKPLZwKR5q>KS%$6KUn{NKUn{NKWYDe zKYIUvKYIUvKa2l=Kb!x5Kb!x5Kb!x5Kdk?MKdk?MKfV8dKfV8dKfV8dKhFPuKhFPu zKhFPuKhFPuKhFPuKi>a;Ki>a;Ki>a;Ki>a;Ki>a;Ki>a;Kkxs4Kkxs4Kkxs4Kkxs4 zKkxs4Kkxs4Kkxs4Kkxs4Kkxs4Kkxs4Kkxs4Kkxs4Kkxs4KL7xLKL7xLKL7xLKL7xL zKL7xLKL7xLKL7xLKL7xLKL7xLKL7xLKL7xLKL7v#K>z>%!2kfrK>z>%KML0)Jpcdz z0000000RI30Ic{^V!-%QVi^Dc01p5F00IC2000000000001m#uy{sRbzLkO+zm}Co zzEpBPzLEnPzXq5-y#k=>y|ydVy}5E8ziPgsz8nWGzH9T3y;`0ZJ|V5qJ_=?GKYcvB zJ`^GSzK^LxKL~g0zY6m%K6R5!K~PhRzijNHzqJ*D!5nMBL84-^!2jnkzAsNFzMjry zzW-Oky_-Chz03HAzARzQy~`m}y+EcFzV0LUy`cR#zT2k*KIvcLKGn+nz8%XeKIf=Y zJ^@ckzV`#xK1X-JKdt#izd)oQ!7IYeKESd*K*@GJZk&bJ)e$4K!`?@KRGCYzeif?zl&5yzG`zaz0%f!I-z7@KAzGMK7iNvKHUt@K70=Y zKl<`=zBh|^J}+*wKDP}szldWdK@z1YKvU2gz%>Vkz<3zFEO9zoY5A zK0q?{KK=DNKBITiz6g+EKPPLGz#$*JKj8DLKB3m=zx7-3!3eCeK>V@)I%*aDy(RUO zy(wmcz5MA~y(5O*y%hPsy({gGy-Ta%yr=9$zItxqy{sIPK544Yz6Z;_JtJk4zRBlV zzx=|>z0_ZCzHfn~zYT1WKby%%K)WZ8y+T{=zj4VaKzQ!yz#6C=K!=Kwx;B`By?-GZ zy^3-fzU5>RKKIHzK5@$YJ$2lgJw`$BJ;*4(Jx9TfJrK zz6jsoJiFcuJ&2}9yS;y=5Guz7?yczH8GsKC2&q zy|$JnuM%JE|L=yyCH6KBBVWz3ig_z@-suKO6`hz=i{vKe$$V zy)Y8?zq-JkKCH_AJ}QuDKW0l-K9d+0z+YYrKqnN>z>rSaKdGTPzi`?@JWb9TJ#Fh^ zJ#B)yJo7`4yrKF^^%J~DJHztCQ7KGhv? zy+KNBKGsEK!131zKyZKhhjczjuTqKta_! zKNDuIzugHkzeidez&@_szk~XGK+OU2KV{(fK1*3O!GM!+O8HB}zJ49}JzgwmzxfnA zKr_VAKj;5pKLXWXKP}IjKQ7{5zoxdNK3(}pKcG=^KZZ9Gzdp-dz#@raKdnB!J&=PQ z!E{~7J__<9KB_AN!0@MpK++bBJvuxTzIzaozC|p;K6FNuzo#AJyqcLE!4O@Vz%!?9 zK#SmaKpLZAKmY&$0000003cdZVohmoXLl`kHZ&k|b#8QNZDk;4VQFl2a%DOS06}DA za%FRKASG}hXkl|8Zf_uIZDD6+EpugKbZKvH03aY%X>xRRVQf)#Wpa5SW?^Y;Wn>^` zZ*m|dWFTUBbY*iOVRRs2WMpz>b8{diZ~y=R03aY%X>xRRVQf%xZ**m2bRcG7X>4U= zAa`kWXdq*6WMv>QcqMoM03aY$ZfkCDcWxkXb7gg8Zy<7IY;R*>bZKvHAaHVTbZ=vC zY#?@Ja&u{KZXhLOE(!nu03aY$ZfkCDcWxkXb7gg8Zy<7IY;R*>bZKvHAYy5BAaiMY zWgsPFE(!nu03b6|Vn8rdVsmkFbN~PV0002M{{R5Me*gf0e`R=Z0001hfA2Z^{z5K> zKv6I))euRCz-(`40001h{{R5Me*gf0e`9ZR0002Me{gSi0002Me*gf${{R4hKL7xL ze*gf0{{R30KL7v#0002M|NsBrK>z>%&j0`b0AF8lZ+B?`0002M{{R5Me*gf0e*gf0 z|5a{lZf|#PAZ2oLZ*l+t0AFokbZBLAawsV}ASH7kX>K4Tb0{TeEFdLkDIg#ya%FUO zVQf7mXDJE*06}tRb!}yCbRcAJZDDC{AZ2oLZ*m|gL{CjYNlqyML2_qxZDnqBAaiMM zXLW30a%psVAShEwM@}gK08e&ha%OCAcOY_MZf9j6WpZ+Fav&&ARz*@qOix!S090sY zAaZ4Mb!>DXX>%ZSZ*L%TZDDL|AarjaVr3w5WpHw3b7gLHWn>^IRZc`jQbtTqS1AAh z090>uVQe65Z*y}XZ)PBKX=iR}W@%$#ZewL2C{#>OQ&TAb08n9abZKF1AZ%}Qb0BYK zAaiMFZfRy|V_|M%WgsX}Oixo&DF6Tf04xDiVoL#3VrT(WVu%4$V!#1ZV&nl-Vn;(m zIv^-VPE{!&G%h$UF(5K9F*7hVGch12MrCAga$z7%X>LbXAT%yGE-@`LE@opgFev~4 z07pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbXAT%yGE;21KE@opg zFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbXAT%yGE;21K zE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbXAT%yG zE;21KE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbX zAT%yGE;21KE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7% zX>LbXAT%yGE;21KE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAg za$z7%X>LbXAT%yGE;21KE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5 zMrCAga$z7%X>LbXAT%yGE;21KE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kR zF*YD5MrCAga$z7%X>LbXAT%yGE;21KE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTA zF*7kRF*YD5MrCAga$z7%X>LbXAT%yGE;21KE@opgFev~407pYZIv^-VPE{!&G%h$U zG9WTAF*7kRF*YD5MrCAga$z7%X>LbXAT%yGE;21KE@opgFev~407pYZIv^-VPE{!& zG%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbXAT%yGE;21KE@opgFev~407pYZIv^-V zPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbXAT%yGE;21KE@opgFev~407pYZ zIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbXAT%yGE;21KE@opgFev~4 z07pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbXAT%yGE;21KE@opg zFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbXAT%yGE;21K zE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbXAT%yG zE;21KE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbX zAT%yGE;21KE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7% zX>LbXAT%yGE;21KE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAg za$z7%X>LbXAT%yGE;21KE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5 zMrCAga$z7%X>LbXAT%yGE;21KE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kR zF*YD5MrCAga$z7%X>LbXAT%yGE;21KE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTA zF*7kRF*YD5MrCAga$z7%X>LbXAT%yGE;21KE@opgFev~407pYZIv^-VPE{!&G%h$U zG9WTAF*7kRF*YD5MrCAga$z7%X>LbXAT%yGE;21KE@opgFev~407pYZIv^-VPE{!& zG%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbXAT%yGE;21KE@opgFev~407pYZIv^-V zPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbXAT%yGE;21KE@opgFev~407pYZ zIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbXAT%yGE;21KE@opgFev~4 z07pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbXAT%yGE;21KE@opg zFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbXAT%yGE;21K zE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbXAT%yG zE;21KE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbX zAT%yGE;21KE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7% zX>LbXAT%yGE;21KE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAg za$z7%X>LbXAT%yGE;21KE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5 zMrCAga$z7%X>LbXAT%yGE;21KE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kR zF*YD5MrCAga$z7%X>LbXAT%yGE;21KE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTA zF*7kRF*YD5MrCAga$z7%X>LbXAT%yGE;21KE@opgFev~407pYZIv^-VPE{!&G%h$U zG9WTAF*7kRF*YD5MrCAga$z7%X>LbXAT%yGE;21KE@opgFev~407pYZIv^-VPE{!& zG%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbXAT%yGE;21KE@opgFev~407pYZIv^-V zPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbXAT%yGE;21KE@opgFev~407pYZ zIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbXAT%yGE;21KE@opgFev~4 z07pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbXAT%yGE;21KE@opg zFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbXAT%yGE;21K zE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbXAT%yG zE;21KE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbX zAT%yGE;21KE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7% zX>LbXAT%yGE;21KE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAg za$z7%X>LbXAT%yGE;21KE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5 zMrCAga$z7%X>LbXAT%yGE;21KE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kR zF*YD5MrCAga$z7%X>LbXAT%yGE;21KE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTA zF*7kRF*YD5MrCAga$z7%X>LbXAT%yGE;21KE@opgFev~407pYZIv^-VPE{!&G%h$U zG9WTAF*7kRF*YD5MrCAga$z7%X>LbXAT%yGE;21KE@opgFev~407pYZIv^-VPE{!& zG%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbXAT%yGE;21KE@opgFev~407pYZIv^-V zPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbXAT%yGE;21KE@opgFev~407pYZ zIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbXAT%yGE;21KE@opgFev~4 z07pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbXAT%yGE;21KE@opg zFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbXAT%yGE;21K zE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbXAT%yG zE;21KE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbX zAT%yGE;21KE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7% zX>LbXAT%yGE;21KE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAg za$z7%X>LbXAT%yGE;21KE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5 zMrCAga$z7%X>LbXAT%yGE;21KE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kR zF*YD5MrCAga$z7%X>LbXAT%yGE;21KE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTA zF*7kRF*YD5MrCAga$z7%X>LbXAT%yGE;21KE@opgFev~407pYZIv^-VPE{!&G%h$U zG9WTAF*7kRF*YD5MrCAga$z7%X>LbXAT%yGE;21KE@opgFev~407pYZIv^-VPE{!& zG%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbXAT%yGE;21KE@opgFev~407pYZIv^-V zPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbXAT%yGE;21KE@opgFev~407pYZ zIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbXAT%yGE;21KE@opgFev~4 z07pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbXAT%yGE;21KE@opg zFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbXAT%yGE;21K zE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbXAT%yG zE;21KE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbX zAT%yGE;21KE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7% zX>LbXAT%yGE;21KE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAg za$z7%X>LbXAT%yGE;21KE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5 zMrCAga$z7%X>LbXAT%yGE;21KE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kR zF*YD5MrCAga$z7%X>LbXAT%yGE;21KE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTA zF*7kRF*YD5MrCAga$z7%X>LbXAT%yGE;21KE@opgFev~407pYZIv^-VPE{!&G%h$U zG9WTAF*7kRF*YD5MrCAga$z7%X>LbXAT%yGE;21KE@opgFev~407pYZIv^-VPE{!& zG%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbXAT%yGE;21KE@opgFev~407pYZIv^-V zPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbXAT%yGE;21KE@opgFev~407pYZ zIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbXAT%yGE;21KE@opgFev~4 z07pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbXAT%yGE;21KE@opg zFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbXAT%yGE;21K zE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbXAT%yG zE;21KE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbX zAT%yGE;21KE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7% zX>LbXAT%yGE;21KE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAg za$z7%X>LbXAT%yGE;21KE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5 zMrCAga$z7%X>LbXAT%yGE;21KE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kR zF*YD5MrCAga$z7%X>LbXAT%yGE;21KE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTA zF*7kRF*YD5MrCAga$z7%X>LbXAT%yGE;21KE@opgFev~407pYZIv^-VPE{!&G%h$U zG9WTAF*7kRF*YD5MrCAga$z7%X>LbXAT%yGE;21KE@opgFev~407pYZIv^-VPE{!& zG%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbXAT%yGE;21KE@opgFev~407pYZIv^-V zPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbXAT%yGE;21KE@opgFev~407pYZ zIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbXAT%yGE;21KE@opgFev~4 z07pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbXAT%yGE;21KE@opg zFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbXAT%yGE;21K zE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbXAT%yG zE;21KE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbX zAT%yGE;21KE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7% zX>LbXAT%yGE;21KE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAg za$z7%X>LbXAT%yGE;21KE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5 zMrCAga$z7%X>LbXAT%yGE;21KE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kR zF*YD5MrCAga$z7%X>LbXAT%yGE;21KE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTA zF*7kRF*YD5MrCAga$z7%X>LbXAT%yGE;21KE@opgFev~407pYZIv^-VPE{!&G%h$U zG9WTAF*7kRF*YD5MrCAga$z7%X>LbXAT%yGE;21KE@opgFev~407pYZIv^-VPE{!& zG%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbXAT%yGE;21KE@opgFev~407pYZIv^-V zPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbXAT%yGE;21KE@opgFev~407pYZ zIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbXAT%yGE;21KE@opgFev~4 z07pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbXAT%yGE;21KE@opg zFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbXAT%yGE;21K zE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbXAT%yG zE;21KE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbX zAT%yGE;21KE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7% zX>LbXAT%yGE;21KE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAg za$z7%X>LbXAT%yGE;21KE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5 zMrCAga$z7%X>LbXAT%yGE;21KE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kR zF*YD5MrCAga$z7%X>LbXAT%yGE;21KE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTA zF*7kRF*YD5MrCAga$z7%X>LbXAT%yGE;21KE@opgFev~407pYZIv^-VPE{!&G%h$U zG9WTAF*7kRF*YD5MrCAga$z7%X>LbXAT%yGE;21KE@opgFev~407pYZIv^-VPE{!& zG%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbXAT%yGE;21KE@opgFev~407pYZIv^-V zPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbXAT%yGE;21KE@opgFev~407pYZ zIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbXAT%yGE;21KE@opgFev~4 z07pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbXAT%yGE;21KE@opg zFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbXAT%yGE;21K zE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbXAT%yG zE;21KE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbX zAT%yGE;21KE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7% zX>LbXAT%yGE;21KE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAg za$z7%X>LbXAT%yGE;21KE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5 zMrCAga$z7%X>LbXAT%yGE;21KE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kR zF*YD5MrCAga$z7%X>LbXAT%yGE;21KE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTA zF*7kRF*YD5MrCAga$z7%X>LbXAT%yGE;21KE@opgFev~407pYZIv^-VPE{!&G%h$U zG9WTAF*7kRF*YD5MrCAga$z7%X>LbXAT%yGE;21KE@opgFev~407pYZIv^-VPE{!& zG%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbXAT%yGE;21KE@opgFev~407pYZIv^-V zPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbXAT%yGE;21KE@opgFev~407pYZ zIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbXAT%yGE;21KE@opgFev~4 z07pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbXAT%yGE;21KE@opg zFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbXAT%yGE;21K zE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbXAT%yG zE;21KE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7%X>LbX zAT%yGE;21KE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MrCAga$z7% zX>LbXAT%yGE;21KE@opgFev~407pYZIv^-VPE{!&G%h$UG9WTAF*7kRF*YD5MgagO z|Jh|^Z*pNEO=)gNS0FSlI4&|RF)n6fGB7Cs002irLpmTRM^05KAT%yGE-@f7FflVQ zH8U|FC`M&uZ*pNEO=)gNS0FSlI4&_QGcIOhGB7Cs002irLpmTRM^05KAT%yGE-@f7 zFflVQH8U|FC`M&uZ*pNEO=)gNS0FSlI4&_QGcIOhGB7Cs002irLpmTRM^05KAT%yG zE-@f7FflVQH8U|FC`M&uZ*pNEO=)gNS0FSlI4&_QGcIOhGB7Cs002irLpmTRM^05K zAT%yGE-@f7FflVQH8U|FC`M&uZ*pNEO=)gNS0FSlI4&_QGcIOhGB7Cs002irLpmTR zM^05KAT%yGE-@f7FflVQH8U|FC`M&uZ*pNEO=)gNS0FSlI4&_QGcIOhGB7Cs002ir zLpmTRM^05KAT%yGE-@f7FflVQH8U|FC`M&uZ*pNEO=)gNS0FSlI4&_QGcIOhGB7Cs z002irLpmTRM^05KAT%yGE-@f7FflVQH8U|FC`M&uZ*pNEO=)gNS0FSlI4&_QGcIOh zGB7Cs002irLpmTRM^05KAT%yGE-@f7FflVQH8U|FC`M&uZ*pNEO=)gNS0FSlI4&_Q zGcIOhGB7Cs002irLpmTRM^05KAT%yGE-@f7FflVQH8U|FC`M&uZ*pNEO=)gNS0FSl zI4&_QGcIOhGB7Cs002irLpmTRM^05KAT%yGE-@f7FflVQH8U|FC`M&uZ*pNEO=)gN zS0FSlI4&_QGcIOhGB7Cs002irLpmTRM^05KAT%yGE-@f7FflVQH8U|FC`M&uZ*pNE zO=)gNS0FSlI4&_QGcIOhGB7Cs002irLpmTRM^05KAT%yGE-@f7FflVQH8U|FC`M&u zZ*pNEO=)gNS0FSlI4&_QGcIOhGB7Cs002irLpmTRM^05KAT%yGE;1l8FflVRFfleD zC`M&uZ*pNEO=)gNS0FSlI4&|RF)n6fGB7Cs002irLpmTRM^05KAT%yGE-@f7FflVQ zH8U|FC`M&uZ*pNEO=)gNS0FSlI4&_QGcIOhGB7Cs002irLpmTRM^05KAT%yGE-@f7 zFflVQH8U|FC`M&uZ*pNEO=)gNS0FSlI4&_QGcIOhGB7Cs002irLpmTRM^05KAT%yG zE-@f7FflVQH8U|FC`M&uZ*pNEO=)gNS0FSlI4&_QGcIOhGB7Cs002irLpmTRM^05K zAT%yGE-@f7FflVQH8U|FC`M&uZ*pNEO=)gNS0FSlI4&_QGcIOhGB7Cs002irLpmTR zM^05KAT%yGE-@f7FflVQH8U|FC`M&uZ*pNEO=)gNS0FSlI4&_QGcIOhGB7Cs002ir zLpmTRM^05KAT%yGE-@f7FflVQH8U|FC`M&uZ*pNEO=)gNS0FSlI4&_QGcIOhGB7Cs z002irLpmTRM^05KAT%yGE-@f7FflVQH8U|FC`M&uZ*pNEO=)gNS0FSlI4&_QGcIOh zGB7Cs002irLpmTRM^05KAT%yGE-@f7FflVQH8U|FC`M&uZ*pNEO=)gNS0FSlI4&_Q zGcIOhGB7Cs002irLpmTRM^05KAT%yGE-@f7FflVQH8U|FC`M&uZ*pNEO=)gNS0FSl zI4&_QGcIOhGB7Cs002irLpmTRM^05KAT%yGE-@f7FflVQH8U|FC`M&uZ*pNEO=)gN zS0FSlI4&_QGcIOhGB7Cs002irLpmTRM^05KAT%yGE;1l8FflVRFfleDC`M&uZ*pNE zO=)gNS0FSlI4&|RF)n6fGB7Cs0000000000000000000000000000000000000000 z00000000000000000000000000000000000000000000000000000000000000000 z00000000000000000000000000000000000000000000000000000000000000000 z0000000000000000000000000000000000000000007Vv&r$#Y0025d1ONd5001=r z001=r001aJ1OWU%1OU)M1OULd0syeM0s!!@0syeE0sxS*0s!#20sxS%0syeC0sz4K z0szqb0suh90sx@>0sx=}0{~FN0swHs0sz28006Kv0|4+d0|3A)0|3A*0{}2I0{|c@ z0|0<30{|c+0|4M60{{>q0{~zn0|3A%0{|c%0{}1|0|1aa0|2l*0{{Rb0|0;?0|0Oz z0|2110ss)X0st_%0swHi0s!!`0sufd0{}3f0ss&@0|0QB0swF|0{}oZ0{~z(0|1aU z0{|d30{}2J0|4+j0|1~o0|1~!002lr1OQY*1OR421OR$M1OSUd1OT8y1OU831OU=P z1OVhh1OV_t1ONg<1OOI81OO*P1OPlk1OQS)1OQ}11OR+P1OSvn1OT!_1OU=Q1OV?t z1OO031OPHb1OQM(1ORA61OSFa1OTQ)1OUTC1OVhj1ONs_1OOyO1OPxq1OQ$|1OSIc z1OT!{1OVAZ1OW3z1ON|41OO>U1OP-v1OR461ORqM1OSLe1OS>w1OTl@1OUTE1OVJd z1OWC%1OOIC1OPEd1OP}!1OQ$~1OR$R1ONa40RRF30{{d71po#B2LK2F2>=QJ3jhoN z4FC=R4*(DV5dabZ695zd6#y0h7XTOl82}mp8vq;t9RMBx9{?Z#Apjx(BLE}-B>*M> zCjck_DF7+}D*!A2EdVY6F90wAF#s|EGXOLIY-wU|aCLJnFfL?lYyfX?b#q^2Wn*t- zWdLt*b#q^2Wn*t-WnX4&Z((!*Z*X;UUu0!tZ)9b1Ut@A*VRU5xZ*X;UUu0!tZ)9b1 zUt@G^0B>-0b6;d-V{c?-a$jU+b98cVc>r&4b#q^2Wn*t-WpZCbO3K~b#q^3Zewp` zWdLt*b#q^3Zewp`WnX4&Z((!*Z*X;UUuAA%Z)9b1Ut@A*VRU5xZ*X;UUuAA%Z)9b1 zUt@G^0B>-0b6;g{V{c?-a$jU+b98cVc>r&4b#q^3Zewp`WpZCbO3K~b#q^5WprP5WpZ<-b#q^Bb!>ELb98cL zVQpVzWn*t-WdLt*b#q^Bb!>ELb98cLVQpVzWn*t-WnX4&Z((!*Z*X;UUu|`4bZK*R za%Ev{Uu0!tZ)9b1Ut@A*VRU5xZ*X;UUu|`4bZK*Ra%Ev{Uu0!tZ)9b1Ut@G^0B>-0 zb6;(BY;r&4b#q^Bb!>ELb98cLVQpVzWn*t- zWpZCbO3K~b#q^Bb!>EL zb98cLVQpV!Zewp`WdLt*b#q^Bb!>ELb98cLVQpV!Zewp`WnX4&Z((!*Z*X;UUu|`4 zbZK*Ra%Ev{UuAA%Z)9b1Ut@A*VRU5xZ*X;UUu|`4bZK*Ra%Ev{UuAA%Z)9b1Ut@G^ z0B>-0b6;(BY;V{c?-a$jU+b98cVc>r&4b#q^Bb!>ELb98cLVQpV! zZewp`WpZCbO3K~b#q^B zb!>ELb98cLVQpVELb98cLVQpV0B>-0b6;(BY;WprO>WprO+VQyr1X=HS00B>-0b6;>_ zV{2t}UuR`>Uv6SwV`yP+Ze?t90B>-0b6;>_V{2t}UuR`>Uv6SwW^!R|Wpe;;aCLKE zaA9L>WprO>WprO|VqbG%ZE$R5a{zB}b#q^EVPk7$bYEv>bYF8}ZE$R5b6;>}a$jb0 zVQpmqZ*X;UUvOb#Yh`p_aA9NsZ*X;UUvOb#Yh`p_aA9(DWdLt*b#q^EVPk7$bYFFD zaA9NsZ*X;UUvOh>UvqC}bYEj^X>b5-0b6;|0 zaA9L>WprtJWpZC*a%Ew3WdLt*b#q^GWpH6*Yh`q4dS!B7WMy-7a&LJ6Z*X;UUvgz| zVPk7$bZL5Ja$jdbO3K~b#q^GWpH6*Yh`q4dS!B7Z*_D4Z*X;UUvgz| zVPk7$bZL5Ja$j$CbYF5|Zf9ixZ*X;UUvqSFWpZ+FasU7T00000000000000000000 z00000000000000000000000000000000000000000000000000000000000000000 z000000000000000000000000000000000000000000000000000000000000002Bt z1ONa400000007if1OWI@1OS*&1ONa400000002N$1OQY~1ONa4000000000000000 z00000006X61OUuY1OVhw1OWI^1ON_F1OOyb1OPNr1OP}<1OQr61ORkW1OS9m1OT8? z1OUEL1OU`h1OV((1ONt81OOyc1OO~k1OPx&1OQG`1ORGN1OR+f1ONa4004|r1OS>- z1OTp61OU8K1OUcU1OU=g1OVDo1OVbw1OV()1OWC^1ONb31ON(D1OO6L1OOaV1OOyd z1OP5n1OPZx1OP%*1OQA_1OQf41OQ-E1ORAM1ORYU1ONa4006X61OUuY1OVhw1OWI^ z1ON_F1OOyb1OPNr1OP}<1OQr61ORkW1OS9m1OT8?1OUEL1OU`h1OV((1ONt81OOyc z1OO~k1OPx&1OQG`1ORGN1OR+f1ONa4004|r1OS>-1OTp61OU8K1OUcU1OU=g1OVDo z1OVbw1OV()1OWC^1ONb31ON(D1OO6L1OOaV1OOyd1OP5n1OPZx1OP%*1OQA_1OQf4 z1OQ-E1ORAM1ORYU1ONa4007hgL}hGcbY(+wX>@60VQf=nV{~b6ZUFB9MQ(Iuazk=y zbZKK@Y*S@pbZKvH006`RM`d(Fb#iiLZgfy`Z)0V1a{$EwM`d(Fb#iiLZgfy`Z)0V1 zb4g?X$pJ@YbVGG=a%FCGRA_Q#VPr{U00095M`d(OVRLjva&m8S000#NM`d(PZ)A0B zWk_LeWNc+Y002b-M`d(Sa&KcnWMpz>b8`RydjdygbW?eAbY*Q+X>Daeb4F=wWmIWx zWdN4~M`d(WX=7_cZ*^{T008R(Np5L$X<=+>dSyd$X>@60VQf=nV{~b6ZUFKENp5sy za%^v7Yh`3ZZ*6d4a%Dw$V`yP+XJr5Y^a4q4bY*gEZ)0m^WJP#mXkl(=WdJ4vOl4tq zWkYglbZKK@Y*S@pbZKvH005H%QFUc zVQyq>Wn@KoV`Xr3X>V>uX>4?5asYG$Q*32rZ~%e?RAq8)X>MV3Wl(Z&V`X!5004;u zRBUrcWpq|yY;|P-mIPI9Xkl(-Y-MCccw=R7bZKvHMrmwxWpV%jwggsba&&cJY*2D< zbY)|7006lJR%vo{bzy8#b!BpS001`tUteTwY;SI5cxiM1YXDzgb7gdOb7gXEVRUF^ za&iCwjsRa_ZF6T|Wq4_H007VcUuAM~Zf^hpFacj_ZfSIMWpZr*Gyz{}Z(;xdl>uLD zZ)0l!G6G+3Ze@6BbO7)IUv+M5Z)0l!P6K0MY;131003wMW^i(8ZggeWn};W zdIM&6a%psB005-}Y;R{VFaV|lZDDL|Z({%et^;jlZDVkG006H8ZDnn3Z+2w>u>);o zZF6OG007+sb98cSWo`ff-~)4Xa&BX7Z~#CAVPbD`bO1&KVRT_`G5}r#c4lyLX>N38 z0000`1ONa~1ONa~1ONa~1ONa~1ONa~1ONa~1ONa~1ONa~1ONa~1ONa~1ONa~1ONa~ z1ONa~1ONa~1ONa~1ONa~1ONa~1ONa~1ONa~1ONa~1ONa~1OQ7#QcguoGcqn@Y-|7k z000zF1OOCJ1OOCJ1OOCJ1OOCJ1OOCJ1OOCJ1OOCJ1OOCJ1OOCJ1OOCJ1OOCJ1OOCJ z1OOCJ1OOCJ1OOCJ1OOCJ1OOCJ1OOCJ1OOCJ1OOCJ1OOCJ1OOCJ1ORPwc4KmME@W(M z00000000000000000000000000000000000000000000000000000000000000000 z00000000000000000000000000000000000000000000000000000000000000000 z00000000000000000000000000000000000000000000000000000000000000000 z0000000000000000000000000000000000000000000000000GP+|Z800000001Cb zQ)19rQ(^!B0000000000000000000000000000000000000000000000000000000 z00000000000000000000000000000000000000000000000000000000000000000 z00000000000000000000000000000000000000000000000000000000000000000 z00000000000000000000000000000000000000000000000000000000000000000 z00000000000000000000000000000000000000000000000000000000000000000 z00000000000000000000000000000000000000000000000000000000000000000 z00000000000000000000000000000000000000000000000000000000000000000 z00000000000000000000000000000000000000000000000000000000000000000 z00000000000000000000000000000000000000000000000000000000000000000 z00000000000000000000000000001RRAL-(RALY?RALxlRAK-C000000000000000 z00000000000000000000000000000000000000000000000000000000000000000 z00000000000000000000000000000000000000000000000000000000000000000 z00000000000000000000000000000000000000000000000000000000000000000 z00000000000000000000000000000000000000000000000000000000000000000 z00000000000000000000000000000000000000000000000000000000000000000 z00000000000000000000000000000000000000000000000000000000000000000 z00000000000000000000000000000000000000000000000000000000000000000 z00000000000000000000000000000000000000000000000000000000000000000 z00000000000000000000000000000000000000000000000000000000000000000 z00000000000000G003wJ0012@A}~=fVlZbgZ7`BBpfK7n<1iC3BQY^CJ~31=crlnU zt1;Oz<1y_q05S$LJ2F8sR5DvKVKRs^k20Dv!!pV;12cIvu{6Ro$TZwENyTO96Mb*T0E;f@H`$pK0Q=Dsy#41Qa)Hd zUOwJG0001h004LZ000CqJur(fvM|Ch$S~M2Krx>&;4&vOGc(aMLNrJ;PBdaPhBTBk zq%^@a6*cEI2R1)8mNvpR?lv4ZNjFhAU^q}Z_B#4IOFYm#^*qEq(>>um@;x6uH9kE) zNj_LUWV&M8abyt0001>000;O z005gYqcN2;r!(p|J~$IU00026001Ze006r%7&WamO*eWs%sSyZEjw2`yF1f68a!7# z<2>y=OFaO<000aC008 zU^3-0^D;LxSTiOxKr~7@sX7xoayz{{#yinF>pSy1!aO8BEIk@N0002+001Na007fB z-Zz{$$~gEq@j22vkv!Kt-8}Ls5&o=uu$2aRYUN~(yl|BFv0RSif003q)r5H|!kU^opq8#%N&LppRi z$U0v;Wjg>M0RRvH006x-lr@<*0000m0RS`r008PZNja7|(K-A%Dmwl;96Mw?mpczU zF+7JntUSp)`8+W_Q$2b;s67Ne0Y3mh0RS8T002!eQZSJ)dohbK!7;`$-ZAYl5H$c$ z0RRjD007N70001B0RTV%002WZZ8erP#xpLJkhCHJ@|2!r=#Xadg0001R0RT7v006f!zA@Y}@-g`_6*3|+Z8CH+k}{|= zu`0RRjD003(|hdcnV z0RVsi008tgpEuxjeo+9z7;K0002+0RSif005XVzcFevZ!?-R5H#X69W?0000W0suGw002BUM>kzJZa0WGnK!C8!8e0CjXJ72u{uLOeLk2z z#6HYEGe2oRl0UIO(LeD&0000m0stHU001N~E;UFsU^Z$tcs7nUJ~|OS0000$0st@o z003(;!ZV0BygAu9S30jc;5zO)KRbOpgF6pB9zHETJ3di9aXybep*{dm0swFT0018_ zGB83gUod4baWJzoZZ>o_zBa@*7B@jRM>knFUpJyRt~a_j;WzX*{Wo7YoH*Dx-Z%+4 zDLG0xUO8$xd^xB&D?4&LXgzX0oju1r&pq-z1wIWv89q2ZUp}QiB0o1jJwH!B0001B z0sve9006Qu&oCu1#4*${5;7Pv9x_5QS2ABRYchv2jWU5V*fZoa>@ylQAT=B}A~z^E zGB<)Zu{T6GemIIamN=j|s5rkk#W>42=r}VuKRIVPEIP$J0001R0su4s008wcY%$6) z0ss&I z008eaV>Wg>d^-TJ0strg007r8L^x77d^o#09y7^C_PF&+&%I=89q2Z0002c z0st%k0062qur${;>NX%ZCO5)6@;fCxFg;8?Zas)S<~^l9*+1ex^gjUL0ss^M0056L zlrSqYygI}>0002+0st@o001vBLNRqQoinR5lQ!2jC^uF&z&Y7DGCDmvNIFwG>pM$4 zlRN}HJv{&b0{{>J0031ur8wp|`8WU&0{|ER004k6mN6nSHZn*u#XieE0000W0{|QV z002)gS}J007@JA2dxr+dlw60|4Lv006@<#xc+_ z^)df35;7t(Try}ff-;OUw=%&p)H38U{xU){k~5eyq%#&YA~ZZSMl@_Rd^CbIpftZU z$u!e60yPXZIyFi)P&INj!!^$}<~8g!3N{-yAvQfWP&QmPsW$F5OgC0Hzc-j3NoECr!vDb*fJ+Gcr$`Csx!1RzBBbR zXg0Jq;5O^Jr|FgQIp^*9?jEIDU6e>s^sCOR`ZQaV^VZ93ID@H!1U zKs!-8k~_CM!8`RkMLbG`$v^Ht0001B0{{R4 z000m$CNMfMQ7~gLZ!m^1mN2C-zcA7;;xPI!7BMO@J268sOfg$AeldeFt}(PRzA?u! z%rV4+p zZ!?rLnlrgGzcb7;_A>@F4>TGyB{VBEH8ejoQ#4sL9W^mEKQ%=)Of{)Bxi!r-(lz!q z12z#h7&az0IW|W&O*T|ElQy9?uQtUt)Hdig?KUenK{rn~RX1ceb2p+lfH<)@(m3=u zG&x2&e>sym!8rgrD>^+oojOE2TRUw#n>(&Mz&kEHKs=Q^dp)u}cRm1c0{~0{|cZ006)_)H&EW+&Snu>^bl`^f~xB06GLZ2s!|;0{|ER008VU@G&ehFfwd1 za5AhiurdJ90{}n(001O2C^IZGcr$!6fHTN6%rww6)HK*M+%#x4Y&CE-bTxQ2d^LbI zs5q=RusF0hxH!Bxz&PAJ;5`7~0|00M008JX>^Sf^^f>r9{5SwP1UU#f3^@=vs5-1V zusXClxH`N#z&gY_$U4kA&^pvQ*gD)g;5y_w=sN5=@H+H5_&WSL06PRb2s;cr5IYn* z7&{z0AUh;GC_5}WFgr9mI6DCF0{{>J005*os5oFh000001OONS003MtU@#0a5Hb`p z7&06(ATj`81ON~K000ay7%&_#0001R1ON~K000Cq2rvvV5HJ7$000000000000000 z00000000000000000000000000000000000000000000000000000000000000000 z00000000000000000000000000000000000000000000000000000000000000000 z00000000000000000000000000000000000000000000000000000000000000000 z00000000000000000000000000000000000000000000000000000000000000000 z00000000000000000000000000000000000000000000000000000000000000000 J00000005sre0u-@ diff --git a/src/Discord.Net/API/Auth.cs b/src/Discord.Net/API/Auth.cs new file mode 100644 index 000000000..ce62d8eb2 --- /dev/null +++ b/src/Discord.Net/API/Auth.cs @@ -0,0 +1,29 @@ +//Ignore unused/unassigned variable warnings +#pragma warning disable CS0649 +#pragma warning disable CS0169 + +using Newtonsoft.Json; + +namespace Discord.API +{ + //Gateway + public class GatewayResponse + { + [JsonProperty("url")] + public string Url; + } + + //Login + public sealed class LoginRequest + { + [JsonProperty("email")] + public string Email; + [JsonProperty("password")] + public string Password; + } + public sealed class LoginResponse + { + [JsonProperty("token")] + public string Token; + } +} diff --git a/src/Discord.Net/API/Bans.cs b/src/Discord.Net/API/Bans.cs new file mode 100644 index 000000000..54dfa1518 --- /dev/null +++ b/src/Discord.Net/API/Bans.cs @@ -0,0 +1,6 @@ +namespace Discord.API +{ + //Events + internal sealed class BanAddEvent : MemberReference { } + internal sealed class BanRemoveEvent : MemberReference { } +} diff --git a/src/Discord.Net/API/Channels.cs b/src/Discord.Net/API/Channels.cs new file mode 100644 index 000000000..030bda73d --- /dev/null +++ b/src/Discord.Net/API/Channels.cs @@ -0,0 +1,100 @@ +//Ignore unused/unassigned variable warnings +#pragma warning disable CS0649 +#pragma warning disable CS0169 + +using Newtonsoft.Json; +using System.Collections; +using System.Collections.Generic; + +namespace Discord.API +{ + //Common + public class ChannelReference + { + [JsonProperty("id")] + public string Id; + [JsonProperty("guild_id")] + public string GuildId; + [JsonProperty("name")] + public string Name; + [JsonProperty("type")] + public string Type; + } + public class ChannelInfo : ChannelReference + { + public sealed class PermissionOverwrite + { + [JsonProperty("type")] + public string Type; + [JsonProperty("id")] + public string Id; + [JsonProperty("deny")] + public uint Deny; + [JsonProperty("allow")] + public uint Allow; + } + + [JsonProperty("last_message_id")] + public string LastMessageId; + [JsonProperty("is_private")] + public bool IsPrivate; + [JsonProperty("position")] + public int? Position; + [JsonProperty(PropertyName = "topic")] + public string Topic; + [JsonProperty("permission_overwrites")] + public PermissionOverwrite[] PermissionOverwrites; + [JsonProperty("recipient")] + public UserReference Recipient; + } + + //Create + public class CreateChannelRequest + { + [JsonProperty("name")] + public string Name; + [JsonProperty("type")] + public string Type; + } + public class CreatePMChannelRequest + { + [JsonProperty("recipient_id")] + public string RecipientId; + } + public class CreateChannelResponse : ChannelInfo { } + + //Edit + public class EditChannelRequest + { + [JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)] + public string Name; + [JsonProperty("topic", NullValueHandling = NullValueHandling.Ignore)] + public string Topic; + } + public class EditChannelResponse : ChannelInfo { } + + //Destroy + public class DestroyChannelResponse : ChannelInfo { } + + //Reorder + public class ReorderChannelsRequest : IEnumerable + { + public sealed class Channel + { + [JsonProperty("id")] + public string Id; + [JsonProperty("position")] + public uint Position; + } + private IEnumerable _channels; + public ReorderChannelsRequest(IEnumerable channels) { _channels = channels; } + + public IEnumerator GetEnumerator() => _channels.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => _channels.GetEnumerator(); + } + + //Events + internal sealed class ChannelCreateEvent : ChannelInfo { } + internal sealed class ChannelDeleteEvent : ChannelInfo { } + internal sealed class ChannelUpdateEvent : ChannelInfo { } +} diff --git a/src/Discord.Net/API/Common.cs b/src/Discord.Net/API/Common.cs deleted file mode 100644 index 9885b3cd5..000000000 --- a/src/Discord.Net/API/Common.cs +++ /dev/null @@ -1,318 +0,0 @@ -//Ignore unused/unassigned variable warnings -#pragma warning disable CS0649 -#pragma warning disable CS0169 - -using Newtonsoft.Json; -using System; - -namespace Discord.API -{ - //User - public class UserReference - { - [JsonProperty("username")] - public string Username; - [JsonProperty("id")] - public string Id; - [JsonProperty("discriminator")] - public string Discriminator; - [JsonProperty("avatar")] - public string Avatar; - } - public class SelfUserInfo : UserReference - { - [JsonProperty("email")] - public string Email; - [JsonProperty("verified")] - public bool IsVerified; - } - - //Members - public class MemberReference - { - [JsonProperty("user_id")] - public string UserId; - [JsonProperty("guild_id")] - public string GuildId; - - [JsonProperty("user")] - private UserReference _user; - public UserReference User - { - get { return _user; } - set - { - _user = value; - UserId = User.Id; - } - } - } - public class MemberInfo : MemberReference - { - [JsonProperty("joined_at")] - public DateTime? JoinedAt; - [JsonProperty("roles")] - public string[] Roles; - } - public class ExtendedMemberInfo : MemberInfo - { - [JsonProperty("mute")] - public bool? IsServerMuted; - [JsonProperty("deaf")] - public bool? IsServerDeafened; - } - public class PresenceMemberInfo : MemberReference - { - [JsonProperty("game_id")] - public string GameId; - [JsonProperty("status")] - public string Status; - } - public class VoiceMemberInfo : MemberReference - { - [JsonProperty("channel_id")] - public string ChannelId; - [JsonProperty("session_id")] - public string SessionId; - [JsonProperty("token")] - public string Token; - - [JsonProperty("self_mute")] - public bool? IsSelfMuted; - [JsonProperty("self_deaf")] - public bool? IsSelfDeafened; - [JsonProperty("mute")] - public bool? IsServerMuted; - [JsonProperty("deaf")] - public bool? IsServerDeafened; - [JsonProperty("suppress")] - public bool? IsServerSuppressed; - } - - //Channels - public class ChannelReference - { - [JsonProperty("id")] - public string Id; - [JsonProperty("guild_id")] - public string GuildId; - [JsonProperty("name")] - public string Name; - [JsonProperty("type")] - public string Type; - } - public class ChannelInfo : ChannelReference - { - public sealed class PermissionOverwrite - { - [JsonProperty("type")] - public string Type; - [JsonProperty("id")] - public string Id; - [JsonProperty("deny")] - public uint Deny; - [JsonProperty("allow")] - public uint Allow; - } - - [JsonProperty("last_message_id")] - public string LastMessageId; - [JsonProperty("is_private")] - public bool IsPrivate; - [JsonProperty("position")] - public int? Position; - [JsonProperty(PropertyName = "topic")] - public string Topic; - [JsonProperty("permission_overwrites")] - public PermissionOverwrite[] PermissionOverwrites; - [JsonProperty("recipient")] - public UserReference Recipient; - } - - //Guilds (Servers) - public class GuildReference - { - [JsonProperty("id")] - public string Id; - [JsonProperty("name")] - public string Name; - } - public class GuildInfo : GuildReference - { - [JsonProperty("afk_channel_id")] - public string AFKChannelId; - [JsonProperty("afk_timeout")] - public int AFKTimeout; - [JsonProperty("embed_channel_id")] - public string EmbedChannelId; - [JsonProperty("embed_enabled")] - public bool EmbedEnabled; - [JsonProperty("icon")] - public string Icon; - [JsonProperty("joined_at")] - public DateTime? JoinedAt; - [JsonProperty("owner_id")] - public string OwnerId; - [JsonProperty("region")] - public string Region; - [JsonProperty("roles")] - public RoleInfo[] Roles; - } - public class ExtendedGuildInfo : GuildInfo - { - [JsonProperty("channels")] - public ChannelInfo[] Channels; - [JsonProperty("members")] - public ExtendedMemberInfo[] Members; - [JsonProperty("presences")] - public PresenceMemberInfo[] Presences; - [JsonProperty("voice_states")] - public VoiceMemberInfo[] VoiceStates; - [JsonProperty("unavailable")] - public bool Unavailable; - } - - //Messages - public class MessageReference - { - [JsonProperty("id")] - public string Id; - [JsonProperty("channel_id")] - public string ChannelId; - [JsonProperty("message_id")] - public string MessageId { get { return Id; } set { Id = value; } } - } - public class Message : MessageReference - { - public sealed class Attachment - { - [JsonProperty("id")] - public string Id; - [JsonProperty("url")] - public string Url; - [JsonProperty("proxy_url")] - public string ProxyUrl; - [JsonProperty("size")] - public int Size; - [JsonProperty("filename")] - public string Filename; - [JsonProperty("width")] - public int Width; - [JsonProperty("height")] - public int Height; - } - public sealed class Embed - { - public sealed class Reference - { - [JsonProperty("url")] - public string Url; - [JsonProperty("name")] - public string Name; - } - public sealed class ThumbnailInfo - { - [JsonProperty("url")] - public string Url; - [JsonProperty("proxy_url")] - public string ProxyUrl; - [JsonProperty("width")] - public int Width; - [JsonProperty("height")] - public int Height; - } - - [JsonProperty("url")] - public string Url; - [JsonProperty("type")] - public string Type; - [JsonProperty("title")] - public string Title; - [JsonProperty("description")] - public string Description; - [JsonProperty("author")] - public Reference Author; - [JsonProperty("provider")] - public Reference Provider; - [JsonProperty("thumbnail")] - public ThumbnailInfo Thumbnail; - } - - [JsonProperty("tts")] - public bool? IsTextToSpeech; - [JsonProperty("mention_everyone")] - public bool? IsMentioningEveryone; - [JsonProperty("timestamp")] - public DateTime? Timestamp; - [JsonProperty("edited_timestamp")] - public DateTime? EditedTimestamp; - [JsonProperty("mentions")] - public UserReference[] Mentions; - [JsonProperty("embeds")] - public Embed[] Embeds; //TODO: Parse this - [JsonProperty("attachments")] - public Attachment[] Attachments; - [JsonProperty("content")] - public string Content; - [JsonProperty("author")] - public UserReference Author; - [JsonProperty("nonce")] - public string Nonce; - } - - //Roles - public class RoleReference - { - [JsonProperty("guild_id")] - public string GuildId; - [JsonProperty("role_id")] - public string RoleId; - } - public class RoleInfo - { - [JsonProperty("permissions")] - public uint? Permissions; - [JsonProperty("name")] - public string Name; - [JsonProperty("position")] - public int? Position; - [JsonProperty("hoist")] - public bool? Hoist; - [JsonProperty("color")] - public uint? Color; - [JsonProperty("id")] - public string Id; - [JsonProperty("managed")] - public bool? Managed; - } - - //Invites - public class Invite - { - [JsonProperty("inviter")] - public UserReference Inviter; - [JsonProperty("guild")] - public GuildReference Guild; - [JsonProperty("channel")] - public ChannelReference Channel; - [JsonProperty("code")] - public string Code; - [JsonProperty("xkcdpass")] - public string XkcdPass; - } - public class ExtendedInvite : Invite - { - [JsonProperty("max_age")] - public int ?MaxAge; - [JsonProperty("max_uses")] - public int? MaxUses; - [JsonProperty("revoked")] - public bool? IsRevoked; - [JsonProperty("temporary")] - public bool? IsTemporary; - [JsonProperty("uses")] - public int? Uses; - [JsonProperty("created_at")] - public DateTime? CreatedAt; - } -} diff --git a/src/Discord.Net/API/Endpoints.cs b/src/Discord.Net/API/Endpoints.cs index 706607eca..67c256a88 100644 --- a/src/Discord.Net/API/Endpoints.cs +++ b/src/Discord.Net/API/Endpoints.cs @@ -1,9 +1,10 @@ namespace Discord.API { - internal static class Endpoints + public static class Endpoints { public const string BaseStatusApi = "https://status.discordapp.com/api/v2/"; public const string BaseApi = "https://discordapp.com/api/"; + public const string Gateway = "gateway"; public const string Auth = "auth"; @@ -39,7 +40,7 @@ public const string Voice = "voice"; public const string VoiceRegions = "voice/regions"; - public const string VoiceIce = "voice/ice"; + //public const string VoiceIce = "voice/ice"; public const string StatusActiveMaintenance = "scheduled-maintenances/active.json"; public const string StatusUpcomingMaintenance = "scheduled-maintenances/upcoming.json"; diff --git a/src/Discord.Net/API/Invites.cs b/src/Discord.Net/API/Invites.cs new file mode 100644 index 000000000..62d2049b3 --- /dev/null +++ b/src/Discord.Net/API/Invites.cs @@ -0,0 +1,59 @@ +//Ignore unused/unassigned variable warnings +#pragma warning disable CS0649 +#pragma warning disable CS0169 + +using Newtonsoft.Json; +using System; + +namespace Discord.API +{ + //Common + public class InviteReference + { + [JsonProperty("inviter")] + public UserReference Inviter; + [JsonProperty("guild")] + public GuildReference Guild; + [JsonProperty("channel")] + public ChannelReference Channel; + [JsonProperty("code")] + public string Code; + [JsonProperty("xkcdpass")] + public string XkcdPass; + } + public class InviteInfo : InviteReference + { + [JsonProperty("max_age")] + public int? MaxAge; + [JsonProperty("max_uses")] + public int? MaxUses; + [JsonProperty("revoked")] + public bool? IsRevoked; + [JsonProperty("temporary")] + public bool? IsTemporary; + [JsonProperty("uses")] + public int? Uses; + [JsonProperty("created_at")] + public DateTime? CreatedAt; + } + + //Create + public class CreateInviteRequest + { + [JsonProperty("max_age")] + public int MaxAge; + [JsonProperty("max_uses")] + public int MaxUses; + [JsonProperty("temporary")] + public bool IsTemporary; + [JsonProperty("xkcdpass")] + public bool WithXkcdPass; + } + public class CreateInviteResponse : InviteInfo { } + + //Get + public class GetInviteResponse : InviteReference { } + + //Accept + public class AcceptInviteResponse : InviteReference { } +} diff --git a/src/Discord.Net/API/Maintenance.cs b/src/Discord.Net/API/Maintenance.cs new file mode 100644 index 000000000..337b89453 --- /dev/null +++ b/src/Discord.Net/API/Maintenance.cs @@ -0,0 +1,33 @@ +//Ignore unused/unassigned variable warnings +#pragma warning disable CS0649 +#pragma warning disable CS0169 + +using Newtonsoft.Json; +using System; + +namespace Discord.API +{ + public class GetIncidentsResponse + { + [JsonProperty("page")] + public PageData Page; + [JsonProperty("scheduled_maintenances")] + public MaintenanceData[] ScheduledMaintenances; + + public sealed class PageData + { + [JsonProperty("id")] + public string Id; + [JsonProperty("name")] + public string Name; + [JsonProperty("url")] + public string Url; + [JsonProperty("updated-at")] + public DateTime? UpdatedAt; + } + + public sealed class MaintenanceData + { + } + } +} diff --git a/src/Discord.Net/API/Members.cs b/src/Discord.Net/API/Members.cs new file mode 100644 index 000000000..9858e51ab --- /dev/null +++ b/src/Discord.Net/API/Members.cs @@ -0,0 +1,88 @@ +//Ignore unused/unassigned variable warnings +#pragma warning disable CS0649 +#pragma warning disable CS0169 + +using Newtonsoft.Json; +using System; +using System.Collections.Generic; + +namespace Discord.API +{ + //Common + public class MemberReference + { + [JsonProperty("user_id")] + public string UserId; + [JsonProperty("guild_id")] + public string GuildId; + + [JsonProperty("user")] + private UserReference _user; + public UserReference User + { + get { return _user; } + set + { + _user = value; + UserId = User.Id; + } + } + } + public class MemberInfo : MemberReference + { + [JsonProperty("joined_at")] + public DateTime? JoinedAt; + [JsonProperty("roles")] + public string[] Roles; + } + public class ExtendedMemberInfo : MemberInfo + { + [JsonProperty("mute")] + public bool? IsServerMuted; + [JsonProperty("deaf")] + public bool? IsServerDeafened; + } + public class PresenceInfo : MemberReference + { + [JsonProperty("game_id")] + public string GameId; + [JsonProperty("status")] + public string Status; + } + public class VoiceMemberInfo : MemberReference + { + [JsonProperty("channel_id")] + public string ChannelId; + [JsonProperty("session_id")] + public string SessionId; + [JsonProperty("token")] + public string Token; + + [JsonProperty("self_mute")] + public bool? IsSelfMuted; + [JsonProperty("self_deaf")] + public bool? IsSelfDeafened; + [JsonProperty("mute")] + public bool? IsServerMuted; + [JsonProperty("deaf")] + public bool? IsServerDeafened; + [JsonProperty("suppress")] + public bool? IsServerSuppressed; + } + + public class EditMemberRequest + { + [JsonProperty(PropertyName = "mute", NullValueHandling = NullValueHandling.Ignore)] + public bool? Mute; + [JsonProperty(PropertyName = "deaf", NullValueHandling = NullValueHandling.Ignore)] + public bool? Deaf; + [JsonProperty(PropertyName = "roles", NullValueHandling = NullValueHandling.Ignore)] + public IEnumerable Roles; + } + + //Events + internal sealed class MemberAddEvent : MemberInfo { } + internal sealed class MemberUpdateEvent : MemberInfo { } + internal sealed class MemberRemoveEvent : MemberInfo { } + internal sealed class MemberVoiceStateUpdateEvent : VoiceMemberInfo { } +} diff --git a/src/Discord.Net/API/Messages.cs b/src/Discord.Net/API/Messages.cs new file mode 100644 index 000000000..8fc031dd1 --- /dev/null +++ b/src/Discord.Net/API/Messages.cs @@ -0,0 +1,133 @@ +//Ignore unused/unassigned variable warnings +#pragma warning disable CS0649 +#pragma warning disable CS0169 + +using Newtonsoft.Json; +using System; +using System.Collections.Generic; + +namespace Discord.API +{ + //Common + public class MessageReference + { + [JsonProperty("id")] + public string Id; + [JsonProperty("channel_id")] + public string ChannelId; + [JsonProperty("message_id")] + public string MessageId { get { return Id; } set { Id = value; } } + } + public class MessageInfo : MessageReference + { + public sealed class Attachment + { + [JsonProperty("id")] + public string Id; + [JsonProperty("url")] + public string Url; + [JsonProperty("proxy_url")] + public string ProxyUrl; + [JsonProperty("size")] + public int Size; + [JsonProperty("filename")] + public string Filename; + [JsonProperty("width")] + public int Width; + [JsonProperty("height")] + public int Height; + } + + public sealed class Embed + { + public sealed class Reference + { + [JsonProperty("url")] + public string Url; + [JsonProperty("name")] + public string Name; + } + + public sealed class ThumbnailInfo + { + [JsonProperty("url")] + public string Url; + [JsonProperty("proxy_url")] + public string ProxyUrl; + [JsonProperty("width")] + public int Width; + [JsonProperty("height")] + public int Height; + } + + [JsonProperty("url")] + public string Url; + [JsonProperty("type")] + public string Type; + [JsonProperty("title")] + public string Title; + [JsonProperty("description")] + public string Description; + [JsonProperty("author")] + public Reference Author; + [JsonProperty("provider")] + public Reference Provider; + [JsonProperty("thumbnail")] + public ThumbnailInfo Thumbnail; + } + + [JsonProperty("tts")] + public bool? IsTextToSpeech; + [JsonProperty("mention_everyone")] + public bool? IsMentioningEveryone; + [JsonProperty("timestamp")] + public DateTime? Timestamp; + [JsonProperty("edited_timestamp")] + public DateTime? EditedTimestamp; + [JsonProperty("mentions")] + public UserReference[] Mentions; + [JsonProperty("embeds")] + public Embed[] Embeds; //TODO: Parse this + [JsonProperty("attachments")] + public Attachment[] Attachments; + [JsonProperty("content")] + public string Content; + [JsonProperty("author")] + public UserReference Author; + [JsonProperty("nonce")] + public string Nonce; + } + + //Create + internal sealed class SendMessageRequest + { + [JsonProperty("content")] + public string Content; + [JsonProperty("mentions")] + public IEnumerable Mentions; + [JsonProperty("nonce", NullValueHandling = NullValueHandling.Ignore)] + public string Nonce; + [JsonProperty("tts", NullValueHandling = NullValueHandling.Ignore)] + public bool IsTTS; + } + public sealed class SendMessageResponse : MessageInfo { } + + //Edit + internal sealed class EditMessageRequest + { + [JsonProperty("content", NullValueHandling = NullValueHandling.Ignore)] + public string Content; + [JsonProperty("mentions", NullValueHandling = NullValueHandling.Ignore)] + public IEnumerable Mentions; + } + public sealed class EditMessageResponse : MessageInfo { } + + //Get + public sealed class GetMessagesResponse : List { } + + //Events + internal sealed class MessageCreateEvent : MessageInfo { } + internal sealed class MessageUpdateEvent : MessageInfo { } + internal sealed class MessageDeleteEvent : MessageReference { } + internal sealed class MessageAckEvent : MessageReference { } +} diff --git a/src/Discord.Net/API/Permissions.cs b/src/Discord.Net/API/Permissions.cs new file mode 100644 index 000000000..df8a82353 --- /dev/null +++ b/src/Discord.Net/API/Permissions.cs @@ -0,0 +1,21 @@ +//Ignore unused/unassigned variable warnings +#pragma warning disable CS0649 +#pragma warning disable CS0169 + +using Newtonsoft.Json; + +namespace Discord.API +{ + //Create/Edit + internal sealed class SetChannelPermissionsRequest + { + [JsonProperty("id")] + public string Id; + [JsonProperty("type")] + public string Type; + [JsonProperty("allow")] + public uint Allow; + [JsonProperty("deny")] + public uint Deny; + } +} diff --git a/src/Discord.Net/API/Presence.cs b/src/Discord.Net/API/Presence.cs new file mode 100644 index 000000000..07a1d8f56 --- /dev/null +++ b/src/Discord.Net/API/Presence.cs @@ -0,0 +1,33 @@ +//Ignore unused/unassigned variable warnings +#pragma warning disable CS0649 +#pragma warning disable CS0169 + +using Newtonsoft.Json; + +namespace Discord.API +{ + //Commands + internal sealed class UpdateStatusCommand : WebSocketMessage + { + public UpdateStatusCommand() : base(3) { } + public class Data + { + [JsonProperty("idle_since")] + public ulong? IdleSince; + [JsonProperty("game_id")] + public int? GameId; + } + } + + //Events + internal sealed class TypingStartEvent + { + [JsonProperty("user_id")] + public string UserId; + [JsonProperty("channel_id")] + public string ChannelId; + [JsonProperty("timestamp")] + public int Timestamp; + } + internal sealed class PresenceUpdateEvent : PresenceInfo { } +} diff --git a/src/Discord.Net/API/Requests.cs b/src/Discord.Net/API/Requests.cs deleted file mode 100644 index b60ee0fd8..000000000 --- a/src/Discord.Net/API/Requests.cs +++ /dev/null @@ -1,178 +0,0 @@ -//Ignore unused/unassigned variable warnings -#pragma warning disable CS0649 -#pragma warning disable CS0169 - -using Newtonsoft.Json; -using System.Collections.Generic; -using System.Collections; - -namespace Discord.API -{ - //Auth - internal sealed class RegisterRequest - { - [JsonProperty("fingerprint")] - public string Fingerprint; - [JsonProperty("username")] - public string Username; - } - internal sealed class LoginRequest - { - [JsonProperty("email")] - public string Email; - [JsonProperty("password")] - public string Password; - } - - //Channels - internal sealed class CreateChannelRequest - { - [JsonProperty("name")] - public string Name; - [JsonProperty("type")] - public string Type; - } - internal sealed class CreatePMChannelRequest - { - [JsonProperty("recipient_id")] - public string RecipientId; - } - internal sealed class EditChannelRequest - { - [JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)] - public string Name; - [JsonProperty("topic", NullValueHandling = NullValueHandling.Ignore)] - public string Topic; - } - internal sealed class ReorderChannelsRequest : IEnumerable - { - public sealed class Channel - { - [JsonProperty("id")] - public string Id; - [JsonProperty("position")] - public uint Position; - } - private IEnumerable _channels; - public ReorderChannelsRequest(IEnumerable channels) { _channels = channels; } - - public IEnumerator GetEnumerator() =>_channels.GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() => _channels.GetEnumerator(); - } - - //Invites - internal sealed class CreateInviteRequest - { - [JsonProperty("max_age")] - public int MaxAge; - [JsonProperty("max_uses")] - public int MaxUses; - [JsonProperty("temporary")] - public bool IsTemporary; - [JsonProperty("xkcdpass")] - public bool WithXkcdPass; - } - - //Members - internal sealed class EditMemberRequest - { - [JsonProperty(PropertyName = "mute", NullValueHandling = NullValueHandling.Ignore)] - public bool? Mute; - [JsonProperty(PropertyName = "deaf", NullValueHandling = NullValueHandling.Ignore)] - public bool? Deaf; - [JsonProperty(PropertyName = "roles", NullValueHandling = NullValueHandling.Ignore)] - public IEnumerable Roles; - } - - //Messages - internal sealed class SendMessageRequest - { - [JsonProperty("content")] - public string Content; - [JsonProperty("mentions")] - public IEnumerable Mentions; - [JsonProperty("nonce", NullValueHandling = NullValueHandling.Ignore)] - public string Nonce; - [JsonProperty("tts", NullValueHandling = NullValueHandling.Ignore)] - public bool IsTTS; - } - internal sealed class EditMessageRequest - { - [JsonProperty("content", NullValueHandling = NullValueHandling.Ignore)] - public string Content; - [JsonProperty("mentions", NullValueHandling = NullValueHandling.Ignore)] - public IEnumerable Mentions; - } - - //Permissions - internal sealed class SetChannelPermissionsRequest //Both creates and modifies - { - [JsonProperty("id")] - public string Id; - [JsonProperty("type")] - public string Type; - [JsonProperty("allow")] - public uint Allow; - [JsonProperty("deny")] - public uint Deny; - } - - //Profile - internal sealed class EditProfileRequest - { - [JsonProperty(PropertyName = "password")] - public string CurrentPassword; - [JsonProperty(PropertyName = "email", NullValueHandling = NullValueHandling.Ignore)] - public string Email; - [JsonProperty(PropertyName = "new_password")] - public string Password; - [JsonProperty(PropertyName = "username", NullValueHandling = NullValueHandling.Ignore)] - public string Username; - [JsonProperty(PropertyName = "avatar", NullValueHandling = NullValueHandling.Ignore)] - public string Avatar; - } - - //Roles - internal sealed class EditRoleRequest - { - [JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)] - public string Name; - [JsonProperty("permissions", NullValueHandling = NullValueHandling.Ignore)] - public uint? Permissions; - [JsonProperty("hoist", NullValueHandling = NullValueHandling.Ignore)] - public bool? Hoist; - [JsonProperty("color", NullValueHandling = NullValueHandling.Ignore)] - public uint? Color; - } - internal sealed class ReorderRolesRequest : IEnumerable - { - public sealed class Role - { - [JsonProperty("id")] - public string Id; - [JsonProperty("position")] - public uint Position; - } - private IEnumerable _roles; - public ReorderRolesRequest(IEnumerable roles) { _roles = roles; } - - public IEnumerator GetEnumerator() => _roles.GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() => _roles.GetEnumerator(); - } - - //Servers - internal sealed class CreateServerRequest - { - [JsonProperty("name")] - public string Name; - [JsonProperty("region")] - public string Region; - } - internal sealed class EditServerRequest - { - [JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)] - public string Name; - [JsonProperty("region", NullValueHandling = NullValueHandling.Ignore)] - public string Region; - } -} diff --git a/src/Discord.Net/API/Responses.cs b/src/Discord.Net/API/Responses.cs deleted file mode 100644 index c26dc701f..000000000 --- a/src/Discord.Net/API/Responses.cs +++ /dev/null @@ -1,106 +0,0 @@ -//Ignore unused/unassigned variable warnings -#pragma warning disable CS0649 -#pragma warning disable CS0169 - -using Newtonsoft.Json; -using System; -using System.Collections.Generic; - -namespace Discord.API -{ - //Auth - public sealed class GatewayResponse - { - [JsonProperty("url")] - public string Url; - } - public sealed class LoginResponse - { - [JsonProperty("token")] - public string Token; - } - - //Channels - public sealed class CreateChannelResponse : ChannelInfo { } - public sealed class DestroyChannelResponse : ChannelInfo { } - public sealed class EditChannelResponse : ChannelInfo { } - - //Invites - public sealed class CreateInviteResponse : ExtendedInvite { } - public sealed class GetInviteResponse : Invite { } - public sealed class AcceptInviteResponse : Invite { } - - //Messages - public sealed class SendMessageResponse : Message { } - public sealed class EditMessageResponse : Message { } - public sealed class GetMessagesResponse : List { } - - //Profile - public sealed class EditProfileResponse : SelfUserInfo { } - - //Roles - public sealed class CreateRoleResponse : RoleInfo { } - public sealed class EditRoleResponse : RoleInfo { } - - //Servers - public sealed class CreateServerResponse : GuildInfo { } - public sealed class DeleteServerResponse : GuildInfo { } - public sealed class EditServerResponse : GuildInfo { } - - //Voice - public sealed class GetRegionsResponse : List - { - public sealed class RegionData - { - [JsonProperty("sample_hostname")] - public string Hostname; - [JsonProperty("sample_port")] - public int Port; - [JsonProperty("id")] - public string Id; - [JsonProperty("name")] - public string Name; - } - } - public sealed class GetIceResponse - { - [JsonProperty("ttl")] - public string TTL; - [JsonProperty("servers")] - public ServerData[] Servers; - - public sealed class ServerData - { - [JsonProperty("url")] - public string URL; - [JsonProperty("username")] - public string Username; - [JsonProperty("credential")] - public string Credential; - } - } - - public sealed class GetIncidentsResponse - { - [JsonProperty("page")] - public PageData Page; - [JsonProperty("scheduled_maintenances")] - public MaintenanceData[] ScheduledMaintenances; - - public sealed class PageData - { - [JsonProperty("id")] - public string Id; - [JsonProperty("name")] - public string Name; - [JsonProperty("url")] - public string Url; - [JsonProperty("updated-at")] - public DateTime? UpdatedAt; - } - - public sealed class MaintenanceData - { - } - } -} diff --git a/src/Discord.Net/API/RestClient.BuiltIn.cs b/src/Discord.Net/API/RestClient.BuiltIn.cs deleted file mode 100644 index ec5b6bb61..000000000 --- a/src/Discord.Net/API/RestClient.BuiltIn.cs +++ /dev/null @@ -1,67 +0,0 @@ -/* -using Discord.API; -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.API -{ - internal class BuiltInRestEngine : IRestEngine - { - private readonly HttpClient _client; - - public BuiltInRestEngine(string userAgent, int timeout) - { - _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); - _client.Timeout = TimeSpan.FromMilliseconds(timeout); - } - - public void SetToken(string token) - { - _client.DefaultRequestHeaders.Remove("authorization"); - if (token != null) - _client.DefaultRequestHeaders.Add("authorization", token); - } - - public async Task Send(HttpMethod method, string path, string json, CancellationToken cancelToken) - { - using (var request = new HttpRequestMessage(method, Endpoints.BaseApi + path)) - { - if (json != null) - request.Content = new StringContent(json, Encoding.UTF8, "application/json"); - return await Send(request, cancelToken); - } - } - public async Task SendFile(HttpMethod method, string path, string filePath, CancellationToken cancelToken) - { - using (var request = new HttpRequestMessage(method, Endpoints.BaseApi + 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 await 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); - } - } -} -*/ \ No newline at end of file diff --git a/src/Discord.Net/API/Roles.cs b/src/Discord.Net/API/Roles.cs new file mode 100644 index 000000000..ba1517513 --- /dev/null +++ b/src/Discord.Net/API/Roles.cs @@ -0,0 +1,87 @@ +//Ignore unused/unassigned variable warnings +#pragma warning disable CS0649 +#pragma warning disable CS0169 + +using Newtonsoft.Json; +using System.Collections; +using System.Collections.Generic; + +namespace Discord.API +{ + //Common + public class RoleReference + { + [JsonProperty("guild_id")] + public string GuildId; + [JsonProperty("role_id")] + public string RoleId; + } + public class RoleInfo + { + [JsonProperty("permissions")] + public uint? Permissions; + [JsonProperty("name")] + public string Name; + [JsonProperty("position")] + public int? Position; + [JsonProperty("hoist")] + public bool? Hoist; + [JsonProperty("color")] + public uint? Color; + [JsonProperty("id")] + public string Id; + [JsonProperty("managed")] + public bool? Managed; + } + + //Create + public sealed class CreateRoleResponse : RoleInfo { } + + //Edit + public sealed class EditRoleRequest + { + [JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)] + public string Name; + [JsonProperty("permissions", NullValueHandling = NullValueHandling.Ignore)] + public uint? Permissions; + [JsonProperty("hoist", NullValueHandling = NullValueHandling.Ignore)] + public bool? Hoist; + [JsonProperty("color", NullValueHandling = NullValueHandling.Ignore)] + public uint? Color; + } + public sealed class EditRoleResponse : RoleInfo { } + + //Reorder + public sealed class ReorderRolesRequest : IEnumerable + { + public sealed class Role + { + [JsonProperty("id")] + public string Id; + [JsonProperty("position")] + public uint Position; + } + private IEnumerable _roles; + public ReorderRolesRequest(IEnumerable roles) { _roles = roles; } + + public IEnumerator GetEnumerator() => _roles.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => _roles.GetEnumerator(); + } + + //Events + internal sealed class RoleCreateEvent + { + [JsonProperty("guild_id")] + public string GuildId; + [JsonProperty("role")] + public RoleInfo Data; + } + internal sealed class RoleUpdateEvent + { + [JsonProperty("guild_id")] + public string GuildId; + [JsonProperty("role")] + public RoleInfo Data; + } + internal sealed class RoleDeleteEvent : RoleReference { } +} diff --git a/src/Discord.Net/API/Servers.cs b/src/Discord.Net/API/Servers.cs new file mode 100644 index 000000000..b86a94ffa --- /dev/null +++ b/src/Discord.Net/API/Servers.cs @@ -0,0 +1,80 @@ +//Ignore unused/unassigned variable warnings +#pragma warning disable CS0649 +#pragma warning disable CS0169 + +using Newtonsoft.Json; +using System; + +namespace Discord.API +{ + //Common + public class GuildReference + { + [JsonProperty("id")] + public string Id; + [JsonProperty("name")] + public string Name; + } + public class GuildInfo : GuildReference + { + [JsonProperty("afk_channel_id")] + public string AFKChannelId; + [JsonProperty("afk_timeout")] + public int AFKTimeout; + [JsonProperty("embed_channel_id")] + public string EmbedChannelId; + [JsonProperty("embed_enabled")] + public bool EmbedEnabled; + [JsonProperty("icon")] + public string Icon; + [JsonProperty("joined_at")] + public DateTime? JoinedAt; + [JsonProperty("owner_id")] + public string OwnerId; + [JsonProperty("region")] + public string Region; + [JsonProperty("roles")] + public RoleInfo[] Roles; + } + public class ExtendedGuildInfo : GuildInfo + { + [JsonProperty("channels")] + public ChannelInfo[] Channels; + [JsonProperty("members")] + public ExtendedMemberInfo[] Members; + [JsonProperty("presences")] + public PresenceInfo[] Presences; + [JsonProperty("voice_states")] + public VoiceMemberInfo[] VoiceStates; + [JsonProperty("unavailable")] + public bool Unavailable; + } + + //Create + internal sealed class CreateServerRequest + { + [JsonProperty("name")] + public string Name; + [JsonProperty("region")] + public string Region; + } + public sealed class CreateServerResponse : GuildInfo { } + + //Edit + internal sealed class EditServerRequest + { + [JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)] + public string Name; + [JsonProperty("region", NullValueHandling = NullValueHandling.Ignore)] + public string Region; + } + public sealed class EditServerResponse : GuildInfo { } + + //Delete + public sealed class DeleteServerResponse : GuildInfo { } + + //Events + internal sealed class GuildCreateEvent : ExtendedGuildInfo { } + internal sealed class GuildUpdateEvent : GuildInfo { } + internal sealed class GuildDeleteEvent : ExtendedGuildInfo { } +} diff --git a/src/Discord.Net/API/Users.cs b/src/Discord.Net/API/Users.cs new file mode 100644 index 000000000..52bb8d6a2 --- /dev/null +++ b/src/Discord.Net/API/Users.cs @@ -0,0 +1,47 @@ +//Ignore unused/unassigned variable warnings +#pragma warning disable CS0649 +#pragma warning disable CS0169 + +using Newtonsoft.Json; + +namespace Discord.API +{ + //Common + public class UserReference + { + [JsonProperty("username")] + public string Username; + [JsonProperty("id")] + public string Id; + [JsonProperty("discriminator")] + public string Discriminator; + [JsonProperty("avatar")] + public string Avatar; + } + public class UserInfo : UserReference + { + [JsonProperty("email")] + public string Email; + [JsonProperty("verified")] + public bool? IsVerified; + } + + //Edit + internal sealed class EditUserRequest + { + [JsonProperty(PropertyName = "password")] + public string CurrentPassword; + [JsonProperty(PropertyName = "email", NullValueHandling = NullValueHandling.Ignore)] + public string Email; + [JsonProperty(PropertyName = "new_password")] + public string Password; + [JsonProperty(PropertyName = "username", NullValueHandling = NullValueHandling.Ignore)] + public string Username; + [JsonProperty(PropertyName = "avatar", NullValueHandling = NullValueHandling.Ignore)] + public string Avatar; + } + public sealed class EditUserResponse : UserInfo { } + + //Events + internal sealed class UserUpdateEvent : UserInfo { } +} diff --git a/src/Discord.Net/API/Voice.cs b/src/Discord.Net/API/Voice.cs new file mode 100644 index 000000000..2bf30fe53 --- /dev/null +++ b/src/Discord.Net/API/Voice.cs @@ -0,0 +1,153 @@ +//Ignore unused/unassigned variable warnings +#pragma warning disable CS0649 +#pragma warning disable CS0169 + +using Newtonsoft.Json; +using System.Collections.Generic; + +namespace Discord.API +{ + public class GetRegionsResponse : List + { + public sealed class RegionData + { + [JsonProperty("sample_hostname")] + public string Hostname; + [JsonProperty("sample_port")] + public int Port; + [JsonProperty("id")] + public string Id; + [JsonProperty("name")] + public string Name; + } + } + + public class GetIceResponse + { + [JsonProperty("ttl")] + public string TTL; + [JsonProperty("servers")] + public ServerData[] Servers; + + public sealed class ServerData + { + [JsonProperty("url")] + public string URL; + [JsonProperty("username")] + public string Username; + [JsonProperty("credential")] + public string Credential; + } + } + + //Commands + internal sealed class JoinVoiceCommand : WebSocketMessage + { + public JoinVoiceCommand() : base(4) { } + public class Data + { + [JsonProperty("guild_id")] + public string ServerId; + [JsonProperty("channel_id")] + public string ChannelId; + [JsonProperty("self_mute")] + public string SelfMute; + [JsonProperty("self_deaf")] + public string SelfDeaf; + } + } + + //Events + internal sealed class VoiceServerUpdateEvent + { + [JsonProperty("guild_id")] + public string GuildId; + [JsonProperty("endpoint")] + public string Endpoint; + [JsonProperty("token")] + public string Token; + } + + //Commands (Voice) + internal sealed class VoiceLoginCommand : WebSocketMessage + { + public VoiceLoginCommand() : base(0) { } + public class Data + { + [JsonProperty("server_id")] + public string ServerId; + [JsonProperty("user_id")] + public string UserId; + [JsonProperty("session_id")] + public string SessionId; + [JsonProperty("token")] + public string Token; + } + } + internal sealed class VoiceLogin2Command : WebSocketMessage + { + public VoiceLogin2Command() : base(1) { } + public class Data + { + public class SocketInfo + { + [JsonProperty("address")] + public string Address; + [JsonProperty("port")] + public int Port; + [JsonProperty("mode")] + public string Mode = "xsalsa20_poly1305"; + } + [JsonProperty("protocol")] + public string Protocol = "udp"; + [JsonProperty("data")] + public SocketInfo SocketData = new SocketInfo(); + } + } + internal sealed class VoiceKeepAliveCommand : WebSocketMessage + { + public VoiceKeepAliveCommand() : base(3, null) { } + } + internal sealed class IsTalkingCommand : WebSocketMessage + { + public IsTalkingCommand() : base(5) { } + public class Data + { + [JsonProperty("delay")] + public int Delay; + [JsonProperty("speaking")] + public bool IsSpeaking; + } + } + + //Events (Voice) + public class VoiceReadyEvent + { + [JsonProperty("ssrc")] + public uint SSRC; + [JsonProperty("port")] + public ushort Port; + [JsonProperty("modes")] + public string[] Modes; + [JsonProperty("heartbeat_interval")] + public int HeartbeatInterval; + } + + public class JoinServerEvent + { + [JsonProperty("secret_key")] + public byte[] SecretKey; + [JsonProperty("mode")] + public string Mode; + } + + public class IsTalkingEvent + { + [JsonProperty("user_id")] + public string UserId; + [JsonProperty("ssrc")] + public uint SSRC; + [JsonProperty("speaking")] + public bool IsSpeaking; + } +} diff --git a/src/Discord.Net/API/WebSockets.cs b/src/Discord.Net/API/WebSockets.cs new file mode 100644 index 000000000..246bd2c6a --- /dev/null +++ b/src/Discord.Net/API/WebSockets.cs @@ -0,0 +1,112 @@ +//Ignore unused/unassigned variable warnings +#pragma warning disable CS0649 +#pragma warning disable CS0169 + +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System.Collections.Generic; + +namespace Discord.API +{ + //Common + public class WebSocketMessage + { + [JsonProperty("op")] + public int Operation; + [JsonProperty("d")] + public object Payload; + [JsonProperty("t", NullValueHandling = NullValueHandling.Ignore)] + public string Type; + [JsonProperty("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; } + } + } + + //Commands + internal sealed class KeepAliveCommand : WebSocketMessage + { + public KeepAliveCommand() : base(1, EpochTime.GetMilliseconds()) { } + } + internal sealed class LoginCommand : WebSocketMessage + { + public LoginCommand() : base(2) { } + public class Data + { + [JsonProperty("token")] + public string Token; + [JsonProperty("v")] + public int Version = 3; + [JsonProperty("properties")] + public Dictionary Properties = new Dictionary(); + } + } + internal sealed class ResumeCommand : WebSocketMessage + { + public ResumeCommand() : base(6) { } + public class Data + { + [JsonProperty("session_id")] + public string SessionId; + [JsonProperty("seq")] + public int Sequence; + } + } + + //Events + internal sealed class ReadyEvent + { + public sealed class ReadStateInfo + { + [JsonProperty("id")] + public string ChannelId; + [JsonProperty("mention_count")] + public int MentionCount; + [JsonProperty("last_message_id")] + public string LastMessageId; + } + + [JsonProperty("v")] + public int Version; + [JsonProperty("user")] + public UserInfo User; + [JsonProperty("session_id")] + public string SessionId; + [JsonProperty("read_state")] + public ReadStateInfo[] ReadState; + [JsonProperty("guilds")] + public ExtendedGuildInfo[] Guilds; + [JsonProperty("private_channels")] + public ChannelInfo[] PrivateChannels; + [JsonProperty("heartbeat_interval")] + public int HeartbeatInterval; + } + internal sealed class ResumedEvent + { + [JsonProperty("heartbeat_interval")] + public int HeartbeatInterval; + } + + internal sealed class RedirectEvent + { + [JsonProperty("url")] + public string Url; + } +} diff --git a/src/Discord.Net/DiscordClient.API.cs b/src/Discord.Net/DiscordClient.API.cs index b90f23d52..d8642f9ba 100644 --- a/src/Discord.Net/DiscordClient.API.cs +++ b/src/Discord.Net/DiscordClient.API.cs @@ -1,4 +1,5 @@ using Discord.API; +using Discord.Net; using System; using System.Collections.Generic; using System.Linq; @@ -312,7 +313,7 @@ namespace Discord { var msg = _messages.GetOrAdd("nonce_" + nonce, channel.Id, CurrentUserId); var currentMember = _members[msg.UserId, channel.ServerId]; - msg.Update(new API.Message + msg.Update(new MessageInfo { Content = blockText, Timestamp = DateTime.UtcNow, @@ -611,26 +612,20 @@ namespace Discord } //Profile - public Task EditProfile(string currentPassword = "", + public Task EditProfile(string currentPassword = "", string username = null, string email = null, string password = null, AvatarImageType avatarType = AvatarImageType.Png, byte[] avatar = null) { if (currentPassword == null) throw new ArgumentNullException(nameof(currentPassword)); - return _api.EditProfile(currentPassword: currentPassword, username: username ?? _currentUser?.Name, email: email ?? _currentUser?.Email, password: password, + return _api.EditUser(currentPassword: currentPassword, username: username ?? _currentUser?.Name, email: email ?? _currentUser?.Email, password: password, avatarType: avatarType, avatar: avatar); } public Task SetStatus(string status) { - switch (status) - { - case UserStatus.Online: - case UserStatus.Away: - _status = status; - break; - default: - throw new ArgumentException($"Invalid status, must be {UserStatus.Online} or {UserStatus.Away}"); - } + if (status != UserStatus.Online && status != UserStatus.Idle) + throw new ArgumentException($"Invalid status, must be {UserStatus.Online} or {UserStatus.Idle}"); + _status = status; return SendStatus(); } public Task SetGame(int? gameId) @@ -640,7 +635,7 @@ namespace Discord } private Task SendStatus() { - _dataSocket.SendStatus(_status == UserStatus.Away ? EpochTime.GetMilliseconds() - (10 * 60 * 1000) : (ulong?)null, _gameId); + _dataSocket.SendStatus(_status == UserStatus.Idle ? EpochTime.GetMilliseconds() - (10 * 60 * 1000) : (ulong?)null, _gameId); return TaskHelper.CompletedTask; } diff --git a/src/Discord.Net/DiscordClient.cs b/src/Discord.Net/DiscordClient.cs index be80cddb1..f32fdd487 100644 --- a/src/Discord.Net/DiscordClient.cs +++ b/src/Discord.Net/DiscordClient.cs @@ -1,7 +1,6 @@ using Discord.API; using Discord.Collections; -using Discord.WebSockets; -using Discord.WebSockets.Data; +using Discord.Net; using Newtonsoft.Json; using System; using System.Collections.Concurrent; @@ -9,7 +8,6 @@ using System.Collections.Generic; using System.Linq; using System.Net; using System.Threading.Tasks; -using VoiceWebSocket = Discord.WebSockets.Voice.VoiceWebSocket; namespace Discord { @@ -474,7 +472,7 @@ namespace Discord //Members case "GUILD_MEMBER_ADD": { - var data = e.Payload.ToObject(_serializer); + var data = e.Payload.ToObject(_serializer); var user = _users.GetOrAdd(data.User.Id); user.Update(data.User); var member = _members.GetOrAdd(data.User.Id, data.GuildId); @@ -486,7 +484,7 @@ namespace Discord break; case "GUILD_MEMBER_UPDATE": { - var data = e.Payload.ToObject(_serializer); + var data = e.Payload.ToObject(_serializer); var member = _members[data.User.Id, data.GuildId]; if (member != null) { @@ -497,7 +495,7 @@ namespace Discord break; case "GUILD_MEMBER_REMOVE": { - var data = e.Payload.ToObject(_serializer); + var data = e.Payload.ToObject(_serializer); var member = _members.TryRemove(data.UserId, data.GuildId); if (member != null) RaiseUserRemoved(member); @@ -507,7 +505,7 @@ namespace Discord //Roles case "GUILD_ROLE_CREATE": { - var data = e.Payload.ToObject(_serializer); + var data = e.Payload.ToObject(_serializer); var role = _roles.GetOrAdd(data.Data.Id, data.GuildId, false); role.Update(data.Data); var server = _servers[data.GuildId]; @@ -518,7 +516,7 @@ namespace Discord break; case "GUILD_ROLE_UPDATE": { - var data = e.Payload.ToObject(_serializer); + var data = e.Payload.ToObject(_serializer); var role = _roles[data.Data.Id]; if (role != null) role.Update(data.Data); @@ -527,7 +525,7 @@ namespace Discord break; case "GUILD_ROLE_DELETE": { - var data = e.Payload.ToObject(_serializer); + var data = e.Payload.ToObject(_serializer); var server = _servers[data.GuildId]; if (server != null) server.RemoveRole(data.RoleId); @@ -540,7 +538,7 @@ namespace Discord //Bans case "GUILD_BAN_ADD": { - var data = e.Payload.ToObject(_serializer); + var data = e.Payload.ToObject(_serializer); var server = _servers[data.GuildId]; if (server != null) { @@ -551,7 +549,7 @@ namespace Discord break; case "GUILD_BAN_REMOVE": { - var data = e.Payload.ToObject(_serializer); + var data = e.Payload.ToObject(_serializer); var server = _servers[data.GuildId]; if (server != null && server.RemoveBan(data.User?.Id)) RaiseBanRemoved(data.User?.Id, server); @@ -682,7 +680,7 @@ namespace Discord //Voice case "VOICE_STATE_UPDATE": { - var data = e.Payload.ToObject(_serializer); + var data = e.Payload.ToObject(_serializer); var member = _members[data.UserId, data.GuildId]; if (member != null) { diff --git a/src/Discord.Net/DiscordSimpleClient.cs b/src/Discord.Net/DiscordSimpleClient.cs index d9bb88e7f..9dde13ef0 100644 --- a/src/Discord.Net/DiscordSimpleClient.cs +++ b/src/Discord.Net/DiscordSimpleClient.cs @@ -1,11 +1,10 @@ -using Discord.WebSockets.Data; +using Discord.Net; using System; using System.Collections.Generic; using System.Linq; using System.Runtime.ExceptionServices; using System.Threading; using System.Threading.Tasks; -using VoiceWebSocket = Discord.WebSockets.Voice.VoiceWebSocket; namespace Discord { diff --git a/src/Discord.Net/Helpers/Extensions.cs b/src/Discord.Net/Helpers/Extensions.cs index 20e6a6d41..45aad0c5b 100644 --- a/src/Discord.Net/Helpers/Extensions.cs +++ b/src/Discord.Net/Helpers/Extensions.cs @@ -6,6 +6,53 @@ namespace Discord { internal static class Extensions { + public static async Task Timeout(this Task self, int milliseconds) + { + Task timeoutTask = Task.Delay(milliseconds); + Task finishedTask = await Task.WhenAny(self, timeoutTask).ConfigureAwait(false); + if (finishedTask == timeoutTask) + throw new TimeoutException(); + else + await self.ConfigureAwait(false); + } + public static async Task Timeout(this Task self, int milliseconds) + { + Task timeoutTask = Task.Delay(milliseconds); + Task finishedTask = await Task.WhenAny(self, timeoutTask).ConfigureAwait(false); + if (finishedTask == timeoutTask) + throw new TimeoutException(); + else + return await self.ConfigureAwait(false); + } + public static async Task Timeout(this Task self, int milliseconds, CancellationTokenSource timeoutToken) + { + try + { + timeoutToken.CancelAfter(milliseconds); + await self.ConfigureAwait(false); + } + catch (OperationCanceledException) + { + if (timeoutToken.IsCancellationRequested) + throw new TimeoutException(); + throw; + } + } + public static async Task Timeout(this Task self, int milliseconds, CancellationTokenSource timeoutToken) + { + try + { + timeoutToken.CancelAfter(milliseconds); + return await self.ConfigureAwait(false); + } + catch (OperationCanceledException) + { + if (timeoutToken.IsCancellationRequested) + throw new TimeoutException(); + throw; + } + } + public static async Task Wait(this CancellationTokenSource tokenSource) { var token = tokenSource.Token; diff --git a/src/Discord.Net/Format.cs b/src/Discord.Net/Helpers/Format.cs similarity index 100% rename from src/Discord.Net/Format.cs rename to src/Discord.Net/Helpers/Format.cs diff --git a/src/Discord.Net/Mention.cs b/src/Discord.Net/Helpers/Mention.cs similarity index 100% rename from src/Discord.Net/Mention.cs rename to src/Discord.Net/Helpers/Mention.cs diff --git a/src/Discord.Net/Helpers/CollectionHelper.cs b/src/Discord.Net/Helpers/Shared/CollectionHelper.cs similarity index 100% rename from src/Discord.Net/Helpers/CollectionHelper.cs rename to src/Discord.Net/Helpers/Shared/CollectionHelper.cs diff --git a/src/Discord.Net/Shared/TaskHelper.cs b/src/Discord.Net/Helpers/Shared/TaskHelper.cs similarity index 100% rename from src/Discord.Net/Shared/TaskHelper.cs rename to src/Discord.Net/Helpers/Shared/TaskHelper.cs diff --git a/src/Discord.Net/Helpers/TaskExtensions.cs b/src/Discord.Net/Helpers/TaskExtensions.cs deleted file mode 100644 index fa555dc0a..000000000 --- a/src/Discord.Net/Helpers/TaskExtensions.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace Discord -{ - public static class TaskExtensions - { - public static async Task Timeout(this Task self, int milliseconds) - { - Task timeoutTask = Task.Delay(milliseconds); - Task finishedTask = await Task.WhenAny(self, timeoutTask); - if (finishedTask == timeoutTask) - throw new TimeoutException(); - else - await self; - } - public static async Task Timeout(this Task self, int milliseconds) - { - Task timeoutTask = Task.Delay(milliseconds); - Task finishedTask = await Task.WhenAny(self, timeoutTask).ConfigureAwait(false); - if (finishedTask == timeoutTask) - throw new TimeoutException(); - else - return await self.ConfigureAwait(false); - } - public static async Task Timeout(this Task self, int milliseconds, CancellationTokenSource timeoutToken) - { - try - { - timeoutToken.CancelAfter(milliseconds); - await self.ConfigureAwait(false); - } - catch (OperationCanceledException) - { - if (timeoutToken.IsCancellationRequested) - throw new TimeoutException(); - throw; - } - } - public static async Task Timeout(this Task self, int milliseconds, CancellationTokenSource timeoutToken) - { - try - { - timeoutToken.CancelAfter(milliseconds); - return await self.ConfigureAwait(false); - } - catch (OperationCanceledException) - { - if (timeoutToken.IsCancellationRequested) - throw new TimeoutException(); - throw; - } - } - } -} diff --git a/src/Discord.Net/TimeoutException.cs b/src/Discord.Net/Helpers/TimeoutException.cs similarity index 100% rename from src/Discord.Net/TimeoutException.cs rename to src/Discord.Net/Helpers/TimeoutException.cs diff --git a/src/Discord.Net/Audio/Opus.cs b/src/Discord.Net/Interop/Opus.cs similarity index 99% rename from src/Discord.Net/Audio/Opus.cs rename to src/Discord.Net/Interop/Opus.cs index 20f65be06..78c7b39d9 100644 --- a/src/Discord.Net/Audio/Opus.cs +++ b/src/Discord.Net/Interop/Opus.cs @@ -1,7 +1,7 @@ using System; using System.Runtime.InteropServices; -namespace Discord.Audio +namespace Discord.Interop { internal unsafe static class Opus { diff --git a/src/Discord.Net/Audio/OpusDecoder.cs b/src/Discord.Net/Interop/OpusDecoder.cs similarity index 99% rename from src/Discord.Net/Audio/OpusDecoder.cs rename to src/Discord.Net/Interop/OpusDecoder.cs index e6c9cd400..0341a8acf 100644 --- a/src/Discord.Net/Audio/OpusDecoder.cs +++ b/src/Discord.Net/Interop/OpusDecoder.cs @@ -1,6 +1,6 @@ using System; -namespace Discord.Audio +namespace Discord.Interop { /// Opus codec wrapper. internal class OpusDecoder : IDisposable diff --git a/src/Discord.Net/Audio/OpusEncoder.cs b/src/Discord.Net/Interop/OpusEncoder.cs similarity index 99% rename from src/Discord.Net/Audio/OpusEncoder.cs rename to src/Discord.Net/Interop/OpusEncoder.cs index c5e033a2b..d6209927f 100644 --- a/src/Discord.Net/Audio/OpusEncoder.cs +++ b/src/Discord.Net/Interop/OpusEncoder.cs @@ -1,6 +1,6 @@ using System; -namespace Discord.Audio +namespace Discord.Interop { /// Opus codec wrapper. internal class OpusEncoder : IDisposable diff --git a/src/Discord.Net/Audio/Sodium.cs b/src/Discord.Net/Interop/Sodium.cs similarity index 97% rename from src/Discord.Net/Audio/Sodium.cs rename to src/Discord.Net/Interop/Sodium.cs index ec7dde612..8d5d7e3cd 100644 --- a/src/Discord.Net/Audio/Sodium.cs +++ b/src/Discord.Net/Interop/Sodium.cs @@ -1,6 +1,6 @@ using System.Runtime.InteropServices; -namespace Discord.Audio +namespace Discord.Interop { internal unsafe static class Sodium { diff --git a/src/Discord.Net/Models/Channel.cs b/src/Discord.Net/Models/Channel.cs index 106335e8a..b7df1a2df 100644 --- a/src/Discord.Net/Models/Channel.cs +++ b/src/Discord.Net/Models/Channel.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json; +using Discord.API; +using Newtonsoft.Json; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; @@ -99,16 +100,16 @@ namespace Discord _areMembersStale = true; } - internal void Update(API.ChannelReference model) + internal void Update(ChannelReference model) { if (model.Name != null) Name = model.Name; if (model.Type != null) Type = model.Type; } - internal void Update(API.ChannelInfo model) + internal void Update(ChannelInfo model) { - Update(model as API.ChannelReference); + Update(model as ChannelReference); if (model.Position != null) Position = model.Position.Value; diff --git a/src/Discord.Net/Models/Invite.cs b/src/Discord.Net/Models/Invite.cs index 1c523fc16..bdf1e6518 100644 --- a/src/Discord.Net/Models/Invite.cs +++ b/src/Discord.Net/Models/Invite.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json; +using Discord.API; +using Newtonsoft.Json; namespace Discord { @@ -53,7 +54,7 @@ namespace Discord public override string ToString() => XkcdPass ?? Id; - internal void Update(API.Invite model) + internal void Update(InviteReference model) { if (model.Channel != null) ChannelId = model.Channel.Id; @@ -61,9 +62,9 @@ namespace Discord InviterId = model.Inviter.Id; } - internal void Update(API.ExtendedInvite model) + internal void Update(InviteInfo model) { - Update(model as API.Invite); + Update(model as InviteReference); if (model.IsRevoked != null) IsRevoked = model.IsRevoked.Value; if (model.IsTemporary != null) diff --git a/src/Discord.Net/Models/Member.cs b/src/Discord.Net/Models/Member.cs index c784fbb47..4360465de 100644 --- a/src/Discord.Net/Models/Member.cs +++ b/src/Discord.Net/Models/Member.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json; +using Discord.API; +using Newtonsoft.Json; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -77,7 +78,7 @@ namespace Discord public override string ToString() => UserId; - internal void Update(API.UserReference model) + internal void Update(UserReference model) { if (model.Avatar != null) AvatarId = model.Avatar; @@ -86,7 +87,7 @@ namespace Discord if (model.Username != null) Name = model.Username; } - internal void Update(API.MemberInfo model) + internal void Update(MemberInfo model) { if (model.User != null) Update(model.User); @@ -102,7 +103,7 @@ namespace Discord UpdatePermissions(); } - internal void Update(API.ExtendedMemberInfo model) + internal void Update(ExtendedMemberInfo model) { Update(model as API.MemberInfo); if (model.IsServerDeafened != null) @@ -110,7 +111,7 @@ namespace Discord if (model.IsServerMuted != null) IsServerMuted = model.IsServerMuted.Value; } - internal void Update(API.PresenceMemberInfo model) + internal void Update(PresenceInfo model) { //Allows null if (Status != model.Status) @@ -121,7 +122,7 @@ namespace Discord } GameId = model.GameId; } - internal void Update(API.VoiceMemberInfo model) + internal void Update(VoiceMemberInfo model) { if (model.IsServerDeafened != null) IsServerDeafened = model.IsServerDeafened.Value; diff --git a/src/Discord.Net/Models/Message.cs b/src/Discord.Net/Models/Message.cs index b0cab1347..3d00a1265 100644 --- a/src/Discord.Net/Models/Message.cs +++ b/src/Discord.Net/Models/Message.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json; +using Discord.API; +using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Linq; @@ -175,7 +176,7 @@ namespace Discord MentionIds = _initialMentions; } - internal void Update(API.Message model) + internal void Update(MessageInfo model) { if (model.Attachments != null) { diff --git a/src/Discord.Net/Models/Role.cs b/src/Discord.Net/Models/Role.cs index 50c889e06..678bb31e1 100644 --- a/src/Discord.Net/Models/Role.cs +++ b/src/Discord.Net/Models/Role.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json; +using Discord.API; +using Newtonsoft.Json; using System.Collections.Generic; using System.Linq; @@ -52,7 +53,7 @@ namespace Discord Position = int.MinValue; } - internal void Update(API.RoleInfo model) + internal void Update(RoleInfo model) { if (model.Name != null) Name = model.Name; diff --git a/src/Discord.Net/Models/Server.cs b/src/Discord.Net/Models/Server.cs index 3d6554d42..c3f59fd45 100644 --- a/src/Discord.Net/Models/Server.cs +++ b/src/Discord.Net/Models/Server.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json; +using Discord.API; +using Newtonsoft.Json; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -103,7 +104,7 @@ namespace Discord _roles = new ConcurrentDictionary(); } - internal void Update(API.GuildInfo model) + internal void Update(GuildInfo model) { AFKChannelId = model.AFKChannelId; AFKTimeout = model.AFKTimeout; @@ -124,9 +125,9 @@ namespace Discord isEveryone = false; } } - internal void Update(API.ExtendedGuildInfo model) + internal void Update(ExtendedGuildInfo model) { - Update(model as API.GuildInfo); + Update(model as GuildInfo); var channels = _client.Channels; foreach (var subModel in model.Channels) diff --git a/src/Discord.Net/Models/User.cs b/src/Discord.Net/Models/User.cs index d225c9b6d..a0104bcb3 100644 --- a/src/Discord.Net/Models/User.cs +++ b/src/Discord.Net/Models/User.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json; +using Discord.API; +using Newtonsoft.Json; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -77,7 +78,7 @@ namespace Discord _servers = new ConcurrentDictionary(); } - internal void Update(API.UserReference model) + internal void Update(UserReference model) { if (model.Avatar != null) AvatarId = model.Avatar; @@ -86,11 +87,14 @@ namespace Discord if (model.Username != null) Name = model.Username; } - internal void Update(API.SelfUserInfo model) + internal void Update(UserInfo model) { - Update(model as API.UserReference); - Email = model.Email; - IsVerified = model.IsVerified; + Update(model as UserReference); + + if (model.Email != null) + Email = model.Email; + if (model.IsVerified != null) + IsVerified = model.IsVerified; } internal void UpdateActivity(DateTime? activity = null) { diff --git a/src/Discord.Net/WebSockets/Data/DataWebSocket.cs b/src/Discord.Net/Net/DataWebSocket.cs similarity index 98% rename from src/Discord.Net/WebSockets/Data/DataWebSocket.cs rename to src/Discord.Net/Net/DataWebSocket.cs index 53e917f9f..d95423348 100644 --- a/src/Discord.Net/WebSockets/Data/DataWebSocket.cs +++ b/src/Discord.Net/Net/DataWebSocket.cs @@ -1,9 +1,10 @@ -using Newtonsoft.Json; +using Discord.API; +using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; using System.Threading.Tasks; -namespace Discord.WebSockets.Data +namespace Discord.Net { internal partial class DataWebSocket : WebSocket { diff --git a/src/Discord.Net/WebSockets/Data/DataWebSockets.Events.cs b/src/Discord.Net/Net/DataWebSockets.Events.cs similarity index 94% rename from src/Discord.Net/WebSockets/Data/DataWebSockets.Events.cs rename to src/Discord.Net/Net/DataWebSockets.Events.cs index 410829069..6f5177fe1 100644 --- a/src/Discord.Net/WebSockets/Data/DataWebSockets.Events.cs +++ b/src/Discord.Net/Net/DataWebSockets.Events.cs @@ -1,7 +1,7 @@ using Newtonsoft.Json.Linq; using System; -namespace Discord.WebSockets.Data +namespace Discord.Net { internal sealed class WebSocketEventEventArgs : EventArgs { diff --git a/src/Discord.Net/DiscordAPIClient.cs b/src/Discord.Net/Net/DiscordAPIClient.cs similarity index 96% rename from src/Discord.Net/DiscordAPIClient.cs rename to src/Discord.Net/Net/DiscordAPIClient.cs index 44c5b6dc4..bb5f0fb06 100644 --- a/src/Discord.Net/DiscordAPIClient.cs +++ b/src/Discord.Net/Net/DiscordAPIClient.cs @@ -5,7 +5,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -namespace Discord +namespace Discord.Net { /// A lightweight wrapper around the Discord API. public class DiscordAPIClient @@ -225,26 +225,6 @@ namespace Discord return _rest.Delete(Endpoints.ChannelPermission(channelId, userOrRoleId), null); } - //Profile - public Task EditProfile(string currentPassword = "", - string username = null, string email = null, string password = null, - AvatarImageType avatarType = AvatarImageType.Png, byte[] avatar = null) - { - if (currentPassword == null) throw new ArgumentNullException(nameof(currentPassword)); - - string avatarBase64 = null; - if (avatarType == AvatarImageType.None) - avatarBase64 = ""; - else if (avatar != null) - { - string base64 = Convert.ToBase64String(avatar); - string type = avatarType == AvatarImageType.Jpeg ? "image/jpeg;base64" : "image/png;base64"; - avatarBase64 = $"data:{type},{base64}"; - } - var request = new EditProfileRequest { CurrentPassword = currentPassword, Username = username, Email = email, Password = password, Avatar = avatarBase64 }; - return _rest.Patch(Endpoints.UserMe, request); - } - //Roles public Task CreateRole(string serverId) { @@ -302,10 +282,30 @@ namespace Discord return _rest.Patch(Endpoints.Server(serverId), request); } + //User + public Task EditUser(string currentPassword = "", + string username = null, string email = null, string password = null, + AvatarImageType avatarType = AvatarImageType.Png, byte[] avatar = null) + { + if (currentPassword == null) throw new ArgumentNullException(nameof(currentPassword)); + + string avatarBase64 = null; + if (avatarType == AvatarImageType.None) + avatarBase64 = ""; + else if (avatar != null) + { + string base64 = Convert.ToBase64String(avatar); + string type = avatarType == AvatarImageType.Jpeg ? "image/jpeg;base64" : "image/png;base64"; + avatarBase64 = $"data:{type},{base64}"; + } + var request = new EditUserRequest { CurrentPassword = currentPassword, Username = username, Email = email, Password = password, Avatar = avatarBase64 }; + return _rest.Patch(Endpoints.UserMe, request); + } + //Voice public Task GetVoiceRegions() => _rest.Get(Endpoints.VoiceRegions); - public Task GetVoiceIce() - => _rest.Get(Endpoints.VoiceIce); + /*public Task GetVoiceIce() + => _rest.Get(Endpoints.VoiceIce);*/ } } diff --git a/src/Discord.Net/API/HttpException.cs b/src/Discord.Net/Net/HttpException.cs similarity index 68% rename from src/Discord.Net/API/HttpException.cs rename to src/Discord.Net/Net/HttpException.cs index e0dba7d06..84ae66530 100644 --- a/src/Discord.Net/API/HttpException.cs +++ b/src/Discord.Net/Net/HttpException.cs @@ -1,14 +1,14 @@ using System; using System.Net; -namespace Discord.API +namespace Discord.Net { public class HttpException : Exception { public HttpStatusCode StatusCode { get; } public HttpException(HttpStatusCode statusCode) - : base($"The server responded with error {statusCode}") + : base($"The server responded with error {(int)statusCode} ({statusCode})") { StatusCode = statusCode; } diff --git a/src/Discord.Net/API/RestClient.Events.cs b/src/Discord.Net/Net/RestClient.Events.cs similarity index 97% rename from src/Discord.Net/API/RestClient.Events.cs rename to src/Discord.Net/Net/RestClient.Events.cs index d5d257d49..85c4d02a1 100644 --- a/src/Discord.Net/API/RestClient.Events.cs +++ b/src/Discord.Net/Net/RestClient.Events.cs @@ -1,7 +1,7 @@ using System; using System.Net.Http; -namespace Discord.API +namespace Discord.Net { internal partial class RestClient { diff --git a/src/Discord.Net/API/RestClient.SharpRest.cs b/src/Discord.Net/Net/RestClient.SharpRest.cs similarity index 80% rename from src/Discord.Net/API/RestClient.SharpRest.cs rename to src/Discord.Net/Net/RestClient.SharpRest.cs index a7391fe14..31e4f4925 100644 --- a/src/Discord.Net/API/RestClient.SharpRest.cs +++ b/src/Discord.Net/Net/RestClient.SharpRest.cs @@ -1,17 +1,17 @@ -using RestSharp; +using Discord.API; +using RestSharp; using System; -using System.IO; using System.Net.Http; using System.Threading; using System.Threading.Tasks; -namespace Discord.API +namespace Discord.Net { - internal class RestSharpRestEngine : IRestEngine + internal partial class RestClient { - private readonly RestSharp.RestClient _client; + private RestSharp.RestClient _client; - public RestSharpRestEngine(string userAgent, int timeout) + partial void Initialize(string userAgent, int timeout) { _client = new RestSharp.RestClient(Endpoints.BaseApi) { @@ -24,20 +24,20 @@ namespace Discord.API _client.ReadWriteTimeout = timeout; } - public void SetToken(string token) + internal 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) + private Task SendInternal(HttpMethod method, string path, string json, CancellationToken cancelToken) { var request = new RestRequest(path, GetMethod(method)); request.AddParameter("application/json", json, ParameterType.RequestBody); return Send(request, cancelToken); } - public Task SendFile(HttpMethod method, string path, string filePath, CancellationToken cancelToken) + private Task SendFileInternal(HttpMethod method, string path, string filePath, CancellationToken cancelToken) { var request = new RestRequest(path, Method.POST); request.AddFile("file", filePath); diff --git a/src/Discord.Net/API/RestClient.cs b/src/Discord.Net/Net/RestClient.cs similarity index 90% rename from src/Discord.Net/API/RestClient.cs rename to src/Discord.Net/Net/RestClient.cs index 707bd785b..be1d9e4a6 100644 --- a/src/Discord.Net/API/RestClient.cs +++ b/src/Discord.Net/Net/RestClient.cs @@ -1,32 +1,25 @@ -using Newtonsoft.Json; +using Discord.API; +using Newtonsoft.Json; using System; using System.Diagnostics; using System.Net.Http; using System.Threading; using System.Threading.Tasks; -namespace Discord.API +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, string userAgent, int timeout) { _logLevel = logLevel; - - _engine = new RestSharpRestEngine(userAgent, timeout); } + partial void Initialize(string userAgent, int timeout); + //DELETE private static readonly HttpMethod _delete = HttpMethod.Delete; internal Task Delete(string path, object data) where ResponseT : class => Send(_delete, path, data); @@ -37,12 +30,14 @@ namespace Discord.API internal Task Delete(string path) => Send(_delete, path); + //GET 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); + //PATCH private static readonly HttpMethod _patch = new HttpMethod("PATCH"); internal Task Patch(string path, object data) where ResponseT : class => Send(_patch, path, data); @@ -97,7 +92,7 @@ namespace Discord.API if (_logLevel >= LogMessageSeverity.Verbose) stopwatch = Stopwatch.StartNew(); - string responseJson = await _engine.Send(method, path, requestJson, _cancelToken).ConfigureAwait(false); + string responseJson = await SendInternal(method, path, requestJson, _cancelToken).ConfigureAwait(false); #if TEST_RESPONSES if (!hasResponse && !string.IsNullOrEmpty(responseJson)) @@ -136,7 +131,7 @@ namespace Discord.API if (_logLevel >= LogMessageSeverity.Verbose) stopwatch = Stopwatch.StartNew(); - string responseJson = await _engine.SendFile(method, path, filePath, _cancelToken).ConfigureAwait(false); + string responseJson = await SendFileInternal(method, path, filePath, _cancelToken).ConfigureAwait(false); #if TEST_RESPONSES if (!hasResponse && !string.IsNullOrEmpty(responseJson)) @@ -173,7 +168,6 @@ namespace Discord.API #endif } - internal void SetToken(string token) => _engine.SetToken(token); internal void SetCancelToken(CancellationToken token) => _cancelToken = token; } } diff --git a/src/Discord.Net/WebSockets/Voice/VoiceBuffer.cs b/src/Discord.Net/Net/VoiceBuffer.cs similarity index 92% rename from src/Discord.Net/WebSockets/Voice/VoiceBuffer.cs rename to src/Discord.Net/Net/VoiceBuffer.cs index 8f3dc9f53..29bcb8e77 100644 --- a/src/Discord.Net/WebSockets/Voice/VoiceBuffer.cs +++ b/src/Discord.Net/Net/VoiceBuffer.cs @@ -1,7 +1,7 @@ using System; using System.Threading; -namespace Discord.WebSockets.Voice +namespace Discord.Net { internal class VoiceBuffer : IDiscordVoiceBuffer { @@ -46,7 +46,11 @@ namespace Discord.WebSockets.Voice if (_readCursor == nextPosition) { _notOverflowEvent.Reset(); - _notOverflowEvent.Wait(cancelToken); + try + { + _notOverflowEvent.Wait(cancelToken); + } + catch (OperationCanceledException) { return; } } if (i == wholeFrames) @@ -100,7 +104,11 @@ namespace Discord.WebSockets.Voice _isClearing = true; for (int i = 0; i < _frameCount; i++) Buffer.BlockCopy(_blankFrame, 0, _buffer, i * _frameCount, i++); - _underflowEvent.Wait(cancelToken); + try + { + _underflowEvent.Wait(cancelToken); + } + catch (OperationCanceledException) { } _writeCursor = 0; _readCursor = 0; _isClearing = false; diff --git a/src/Discord.Net/WebSockets/Voice/VoiceWebSocket.Events.cs b/src/Discord.Net/Net/VoiceWebSocket.Events.cs similarity index 95% rename from src/Discord.Net/WebSockets/Voice/VoiceWebSocket.Events.cs rename to src/Discord.Net/Net/VoiceWebSocket.Events.cs index e2e676986..82f525158 100644 --- a/src/Discord.Net/WebSockets/Voice/VoiceWebSocket.Events.cs +++ b/src/Discord.Net/Net/VoiceWebSocket.Events.cs @@ -1,6 +1,6 @@ using System; -namespace Discord.WebSockets.Voice +namespace Discord.Net { internal sealed class IsTalkingEventArgs : EventArgs { diff --git a/src/Discord.Net/WebSockets/Voice/VoiceWebSocket.cs b/src/Discord.Net/Net/VoiceWebSocket.cs similarity index 98% rename from src/Discord.Net/WebSockets/Voice/VoiceWebSocket.cs rename to src/Discord.Net/Net/VoiceWebSocket.cs index 76a42d6b4..59f48da47 100644 --- a/src/Discord.Net/WebSockets/Voice/VoiceWebSocket.cs +++ b/src/Discord.Net/Net/VoiceWebSocket.cs @@ -1,5 +1,6 @@ #define USE_THREAD -using Discord.Audio; +using Discord.API; +using Discord.Interop; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; @@ -13,7 +14,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; -namespace Discord.WebSockets.Voice +namespace Discord.Net { internal partial class VoiceWebSocket : WebSocket { @@ -109,8 +110,8 @@ namespace Discord.WebSockets.Voice #if !DNX451 && !__MonoCS__ _udp.AllowNatTraversal(true); #endif - - LoginCommand msg = new LoginCommand(); + + VoiceLoginCommand msg = new VoiceLoginCommand(); msg.Payload.ServerId = _serverId; msg.Payload.SessionId = _sessionId; msg.Payload.Token = _token; @@ -238,7 +239,7 @@ namespace Discord.WebSockets.Voice int port = packet[68] | packet[69] << 8; string ip = Encoding.ASCII.GetString(packet, 4, 70 - 6).TrimEnd('\0'); - var login2 = new Login2Command(); + var login2 = new VoiceLogin2Command(); login2.Payload.Protocol = "udp"; login2.Payload.SocketData.Address = ip; login2.Payload.SocketData.Mode = _encryptionMode; @@ -458,7 +459,7 @@ namespace Discord.WebSockets.Voice { if (_state != (int)WebSocketState.Connected) { - 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); diff --git a/src/Discord.Net/WebSockets/WebSocket.BuiltIn.cs b/src/Discord.Net/Net/WebSocket.BuiltIn.cs.old similarity index 99% rename from src/Discord.Net/WebSockets/WebSocket.BuiltIn.cs rename to src/Discord.Net/Net/WebSocket.BuiltIn.cs.old index e413c2432..af375e743 100644 --- a/src/Discord.Net/WebSockets/WebSocket.BuiltIn.cs +++ b/src/Discord.Net/Net/WebSocket.BuiltIn.cs.old @@ -1,5 +1,4 @@ -/* -using Discord.Helpers; +using Discord.Helpers; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -149,5 +148,4 @@ namespace Discord.WebSockets _sendQueue.Enqueue(message); } } -} -*/ \ No newline at end of file +} \ No newline at end of file diff --git a/src/Discord.Net/WebSockets/WebSocket.Events.cs b/src/Discord.Net/Net/WebSocket.Events.cs similarity index 96% rename from src/Discord.Net/WebSockets/WebSocket.Events.cs rename to src/Discord.Net/Net/WebSocket.Events.cs index 9824fbff8..1c2383198 100644 --- a/src/Discord.Net/WebSockets/WebSocket.Events.cs +++ b/src/Discord.Net/Net/WebSocket.Events.cs @@ -1,6 +1,6 @@ using System; -namespace Discord.WebSockets +namespace Discord.Net { internal abstract partial class WebSocket { diff --git a/src/Discord.Net/WebSockets/WebSocket.WebSocketSharp.cs b/src/Discord.Net/Net/WebSocket.WebSocketSharp.cs similarity index 98% rename from src/Discord.Net/WebSockets/WebSocket.WebSocketSharp.cs rename to src/Discord.Net/Net/WebSocket.WebSocketSharp.cs index c55196259..f245dd24e 100644 --- a/src/Discord.Net/WebSockets/WebSocket.WebSocketSharp.cs +++ b/src/Discord.Net/Net/WebSocket.WebSocketSharp.cs @@ -5,7 +5,7 @@ using System.Threading; using System.Threading.Tasks; using WSSharpNWebSocket = WebSocketSharp.WebSocket; -namespace Discord.WebSockets +namespace Discord.Net { internal class WSSharpWebSocketEngine : IWebSocketEngine { diff --git a/src/Discord.Net/WebSockets/WebSocket.cs b/src/Discord.Net/Net/WebSocket.cs similarity index 99% rename from src/Discord.Net/WebSockets/WebSocket.cs rename to src/Discord.Net/Net/WebSocket.cs index 6f9165f8c..cf93518c3 100644 --- a/src/Discord.Net/WebSockets/WebSocket.cs +++ b/src/Discord.Net/Net/WebSocket.cs @@ -6,7 +6,7 @@ using System.Runtime.ExceptionServices; using System.Threading; using System.Threading.Tasks; -namespace Discord.WebSockets +namespace Discord.Net { public enum WebSocketState : byte { diff --git a/src/Discord.Net/WebSockets/Data/Commands.cs b/src/Discord.Net/WebSockets/Data/Commands.cs deleted file mode 100644 index 90b4e59d3..000000000 --- a/src/Discord.Net/WebSockets/Data/Commands.cs +++ /dev/null @@ -1,64 +0,0 @@ -//Ignore unused/unassigned variable warnings -#pragma warning disable CS0649 -#pragma warning disable CS0169 - -using Newtonsoft.Json; -using System.Collections.Generic; - -namespace Discord.WebSockets.Data -{ - internal sealed class KeepAliveCommand : WebSocketMessage - { - public KeepAliveCommand() : base(1, EpochTime.GetMilliseconds()) { } - } - internal sealed class LoginCommand : WebSocketMessage - { - public LoginCommand() : base(2) { } - public class Data - { - [JsonProperty("token")] - public string Token; - [JsonProperty("v")] - public int Version = 3; - [JsonProperty("properties")] - public Dictionary Properties = new Dictionary(); - } - } - internal sealed class UpdateStatusCommand : WebSocketMessage - { - public UpdateStatusCommand() : base(3) { } - public class Data - { - [JsonProperty("idle_since")] - public ulong? IdleSince; - [JsonProperty("game_id")] - public int? GameId; - } - } - internal sealed class JoinVoiceCommand : WebSocketMessage - { - public JoinVoiceCommand() : base(4) { } - public class Data - { - [JsonProperty("guild_id")] - public string ServerId; - [JsonProperty("channel_id")] - public string ChannelId; - [JsonProperty("self_mute")] - public string SelfMute; - [JsonProperty("self_deaf")] - public string SelfDeaf; - } - } - internal sealed class ResumeCommand : WebSocketMessage - { - public ResumeCommand() : base(6) { } - public class Data - { - [JsonProperty("session_id")] - public string SessionId; - [JsonProperty("seq")] - public int Sequence; - } - } -} diff --git a/src/Discord.Net/WebSockets/Data/Events.cs b/src/Discord.Net/WebSockets/Data/Events.cs deleted file mode 100644 index 2ed5c4b4b..000000000 --- a/src/Discord.Net/WebSockets/Data/Events.cs +++ /dev/null @@ -1,115 +0,0 @@ -//Ignore unused/unassigned variable warnings -#pragma warning disable CS0649 -#pragma warning disable CS0169 - -using Discord.API; -using Newtonsoft.Json; - -namespace Discord.WebSockets.Data -{ - internal sealed class ReadyEvent - { - public sealed class ReadStateInfo - { - [JsonProperty("id")] - public string ChannelId; - [JsonProperty("mention_count")] - public int MentionCount; - [JsonProperty("last_message_id")] - public string LastMessageId; - } - - [JsonProperty("v")] - public int Version; - [JsonProperty("user")] - public SelfUserInfo User; - [JsonProperty("session_id")] - public string SessionId; - [JsonProperty("read_state")] - public ReadStateInfo[] ReadState; - [JsonProperty("guilds")] - public ExtendedGuildInfo[] Guilds; - [JsonProperty("private_channels")] - public ChannelInfo[] PrivateChannels; - [JsonProperty("heartbeat_interval")] - public int HeartbeatInterval; - } - internal sealed class ResumedEvent - { - [JsonProperty("heartbeat_interval")] - public int HeartbeatInterval; - } - - internal sealed class RedirectEvent - { - [JsonProperty("url")] - public string Url; - } - - //Servers - internal sealed class GuildCreateEvent : ExtendedGuildInfo { } - internal sealed class GuildUpdateEvent : GuildInfo { } - internal sealed class GuildDeleteEvent : ExtendedGuildInfo { } - - //Channels - internal sealed class ChannelCreateEvent : ChannelInfo { } - internal sealed class ChannelDeleteEvent : ChannelInfo { } - internal sealed class ChannelUpdateEvent : ChannelInfo { } - - //Memberships - internal sealed class GuildMemberAddEvent : MemberInfo { } - internal sealed class GuildMemberUpdateEvent : MemberInfo { } - internal sealed class GuildMemberRemoveEvent : MemberInfo { } - - //Roles - internal sealed class GuildRoleCreateEvent - { - [JsonProperty("guild_id")] - public string GuildId; - [JsonProperty("role")] - public RoleInfo Data; - } - internal sealed class GuildRoleUpdateEvent - { - [JsonProperty("guild_id")] - public string GuildId; - [JsonProperty("role")] - public RoleInfo Data; - } - internal sealed class GuildRoleDeleteEvent : RoleReference { } - - //Bans - internal sealed class GuildBanAddEvent : MemberReference { } - internal sealed class GuildBanRemoveEvent : MemberReference { } - - //User - internal sealed class UserUpdateEvent : SelfUserInfo { } - internal sealed class PresenceUpdateEvent : PresenceMemberInfo { } - internal sealed class VoiceStateUpdateEvent : VoiceMemberInfo { } - - //Chat - internal sealed class MessageCreateEvent : API.Message { } - internal sealed class MessageUpdateEvent : API.Message { } - internal sealed class MessageDeleteEvent : MessageReference { } - internal sealed class MessageAckEvent : MessageReference { } - internal sealed class TypingStartEvent - { - [JsonProperty("user_id")] - public string UserId; - [JsonProperty("channel_id")] - public string ChannelId; - [JsonProperty("timestamp")] - public int Timestamp; - } - - //Voice - internal sealed class VoiceServerUpdateEvent - { - [JsonProperty("guild_id")] - public string GuildId; - [JsonProperty("endpoint")] - public string Endpoint; - [JsonProperty("token")] - public string Token; - } -} diff --git a/src/Discord.Net/WebSockets/Voice/Commands.cs b/src/Discord.Net/WebSockets/Voice/Commands.cs deleted file mode 100644 index 740f235bc..000000000 --- a/src/Discord.Net/WebSockets/Voice/Commands.cs +++ /dev/null @@ -1,59 +0,0 @@ -//Ignore unused/unassigned variable warnings -#pragma warning disable CS0649 -#pragma warning disable CS0169 - -using Newtonsoft.Json; - -namespace Discord.WebSockets.Voice -{ - internal sealed class LoginCommand : WebSocketMessage - { - public LoginCommand() : base(0) { } - public class Data - { - [JsonProperty("server_id")] - public string ServerId; - [JsonProperty("user_id")] - public string UserId; - [JsonProperty("session_id")] - public string SessionId; - [JsonProperty("token")] - public string Token; - } - } - internal sealed class Login2Command : WebSocketMessage - { - public Login2Command() : base(1) { } - public class Data - { - public class SocketInfo - { - [JsonProperty("address")] - public string Address; - [JsonProperty("port")] - public int Port; - [JsonProperty("mode")] - public string Mode = "xsalsa20_poly1305"; - } - [JsonProperty("protocol")] - public string Protocol = "udp"; - [JsonProperty("data")] - public SocketInfo SocketData = new SocketInfo(); - } - } - internal sealed class KeepAliveCommand : WebSocketMessage - { - public KeepAliveCommand() : base(3, null) { } - } - internal sealed class IsTalkingCommand : WebSocketMessage - { - public IsTalkingCommand() : base(5) { } - public class Data - { - [JsonProperty("delay")] - public int Delay; - [JsonProperty("speaking")] - public bool IsSpeaking; - } - } -} diff --git a/src/Discord.Net/WebSockets/Voice/Events.cs b/src/Discord.Net/WebSockets/Voice/Events.cs deleted file mode 100644 index a2bd730df..000000000 --- a/src/Discord.Net/WebSockets/Voice/Events.cs +++ /dev/null @@ -1,38 +0,0 @@ -//Ignore unused/unassigned variable warnings -#pragma warning disable CS0649 -#pragma warning disable CS0169 - -using Newtonsoft.Json; - -namespace Discord.WebSockets.Voice -{ - internal sealed class ReadyEvent - { - [JsonProperty("ssrc")] - public uint SSRC; - [JsonProperty("port")] - public ushort Port; - [JsonProperty("modes")] - public string[] Modes; - [JsonProperty("heartbeat_interval")] - public int HeartbeatInterval; - } - - internal sealed class JoinServerEvent - { - [JsonProperty("secret_key")] - public byte[] SecretKey; - [JsonProperty("mode")] - public string Mode; - } - - internal sealed class IsTalkingEvent - { - [JsonProperty("user_id")] - public string UserId; - [JsonProperty("ssrc")] - public uint SSRC; - [JsonProperty("speaking")] - public bool IsSpeaking; - } -} diff --git a/src/Discord.Net/WebSockets/WebSocketMessage.cs b/src/Discord.Net/WebSockets/WebSocketMessage.cs deleted file mode 100644 index c63bd4787..000000000 --- a/src/Discord.Net/WebSockets/WebSocketMessage.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; - -namespace Discord.WebSockets -{ - public class WebSocketMessage - { - [JsonProperty("op")] - public int Operation; - [JsonProperty("d")] - public object Payload; - [JsonProperty("t", NullValueHandling = NullValueHandling.Ignore)] - public string Type; - [JsonProperty("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/packages.config b/src/Discord.Net/packages.config deleted file mode 100644 index 505e58836..000000000 --- a/src/Discord.Net/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file From a844fa9bf3595dc85f6f8afb21119e8d7513f4e7 Mon Sep 17 00:00:00 2001 From: RogueException Date: Sun, 18 Oct 2015 08:45:00 -0300 Subject: [PATCH 006/109] 0.8.0-beta1 --- .../Discord.Net.Commands.csproj | 5 +- src/Discord.Net.Commands/project.json | 6 +- src/Discord.Net.Net45/Properties/AssemblyInfo.cs | 4 +- src/Discord.Net/project.json | 95 +++++++++++----------- 4 files changed, 57 insertions(+), 53 deletions(-) diff --git a/src/Discord.Net.Commands.Net45/Discord.Net.Commands.csproj b/src/Discord.Net.Commands.Net45/Discord.Net.Commands.csproj index b1540a5e3..f05fa2e2f 100644 --- a/src/Discord.Net.Commands.Net45/Discord.Net.Commands.csproj +++ b/src/Discord.Net.Commands.Net45/Discord.Net.Commands.csproj @@ -52,7 +52,10 @@ CommandsPlugin.Events.cs - + + Shared\CollectionHelper.cs + + Shared\TaskHelper.cs diff --git a/src/Discord.Net.Commands/project.json b/src/Discord.Net.Commands/project.json index 8b8d7fbef..5e7ee79e8 100644 --- a/src/Discord.Net.Commands/project.json +++ b/src/Discord.Net.Commands/project.json @@ -1,11 +1,11 @@ { - "version": "0.7.4", + "version": "0.8.0-beta1", "description": "A Discord.Net extension adding basic command support.", "authors": [ "RogueException" ], "tags": [ "discord", "discordapp" ], "projectUrl": "https://github.com/RogueException/Discord.Net", "licenseUrl": "http://opensource.org/licenses/MIT", - "compile": ["**/*.cs", "../Discord.Net/Shared/*.cs"], + "compile": ["**/*.cs", "../Discord.Net/Helpers/Shared/*.cs"], "repository": { "type": "git", "url": "git://github.com/RogueException/Discord.Net" @@ -14,7 +14,7 @@ "warningsAsErrors": true }, "dependencies": { - "Discord.Net": "0.7.4" + "Discord.Net": "0.8.0-beta1" }, "frameworks": { "net45": { }, diff --git a/src/Discord.Net.Net45/Properties/AssemblyInfo.cs b/src/Discord.Net.Net45/Properties/AssemblyInfo.cs index 2eb34b7d3..857ea2201 100644 --- a/src/Discord.Net.Net45/Properties/AssemblyInfo.cs +++ b/src/Discord.Net.Net45/Properties/AssemblyInfo.cs @@ -13,5 +13,5 @@ using System.Runtime.InteropServices; [assembly: ComVisible(false)] [assembly: Guid("76ea00e6-ea24-41e1-acb2-639c0313fa80")] -[assembly: AssemblyVersion("0.7.3.0")] -[assembly: AssemblyFileVersion("0.7.3.0")] +[assembly: AssemblyVersion("0.8.0")] +[assembly: AssemblyFileVersion("0.8.0")] diff --git a/src/Discord.Net/project.json b/src/Discord.Net/project.json index a1f041cf4..8a2b2e1b2 100644 --- a/src/Discord.Net/project.json +++ b/src/Discord.Net/project.json @@ -1,47 +1,48 @@ -{ - "version": "0.7.4", - "description": "An unofficial .Net API wrapper for the Discord client.", - "authors": [ "RogueException" ], - "tags": [ "discord", "discordapp" ], - "projectUrl": "https://github.com/RogueException/Discord.Net", - "licenseUrl": "http://opensource.org/licenses/MIT", - "repository": { - "type": "git", - "url": "git://github.com/RogueException/Discord.Net" - }, - "compilationOptions": { - "allowUnsafe": true - }, - "configurations": { - "FullDebug": { - "compilationOptions": { - "define": [ "DEBUG", "TRACE", "TEST_RESPONSES" ] - } - } - }, - - "dependencies": { - "Newtonsoft.Json": "7.0.1" - }, - - "frameworks": { - "net45": { - "dependencies": { - "RestSharp": "105.2.3", - "WebSocketSharp": "1.0.3-rc9" - }, - "frameworkAssemblies": { - "System.Net.Http": "4.0.0.0" - } - }, - "dnx451": { - "dependencies": { - "RestSharp": "105.2.3", - "WebSocketSharp": "1.0.3-rc9" - }, - "frameworkAssemblies": { - "System.Net.Http": "4.0.0.0" - } - } - } -} +{ + "version": "0.8.0-beta1", + "description": "An unofficial .Net API wrapper for the Discord client.", + "authors": [ + "RogueException" + ], + "tags": [ + "discord", + "discordapp" + ], + "projectUrl": "https://github.com/RogueException/Discord.Net", + "licenseUrl": "http://opensource.org/licenses/MIT", + "repository": { + "type": "git", + "url": "git://github.com/RogueException/Discord.Net" + }, + "compilationOptions": { + "allowUnsafe": true + }, + "configurations": { + "FullDebug": { + "compilationOptions": { + "define": [ + "DEBUG", + "TRACE", + "TEST_RESPONSES" + ] + } + } + }, + "dependencies": { + "Newtonsoft.Json": "7.0.1", + "RestSharp": "105.2.3", + "WebSocketSharp": "1.0.3-rc9" + }, + "frameworks": { + "net45": { + "frameworkAssemblies": { + "System.Net.Http": "4.0.0.0" + } + }, + "dnx451": { + "frameworkAssemblies": { + "System.Net.Http": "4.0.0.0" + } + } + } +} \ No newline at end of file From bd67e1dc53b15371f82787a55acda88238009c74 Mon Sep 17 00:00:00 2001 From: RogueException Date: Sun, 18 Oct 2015 08:45:20 -0300 Subject: [PATCH 007/109] Fixed project references --- Discord.Net.sln | 47 +++++++++++++++++++++++++++++------------------ 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/Discord.Net.sln b/Discord.Net.sln index 900f44b13..bac38366c 100644 --- a/Discord.Net.sln +++ b/Discord.Net.sln @@ -1,6 +1,6 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 2012 +# Visual Studio 14 VisualStudioVersion = 14.0.23107.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{8D7989F0-66CE-4DBB-8230-D8C811E9B1D7}" @@ -13,10 +13,6 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net.Commands", "src EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "net45", "net45", "{DF03D4E8-38F6-4FE1-BC52-E38124BE8AFD}" EndProject -Project("{9344BDBB-3E7F-41FC-A0DD-8665D75EE146}") = "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}") = "test", "test", "{6317A2E6-8E36-4C3E-949B-3F10EC888AB9}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Discord.Net.Tests", "test\Discord.Net.Tests\Discord.Net.Tests.csproj", "{855D6B1D-847B-42DA-BE6A-23683EA89511}" @@ -26,43 +22,58 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution global.json = global.json EndProjectSection 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 Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + FullDebug|Any CPU = FullDebug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution + {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}.FullDebug|Any CPU.ActiveCfg = FullDebug|Any CPU + {ACFB060B-EC8A-4926-B293-04C01E17EE23}.FullDebug|Any CPU.Build.0 = FullDebug|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}.FullDebug|Any CPU.ActiveCfg = Debug|Any CPU + {19793545-EF89-48F4-8100-3EBAAD0A9141}.FullDebug|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 - {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 {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}.FullDebug|Any CPU.ActiveCfg = Debug|Any CPU + {855D6B1D-847B-42DA-BE6A-23683EA89511}.FullDebug|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 {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}.FullDebug|Any CPU.ActiveCfg = FullDebug|Any CPU + {8D71A857-879A-4A10-859E-5FF824ED6688}.FullDebug|Any CPU.Build.0 = FullDebug|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 - {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 + {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}.FullDebug|Any CPU.ActiveCfg = Debug|Any CPU + {1B5603B4-6F8F-4289-B945-7BAAE523D740}.FullDebug|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 {EA68EBE2-51C8-4440-9EF7-D633C90A5D35} = {8D7989F0-66CE-4DBB-8230-D8C811E9B1D7} - {DF03D4E8-38F6-4FE1-BC52-E38124BE8AFD} = {8D7989F0-66CE-4DBB-8230-D8C811E9B1D7} {ACFB060B-EC8A-4926-B293-04C01E17EE23} = {EA68EBE2-51C8-4440-9EF7-D633C90A5D35} {19793545-EF89-48F4-8100-3EBAAD0A9141} = {EA68EBE2-51C8-4440-9EF7-D633C90A5D35} + {DF03D4E8-38F6-4FE1-BC52-E38124BE8AFD} = {8D7989F0-66CE-4DBB-8230-D8C811E9B1D7} + {855D6B1D-847B-42DA-BE6A-23683EA89511} = {6317A2E6-8E36-4C3E-949B-3F10EC888AB9} {8D71A857-879A-4A10-859E-5FF824ED6688} = {DF03D4E8-38F6-4FE1-BC52-E38124BE8AFD} {1B5603B4-6F8F-4289-B945-7BAAE523D740} = {DF03D4E8-38F6-4FE1-BC52-E38124BE8AFD} - {855D6B1D-847B-42DA-BE6A-23683EA89511} = {6317A2E6-8E36-4C3E-949B-3F10EC888AB9} - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE EndGlobalSection EndGlobal From 916d3efeb5228c7d8777fdced0413d31fecf7dd5 Mon Sep 17 00:00:00 2001 From: RogueException Date: Sun, 18 Oct 2015 08:59:06 -0300 Subject: [PATCH 008/109] Added pragmas to API/Bans --- src/Discord.Net/API/Bans.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Discord.Net/API/Bans.cs b/src/Discord.Net/API/Bans.cs index 54dfa1518..f6afd0201 100644 --- a/src/Discord.Net/API/Bans.cs +++ b/src/Discord.Net/API/Bans.cs @@ -1,4 +1,8 @@ -namespace Discord.API +//Ignore unused/unassigned variable warnings +#pragma warning disable CS0649 +#pragma warning disable CS0169 + +namespace Discord.API { //Events internal sealed class BanAddEvent : MemberReference { } From 44db9c4979615c5edc34181ff552d63625265a18 Mon Sep 17 00:00:00 2001 From: RogueException Date: Sun, 18 Oct 2015 08:59:48 -0300 Subject: [PATCH 009/109] Removed leftover DNXCore50 reference --- src/Discord.Net/Helpers/Shared/TaskHelper.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Discord.Net/Helpers/Shared/TaskHelper.cs b/src/Discord.Net/Helpers/Shared/TaskHelper.cs index 0dd82d5b0..22a60f2b2 100644 --- a/src/Discord.Net/Helpers/Shared/TaskHelper.cs +++ b/src/Discord.Net/Helpers/Shared/TaskHelper.cs @@ -7,11 +7,7 @@ namespace Discord public static Task CompletedTask { get; } static TaskHelper() { -#if DNXCORE50 - CompletedTask = Task.CompletedTask; -#else CompletedTask = Task.Delay(0); -#endif } } } From 443c43b643658025bbeb0aac1d570bbf8b6ca50f Mon Sep 17 00:00:00 2001 From: RogueException Date: Sun, 18 Oct 2015 09:11:36 -0300 Subject: [PATCH 010/109] Renamed MessageCleaner to MentionHelper, added Mentionhelper.GetUserIds --- src/Discord.Net.Net45/Discord.Net.csproj | 7 +++-- src/Discord.Net/Collections/Messages.cs | 4 --- src/Discord.Net/Helpers/MentionHelper.cs | 48 +++++++++++++++++++++++++++++++ src/Discord.Net/Helpers/MessageCleaner.cs | 43 --------------------------- src/Discord.Net/Models/Message.cs | 2 +- 5 files changed, 54 insertions(+), 50 deletions(-) create mode 100644 src/Discord.Net/Helpers/MentionHelper.cs delete mode 100644 src/Discord.Net/Helpers/MessageCleaner.cs diff --git a/src/Discord.Net.Net45/Discord.Net.csproj b/src/Discord.Net.Net45/Discord.Net.csproj index b9c24516b..063d12d7d 100644 --- a/src/Discord.Net.Net45/Discord.Net.csproj +++ b/src/Discord.Net.Net45/Discord.Net.csproj @@ -160,6 +160,9 @@ DiscordSimpleClientConfig.cs + + Enums\AvatarImageType.cs + Enums\ChannelTypes.cs @@ -184,8 +187,8 @@ Helpers\Mention.cs - - Helpers\MessageCleaner.cs + + Helpers\MentionHelper.cs Helpers\Shared\CollectionHelper.cs diff --git a/src/Discord.Net/Collections/Messages.cs b/src/Discord.Net/Collections/Messages.cs index 1a17bed1b..0943c8a9d 100644 --- a/src/Discord.Net/Collections/Messages.cs +++ b/src/Discord.Net/Collections/Messages.cs @@ -2,11 +2,9 @@ { public sealed class Messages : AsyncCollection { - private readonly MessageCleaner _msgCleaner; internal Messages(DiscordClient client, object writerLock) : base(client, writerLock) { - _msgCleaner = new MessageCleaner(client); } internal Message GetOrAdd(string id, string channelId, string userId) => GetOrAdd(id, () => new Message(_client, id, channelId, userId)); @@ -29,7 +27,5 @@ } internal Message this[string id] => Get(id); - - internal string CleanText(string text) => _msgCleaner.Clean(text); } } diff --git a/src/Discord.Net/Helpers/MentionHelper.cs b/src/Discord.Net/Helpers/MentionHelper.cs new file mode 100644 index 000000000..b630c61a3 --- /dev/null +++ b/src/Discord.Net/Helpers/MentionHelper.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; + +namespace Discord +{ + internal static class MentionHelper + { + private static readonly Regex _userRegex, _channelRegex; + + static MentionHelper() + { + _userRegex = new Regex(@"<@(\d+?)>", RegexOptions.Compiled); + _channelRegex = new Regex(@"<#(\d+?)>", RegexOptions.Compiled); + } + + public static string ConvertToNames(DiscordClient client, string text) + { + text = _userRegex.Replace(text, 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; + })); + text = _channelRegex.Replace(text, 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; + })); + return text; + } + + public static IEnumerable GetUserIds(string text) + { + return _userRegex.Matches(text) + .OfType() + .Select(x => x.Groups[1].Value) + .Where(x => x != null); + } + } +} diff --git a/src/Discord.Net/Helpers/MessageCleaner.cs b/src/Discord.Net/Helpers/MessageCleaner.cs deleted file mode 100644 index 4ccaf04e0..000000000 --- a/src/Discord.Net/Helpers/MessageCleaner.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System.Text.RegularExpressions; - -namespace Discord -{ - //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/Models/Message.cs b/src/Discord.Net/Models/Message.cs index 3d00a1265..a23644448 100644 --- a/src/Discord.Net/Models/Message.cs +++ b/src/Discord.Net/Models/Message.cs @@ -114,7 +114,7 @@ namespace Discord public string RawText { get; private 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.Messages.CleanText(RawText)); + public string Text => _cleanText != null ? _cleanText : (_cleanText = MentionHelper.ConvertToNames(_client, RawText)); /// Returns the timestamp for when this message was sent. public DateTime Timestamp { get; private set; } /// Returns the timestamp for when this message was last edited. From c6a3fad80f51344226f19ab78222d252153d72c8 Mon Sep 17 00:00:00 2001 From: RogueException Date: Sun, 18 Oct 2015 09:11:54 -0300 Subject: [PATCH 011/109] Isolated AvatarImageType --- src/Discord.Net/DiscordClient.API.cs | 7 ------- src/Discord.Net/Enums/AvatarImageType.cs | 9 +++++++++ 2 files changed, 9 insertions(+), 7 deletions(-) create mode 100644 src/Discord.Net/Enums/AvatarImageType.cs diff --git a/src/Discord.Net/DiscordClient.API.cs b/src/Discord.Net/DiscordClient.API.cs index d8642f9ba..ae04de4b4 100644 --- a/src/Discord.Net/DiscordClient.API.cs +++ b/src/Discord.Net/DiscordClient.API.cs @@ -8,13 +8,6 @@ using System.Threading.Tasks; namespace Discord { - public enum AvatarImageType - { - None, - Jpeg, - Png - } - public partial class DiscordClient { public const int MaxMessageSize = 2000; diff --git a/src/Discord.Net/Enums/AvatarImageType.cs b/src/Discord.Net/Enums/AvatarImageType.cs new file mode 100644 index 000000000..7b2895a04 --- /dev/null +++ b/src/Discord.Net/Enums/AvatarImageType.cs @@ -0,0 +1,9 @@ +namespace Discord +{ + public enum AvatarImageType + { + None, + Jpeg, + Png + } +} From 3ea472e3dc24dec6c23c4918c89fca247e24118c Mon Sep 17 00:00:00 2001 From: RogueException Date: Sun, 18 Oct 2015 09:17:12 -0300 Subject: [PATCH 012/109] Moved Enums to API/Enums --- src/Discord.Net.Net45/Discord.Net.csproj | 30 +++++++++++----------- src/Discord.Net/{ => API}/Enums/AvatarImageType.cs | 0 src/Discord.Net/{ => API}/Enums/ChannelTypes.cs | 0 .../{ => API}/Enums/PermissionTarget.cs | 0 src/Discord.Net/{ => API}/Enums/Regions.cs | 0 src/Discord.Net/{ => API}/Enums/UserStatus.cs | 0 6 files changed, 15 insertions(+), 15 deletions(-) rename src/Discord.Net/{ => API}/Enums/AvatarImageType.cs (100%) rename src/Discord.Net/{ => API}/Enums/ChannelTypes.cs (100%) rename src/Discord.Net/{ => API}/Enums/PermissionTarget.cs (100%) rename src/Discord.Net/{ => API}/Enums/Regions.cs (100%) rename src/Discord.Net/{ => API}/Enums/UserStatus.cs (100%) diff --git a/src/Discord.Net.Net45/Discord.Net.csproj b/src/Discord.Net.Net45/Discord.Net.csproj index 063d12d7d..ed613a419 100644 --- a/src/Discord.Net.Net45/Discord.Net.csproj +++ b/src/Discord.Net.Net45/Discord.Net.csproj @@ -79,6 +79,21 @@ API\Endpoints.cs + + API\Enums\AvatarImageType.cs + + + API\Enums\ChannelTypes.cs + + + API\Enums\PermissionTarget.cs + + + API\Enums\Regions.cs + + + API\Enums\UserStatus.cs + API\Invites.cs @@ -160,21 +175,6 @@ DiscordSimpleClientConfig.cs - - Enums\AvatarImageType.cs - - - Enums\ChannelTypes.cs - - - Enums\PermissionTarget.cs - - - Enums\Regions.cs - - - Enums\UserStatus.cs - Helpers\EpochTime.cs diff --git a/src/Discord.Net/Enums/AvatarImageType.cs b/src/Discord.Net/API/Enums/AvatarImageType.cs similarity index 100% rename from src/Discord.Net/Enums/AvatarImageType.cs rename to src/Discord.Net/API/Enums/AvatarImageType.cs diff --git a/src/Discord.Net/Enums/ChannelTypes.cs b/src/Discord.Net/API/Enums/ChannelTypes.cs similarity index 100% rename from src/Discord.Net/Enums/ChannelTypes.cs rename to src/Discord.Net/API/Enums/ChannelTypes.cs diff --git a/src/Discord.Net/Enums/PermissionTarget.cs b/src/Discord.Net/API/Enums/PermissionTarget.cs similarity index 100% rename from src/Discord.Net/Enums/PermissionTarget.cs rename to src/Discord.Net/API/Enums/PermissionTarget.cs diff --git a/src/Discord.Net/Enums/Regions.cs b/src/Discord.Net/API/Enums/Regions.cs similarity index 100% rename from src/Discord.Net/Enums/Regions.cs rename to src/Discord.Net/API/Enums/Regions.cs diff --git a/src/Discord.Net/Enums/UserStatus.cs b/src/Discord.Net/API/Enums/UserStatus.cs similarity index 100% rename from src/Discord.Net/Enums/UserStatus.cs rename to src/Discord.Net/API/Enums/UserStatus.cs From f93940ef0d973ae12c977a5386b9d332a8bab899 Mon Sep 17 00:00:00 2001 From: RogueException Date: Sun, 18 Oct 2015 09:17:20 -0300 Subject: [PATCH 013/109] Fixed a project reference --- src/Discord.Net.Commands.Net45/Discord.Net.Commands.csproj | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Discord.Net.Commands.Net45/Discord.Net.Commands.csproj b/src/Discord.Net.Commands.Net45/Discord.Net.Commands.csproj index f05fa2e2f..63ccee124 100644 --- a/src/Discord.Net.Commands.Net45/Discord.Net.Commands.csproj +++ b/src/Discord.Net.Commands.Net45/Discord.Net.Commands.csproj @@ -60,6 +60,12 @@ + + + {8d71a857-879a-4a10-859e-5ff824ed6688} + Discord.Net + + - - - {8D71A857-879A-4A10-859E-5FF824ED6688} - Discord.Net - - \ No newline at end of file From f658035e6eef10fcfa92f1b98058152bf29ee2ff Mon Sep 17 00:00:00 2001 From: RogueException Date: Sun, 18 Oct 2015 09:19:26 -0300 Subject: [PATCH 014/109] Fixed RestClient initialization error --- src/Discord.Net/Net/RestClient.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Discord.Net/Net/RestClient.cs b/src/Discord.Net/Net/RestClient.cs index be1d9e4a6..c956728c9 100644 --- a/src/Discord.Net/Net/RestClient.cs +++ b/src/Discord.Net/Net/RestClient.cs @@ -16,7 +16,8 @@ namespace Discord.Net public RestClient(LogMessageSeverity logLevel, string userAgent, int timeout) { _logLevel = logLevel; - } + Initialize(userAgent, timeout); + } partial void Initialize(string userAgent, int timeout); //DELETE From 712ac2343a7d059a6d871ae145b0f956b1d04234 Mon Sep 17 00:00:00 2001 From: RogueException Date: Sun, 18 Oct 2015 09:24:56 -0300 Subject: [PATCH 015/109] Removed SendMessage's mentionedUsers parameter. --- src/Discord.Net/DiscordClient.API.cs | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/src/Discord.Net/DiscordClient.API.cs b/src/Discord.Net/DiscordClient.API.cs index ae04de4b4..a90898415 100644 --- a/src/Discord.Net/DiscordClient.API.cs +++ b/src/Discord.Net/DiscordClient.API.cs @@ -270,24 +270,12 @@ namespace Discord } //Messages - /// Sends a message to the provided channel, optionally mentioning certain users. - /// While not required, it is recommended to include a mention reference in the text (see User.Mention). - public Task SendMessage(Channel channel, string text, IEnumerable mentionedUsers = null) - => SendMessage(channel, text, mentionedUsers, false); - /// Sends a message to the provided channel, optionally mentioning certain users. - /// While not required, it is recommended to include a mention reference in the text (see User.Mention). - public Task SendMessage(string channelId, string text, IEnumerable mentionedUsers = null) - => SendMessage(_channels[channelId], text, mentionedUsers, false); - /// Sends a message to the provided channel, optionally mentioning certain users. - /// While not required, it is recommended to include a mention reference in the text (see User.Mention). - public Task SendTTSMessage(Channel channel, string text, IEnumerable mentionedUsers = null) - => SendMessage(channel, text, mentionedUsers, true); - /// Sends a message to the provided channel, optionally mentioning certain users. - /// While not required, it is recommended to include a mention reference in the text (see User.Mention). - public Task SendTTSMessage(string channelId, string text, IEnumerable mentionedUsers = null) - => SendMessage(_channels[channelId], text, mentionedUsers, true); - /// Sends a message to the provided channel, optionally mentioning certain users. - /// While not required, it is recommended to include a mention reference in the text (see User.Mention). + /// Sends a message to the provided channel. To include a mention, see the Mention static helper class. + public Task SendMessage(Channel channel, string text) + => SendMessage(channel, text, MentionHelper.GetUserIds(text), false); + /// Sends a message to the provided channel. To include a mention, see the Mention static helper class. + public Task SendMessage(string channelId, string text) + => SendMessage(_channels[channelId], text, MentionHelper.GetUserIds(text), false); private async Task SendMessage(Channel channel, string text, IEnumerable mentionedUsers = null, bool isTextToSpeech = false) { CheckReady(); From 942f59c8176524ef4847c2c6c265c53a1f648273 Mon Sep 17 00:00:00 2001 From: RogueException Date: Sun, 18 Oct 2015 09:25:40 -0300 Subject: [PATCH 016/109] Added Mention.Everyone --- src/Discord.Net/Helpers/Mention.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Discord.Net/Helpers/Mention.cs b/src/Discord.Net/Helpers/Mention.cs index 5dea7bf76..29c3bde54 100644 --- a/src/Discord.Net/Helpers/Mention.cs +++ b/src/Discord.Net/Helpers/Mention.cs @@ -18,5 +18,9 @@ /// Returns the string used to create a channel mention. public static string Channel(string channelId) => $"<#{channelId}>"; + + /// Returns the string used to create a channel mention. + public static string Everyone() + => $"@everyone"; } } From d83d9d0487c714f47d9af1baf5758596110e3dfc Mon Sep 17 00:00:00 2001 From: RogueException Date: Sun, 18 Oct 2015 09:33:59 -0300 Subject: [PATCH 017/109] Dont serialize Channel.Members or Users --- src/Discord.Net/Models/Channel.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Discord.Net/Models/Channel.cs b/src/Discord.Net/Models/Channel.cs index b7df1a2df..1a4c8bf77 100644 --- a/src/Discord.Net/Models/Channel.cs +++ b/src/Discord.Net/Models/Channel.cs @@ -73,8 +73,10 @@ namespace Discord } private string[] _userIds; /// Returns a collection of all users with read access to this channel. + [JsonIgnore] public IEnumerable Members => UserIds.Select(x => _client.Members[x, ServerId]); /// Returns a collection of all users with read access to this channel. + [JsonIgnore] public IEnumerable Users => UserIds.Select(x => _client.Users[x]); /// Returns a collection of the ids of all messages the client has seen posted in this channel. This collection does not guarantee any ordering. From a2480ebaebec490a9246aa4d926d53147a17b538 Mon Sep 17 00:00:00 2001 From: RogueException Date: Sun, 18 Oct 2015 09:38:25 -0300 Subject: [PATCH 018/109] Added more JsonIgnores --- src/Discord.Net/Models/Member.cs | 2 ++ src/Discord.Net/Models/Role.cs | 2 ++ src/Discord.Net/Models/Server.cs | 1 + src/Discord.Net/Models/User.cs | 4 ++++ 4 files changed, 9 insertions(+) diff --git a/src/Discord.Net/Models/Member.cs b/src/Discord.Net/Models/Member.cs index 4360465de..f2ede0b59 100644 --- a/src/Discord.Net/Models/Member.cs +++ b/src/Discord.Net/Models/Member.cs @@ -61,9 +61,11 @@ namespace Discord 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. + [JsonIgnore] public IEnumerable Messages => _client.Messages.Where(x => x.UserId == UserId && x.ServerId == ServerId); /// Returns a collection of all channels this user is a member of. + [JsonIgnore] public IEnumerable Channels => _client.Channels.Where(x => x.ServerId == ServerId && x.UserIds.Contains(UserId)); internal Member(DiscordClient client, string userId, string serverId) diff --git a/src/Discord.Net/Models/Role.cs b/src/Discord.Net/Models/Role.cs index 678bb31e1..ebd40a146 100644 --- a/src/Discord.Net/Models/Role.cs +++ b/src/Discord.Net/Models/Role.cs @@ -34,8 +34,10 @@ namespace Discord /// Returns true if this is the role representing all users in a server. public bool IsEveryone { get; } /// Returns a list of the ids of all members in this role. + [JsonIgnore] public IEnumerable MemberIds => IsEveryone ? Server.UserIds : Server.Members.Where(x => x.RoleIds.Contains(Id)).Select(x => x.UserId); /// Returns a list of all members in this role. + [JsonIgnore] public IEnumerable Members => IsEveryone ? Server.Members : Server.Members.Where(x => x.RoleIds.Contains(Id)); internal Role(DiscordClient client, string id, string serverId, bool isEveryone) diff --git a/src/Discord.Net/Models/Server.cs b/src/Discord.Net/Models/Server.cs index c3f59fd45..c5dbff92c 100644 --- a/src/Discord.Net/Models/Server.cs +++ b/src/Discord.Net/Models/Server.cs @@ -85,6 +85,7 @@ namespace Discord /// Return the id of the role representing all users in a server. public string EveryoneRoleId { get; private set; } /// Return the the role representing all users in a server. + [JsonIgnore] public Role EveryoneRole => _client.Roles[EveryoneRoleId]; /// Returns a collection of the ids of all roles within this server. [JsonIgnore] diff --git a/src/Discord.Net/Models/User.cs b/src/Discord.Net/Models/User.cs index a0104bcb3..1cf3787dc 100644 --- a/src/Discord.Net/Models/User.cs +++ b/src/Discord.Net/Models/User.cs @@ -42,12 +42,16 @@ namespace Discord public Channel PrivateChannel => _client.Channels[PrivateChannelId]; /// Returns a collection of all server-specific data for every server this user is a member of. + [JsonIgnore] public IEnumerable Memberships => _servers.Select(x => _client.GetMember(x.Key, Id)); /// Returns a collection of all servers this user is a member of. + [JsonIgnore] public IEnumerable Servers => _servers.Select(x => _client.GetServer(x.Key)); /// Returns a collection of the ids of all servers this user is a member of. + [JsonIgnore] public IEnumerable ServersIds => _servers.Select(x => x.Key); /// Returns a collection of all messages this user has sent that are still in cache. + [JsonIgnore] public IEnumerable Messages => _client.Messages.Where(x => x.UserId == Id); /// Returns the id for the game this user is currently playing. From 23e8ec8a0c0486b94d0da49adc2fc61f5526ba5b Mon Sep 17 00:00:00 2001 From: RogueException Date: Sun, 18 Oct 2015 18:02:47 -0300 Subject: [PATCH 019/109] Renamed DiscordSimpleClient -> DiscordWebSocketClient, moved DiscordAPIClient back into root namespace. --- src/Discord.Net.Net45/Discord.Net.csproj | 22 +++++++++++----------- src/Discord.Net/{Net => }/DiscordAPIClient.cs | 3 ++- src/Discord.Net/DiscordClient.cs | 12 ++++++------ src/Discord.Net/DiscordClientConfig.cs | 2 +- ....Events.cs => DiscordWebSocketClient.Events.cs} | 2 +- ...nt.Voice.cs => DiscordWebSocketClient.Voice.cs} | 2 +- ...rdSimpleClient.cs => DiscordWebSocketClient.cs} | 12 ++++++------ ...ntConfig.cs => DiscordWebSocketClientConfig.cs} | 2 +- src/Discord.Net/Net/DataWebSocket.cs | 2 +- src/Discord.Net/Net/VoiceWebSocket.cs | 2 +- src/Discord.Net/Net/WebSocket.cs | 4 ++-- 11 files changed, 33 insertions(+), 32 deletions(-) rename src/Discord.Net/{Net => }/DiscordAPIClient.cs (99%) rename src/Discord.Net/{DiscordSimpleClient.Events.cs => DiscordWebSocketClient.Events.cs} (98%) rename src/Discord.Net/{DiscordSimpleClient.Voice.cs => DiscordWebSocketClient.Voice.cs} (97%) rename src/Discord.Net/{DiscordSimpleClient.cs => DiscordWebSocketClient.cs} (96%) rename src/Discord.Net/{DiscordSimpleClientConfig.cs => DiscordWebSocketClientConfig.cs} (98%) diff --git a/src/Discord.Net.Net45/Discord.Net.csproj b/src/Discord.Net.Net45/Discord.Net.csproj index ed613a419..703014726 100644 --- a/src/Discord.Net.Net45/Discord.Net.csproj +++ b/src/Discord.Net.Net45/Discord.Net.csproj @@ -148,6 +148,9 @@ Collections\Users.cs + + DiscordAPIClient.cs + DiscordClient.API.cs @@ -163,17 +166,17 @@ DiscordClientConfig.cs - - DiscordSimpleClient.cs + + DiscordWebSocketClient.cs - - DiscordSimpleClient.Events.cs + + DiscordWebSocketClient.Events.cs - - DiscordSimpleClient.Voice.cs + + DiscordWebSocketClient.Voice.cs - - DiscordSimpleClientConfig.cs + + DiscordWebSocketClientConfig.cs Helpers\EpochTime.cs @@ -244,9 +247,6 @@ Net\DataWebSockets.Events.cs - - Net\DiscordAPIClient.cs - Net\HttpException.cs diff --git a/src/Discord.Net/Net/DiscordAPIClient.cs b/src/Discord.Net/DiscordAPIClient.cs similarity index 99% rename from src/Discord.Net/Net/DiscordAPIClient.cs rename to src/Discord.Net/DiscordAPIClient.cs index bb5f0fb06..81a50114e 100644 --- a/src/Discord.Net/Net/DiscordAPIClient.cs +++ b/src/Discord.Net/DiscordAPIClient.cs @@ -1,11 +1,12 @@ using Discord.API; +using Discord.Net; using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; -namespace Discord.Net +namespace Discord { /// A lightweight wrapper around the Discord API. public class DiscordAPIClient diff --git a/src/Discord.Net/DiscordClient.cs b/src/Discord.Net/DiscordClient.cs index f32fdd487..d402ed573 100644 --- a/src/Discord.Net/DiscordClient.cs +++ b/src/Discord.Net/DiscordClient.cs @@ -12,13 +12,13 @@ using System.Threading.Tasks; namespace Discord { /// Provides a connection to the DiscordApp service. - public partial class DiscordClient : DiscordSimpleClient + public partial class DiscordClient : DiscordWebSocketClient { protected readonly DiscordAPIClient _api; private readonly Random _rand; private readonly JsonSerializer _serializer; private readonly ConcurrentQueue _pendingMessages; - private readonly ConcurrentDictionary _voiceClients; + private readonly ConcurrentDictionary _voiceClients; private bool _sentInitialLog; private uint _nextVoiceClientId; private string _status; @@ -59,7 +59,7 @@ namespace Discord if (Config.UseMessageQueue) _pendingMessages = new ConcurrentQueue(); if (Config.EnableVoiceMultiserver) - _voiceClients = new ConcurrentDictionary(); + _voiceClients = new ConcurrentDictionary(); object cacheLock = new object(); _channels = new Channels(this, cacheLock); @@ -747,7 +747,7 @@ namespace Discord return null; } - DiscordSimpleClient client; + DiscordWebSocketClient client; if (_voiceClients.TryGetValue(serverId, out client)) return client; else @@ -768,7 +768,7 @@ namespace Discord config.EnableVoiceMultiserver = false; config.VoiceOnly = true; config.VoiceClientId = unchecked(++_nextVoiceClientId); - return new DiscordSimpleClient(config, serverId); + return new DiscordWebSocketClient(config, serverId); }); client.LogMessage += (s, e) => RaiseOnLog(e.Severity, e.Source, $"(#{client.Config.VoiceClientId}) {e.Message}"); await client.Connect(_gateway, _token).ConfigureAwait(false); @@ -799,7 +799,7 @@ namespace Discord if (Config.EnableVoiceMultiserver) { - DiscordSimpleClient client; + DiscordWebSocketClient client; if (_voiceClients.TryRemove(serverId, out client)) await client.Disconnect(); } diff --git a/src/Discord.Net/DiscordClientConfig.cs b/src/Discord.Net/DiscordClientConfig.cs index a425a6b46..6396b5c82 100644 --- a/src/Discord.Net/DiscordClientConfig.cs +++ b/src/Discord.Net/DiscordClientConfig.cs @@ -1,6 +1,6 @@ namespace Discord { - public class DiscordClientConfig : DiscordSimpleClientConfig + public class DiscordClientConfig : DiscordWebSocketClientConfig { /// Gets or sets the time (in milliseconds) to wait when the message queue is empty before checking again. public int MessageQueueInterval { get { return _messageQueueInterval; } set { SetValue(ref _messageQueueInterval, value); } } diff --git a/src/Discord.Net/DiscordSimpleClient.Events.cs b/src/Discord.Net/DiscordWebSocketClient.Events.cs similarity index 98% rename from src/Discord.Net/DiscordSimpleClient.Events.cs rename to src/Discord.Net/DiscordWebSocketClient.Events.cs index 739ed3a0f..ad9d69fd6 100644 --- a/src/Discord.Net/DiscordSimpleClient.Events.cs +++ b/src/Discord.Net/DiscordWebSocketClient.Events.cs @@ -73,7 +73,7 @@ namespace Discord } } - public partial class DiscordSimpleClient + public partial class DiscordWebSocketClient { public event EventHandler Connected; private void RaiseConnected() diff --git a/src/Discord.Net/DiscordSimpleClient.Voice.cs b/src/Discord.Net/DiscordWebSocketClient.Voice.cs similarity index 97% rename from src/Discord.Net/DiscordSimpleClient.Voice.cs rename to src/Discord.Net/DiscordWebSocketClient.Voice.cs index a30b8b869..e6e2c72a3 100644 --- a/src/Discord.Net/DiscordSimpleClient.Voice.cs +++ b/src/Discord.Net/DiscordWebSocketClient.Voice.cs @@ -23,7 +23,7 @@ namespace Discord Task WaitVoice(); } - public partial class DiscordSimpleClient : IDiscordVoiceClient + public partial class DiscordWebSocketClient : IDiscordVoiceClient { IDiscordVoiceBuffer IDiscordVoiceClient.OutputBuffer => _voiceSocket.OutputBuffer; diff --git a/src/Discord.Net/DiscordSimpleClient.cs b/src/Discord.Net/DiscordWebSocketClient.cs similarity index 96% rename from src/Discord.Net/DiscordSimpleClient.cs rename to src/Discord.Net/DiscordWebSocketClient.cs index 9dde13ef0..45cac4f17 100644 --- a/src/Discord.Net/DiscordSimpleClient.cs +++ b/src/Discord.Net/DiscordWebSocketClient.cs @@ -17,7 +17,7 @@ namespace Discord } /// Provides a barebones connection to the Discord service - public partial class DiscordSimpleClient + public partial class DiscordWebSocketClient { internal readonly DataWebSocket _dataSocket; internal readonly VoiceWebSocket _voiceSocket; @@ -32,8 +32,8 @@ namespace Discord private bool _wasDisconnectUnexpected; /// Returns the configuration object used to make this client. Note that this object cannot be edited directly - to change the configuration of this client, use the DiscordClient(DiscordClientConfig config) constructor. - public DiscordSimpleClientConfig Config => _config; - protected readonly DiscordSimpleClientConfig _config; + public DiscordWebSocketClientConfig Config => _config; + protected readonly DiscordWebSocketClientConfig _config; /// Returns the id of the current logged-in user. public string CurrentUserId => _currentUserId; @@ -50,9 +50,9 @@ namespace Discord private CancellationToken _cancelToken; /// Initializes a new instance of the DiscordClient class. - public DiscordSimpleClient(DiscordSimpleClientConfig config = null) + public DiscordWebSocketClient(DiscordWebSocketClientConfig config = null) { - _config = config ?? new DiscordSimpleClientConfig(); + _config = config ?? new DiscordWebSocketClientConfig(); _config.Lock(); _enableVoice = _config.EnableVoice; @@ -66,7 +66,7 @@ namespace Discord if (_enableVoice) _voiceSocket = CreateVoiceSocket(); } - internal DiscordSimpleClient(DiscordSimpleClientConfig config = null, string voiceServerId = null) + internal DiscordWebSocketClient(DiscordWebSocketClientConfig config = null, string voiceServerId = null) : this(config) { _voiceServerId = voiceServerId; diff --git a/src/Discord.Net/DiscordSimpleClientConfig.cs b/src/Discord.Net/DiscordWebSocketClientConfig.cs similarity index 98% rename from src/Discord.Net/DiscordSimpleClientConfig.cs rename to src/Discord.Net/DiscordWebSocketClientConfig.cs index 863840b96..b788b3a7b 100644 --- a/src/Discord.Net/DiscordSimpleClientConfig.cs +++ b/src/Discord.Net/DiscordWebSocketClientConfig.cs @@ -12,7 +12,7 @@ namespace Discord Both = Outgoing | Incoming } - public class DiscordSimpleClientConfig + public class DiscordWebSocketClientConfig { /// Specifies the minimum log level severity that will be sent to the LogMessage event. Warning: setting this to debug will really hurt performance but should help investigate any internal issues. public LogMessageSeverity LogLevel { get { return _logLevel; } set { SetValue(ref _logLevel, value); } } diff --git a/src/Discord.Net/Net/DataWebSocket.cs b/src/Discord.Net/Net/DataWebSocket.cs index d95423348..2c800efa5 100644 --- a/src/Discord.Net/Net/DataWebSocket.cs +++ b/src/Discord.Net/Net/DataWebSocket.cs @@ -13,7 +13,7 @@ namespace Discord.Net public string SessionId => _sessionId; private string _sessionId; - public DataWebSocket(DiscordSimpleClient client) + public DataWebSocket(DiscordWebSocketClient client) : base(client) { } diff --git a/src/Discord.Net/Net/VoiceWebSocket.cs b/src/Discord.Net/Net/VoiceWebSocket.cs index 59f48da47..a8074e399 100644 --- a/src/Discord.Net/Net/VoiceWebSocket.cs +++ b/src/Discord.Net/Net/VoiceWebSocket.cs @@ -45,7 +45,7 @@ namespace Discord.Net public string CurrentChannelId => _channelId; public VoiceBuffer OutputBuffer => _sendBuffer; - public VoiceWebSocket(DiscordSimpleClient client) + public VoiceWebSocket(DiscordWebSocketClient client) : base(client) { _rand = new Random(); diff --git a/src/Discord.Net/Net/WebSocket.cs b/src/Discord.Net/Net/WebSocket.cs index cf93518c3..2bb8dfc71 100644 --- a/src/Discord.Net/Net/WebSocket.cs +++ b/src/Discord.Net/Net/WebSocket.cs @@ -34,7 +34,7 @@ namespace Discord.Net internal abstract partial class WebSocket { protected readonly IWebSocketEngine _engine; - protected readonly DiscordSimpleClient _client; + protected readonly DiscordWebSocketClient _client; protected readonly LogMessageSeverity _logLevel; protected readonly ManualResetEventSlim _connectedEvent; @@ -56,7 +56,7 @@ namespace Discord.Net public WebSocketState State => (WebSocketState)_state; protected int _state; - public WebSocket(DiscordSimpleClient client) + public WebSocket(DiscordWebSocketClient client) { _client = client; _logLevel = client.Config.LogLevel; From 98bad639572f7abd4c423642eac70f782516ba57 Mon Sep 17 00:00:00 2001 From: RogueException Date: Sun, 18 Oct 2015 18:25:19 -0300 Subject: [PATCH 020/109] Added Proxy support, split DiscordWebSocketClientConfig into DiscordAPIClientConfig --- src/Discord.Net.Net45/Discord.Net.csproj | 3 ++ src/Discord.Net/DiscordAPIClient.cs | 7 +++- src/Discord.Net/DiscordAPIClientConfig.cs | 50 +++++++++++++++++++++++++ src/Discord.Net/DiscordClient.cs | 3 +- src/Discord.Net/DiscordClientConfig.cs | 2 +- src/Discord.Net/DiscordWebSocketClientConfig.cs | 32 ++-------------- src/Discord.Net/Net/RestClient.SharpRest.cs | 12 +++--- src/Discord.Net/Net/RestClient.cs | 22 +++++------ src/Discord.Net/Net/WebSocket.WebSocketSharp.cs | 15 ++++---- src/Discord.Net/Net/WebSocket.cs | 2 +- 10 files changed, 90 insertions(+), 58 deletions(-) create mode 100644 src/Discord.Net/DiscordAPIClientConfig.cs diff --git a/src/Discord.Net.Net45/Discord.Net.csproj b/src/Discord.Net.Net45/Discord.Net.csproj index 703014726..11f2c1766 100644 --- a/src/Discord.Net.Net45/Discord.Net.csproj +++ b/src/Discord.Net.Net45/Discord.Net.csproj @@ -151,6 +151,9 @@ DiscordAPIClient.cs + + DiscordAPIClientConfig.cs + DiscordClient.API.cs diff --git a/src/Discord.Net/DiscordAPIClient.cs b/src/Discord.Net/DiscordAPIClient.cs index 81a50114e..975b9aa6d 100644 --- a/src/Discord.Net/DiscordAPIClient.cs +++ b/src/Discord.Net/DiscordAPIClient.cs @@ -11,12 +11,15 @@ namespace Discord /// A lightweight wrapper around the Discord API. public class DiscordAPIClient { + private readonly DiscordAPIClientConfig _config; + internal RestClient RestClient => _rest; private readonly RestClient _rest; - public DiscordAPIClient(LogMessageSeverity logLevel, string userAgent, int timeout) + public DiscordAPIClient(DiscordAPIClientConfig config = null) { - _rest = new RestClient(logLevel, userAgent, timeout); + _config = config ?? new DiscordAPIClientConfig(); + _rest = new RestClient(_config); } private string _token; diff --git a/src/Discord.Net/DiscordAPIClientConfig.cs b/src/Discord.Net/DiscordAPIClientConfig.cs new file mode 100644 index 000000000..0fde36c2c --- /dev/null +++ b/src/Discord.Net/DiscordAPIClientConfig.cs @@ -0,0 +1,50 @@ +using System; +using System.Net; +using System.Reflection; + +namespace Discord +{ + public class DiscordAPIClientConfig + { + /// Specifies the minimum log level severity that will be sent to the LogMessage event. Warning: setting this to debug will really hurt 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 an API request to complete. + public int APITimeout { get { return _apiTimeout; } set { SetValue(ref _apiTimeout, value); } } + private int _apiTimeout = 10000; + + /// The proxy to user for API and WebSocket connections. + public string ProxyUrl { get { return _proxyUrl; } set { SetValue(ref _proxyUrl, value); } } + private string _proxyUrl = null; + /// The credentials to use for this proxy. + public NetworkCredential ProxyCredentials { get { return _proxyCredentials; } set { SetValue(ref _proxyCredentials, value); } } + private NetworkCredential _proxyCredentials = null; + + internal string UserAgent + { + get + { + string version = typeof(DiscordClientConfig).GetTypeInfo().Assembly.GetName().Version.ToString(2); + return $"Discord.Net/{version} (https://github.com/RogueException/Discord.Net)"; + } + } + + //Lock + protected bool _isLocked; + internal void Lock() { _isLocked = true; } + protected 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; + } + + public DiscordAPIClientConfig Clone() + { + var config = MemberwiseClone() as DiscordAPIClientConfig; + config._isLocked = false; + return config; + } + } +} \ No newline at end of file diff --git a/src/Discord.Net/DiscordClient.cs b/src/Discord.Net/DiscordClient.cs index d402ed573..8a2709a8d 100644 --- a/src/Discord.Net/DiscordClient.cs +++ b/src/Discord.Net/DiscordClient.cs @@ -55,7 +55,7 @@ namespace Discord : base(config ?? new DiscordClientConfig()) { _rand = new Random(); - _api = new DiscordAPIClient(_config.LogLevel, _config.UserAgent, _config.APITimeout); + _api = new DiscordAPIClient(_config); if (Config.UseMessageQueue) _pendingMessages = new ConcurrentQueue(); if (Config.EnableVoiceMultiserver) @@ -765,7 +765,6 @@ namespace Discord { var config = _config.Clone(); config.LogLevel = _config.LogLevel;// (LogMessageSeverity)Math.Min((int)_config.LogLevel, (int)LogMessageSeverity.Warning); - config.EnableVoiceMultiserver = false; config.VoiceOnly = true; config.VoiceClientId = unchecked(++_nextVoiceClientId); return new DiscordWebSocketClient(config, serverId); diff --git a/src/Discord.Net/DiscordClientConfig.cs b/src/Discord.Net/DiscordClientConfig.cs index 6396b5c82..e5f2cdbac 100644 --- a/src/Discord.Net/DiscordClientConfig.cs +++ b/src/Discord.Net/DiscordClientConfig.cs @@ -25,7 +25,7 @@ public new DiscordClientConfig Clone() { - var config = this.MemberwiseClone() as DiscordClientConfig; + var config = MemberwiseClone() as DiscordClientConfig; config._isLocked = false; return config; } diff --git a/src/Discord.Net/DiscordWebSocketClientConfig.cs b/src/Discord.Net/DiscordWebSocketClientConfig.cs index b788b3a7b..601ad8c21 100644 --- a/src/Discord.Net/DiscordWebSocketClientConfig.cs +++ b/src/Discord.Net/DiscordWebSocketClientConfig.cs @@ -12,12 +12,8 @@ namespace Discord Both = Outgoing | Incoming } - public class DiscordWebSocketClientConfig + public class DiscordWebSocketClientConfig : DiscordAPIClientConfig { - /// Specifies the minimum log level severity that will be sent to the LogMessage event. Warning: setting this to debug will really hurt 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 DiscordClient to connect and initialize. public int ConnectionTimeout { get { return _connectionTimeout; } set { SetValue(ref _connectionTimeout, value); } } private int _connectionTimeout = 30000; @@ -27,9 +23,6 @@ namespace Discord /// Gets or sets the time (in milliseconds) to wait after an reconnect fails before retrying. public int FailedReconnectDelay { get { return _failedReconnectDelay; } set { SetValue(ref _failedReconnectDelay, value); } } private int _failedReconnectDelay = 10000; - /// Max time (in milliseconds) to wait for an API request to complete. - public int APITimeout { get { return _apiTimeout; } set { SetValue(ref _apiTimeout, value); } } - private int _apiTimeout = 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 { return _webSocketInterval; } set { SetValue(ref _webSocketInterval, value); } } @@ -54,28 +47,9 @@ namespace Discord internal virtual bool EnableVoice => _voiceMode != DiscordVoiceMode.Disabled; - internal string UserAgent - { - get - { - string version = typeof(DiscordClientConfig).GetTypeInfo().Assembly.GetName().Version.ToString(2); - return $"Discord.Net/{version} (https://github.com/RogueException/Discord.Net)"; - } - } - - //Lock - protected bool _isLocked; - internal void Lock() { _isLocked = true; } - protected 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; - } - - public DiscordClientConfig Clone() + public new DiscordWebSocketClientConfig Clone() { - var config = this.MemberwiseClone() as DiscordClientConfig; + var config = MemberwiseClone() as DiscordWebSocketClientConfig; config._isLocked = false; return config; } diff --git a/src/Discord.Net/Net/RestClient.SharpRest.cs b/src/Discord.Net/Net/RestClient.SharpRest.cs index 31e4f4925..1a9955233 100644 --- a/src/Discord.Net/Net/RestClient.SharpRest.cs +++ b/src/Discord.Net/Net/RestClient.SharpRest.cs @@ -1,6 +1,7 @@ using Discord.API; using RestSharp; using System; +using System.Net; using System.Net.Http; using System.Threading; using System.Threading.Tasks; @@ -11,18 +12,19 @@ namespace Discord.Net { private RestSharp.RestClient _client; - partial void Initialize(string userAgent, int timeout) + partial void Initialize() { _client = new RestSharp.RestClient(Endpoints.BaseApi) { - PreAuthenticate = false + PreAuthenticate = false, + Proxy = new WebProxy(_config.ProxyUrl, true, new string[0], _config.ProxyCredentials), + ReadWriteTimeout = _config.APITimeout, + UserAgent = _config.UserAgent }; _client.RemoveDefaultParameter("Accept"); _client.AddDefaultHeader("accept", "*/*"); _client.AddDefaultHeader("accept-encoding", "gzip,deflate"); - _client.UserAgent = userAgent; - _client.ReadWriteTimeout = timeout; - } + } internal void SetToken(string token) { diff --git a/src/Discord.Net/Net/RestClient.cs b/src/Discord.Net/Net/RestClient.cs index c956728c9..e8fa94db7 100644 --- a/src/Discord.Net/Net/RestClient.cs +++ b/src/Discord.Net/Net/RestClient.cs @@ -10,15 +10,15 @@ namespace Discord.Net { internal partial class RestClient { - private readonly LogMessageSeverity _logLevel; + private readonly DiscordAPIClientConfig _config; private CancellationToken _cancelToken; - public RestClient(LogMessageSeverity logLevel, string userAgent, int timeout) + public RestClient(DiscordAPIClientConfig config) { - _logLevel = logLevel; - Initialize(userAgent, timeout); + _config = config; + Initialize(); } - partial void Initialize(string userAgent, int timeout); + partial void Initialize(); //DELETE private static readonly HttpMethod _delete = HttpMethod.Delete; @@ -90,7 +90,7 @@ namespace Discord.Net if (content != null) requestJson = JsonConvert.SerializeObject(content); - if (_logLevel >= LogMessageSeverity.Verbose) + if (_config.LogLevel >= LogMessageSeverity.Verbose) stopwatch = Stopwatch.StartNew(); string responseJson = await SendInternal(method, path, requestJson, _cancelToken).ConfigureAwait(false); @@ -100,10 +100,10 @@ namespace Discord.Net throw new Exception("API check failed: Response is not empty."); #endif - if (_logLevel >= LogMessageSeverity.Verbose) + if (_config.LogLevel >= LogMessageSeverity.Verbose) { stopwatch.Stop(); - if (content != null && _logLevel >= LogMessageSeverity.Debug) + if (content != null && _config.LogLevel >= LogMessageSeverity.Debug) { if (path.StartsWith(Endpoints.Auth)) RaiseOnRequest(method, path, "[Hidden]", stopwatch.ElapsedTicks / (double)TimeSpan.TicksPerMillisecond); @@ -129,7 +129,7 @@ namespace Discord.Net { Stopwatch stopwatch = null; - if (_logLevel >= LogMessageSeverity.Verbose) + if (_config.LogLevel >= LogMessageSeverity.Verbose) stopwatch = Stopwatch.StartNew(); string responseJson = await SendFileInternal(method, path, filePath, _cancelToken).ConfigureAwait(false); @@ -139,10 +139,10 @@ namespace Discord.Net throw new Exception("API check failed: Response is not empty."); #endif - if (_logLevel >= LogMessageSeverity.Verbose) + if (_config.LogLevel >= LogMessageSeverity.Verbose) { stopwatch.Stop(); - if (_logLevel >= LogMessageSeverity.Debug) + if (_config.LogLevel >= LogMessageSeverity.Debug) RaiseOnRequest(method, path, filePath, stopwatch.ElapsedTicks / (double)TimeSpan.TicksPerMillisecond); else RaiseOnRequest(method, path, null, stopwatch.ElapsedTicks / (double)TimeSpan.TicksPerMillisecond); diff --git a/src/Discord.Net/Net/WebSocket.WebSocketSharp.cs b/src/Discord.Net/Net/WebSocket.WebSocketSharp.cs index f245dd24e..7a0ac2c30 100644 --- a/src/Discord.Net/Net/WebSocket.WebSocketSharp.cs +++ b/src/Discord.Net/Net/WebSocket.WebSocketSharp.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Net; using System.Threading; using System.Threading.Tasks; using WSSharpNWebSocket = WebSocketSharp.WebSocket; @@ -9,9 +10,8 @@ namespace Discord.Net { internal class WSSharpWebSocketEngine : IWebSocketEngine { + private readonly DiscordWebSocketClientConfig _config; private readonly ConcurrentQueue _sendQueue; - private readonly int _sendInterval; - private readonly string _userAgent; private readonly WebSocket _parent; private WSSharpNWebSocket _webSocket; @@ -22,11 +22,10 @@ namespace Discord.Net ProcessMessage(this, new WebSocketMessageEventArgs(msg)); } - internal WSSharpWebSocketEngine(WebSocket parent, string userAgent, int sendInterval) + internal WSSharpWebSocketEngine(WebSocket parent, DiscordWebSocketClientConfig config) { _parent = parent; - _userAgent = userAgent; - _sendInterval = sendInterval; + _config = config; _sendQueue = new ConcurrentQueue(); } @@ -35,7 +34,8 @@ namespace Discord.Net _webSocket = new WSSharpNWebSocket(host); _webSocket.EmitOnPing = false; _webSocket.EnableRedirection = true; - _webSocket.Compression = WebSocketSharp.CompressionMethod.None; + _webSocket.Compression = WebSocketSharp.CompressionMethod.None; + _webSocket.SetProxy(_config.ProxyUrl, _config.ProxyCredentials.UserName, _config.ProxyCredentials.Password); _webSocket.OnMessage += (s, e) => RaiseProcessMessage(e.Data); _webSocket.OnError += async (s, e) => { @@ -77,6 +77,7 @@ namespace Discord.Net private Task SendAsync(CancellationToken cancelToken) { + var sendInterval = _config.WebSocketInterval; return Task.Run(async () => { try @@ -86,7 +87,7 @@ namespace Discord.Net string json; while (_sendQueue.TryDequeue(out json)) _webSocket.Send(json); - await Task.Delay(_sendInterval, cancelToken).ConfigureAwait(false); + await Task.Delay(sendInterval, cancelToken).ConfigureAwait(false); } } catch (OperationCanceledException) { } diff --git a/src/Discord.Net/Net/WebSocket.cs b/src/Discord.Net/Net/WebSocket.cs index 2bb8dfc71..75bfb136c 100644 --- a/src/Discord.Net/Net/WebSocket.cs +++ b/src/Discord.Net/Net/WebSocket.cs @@ -65,7 +65,7 @@ namespace Discord.Net _cancelToken = new CancellationToken(true); _connectedEvent = new ManualResetEventSlim(false); - _engine = new WSSharpWebSocketEngine(this, client.Config.UserAgent, client.Config.WebSocketInterval); + _engine = new WSSharpWebSocketEngine(this, client.Config); _engine.ProcessMessage += async (s, e) => { if (_logLevel >= LogMessageSeverity.Debug) From 2969361333b48c12c3d12d3223c998c264c512dc Mon Sep 17 00:00:00 2001 From: RogueException Date: Sun, 18 Oct 2015 18:25:53 -0300 Subject: [PATCH 021/109] Fixed error with null proxycredentials --- src/Discord.Net/Net/WebSocket.WebSocketSharp.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Discord.Net/Net/WebSocket.WebSocketSharp.cs b/src/Discord.Net/Net/WebSocket.WebSocketSharp.cs index 7a0ac2c30..0feb6e90a 100644 --- a/src/Discord.Net/Net/WebSocket.WebSocketSharp.cs +++ b/src/Discord.Net/Net/WebSocket.WebSocketSharp.cs @@ -35,7 +35,7 @@ namespace Discord.Net _webSocket.EmitOnPing = false; _webSocket.EnableRedirection = true; _webSocket.Compression = WebSocketSharp.CompressionMethod.None; - _webSocket.SetProxy(_config.ProxyUrl, _config.ProxyCredentials.UserName, _config.ProxyCredentials.Password); + _webSocket.SetProxy(_config.ProxyUrl, _config.ProxyCredentials?.UserName, _config.ProxyCredentials?.Password); _webSocket.OnMessage += (s, e) => RaiseProcessMessage(e.Data); _webSocket.OnError += async (s, e) => { From ee44caf6ab69437756eb2f0036a14c4109b75fe4 Mon Sep 17 00:00:00 2001 From: RogueException Date: Sun, 18 Oct 2015 18:54:23 -0300 Subject: [PATCH 022/109] Reorganized Net namespace, made SharpRest inherit from RestClient. --- src/Discord.Net.Net45/Discord.Net.csproj | 54 ++++++++++++---------- src/Discord.Net/DiscordAPIClient.cs | 3 +- src/Discord.Net/DiscordClient.cs | 2 + src/Discord.Net/DiscordWebSocketClient.Voice.cs | 23 +-------- src/Discord.Net/DiscordWebSocketClient.cs | 1 + .../Net/{ => Rest}/RestClient.Events.cs | 4 +- src/Discord.Net/Net/{ => Rest}/RestClient.cs | 14 +++--- .../SharpRestRestClient.cs} | 15 +++--- src/Discord.Net/Net/Voice/IDiscordVoiceBuffer.cs | 10 ++++ src/Discord.Net/Net/Voice/IDiscordVoiceClient.cs | 16 +++++++ src/Discord.Net/Net/{ => Voice}/VoiceBuffer.cs | 2 +- .../Net/{ => WebSockets}/DataWebSocket.cs | 2 +- .../Net/{ => WebSockets}/DataWebSockets.Events.cs | 2 +- .../Net/{ => WebSockets}/VoiceWebSocket.Events.cs | 2 +- .../Net/{ => WebSockets}/VoiceWebSocket.cs | 3 +- .../Net/{ => WebSockets}/WebSocket.BuiltIn.cs.old | 2 +- .../Net/{ => WebSockets}/WebSocket.Events.cs | 2 +- .../{ => WebSockets}/WebSocket.WebSocketSharp.cs | 2 +- src/Discord.Net/Net/{ => WebSockets}/WebSocket.cs | 2 +- 19 files changed, 91 insertions(+), 70 deletions(-) rename src/Discord.Net/Net/{ => Rest}/RestClient.Events.cs (90%) rename src/Discord.Net/Net/{ => Rest}/RestClient.cs (92%) rename src/Discord.Net/Net/{RestClient.SharpRest.cs => Rest/SharpRestRestClient.cs} (79%) create mode 100644 src/Discord.Net/Net/Voice/IDiscordVoiceBuffer.cs create mode 100644 src/Discord.Net/Net/Voice/IDiscordVoiceClient.cs rename src/Discord.Net/Net/{ => Voice}/VoiceBuffer.cs (99%) rename src/Discord.Net/Net/{ => WebSockets}/DataWebSocket.cs (99%) rename src/Discord.Net/Net/{ => WebSockets}/DataWebSockets.Events.cs (94%) rename src/Discord.Net/Net/{ => WebSockets}/VoiceWebSocket.Events.cs (96%) rename src/Discord.Net/Net/{ => WebSockets}/VoiceWebSocket.cs (99%) rename src/Discord.Net/Net/{ => WebSockets}/WebSocket.BuiltIn.cs.old (99%) rename src/Discord.Net/Net/{ => WebSockets}/WebSocket.Events.cs (95%) rename src/Discord.Net/Net/{ => WebSockets}/WebSocket.WebSocketSharp.cs (98%) rename src/Discord.Net/Net/{ => WebSockets}/WebSocket.cs (99%) diff --git a/src/Discord.Net.Net45/Discord.Net.csproj b/src/Discord.Net.Net45/Discord.Net.csproj index 11f2c1766..e7dda6c3c 100644 --- a/src/Discord.Net.Net45/Discord.Net.csproj +++ b/src/Discord.Net.Net45/Discord.Net.csproj @@ -244,41 +244,47 @@ Models\User.cs - - Net\DataWebSocket.cs - - - Net\DataWebSockets.Events.cs - Net\HttpException.cs - - Net\RestClient.cs + + Net\Rest\RestClient.cs + + + Net\Rest\RestClient.Events.cs + + + Net\Rest\SharpRestRestClient.cs + + + Net\Voice\IDiscordVoiceBuffer.cs + + + Net\Voice\IDiscordVoiceClient.cs - - Net\RestClient.Events.cs + + Net\Voice\VoiceBuffer.cs - - Net\RestClient.SharpRest.cs + + Net\WebSockets\DataWebSocket.cs - - Net\VoiceBuffer.cs + + Net\WebSockets\DataWebSockets.Events.cs - - Net\VoiceWebSocket.cs + + Net\WebSockets\VoiceWebSocket.cs - - Net\VoiceWebSocket.Events.cs + + Net\WebSockets\VoiceWebSocket.Events.cs - - Net\WebSocket.cs + + Net\WebSockets\WebSocket.cs - - Net\WebSocket.Events.cs + + Net\WebSockets\WebSocket.Events.cs - - Net\WebSocket.WebSocketSharp.cs + + Net\WebSockets\WebSocket.WebSocketSharp.cs diff --git a/src/Discord.Net/DiscordAPIClient.cs b/src/Discord.Net/DiscordAPIClient.cs index 975b9aa6d..49e34ea57 100644 --- a/src/Discord.Net/DiscordAPIClient.cs +++ b/src/Discord.Net/DiscordAPIClient.cs @@ -1,5 +1,6 @@ using Discord.API; using Discord.Net; +using Discord.Net.Rest; using System; using System.Collections.Generic; using System.Linq; @@ -19,7 +20,7 @@ namespace Discord public DiscordAPIClient(DiscordAPIClientConfig config = null) { _config = config ?? new DiscordAPIClientConfig(); - _rest = new RestClient(_config); + _rest = new SharpRestRestClient(_config); } private string _token; diff --git a/src/Discord.Net/DiscordClient.cs b/src/Discord.Net/DiscordClient.cs index 8a2709a8d..428a6239b 100644 --- a/src/Discord.Net/DiscordClient.cs +++ b/src/Discord.Net/DiscordClient.cs @@ -1,6 +1,8 @@ using Discord.API; using Discord.Collections; using Discord.Net; +using Discord.Net.Voice; +using Discord.Net.WebSockets; using Newtonsoft.Json; using System; using System.Collections.Concurrent; diff --git a/src/Discord.Net/DiscordWebSocketClient.Voice.cs b/src/Discord.Net/DiscordWebSocketClient.Voice.cs index e6e2c72a3..c45339a11 100644 --- a/src/Discord.Net/DiscordWebSocketClient.Voice.cs +++ b/src/Discord.Net/DiscordWebSocketClient.Voice.cs @@ -1,28 +1,9 @@ -using System; +using Discord.Net.Voice; +using System; using System.Threading.Tasks; namespace Discord { - public interface IDiscordVoiceBuffer - { - int FrameSize { get; } - int FrameCount { get; } - ushort ReadPos { get; } - ushort WritePos { get; } - } - - public interface IDiscordVoiceClient - { - IDiscordVoiceBuffer OutputBuffer { get; } - - Task JoinChannel(string channelId); - - void SendVoicePCM(byte[] data, int count); - void ClearVoicePCM(); - - Task WaitVoice(); - } - public partial class DiscordWebSocketClient : IDiscordVoiceClient { IDiscordVoiceBuffer IDiscordVoiceClient.OutputBuffer => _voiceSocket.OutputBuffer; diff --git a/src/Discord.Net/DiscordWebSocketClient.cs b/src/Discord.Net/DiscordWebSocketClient.cs index 45cac4f17..7ab63a12f 100644 --- a/src/Discord.Net/DiscordWebSocketClient.cs +++ b/src/Discord.Net/DiscordWebSocketClient.cs @@ -1,4 +1,5 @@ using Discord.Net; +using Discord.Net.WebSockets; using System; using System.Collections.Generic; using System.Linq; diff --git a/src/Discord.Net/Net/RestClient.Events.cs b/src/Discord.Net/Net/Rest/RestClient.Events.cs similarity index 90% rename from src/Discord.Net/Net/RestClient.Events.cs rename to src/Discord.Net/Net/Rest/RestClient.Events.cs index 85c4d02a1..88cd55713 100644 --- a/src/Discord.Net/Net/RestClient.Events.cs +++ b/src/Discord.Net/Net/Rest/RestClient.Events.cs @@ -1,9 +1,9 @@ using System; using System.Net.Http; -namespace Discord.Net +namespace Discord.Net.Rest { - internal partial class RestClient + internal abstract partial class RestClient { public class RequestEventArgs : EventArgs { diff --git a/src/Discord.Net/Net/RestClient.cs b/src/Discord.Net/Net/Rest/RestClient.cs similarity index 92% rename from src/Discord.Net/Net/RestClient.cs rename to src/Discord.Net/Net/Rest/RestClient.cs index e8fa94db7..2b161ea0d 100644 --- a/src/Discord.Net/Net/RestClient.cs +++ b/src/Discord.Net/Net/Rest/RestClient.cs @@ -6,19 +6,21 @@ using System.Net.Http; using System.Threading; using System.Threading.Tasks; -namespace Discord.Net +namespace Discord.Net.Rest { - internal partial class RestClient + internal abstract partial class RestClient { - private readonly DiscordAPIClientConfig _config; - private CancellationToken _cancelToken; + protected readonly DiscordAPIClientConfig _config; + protected CancellationToken _cancelToken; public RestClient(DiscordAPIClientConfig config) { _config = config; - Initialize(); } - partial void Initialize(); + + protected internal abstract void SetToken(string token); + protected abstract Task SendInternal(HttpMethod method, string path, string json, CancellationToken cancelToken); + protected abstract Task SendFileInternal(HttpMethod method, string path, string filePath, CancellationToken cancelToken); //DELETE private static readonly HttpMethod _delete = HttpMethod.Delete; diff --git a/src/Discord.Net/Net/RestClient.SharpRest.cs b/src/Discord.Net/Net/Rest/SharpRestRestClient.cs similarity index 79% rename from src/Discord.Net/Net/RestClient.SharpRest.cs rename to src/Discord.Net/Net/Rest/SharpRestRestClient.cs index 1a9955233..918d3d204 100644 --- a/src/Discord.Net/Net/RestClient.SharpRest.cs +++ b/src/Discord.Net/Net/Rest/SharpRestRestClient.cs @@ -6,13 +6,14 @@ using System.Net.Http; using System.Threading; using System.Threading.Tasks; -namespace Discord.Net +namespace Discord.Net.Rest { - internal partial class RestClient + internal sealed class SharpRestRestClient : RestClient { - private RestSharp.RestClient _client; + private readonly RestSharp.RestClient _client; - partial void Initialize() + public SharpRestRestClient(DiscordAPIClientConfig config) + : base(config) { _client = new RestSharp.RestClient(Endpoints.BaseApi) { @@ -26,20 +27,20 @@ namespace Discord.Net _client.AddDefaultHeader("accept-encoding", "gzip,deflate"); } - internal void SetToken(string token) + protected internal override void SetToken(string token) { _client.RemoveDefaultParameter("authorization"); if (token != null) _client.AddDefaultHeader("authorization", token); } - private Task SendInternal(HttpMethod method, string path, string json, CancellationToken cancelToken) + protected override Task SendInternal(HttpMethod method, string path, string json, CancellationToken cancelToken) { var request = new RestRequest(path, GetMethod(method)); request.AddParameter("application/json", json, ParameterType.RequestBody); return Send(request, cancelToken); } - private Task SendFileInternal(HttpMethod method, string path, string filePath, CancellationToken cancelToken) + protected override Task SendFileInternal(HttpMethod method, string path, string filePath, CancellationToken cancelToken) { var request = new RestRequest(path, Method.POST); request.AddFile("file", filePath); diff --git a/src/Discord.Net/Net/Voice/IDiscordVoiceBuffer.cs b/src/Discord.Net/Net/Voice/IDiscordVoiceBuffer.cs new file mode 100644 index 000000000..cf301bef7 --- /dev/null +++ b/src/Discord.Net/Net/Voice/IDiscordVoiceBuffer.cs @@ -0,0 +1,10 @@ +namespace Discord.Net.Voice +{ + public interface IDiscordVoiceBuffer + { + int FrameSize { get; } + int FrameCount { get; } + ushort ReadPos { get; } + ushort WritePos { get; } + } +} diff --git a/src/Discord.Net/Net/Voice/IDiscordVoiceClient.cs b/src/Discord.Net/Net/Voice/IDiscordVoiceClient.cs new file mode 100644 index 000000000..5cbf0a4fe --- /dev/null +++ b/src/Discord.Net/Net/Voice/IDiscordVoiceClient.cs @@ -0,0 +1,16 @@ +using System.Threading.Tasks; + +namespace Discord.Net.Voice +{ + public interface IDiscordVoiceClient + { + IDiscordVoiceBuffer OutputBuffer { get; } + + Task JoinChannel(string channelId); + + void SendVoicePCM(byte[] data, int count); + void ClearVoicePCM(); + + Task WaitVoice(); + } +} diff --git a/src/Discord.Net/Net/VoiceBuffer.cs b/src/Discord.Net/Net/Voice/VoiceBuffer.cs similarity index 99% rename from src/Discord.Net/Net/VoiceBuffer.cs rename to src/Discord.Net/Net/Voice/VoiceBuffer.cs index 29bcb8e77..84d24541a 100644 --- a/src/Discord.Net/Net/VoiceBuffer.cs +++ b/src/Discord.Net/Net/Voice/VoiceBuffer.cs @@ -1,7 +1,7 @@ using System; using System.Threading; -namespace Discord.Net +namespace Discord.Net.Voice { internal class VoiceBuffer : IDiscordVoiceBuffer { diff --git a/src/Discord.Net/Net/DataWebSocket.cs b/src/Discord.Net/Net/WebSockets/DataWebSocket.cs similarity index 99% rename from src/Discord.Net/Net/DataWebSocket.cs rename to src/Discord.Net/Net/WebSockets/DataWebSocket.cs index 2c800efa5..8976948e0 100644 --- a/src/Discord.Net/Net/DataWebSocket.cs +++ b/src/Discord.Net/Net/WebSockets/DataWebSocket.cs @@ -4,7 +4,7 @@ using Newtonsoft.Json.Linq; using System; using System.Threading.Tasks; -namespace Discord.Net +namespace Discord.Net.WebSockets { internal partial class DataWebSocket : WebSocket { diff --git a/src/Discord.Net/Net/DataWebSockets.Events.cs b/src/Discord.Net/Net/WebSockets/DataWebSockets.Events.cs similarity index 94% rename from src/Discord.Net/Net/DataWebSockets.Events.cs rename to src/Discord.Net/Net/WebSockets/DataWebSockets.Events.cs index 6f5177fe1..f823a415a 100644 --- a/src/Discord.Net/Net/DataWebSockets.Events.cs +++ b/src/Discord.Net/Net/WebSockets/DataWebSockets.Events.cs @@ -1,7 +1,7 @@ using Newtonsoft.Json.Linq; using System; -namespace Discord.Net +namespace Discord.Net.WebSockets { internal sealed class WebSocketEventEventArgs : EventArgs { diff --git a/src/Discord.Net/Net/VoiceWebSocket.Events.cs b/src/Discord.Net/Net/WebSockets/VoiceWebSocket.Events.cs similarity index 96% rename from src/Discord.Net/Net/VoiceWebSocket.Events.cs rename to src/Discord.Net/Net/WebSockets/VoiceWebSocket.Events.cs index 82f525158..1146d6cc2 100644 --- a/src/Discord.Net/Net/VoiceWebSocket.Events.cs +++ b/src/Discord.Net/Net/WebSockets/VoiceWebSocket.Events.cs @@ -1,6 +1,6 @@ using System; -namespace Discord.Net +namespace Discord.Net.WebSockets { internal sealed class IsTalkingEventArgs : EventArgs { diff --git a/src/Discord.Net/Net/VoiceWebSocket.cs b/src/Discord.Net/Net/WebSockets/VoiceWebSocket.cs similarity index 99% rename from src/Discord.Net/Net/VoiceWebSocket.cs rename to src/Discord.Net/Net/WebSockets/VoiceWebSocket.cs index a8074e399..3dc7fed89 100644 --- a/src/Discord.Net/Net/VoiceWebSocket.cs +++ b/src/Discord.Net/Net/WebSockets/VoiceWebSocket.cs @@ -1,6 +1,7 @@ #define USE_THREAD using Discord.API; using Discord.Interop; +using Discord.Net.Voice; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; @@ -14,7 +15,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; -namespace Discord.Net +namespace Discord.Net.WebSockets { internal partial class VoiceWebSocket : WebSocket { diff --git a/src/Discord.Net/Net/WebSocket.BuiltIn.cs.old b/src/Discord.Net/Net/WebSockets/WebSocket.BuiltIn.cs.old similarity index 99% rename from src/Discord.Net/Net/WebSocket.BuiltIn.cs.old rename to src/Discord.Net/Net/WebSockets/WebSocket.BuiltIn.cs.old index af375e743..17a8f7be1 100644 --- a/src/Discord.Net/Net/WebSocket.BuiltIn.cs.old +++ b/src/Discord.Net/Net/WebSockets/WebSocket.BuiltIn.cs.old @@ -9,7 +9,7 @@ using System.Threading; using System.Threading.Tasks; using State = System.Net.WebSockets.WebSocketState; -namespace Discord.WebSockets +namespace Discord.Net.WebSockets { internal class BuiltInWebSocketEngine : IWebSocketEngine { diff --git a/src/Discord.Net/Net/WebSocket.Events.cs b/src/Discord.Net/Net/WebSockets/WebSocket.Events.cs similarity index 95% rename from src/Discord.Net/Net/WebSocket.Events.cs rename to src/Discord.Net/Net/WebSockets/WebSocket.Events.cs index 1c2383198..f7b4c078a 100644 --- a/src/Discord.Net/Net/WebSocket.Events.cs +++ b/src/Discord.Net/Net/WebSockets/WebSocket.Events.cs @@ -1,6 +1,6 @@ using System; -namespace Discord.Net +namespace Discord.Net.WebSockets { internal abstract partial class WebSocket { diff --git a/src/Discord.Net/Net/WebSocket.WebSocketSharp.cs b/src/Discord.Net/Net/WebSockets/WebSocket.WebSocketSharp.cs similarity index 98% rename from src/Discord.Net/Net/WebSocket.WebSocketSharp.cs rename to src/Discord.Net/Net/WebSockets/WebSocket.WebSocketSharp.cs index 0feb6e90a..bc5aff3e0 100644 --- a/src/Discord.Net/Net/WebSocket.WebSocketSharp.cs +++ b/src/Discord.Net/Net/WebSockets/WebSocket.WebSocketSharp.cs @@ -6,7 +6,7 @@ using System.Threading; using System.Threading.Tasks; using WSSharpNWebSocket = WebSocketSharp.WebSocket; -namespace Discord.Net +namespace Discord.Net.WebSockets { internal class WSSharpWebSocketEngine : IWebSocketEngine { diff --git a/src/Discord.Net/Net/WebSocket.cs b/src/Discord.Net/Net/WebSockets/WebSocket.cs similarity index 99% rename from src/Discord.Net/Net/WebSocket.cs rename to src/Discord.Net/Net/WebSockets/WebSocket.cs index 75bfb136c..4ef471845 100644 --- a/src/Discord.Net/Net/WebSocket.cs +++ b/src/Discord.Net/Net/WebSockets/WebSocket.cs @@ -6,7 +6,7 @@ using System.Runtime.ExceptionServices; using System.Threading; using System.Threading.Tasks; -namespace Discord.Net +namespace Discord.Net.WebSockets { public enum WebSocketState : byte { From 41b337b2731571716cca2a9acb7cd03d8563679e Mon Sep 17 00:00:00 2001 From: RogueException Date: Sun, 18 Oct 2015 19:31:49 -0300 Subject: [PATCH 023/109] Reverted to old style Rest/WebSocket engines --- src/Discord.Net.Net45/Discord.Net.csproj | 14 +++++++++---- src/Discord.Net/DiscordAPIClient.cs | 2 +- src/Discord.Net/Net/Rest/IRestEngine.cs | 13 ++++++++++++ src/Discord.Net/Net/Rest/RestClient.Events.cs | 4 ++-- src/Discord.Net/Net/Rest/RestClient.cs | 16 +++++++-------- .../{SharpRestRestClient.cs => SharpRestEngine.cs} | 13 ++++++------ src/Discord.Net/Net/WebSockets/IWebSocketEngine.cs | 23 ++++++++++++++++++++++ src/Discord.Net/Net/WebSockets/WebSocket.cs | 17 +--------------- ...t.WebSocketSharp.cs => WebSocketSharpEngine.cs} | 4 ++-- 9 files changed, 67 insertions(+), 39 deletions(-) create mode 100644 src/Discord.Net/Net/Rest/IRestEngine.cs rename src/Discord.Net/Net/Rest/{SharpRestRestClient.cs => SharpRestEngine.cs} (82%) create mode 100644 src/Discord.Net/Net/WebSockets/IWebSocketEngine.cs rename src/Discord.Net/Net/WebSockets/{WebSocket.WebSocketSharp.cs => WebSocketSharpEngine.cs} (94%) diff --git a/src/Discord.Net.Net45/Discord.Net.csproj b/src/Discord.Net.Net45/Discord.Net.csproj index e7dda6c3c..8b2b605bc 100644 --- a/src/Discord.Net.Net45/Discord.Net.csproj +++ b/src/Discord.Net.Net45/Discord.Net.csproj @@ -247,14 +247,17 @@ Net\HttpException.cs + + Net\Rest\IRestEngine.cs + Net\Rest\RestClient.cs Net\Rest\RestClient.Events.cs - - Net\Rest\SharpRestRestClient.cs + + Net\Rest\SharpRestEngine.cs Net\Voice\IDiscordVoiceBuffer.cs @@ -271,6 +274,9 @@ Net\WebSockets\DataWebSockets.Events.cs + + Net\WebSockets\IWebSocketEngine.cs + Net\WebSockets\VoiceWebSocket.cs @@ -283,8 +289,8 @@ Net\WebSockets\WebSocket.Events.cs - - Net\WebSockets\WebSocket.WebSocketSharp.cs + + Net\WebSockets\WebSocketSharpEngine.cs diff --git a/src/Discord.Net/DiscordAPIClient.cs b/src/Discord.Net/DiscordAPIClient.cs index 49e34ea57..bea5e5788 100644 --- a/src/Discord.Net/DiscordAPIClient.cs +++ b/src/Discord.Net/DiscordAPIClient.cs @@ -20,7 +20,7 @@ namespace Discord public DiscordAPIClient(DiscordAPIClientConfig config = null) { _config = config ?? new DiscordAPIClientConfig(); - _rest = new SharpRestRestClient(_config); + _rest = new RestClient(_config); } private string _token; diff --git a/src/Discord.Net/Net/Rest/IRestEngine.cs b/src/Discord.Net/Net/Rest/IRestEngine.cs new file mode 100644 index 000000000..3bed09f40 --- /dev/null +++ b/src/Discord.Net/Net/Rest/IRestEngine.cs @@ -0,0 +1,13 @@ +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +namespace Discord.Net.Rest +{ + 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); + } +} diff --git a/src/Discord.Net/Net/Rest/RestClient.Events.cs b/src/Discord.Net/Net/Rest/RestClient.Events.cs index 88cd55713..b12fe997f 100644 --- a/src/Discord.Net/Net/Rest/RestClient.Events.cs +++ b/src/Discord.Net/Net/Rest/RestClient.Events.cs @@ -3,7 +3,7 @@ using System.Net.Http; namespace Discord.Net.Rest { - internal abstract partial class RestClient + internal sealed partial class RestClient { public class RequestEventArgs : EventArgs { @@ -21,7 +21,7 @@ namespace Discord.Net.Rest } public event EventHandler OnRequest; - protected void RaiseOnRequest(HttpMethod method, string path, string payload, double milliseconds) + private 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/Rest/RestClient.cs b/src/Discord.Net/Net/Rest/RestClient.cs index 2b161ea0d..0cc6fb276 100644 --- a/src/Discord.Net/Net/Rest/RestClient.cs +++ b/src/Discord.Net/Net/Rest/RestClient.cs @@ -8,19 +8,19 @@ using System.Threading.Tasks; namespace Discord.Net.Rest { - internal abstract partial class RestClient + internal sealed partial class RestClient { - protected readonly DiscordAPIClientConfig _config; - protected CancellationToken _cancelToken; + private readonly DiscordAPIClientConfig _config; + private readonly IRestEngine _engine; + private CancellationToken _cancelToken; public RestClient(DiscordAPIClientConfig config) { _config = config; + _engine = new SharpRestEngine(config); } - protected internal abstract void SetToken(string token); - protected abstract Task SendInternal(HttpMethod method, string path, string json, CancellationToken cancelToken); - protected abstract Task SendFileInternal(HttpMethod method, string path, string filePath, CancellationToken cancelToken); + public void SetToken(string token) => _engine.SetToken(token); //DELETE private static readonly HttpMethod _delete = HttpMethod.Delete; @@ -95,7 +95,7 @@ namespace Discord.Net.Rest if (_config.LogLevel >= LogMessageSeverity.Verbose) stopwatch = Stopwatch.StartNew(); - string responseJson = await SendInternal(method, path, requestJson, _cancelToken).ConfigureAwait(false); + string responseJson = await _engine.Send(method, path, requestJson, _cancelToken).ConfigureAwait(false); #if TEST_RESPONSES if (!hasResponse && !string.IsNullOrEmpty(responseJson)) @@ -134,7 +134,7 @@ namespace Discord.Net.Rest if (_config.LogLevel >= LogMessageSeverity.Verbose) stopwatch = Stopwatch.StartNew(); - string responseJson = await SendFileInternal(method, path, filePath, _cancelToken).ConfigureAwait(false); + string responseJson = await _engine.SendFile(method, path, filePath, _cancelToken).ConfigureAwait(false); #if TEST_RESPONSES if (!hasResponse && !string.IsNullOrEmpty(responseJson)) diff --git a/src/Discord.Net/Net/Rest/SharpRestRestClient.cs b/src/Discord.Net/Net/Rest/SharpRestEngine.cs similarity index 82% rename from src/Discord.Net/Net/Rest/SharpRestRestClient.cs rename to src/Discord.Net/Net/Rest/SharpRestEngine.cs index 918d3d204..553746f14 100644 --- a/src/Discord.Net/Net/Rest/SharpRestRestClient.cs +++ b/src/Discord.Net/Net/Rest/SharpRestEngine.cs @@ -8,13 +8,14 @@ using System.Threading.Tasks; namespace Discord.Net.Rest { - internal sealed class SharpRestRestClient : RestClient + internal sealed class SharpRestEngine : IRestEngine { + private readonly DiscordAPIClientConfig _config; private readonly RestSharp.RestClient _client; - public SharpRestRestClient(DiscordAPIClientConfig config) - : base(config) + public SharpRestEngine(DiscordAPIClientConfig config) { + _config = config; _client = new RestSharp.RestClient(Endpoints.BaseApi) { PreAuthenticate = false, @@ -27,20 +28,20 @@ namespace Discord.Net.Rest _client.AddDefaultHeader("accept-encoding", "gzip,deflate"); } - protected internal override void SetToken(string token) + public void SetToken(string token) { _client.RemoveDefaultParameter("authorization"); if (token != null) _client.AddDefaultHeader("authorization", token); } - protected override Task SendInternal(HttpMethod method, string path, string json, CancellationToken cancelToken) + public Task Send(HttpMethod method, string path, string json, CancellationToken cancelToken) { var request = new RestRequest(path, GetMethod(method)); request.AddParameter("application/json", json, ParameterType.RequestBody); return Send(request, cancelToken); } - protected override Task SendFileInternal(HttpMethod method, string path, string filePath, CancellationToken cancelToken) + public Task SendFile(HttpMethod method, string path, string filePath, CancellationToken cancelToken) { var request = new RestRequest(path, Method.POST); request.AddFile("file", filePath); diff --git a/src/Discord.Net/Net/WebSockets/IWebSocketEngine.cs b/src/Discord.Net/Net/WebSockets/IWebSocketEngine.cs new file mode 100644 index 000000000..5ee746542 --- /dev/null +++ b/src/Discord.Net/Net/WebSockets/IWebSocketEngine.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Discord.Net.WebSockets +{ + internal 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(string message); + IEnumerable GetTasks(CancellationToken cancelToken); + } +} diff --git a/src/Discord.Net/Net/WebSockets/WebSocket.cs b/src/Discord.Net/Net/WebSockets/WebSocket.cs index 4ef471845..e673e42cb 100644 --- a/src/Discord.Net/Net/WebSockets/WebSocket.cs +++ b/src/Discord.Net/Net/WebSockets/WebSocket.cs @@ -16,21 +16,6 @@ namespace Discord.Net.WebSockets Disconnecting } - internal 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(string message); - IEnumerable GetTasks(CancellationToken cancelToken); - } - internal abstract partial class WebSocket { protected readonly IWebSocketEngine _engine; @@ -65,7 +50,7 @@ namespace Discord.Net.WebSockets _cancelToken = new CancellationToken(true); _connectedEvent = new ManualResetEventSlim(false); - _engine = new WSSharpWebSocketEngine(this, client.Config); + _engine = new WebSocketSharpEngine(this, client.Config); _engine.ProcessMessage += async (s, e) => { if (_logLevel >= LogMessageSeverity.Debug) diff --git a/src/Discord.Net/Net/WebSockets/WebSocket.WebSocketSharp.cs b/src/Discord.Net/Net/WebSockets/WebSocketSharpEngine.cs similarity index 94% rename from src/Discord.Net/Net/WebSockets/WebSocket.WebSocketSharp.cs rename to src/Discord.Net/Net/WebSockets/WebSocketSharpEngine.cs index bc5aff3e0..0538d160a 100644 --- a/src/Discord.Net/Net/WebSockets/WebSocket.WebSocketSharp.cs +++ b/src/Discord.Net/Net/WebSockets/WebSocketSharpEngine.cs @@ -8,7 +8,7 @@ using WSSharpNWebSocket = WebSocketSharp.WebSocket; namespace Discord.Net.WebSockets { - internal class WSSharpWebSocketEngine : IWebSocketEngine + internal class WebSocketSharpEngine : IWebSocketEngine { private readonly DiscordWebSocketClientConfig _config; private readonly ConcurrentQueue _sendQueue; @@ -22,7 +22,7 @@ namespace Discord.Net.WebSockets ProcessMessage(this, new WebSocketMessageEventArgs(msg)); } - internal WSSharpWebSocketEngine(WebSocket parent, DiscordWebSocketClientConfig config) + internal WebSocketSharpEngine(WebSocket parent, DiscordWebSocketClientConfig config) { _parent = parent; _config = config; From 6ac22aac2142bb8d1f0c7791e29018d043b665e8 Mon Sep 17 00:00:00 2001 From: RogueException Date: Sun, 18 Oct 2015 22:45:24 -0300 Subject: [PATCH 024/109] Renamed DiscordWebSocketClient -> DiscordWSClient --- src/Discord.Net.Net45/Discord.Net.csproj | 16 ++++++++-------- src/Discord.Net/DiscordClient.cs | 12 ++++++------ src/Discord.Net/DiscordClientConfig.cs | 2 +- ...bSocketClient.Events.cs => DiscordWSClient.Events.cs} | 2 +- ...WebSocketClient.Voice.cs => DiscordWSClient.Voice.cs} | 2 +- .../{DiscordWebSocketClient.cs => DiscordWSClient.cs} | 14 +++++++------- ...WebSocketClientConfig.cs => DiscordWSClientConfig.cs} | 6 +++--- src/Discord.Net/Net/WebSockets/DataWebSocket.cs | 2 +- src/Discord.Net/Net/WebSockets/VoiceWebSocket.cs | 2 +- src/Discord.Net/Net/WebSockets/WebSocket.cs | 4 ++-- src/Discord.Net/Net/WebSockets/WebSocketSharpEngine.cs | 4 ++-- 11 files changed, 33 insertions(+), 33 deletions(-) rename src/Discord.Net/{DiscordWebSocketClient.Events.cs => DiscordWSClient.Events.cs} (98%) rename src/Discord.Net/{DiscordWebSocketClient.Voice.cs => DiscordWSClient.Voice.cs} (96%) rename src/Discord.Net/{DiscordWebSocketClient.cs => DiscordWSClient.cs} (95%) rename src/Discord.Net/{DiscordWebSocketClientConfig.cs => DiscordWSClientConfig.cs} (93%) diff --git a/src/Discord.Net.Net45/Discord.Net.csproj b/src/Discord.Net.Net45/Discord.Net.csproj index 8b2b605bc..d92fa3858 100644 --- a/src/Discord.Net.Net45/Discord.Net.csproj +++ b/src/Discord.Net.Net45/Discord.Net.csproj @@ -169,17 +169,17 @@ DiscordClientConfig.cs - - DiscordWebSocketClient.cs + + DiscordWSClient.cs - - DiscordWebSocketClient.Events.cs + + DiscordWSClient.Events.cs - - DiscordWebSocketClient.Voice.cs + + DiscordWSClient.Voice.cs - - DiscordWebSocketClientConfig.cs + + DiscordWSClientConfig.cs Helpers\EpochTime.cs diff --git a/src/Discord.Net/DiscordClient.cs b/src/Discord.Net/DiscordClient.cs index 428a6239b..f44fe86a1 100644 --- a/src/Discord.Net/DiscordClient.cs +++ b/src/Discord.Net/DiscordClient.cs @@ -14,13 +14,13 @@ using System.Threading.Tasks; namespace Discord { /// Provides a connection to the DiscordApp service. - public partial class DiscordClient : DiscordWebSocketClient + public partial class DiscordClient : DiscordWSClient { protected readonly DiscordAPIClient _api; private readonly Random _rand; private readonly JsonSerializer _serializer; private readonly ConcurrentQueue _pendingMessages; - private readonly ConcurrentDictionary _voiceClients; + private readonly ConcurrentDictionary _voiceClients; private bool _sentInitialLog; private uint _nextVoiceClientId; private string _status; @@ -61,7 +61,7 @@ namespace Discord if (Config.UseMessageQueue) _pendingMessages = new ConcurrentQueue(); if (Config.EnableVoiceMultiserver) - _voiceClients = new ConcurrentDictionary(); + _voiceClients = new ConcurrentDictionary(); object cacheLock = new object(); _channels = new Channels(this, cacheLock); @@ -749,7 +749,7 @@ namespace Discord return null; } - DiscordWebSocketClient client; + DiscordWSClient client; if (_voiceClients.TryGetValue(serverId, out client)) return client; else @@ -769,7 +769,7 @@ namespace Discord config.LogLevel = _config.LogLevel;// (LogMessageSeverity)Math.Min((int)_config.LogLevel, (int)LogMessageSeverity.Warning); config.VoiceOnly = true; config.VoiceClientId = unchecked(++_nextVoiceClientId); - return new DiscordWebSocketClient(config, serverId); + return new DiscordWSClient(config, serverId); }); client.LogMessage += (s, e) => RaiseOnLog(e.Severity, e.Source, $"(#{client.Config.VoiceClientId}) {e.Message}"); await client.Connect(_gateway, _token).ConfigureAwait(false); @@ -800,7 +800,7 @@ namespace Discord if (Config.EnableVoiceMultiserver) { - DiscordWebSocketClient client; + DiscordWSClient client; if (_voiceClients.TryRemove(serverId, out client)) await client.Disconnect(); } diff --git a/src/Discord.Net/DiscordClientConfig.cs b/src/Discord.Net/DiscordClientConfig.cs index e5f2cdbac..8e09e0fe0 100644 --- a/src/Discord.Net/DiscordClientConfig.cs +++ b/src/Discord.Net/DiscordClientConfig.cs @@ -1,6 +1,6 @@ namespace Discord { - public class DiscordClientConfig : DiscordWebSocketClientConfig + public class DiscordClientConfig : DiscordWSClientConfig { /// Gets or sets the time (in milliseconds) to wait when the message queue is empty before checking again. public int MessageQueueInterval { get { return _messageQueueInterval; } set { SetValue(ref _messageQueueInterval, value); } } diff --git a/src/Discord.Net/DiscordWebSocketClient.Events.cs b/src/Discord.Net/DiscordWSClient.Events.cs similarity index 98% rename from src/Discord.Net/DiscordWebSocketClient.Events.cs rename to src/Discord.Net/DiscordWSClient.Events.cs index ad9d69fd6..3c5e24390 100644 --- a/src/Discord.Net/DiscordWebSocketClient.Events.cs +++ b/src/Discord.Net/DiscordWSClient.Events.cs @@ -73,7 +73,7 @@ namespace Discord } } - public partial class DiscordWebSocketClient + public partial class DiscordWSClient { public event EventHandler Connected; private void RaiseConnected() diff --git a/src/Discord.Net/DiscordWebSocketClient.Voice.cs b/src/Discord.Net/DiscordWSClient.Voice.cs similarity index 96% rename from src/Discord.Net/DiscordWebSocketClient.Voice.cs rename to src/Discord.Net/DiscordWSClient.Voice.cs index c45339a11..f290809e2 100644 --- a/src/Discord.Net/DiscordWebSocketClient.Voice.cs +++ b/src/Discord.Net/DiscordWSClient.Voice.cs @@ -4,7 +4,7 @@ using System.Threading.Tasks; namespace Discord { - public partial class DiscordWebSocketClient : IDiscordVoiceClient + public partial class DiscordWSClient : IDiscordVoiceClient { IDiscordVoiceBuffer IDiscordVoiceClient.OutputBuffer => _voiceSocket.OutputBuffer; diff --git a/src/Discord.Net/DiscordWebSocketClient.cs b/src/Discord.Net/DiscordWSClient.cs similarity index 95% rename from src/Discord.Net/DiscordWebSocketClient.cs rename to src/Discord.Net/DiscordWSClient.cs index 7ab63a12f..ba24c0271 100644 --- a/src/Discord.Net/DiscordWebSocketClient.cs +++ b/src/Discord.Net/DiscordWSClient.cs @@ -17,8 +17,8 @@ namespace Discord Disconnecting } - /// Provides a barebones connection to the Discord service - public partial class DiscordWebSocketClient + /// Provides a minimalistic websocket connection to the Discord service. + public partial class DiscordWSClient { internal readonly DataWebSocket _dataSocket; internal readonly VoiceWebSocket _voiceSocket; @@ -33,8 +33,8 @@ namespace Discord private bool _wasDisconnectUnexpected; /// Returns the configuration object used to make this client. Note that this object cannot be edited directly - to change the configuration of this client, use the DiscordClient(DiscordClientConfig config) constructor. - public DiscordWebSocketClientConfig Config => _config; - protected readonly DiscordWebSocketClientConfig _config; + public DiscordWSClientConfig Config => _config; + protected readonly DiscordWSClientConfig _config; /// Returns the id of the current logged-in user. public string CurrentUserId => _currentUserId; @@ -51,9 +51,9 @@ namespace Discord private CancellationToken _cancelToken; /// Initializes a new instance of the DiscordClient class. - public DiscordWebSocketClient(DiscordWebSocketClientConfig config = null) + public DiscordWSClient(DiscordWSClientConfig config = null) { - _config = config ?? new DiscordWebSocketClientConfig(); + _config = config ?? new DiscordWSClientConfig(); _config.Lock(); _enableVoice = _config.EnableVoice; @@ -67,7 +67,7 @@ namespace Discord if (_enableVoice) _voiceSocket = CreateVoiceSocket(); } - internal DiscordWebSocketClient(DiscordWebSocketClientConfig config = null, string voiceServerId = null) + internal DiscordWSClient(DiscordWSClientConfig config = null, string voiceServerId = null) : this(config) { _voiceServerId = voiceServerId; diff --git a/src/Discord.Net/DiscordWebSocketClientConfig.cs b/src/Discord.Net/DiscordWSClientConfig.cs similarity index 93% rename from src/Discord.Net/DiscordWebSocketClientConfig.cs rename to src/Discord.Net/DiscordWSClientConfig.cs index 601ad8c21..5a053e8ed 100644 --- a/src/Discord.Net/DiscordWebSocketClientConfig.cs +++ b/src/Discord.Net/DiscordWSClientConfig.cs @@ -12,7 +12,7 @@ namespace Discord Both = Outgoing | Incoming } - public class DiscordWebSocketClientConfig : DiscordAPIClientConfig + public class DiscordWSClientConfig : DiscordAPIClientConfig { /// Max time in milliseconds to wait for DiscordClient to connect and initialize. public int ConnectionTimeout { get { return _connectionTimeout; } set { SetValue(ref _connectionTimeout, value); } } @@ -47,9 +47,9 @@ namespace Discord internal virtual bool EnableVoice => _voiceMode != DiscordVoiceMode.Disabled; - public new DiscordWebSocketClientConfig Clone() + public new DiscordWSClientConfig Clone() { - var config = MemberwiseClone() as DiscordWebSocketClientConfig; + var config = MemberwiseClone() as DiscordWSClientConfig; config._isLocked = false; return config; } diff --git a/src/Discord.Net/Net/WebSockets/DataWebSocket.cs b/src/Discord.Net/Net/WebSockets/DataWebSocket.cs index 8976948e0..10ac57b22 100644 --- a/src/Discord.Net/Net/WebSockets/DataWebSocket.cs +++ b/src/Discord.Net/Net/WebSockets/DataWebSocket.cs @@ -13,7 +13,7 @@ namespace Discord.Net.WebSockets public string SessionId => _sessionId; private string _sessionId; - public DataWebSocket(DiscordWebSocketClient client) + public DataWebSocket(DiscordWSClient client) : base(client) { } diff --git a/src/Discord.Net/Net/WebSockets/VoiceWebSocket.cs b/src/Discord.Net/Net/WebSockets/VoiceWebSocket.cs index 3dc7fed89..78ee27271 100644 --- a/src/Discord.Net/Net/WebSockets/VoiceWebSocket.cs +++ b/src/Discord.Net/Net/WebSockets/VoiceWebSocket.cs @@ -46,7 +46,7 @@ namespace Discord.Net.WebSockets public string CurrentChannelId => _channelId; public VoiceBuffer OutputBuffer => _sendBuffer; - public VoiceWebSocket(DiscordWebSocketClient client) + public VoiceWebSocket(DiscordWSClient client) : base(client) { _rand = new Random(); diff --git a/src/Discord.Net/Net/WebSockets/WebSocket.cs b/src/Discord.Net/Net/WebSockets/WebSocket.cs index e673e42cb..7eeb9acfd 100644 --- a/src/Discord.Net/Net/WebSockets/WebSocket.cs +++ b/src/Discord.Net/Net/WebSockets/WebSocket.cs @@ -19,7 +19,7 @@ namespace Discord.Net.WebSockets internal abstract partial class WebSocket { protected readonly IWebSocketEngine _engine; - protected readonly DiscordWebSocketClient _client; + protected readonly DiscordWSClient _client; protected readonly LogMessageSeverity _logLevel; protected readonly ManualResetEventSlim _connectedEvent; @@ -41,7 +41,7 @@ namespace Discord.Net.WebSockets public WebSocketState State => (WebSocketState)_state; protected int _state; - public WebSocket(DiscordWebSocketClient client) + public WebSocket(DiscordWSClient client) { _client = client; _logLevel = client.Config.LogLevel; diff --git a/src/Discord.Net/Net/WebSockets/WebSocketSharpEngine.cs b/src/Discord.Net/Net/WebSockets/WebSocketSharpEngine.cs index 0538d160a..37dc68d8a 100644 --- a/src/Discord.Net/Net/WebSockets/WebSocketSharpEngine.cs +++ b/src/Discord.Net/Net/WebSockets/WebSocketSharpEngine.cs @@ -10,7 +10,7 @@ namespace Discord.Net.WebSockets { internal class WebSocketSharpEngine : IWebSocketEngine { - private readonly DiscordWebSocketClientConfig _config; + private readonly DiscordWSClientConfig _config; private readonly ConcurrentQueue _sendQueue; private readonly WebSocket _parent; private WSSharpNWebSocket _webSocket; @@ -22,7 +22,7 @@ namespace Discord.Net.WebSockets ProcessMessage(this, new WebSocketMessageEventArgs(msg)); } - internal WebSocketSharpEngine(WebSocket parent, DiscordWebSocketClientConfig config) + internal WebSocketSharpEngine(WebSocket parent, DiscordWSClientConfig config) { _parent = parent; _config = config; From bf35971bc4f30f77898044c0d4c771712bf40029 Mon Sep 17 00:00:00 2001 From: RogueException Date: Sun, 18 Oct 2015 23:07:03 -0300 Subject: [PATCH 025/109] Renamed CreateInvite's channelId to serverOrChannelId --- src/Discord.Net/DiscordClient.API.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Discord.Net/DiscordClient.API.cs b/src/Discord.Net/DiscordClient.API.cs index a90898415..5ebd3b0d5 100644 --- a/src/Discord.Net/DiscordClient.API.cs +++ b/src/Discord.Net/DiscordClient.API.cs @@ -60,10 +60,10 @@ namespace Discord //Channels /// Creates a new channel with the provided name and type (see ChannelTypes). - public Task CreateChannel(Server server, string name, string type) + public Task CreateChannel(Server server, string name, string type = ChannelTypes.Text) => CreateChannel(server?.Id, name, type); /// Creates a new channel with the provided name and type (see ChannelTypes). - public async Task CreateChannel(string serverId, string name, string type) + public async Task CreateChannel(string serverId, string name, string type = ChannelTypes.Text) { CheckReady(); if (serverId == null) throw new ArgumentNullException(nameof(serverId)); @@ -183,14 +183,14 @@ namespace Discord /// If true, a user accepting this invite will be kicked from the server after closing their client. /// If true, creates a human-readable link. Not supported if maxAge is set to 0. /// The max amount of times this invite may be used. - public async Task CreateInvite(string channelId, int maxAge, int maxUses, bool isTemporary, bool hasXkcdPass) + public async Task CreateInvite(string serverOrChannelId, int maxAge, int maxUses, bool isTemporary, bool hasXkcdPass) { CheckReady(); - if (channelId == null) throw new ArgumentNullException(nameof(channelId)); + if (serverOrChannelId == null) throw new ArgumentNullException(nameof(serverOrChannelId)); if (maxAge <= 0) throw new ArgumentOutOfRangeException(nameof(maxAge)); if (maxUses <= 0) throw new ArgumentOutOfRangeException(nameof(maxUses)); - var response = await _api.CreateInvite(channelId, maxAge, maxUses, isTemporary, hasXkcdPass).ConfigureAwait(false); + var response = await _api.CreateInvite(serverOrChannelId, maxAge, maxUses, isTemporary, hasXkcdPass).ConfigureAwait(false); var invite = new Invite(this, response.Code, response.XkcdPass, response.Guild.Id); invite.Update(response); return invite; From d64238629b61816dbf401639bb5bfbbb48a6f341 Mon Sep 17 00:00:00 2001 From: RogueException Date: Sun, 18 Oct 2015 23:26:40 -0300 Subject: [PATCH 026/109] Cleaned CreateInvite overloads --- src/Discord.Net/DiscordClient.API.cs | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/Discord.Net/DiscordClient.API.cs b/src/Discord.Net/DiscordClient.API.cs index 5ebd3b0d5..1f8a271df 100644 --- a/src/Discord.Net/DiscordClient.API.cs +++ b/src/Discord.Net/DiscordClient.API.cs @@ -166,31 +166,31 @@ namespace Discord //Invites /// Creates a new invite to the default channel of the provided server. /// Time (in seconds) until the invite expires. Set to 0 to never expire. - /// If true, a user accepting this invite will be kicked from the server after closing their client. - /// If true, creates a human-readable link. Not supported if maxAge is set to 0. - /// The max amount of times this invite may be used. - public Task CreateInvite(Server server, int maxAge, int maxUses, bool isTemporary, bool hasXkcdPass) - => CreateInvite(server?.DefaultChannelId, maxAge, maxUses, isTemporary, hasXkcdPass); + /// If true, a user accepting this invite will be kicked from the server after closing their client. + /// If true, creates a human-readable link. Not supported if maxAge is set to 0. + /// The max amount of times this invite may be used. Set to 0 to have unlimited uses. + public Task CreateInvite(Server server, int maxAge = 1800, int maxUses = 0, bool tempMembership = false, bool hasXkcd = false) + => CreateInvite(server?.DefaultChannelId, maxAge, maxUses, tempMembership, hasXkcd); /// Creates a new invite to the provided channel. /// Time (in seconds) until the invite expires. Set to 0 to never expire. - /// If true, a user accepting this invite will be kicked from the server after closing their client. - /// If true, creates a human-readable link. Not supported if maxAge is set to 0. - /// The max amount of times this invite may be used. - public Task CreateInvite(Channel channel, int maxAge, int maxUses, bool isTemporary, bool hasXkcdPass) - => CreateInvite(channel?.Id, maxAge, maxUses, isTemporary, hasXkcdPass); + /// If true, a user accepting this invite will be kicked from the server after closing their client. + /// If true, creates a human-readable link. Not supported if maxAge is set to 0. + /// The max amount of times this invite may be used. Set to 0 to have unlimited uses. + public Task CreateInvite(Channel channel, int maxAge = 1800, int maxUses = 0, bool tempMembership = false, bool hasXkcd = false) + => CreateInvite(channel?.Id, maxAge, maxUses, tempMembership, hasXkcd); /// Creates a new invite to the provided channel. /// Time (in seconds) until the invite expires. Set to 0 to never expire. - /// If true, a user accepting this invite will be kicked from the server after closing their client. - /// If true, creates a human-readable link. Not supported if maxAge is set to 0. - /// The max amount of times this invite may be used. - public async Task CreateInvite(string serverOrChannelId, int maxAge, int maxUses, bool isTemporary, bool hasXkcdPass) + /// If true, a user accepting this invite will be kicked from the server after closing their client. + /// If true, creates a human-readable link. Not supported if maxAge is set to 0. + /// The max amount of times this invite may be used. Set to 0 to have unlimited uses. + public async Task CreateInvite(string serverOrChannelId, int maxAge = 1800, int maxUses = 0, bool tempMembership = false, bool hasXkcd = false) { CheckReady(); if (serverOrChannelId == null) throw new ArgumentNullException(nameof(serverOrChannelId)); if (maxAge <= 0) throw new ArgumentOutOfRangeException(nameof(maxAge)); if (maxUses <= 0) throw new ArgumentOutOfRangeException(nameof(maxUses)); - var response = await _api.CreateInvite(serverOrChannelId, maxAge, maxUses, isTemporary, hasXkcdPass).ConfigureAwait(false); + var response = await _api.CreateInvite(serverOrChannelId, maxAge, maxUses, tempMembership, hasXkcd).ConfigureAwait(false); var invite = new Invite(this, response.Code, response.XkcdPass, response.Guild.Id); invite.Update(response); return invite; From db703b51b0f25cb174a17fdedc6a72ff859d3296 Mon Sep 17 00:00:00 2001 From: RogueException Date: Mon, 19 Oct 2015 02:08:14 -0300 Subject: [PATCH 027/109] Use VoiceKeepAlives --- src/Discord.Net/Net/WebSockets/VoiceWebSocket.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Discord.Net/Net/WebSockets/VoiceWebSocket.cs b/src/Discord.Net/Net/WebSockets/VoiceWebSocket.cs index 78ee27271..aea264c6c 100644 --- a/src/Discord.Net/Net/WebSockets/VoiceWebSocket.cs +++ b/src/Discord.Net/Net/WebSockets/VoiceWebSocket.cs @@ -90,7 +90,9 @@ namespace Discord.Net.WebSockets { try { - await Start().ConfigureAwait(false); + //This check is needed in case we start a reconnect before the initial login completes + if (_state != (int)WebSocketState.Disconnected) + await Start().ConfigureAwait(false); break; } catch (OperationCanceledException) { throw; } @@ -533,7 +535,7 @@ namespace Discord.Net.WebSockets protected override object GetKeepAlive() { - return new KeepAliveCommand(); + return new VoiceKeepAliveCommand(); } public void WaitForQueue() From 0aae3fe3a0fb32cefd20002342cc2f30b352e0c4 Mon Sep 17 00:00:00 2001 From: RogueException Date: Mon, 19 Oct 2015 02:13:54 -0300 Subject: [PATCH 028/109] Dont use msg.Member when message queue is enabled --- src/Discord.Net/DiscordClient.API.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Discord.Net/DiscordClient.API.cs b/src/Discord.Net/DiscordClient.API.cs index 1f8a271df..45d392e78 100644 --- a/src/Discord.Net/DiscordClient.API.cs +++ b/src/Discord.Net/DiscordClient.API.cs @@ -293,12 +293,12 @@ namespace Discord if (Config.UseMessageQueue) { var msg = _messages.GetOrAdd("nonce_" + nonce, channel.Id, CurrentUserId); - var currentMember = _members[msg.UserId, channel.ServerId]; + var currentUser = msg.User; msg.Update(new MessageInfo { Content = blockText, Timestamp = DateTime.UtcNow, - Author = new UserReference { Avatar = currentMember.AvatarId, Discriminator = currentMember.Discriminator, Id = CurrentUserId, Username = currentMember.Name }, + Author = new UserReference { Avatar = currentUser.AvatarId, Discriminator = currentUser.Discriminator, Id = CurrentUserId, Username = currentUser.Name }, ChannelId = channel.Id, IsTextToSpeech = isTextToSpeech }); From 7b036446a27670cca5435bb02f8690bc158e7289 Mon Sep 17 00:00:00 2001 From: RogueException Date: Mon, 19 Oct 2015 02:38:23 -0300 Subject: [PATCH 029/109] Fixed voice errors --- src/Discord.Net/DiscordClientConfig.cs | 2 +- src/Discord.Net/Net/Voice/VoiceBuffer.cs | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Discord.Net/DiscordClientConfig.cs b/src/Discord.Net/DiscordClientConfig.cs index 8e09e0fe0..9c81fd5c8 100644 --- a/src/Discord.Net/DiscordClientConfig.cs +++ b/src/Discord.Net/DiscordClientConfig.cs @@ -21,7 +21,7 @@ private bool _ackMessages = false; //Internal - internal override bool EnableVoice => base.EnableVoice && !EnableVoiceMultiserver; + internal override bool EnableVoice => (base.EnableVoice && !EnableVoiceMultiserver) || base.VoiceOnly; public new DiscordClientConfig Clone() { diff --git a/src/Discord.Net/Net/Voice/VoiceBuffer.cs b/src/Discord.Net/Net/Voice/VoiceBuffer.cs index 84d24541a..bc31a8bd3 100644 --- a/src/Discord.Net/Net/Voice/VoiceBuffer.cs +++ b/src/Discord.Net/Net/Voice/VoiceBuffer.cs @@ -32,6 +32,9 @@ namespace Discord.Net.Voice public void Push(byte[] buffer, int bytes, CancellationToken cancelToken) { + if (cancelToken.IsCancellationRequested) + throw new OperationCanceledException("Client is disconnected.", cancelToken); + int wholeFrames = bytes / _frameSize; int expectedBytes = wholeFrames * _frameSize; int lastFrameSize = bytes - expectedBytes; @@ -50,7 +53,10 @@ namespace Discord.Net.Voice { _notOverflowEvent.Wait(cancelToken); } - catch (OperationCanceledException) { return; } + catch (OperationCanceledException ex) + { + throw new OperationCanceledException("Client is disconnected.", ex, cancelToken); + } } if (i == wholeFrames) From c2917c039fafe7036c0667708ab08827b0f06d96 Mon Sep 17 00:00:00 2001 From: RogueException Date: Mon, 19 Oct 2015 02:43:46 -0300 Subject: [PATCH 030/109] Removed default channel's Read permission resolving exception --- src/Discord.Net/Models/Member.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Discord.Net/Models/Member.cs b/src/Discord.Net/Models/Member.cs index f2ede0b59..8c7010adc 100644 --- a/src/Discord.Net/Models/Member.cs +++ b/src/Discord.Net/Models/Member.cs @@ -206,8 +206,8 @@ namespace Discord if (permissions.General_ManagePermissions) permissions.SetRawValueInternal(PackedChannelPermissions.All.RawValue); - else if (server.DefaultChannelId == channelId) - permissions.SetBitInternal(PackedPermissions.Text_ReadMessagesBit, true); + /*else if (server.DefaultChannelId == channelId) + permissions.SetBitInternal(PackedPermissions.Text_ReadMessagesBit, true);*/ if (permissions.RawValue != oldPermissions) channel.InvalidMembersCache(); From 3f14650a28c537f4c1c6c1c1feadf9ea20cc9b4f Mon Sep 17 00:00:00 2001 From: RogueException Date: Mon, 19 Oct 2015 05:58:09 -0300 Subject: [PATCH 031/109] Removed Format.Code's escape parameter --- src/Discord.Net/Helpers/Format.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Discord.Net/Helpers/Format.cs b/src/Discord.Net/Helpers/Format.cs index eda143d2e..489ac22c0 100644 --- a/src/Discord.Net/Helpers/Format.cs +++ b/src/Discord.Net/Helpers/Format.cs @@ -102,12 +102,12 @@ namespace Discord => escape ? $"~~{Escape(text)}~~" : $"~~{text}~~"; /// Returns a markdown-formatted string with strikeout formatting, optionally escaping the contents. - public static string Code(string text, string language = null, bool escape = true) + public static string Code(string text, string language = null) { if (language != null || text.Contains("\n")) - return $"```{language ?? ""}\n{(escape ? Escape(text) : text)}\n```"; + return $"```{language ?? ""}\n{text}\n```"; else - return $"`{(escape ? Escape(text) : text)}`"; + return $"`{text}`"; } /// Returns a markdown-formatted string with multiple formatting, optionally escaping the contents. @@ -121,7 +121,7 @@ namespace Discord if (italics) result = Italics(result, false); if (underline) result = Underline(result, false); if (strikeout) result = Strikeout(result, false); - if (code) result = Code(result, codeLanguage, false); + if (code) result = Code(result, codeLanguage); return result; } } From 104aef128d9619f65de19be086e09a0353ac1426 Mon Sep 17 00:00:00 2001 From: Brandon Smith Date: Mon, 19 Oct 2015 09:14:38 -0300 Subject: [PATCH 032/109] Cleaned up old references to User.UpdateActivity --- src/Discord.Net/DiscordClient.API.cs | 5 +++-- src/Discord.Net/DiscordClient.cs | 10 ++++++---- src/Discord.Net/Models/User.cs | 6 +++--- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/Discord.Net/DiscordClient.API.cs b/src/Discord.Net/DiscordClient.API.cs index 45d392e78..a778f4a74 100644 --- a/src/Discord.Net/DiscordClient.API.cs +++ b/src/Discord.Net/DiscordClient.API.cs @@ -447,13 +447,14 @@ namespace Discord msg.Update(x); if (Config.TrackActivity) { - if (channel.IsPrivate) + /*if (channel.IsPrivate) { var user = msg.User; if (user != null) user.UpdateActivity(msg.EditedTimestamp ?? msg.Timestamp); } - else + else*/ + if (!channel.IsPrivate) { var member = msg.Member; if (member != null) diff --git a/src/Discord.Net/DiscordClient.cs b/src/Discord.Net/DiscordClient.cs index f44fe86a1..183b1a54b 100644 --- a/src/Discord.Net/DiscordClient.cs +++ b/src/Discord.Net/DiscordClient.cs @@ -583,13 +583,14 @@ namespace Discord if (Config.TrackActivity) { var channel = msg.Channel; - if (channel == null || channel.IsPrivate) + /*if (channel == null || channel.IsPrivate) { var user = msg.User; if (user != null) user.UpdateActivity(data.Timestamp); } - else + else*/ + if (channel?.IsPrivate == false) { var member = msg.Member; if (member != null) @@ -664,12 +665,13 @@ namespace Discord } if (Config.TrackActivity) { - if (channel.IsPrivate) + /*if (channel.IsPrivate) { if (user != null) user.UpdateActivity(); } - else + else*/ + if (!channel.IsPrivate) { var member = _members[data.UserId, channel.ServerId]; if (member != null) diff --git a/src/Discord.Net/Models/User.cs b/src/Discord.Net/Models/User.cs index 1cf3787dc..59d7f0292 100644 --- a/src/Discord.Net/Models/User.cs +++ b/src/Discord.Net/Models/User.cs @@ -13,7 +13,7 @@ namespace Discord private readonly DiscordClient _client; private readonly ConcurrentDictionary _servers; private int _refCount; - private DateTime? _lastPrivateActivity; + //private DateTime? _lastPrivateActivity; /// Returns the unique identifier for this user. public string Id { get; } @@ -100,11 +100,11 @@ namespace Discord if (model.IsVerified != null) IsVerified = model.IsVerified; } - internal void UpdateActivity(DateTime? activity = null) + /*internal void UpdateActivity(DateTime? activity = null) { if (_lastPrivateActivity == null || activity > _lastPrivateActivity.Value) _lastPrivateActivity = activity ?? DateTime.UtcNow; - } + }*/ public override string ToString() => Name; From 845d325b940d368809e56707a7b7751cbfa391b7 Mon Sep 17 00:00:00 2001 From: RogueException Date: Mon, 19 Oct 2015 10:06:40 -0300 Subject: [PATCH 033/109] Fixes --- src/Discord.Net/Models/Member.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Discord.Net/Models/Member.cs b/src/Discord.Net/Models/Member.cs index 8c7010adc..b0b4db228 100644 --- a/src/Discord.Net/Models/Member.cs +++ b/src/Discord.Net/Models/Member.cs @@ -115,13 +115,17 @@ namespace Discord } internal void Update(PresenceInfo model) { - //Allows null - if (Status != model.Status) + if (model.User != null) + Update(model.User as UserReference); + + if (model.Status != null && Status != model.Status) { - Status = model.Status; + Status = model.Status; if (Status == UserStatus.Offline) _lastOnline = DateTime.UtcNow; - } + } + + //Allows null GameId = model.GameId; } internal void Update(VoiceMemberInfo model) From 31b2e22f57edf03f0e4c385ab0b34d1da67358d5 Mon Sep 17 00:00:00 2001 From: RogueException Date: Mon, 19 Oct 2015 20:05:24 -0300 Subject: [PATCH 034/109] Added EditServer(icon), Renamed AvatarImageType to ImageType --- src/Discord.Net/API/Channels.cs | 2 +- src/Discord.Net/API/Enums/AvatarImageType.cs | 2 +- src/Discord.Net/API/Members.cs | 6 +++--- src/Discord.Net/API/Servers.cs | 2 ++ src/Discord.Net/API/Users.cs | 10 ++++----- src/Discord.Net/DiscordAPIClient.cs | 32 ++++++++++++++++------------ src/Discord.Net/DiscordClient.API.cs | 18 +++++++--------- 7 files changed, 38 insertions(+), 34 deletions(-) diff --git a/src/Discord.Net/API/Channels.cs b/src/Discord.Net/API/Channels.cs index 030bda73d..ec5c2c5d5 100644 --- a/src/Discord.Net/API/Channels.cs +++ b/src/Discord.Net/API/Channels.cs @@ -40,7 +40,7 @@ namespace Discord.API public bool IsPrivate; [JsonProperty("position")] public int? Position; - [JsonProperty(PropertyName = "topic")] + [JsonProperty("topic")] public string Topic; [JsonProperty("permission_overwrites")] public PermissionOverwrite[] PermissionOverwrites; diff --git a/src/Discord.Net/API/Enums/AvatarImageType.cs b/src/Discord.Net/API/Enums/AvatarImageType.cs index 7b2895a04..738c67a3d 100644 --- a/src/Discord.Net/API/Enums/AvatarImageType.cs +++ b/src/Discord.Net/API/Enums/AvatarImageType.cs @@ -1,6 +1,6 @@ namespace Discord { - public enum AvatarImageType + public enum ImageType { None, Jpeg, diff --git a/src/Discord.Net/API/Members.cs b/src/Discord.Net/API/Members.cs index 9858e51ab..2d2805a8d 100644 --- a/src/Discord.Net/API/Members.cs +++ b/src/Discord.Net/API/Members.cs @@ -72,11 +72,11 @@ namespace Discord.API public class EditMemberRequest { - [JsonProperty(PropertyName = "mute", NullValueHandling = NullValueHandling.Ignore)] + [JsonProperty("mute", NullValueHandling = NullValueHandling.Ignore)] public bool? Mute; - [JsonProperty(PropertyName = "deaf", NullValueHandling = NullValueHandling.Ignore)] + [JsonProperty("deaf", NullValueHandling = NullValueHandling.Ignore)] public bool? Deaf; - [JsonProperty(PropertyName = "roles", NullValueHandling = NullValueHandling.Ignore)] + [JsonProperty("roles", NullValueHandling = NullValueHandling.Ignore)] public IEnumerable Roles; } diff --git a/src/Discord.Net/API/Servers.cs b/src/Discord.Net/API/Servers.cs index b86a94ffa..925d5c923 100644 --- a/src/Discord.Net/API/Servers.cs +++ b/src/Discord.Net/API/Servers.cs @@ -67,6 +67,8 @@ namespace Discord.API public string Name; [JsonProperty("region", NullValueHandling = NullValueHandling.Ignore)] public string Region; + [JsonProperty("icon", NullValueHandling = NullValueHandling.Ignore)] + public string Icon; } public sealed class EditServerResponse : GuildInfo { } diff --git a/src/Discord.Net/API/Users.cs b/src/Discord.Net/API/Users.cs index 52bb8d6a2..a75363d2c 100644 --- a/src/Discord.Net/API/Users.cs +++ b/src/Discord.Net/API/Users.cs @@ -29,15 +29,15 @@ namespace Discord.API //Edit internal sealed class EditUserRequest { - [JsonProperty(PropertyName = "password")] + [JsonProperty("password")] public string CurrentPassword; - [JsonProperty(PropertyName = "email", NullValueHandling = NullValueHandling.Ignore)] + [JsonProperty("email", NullValueHandling = NullValueHandling.Ignore)] public string Email; - [JsonProperty(PropertyName = "new_password")] + [JsonProperty("new_password")] public string Password; - [JsonProperty(PropertyName = "username", NullValueHandling = NullValueHandling.Ignore)] + [JsonProperty("username", NullValueHandling = NullValueHandling.Ignore)] public string Username; - [JsonProperty(PropertyName = "avatar", NullValueHandling = NullValueHandling.Ignore)] + [JsonProperty("avatar", NullValueHandling = NullValueHandling.Ignore)] public string Avatar; } public sealed class EditUserResponse : UserInfo { } diff --git a/src/Discord.Net/DiscordAPIClient.cs b/src/Discord.Net/DiscordAPIClient.cs index bea5e5788..344013019 100644 --- a/src/Discord.Net/DiscordAPIClient.cs +++ b/src/Discord.Net/DiscordAPIClient.cs @@ -279,31 +279,22 @@ namespace Discord return _rest.Delete(Endpoints.Server(serverId)); } - public Task EditServer(string serverId, string name = null, string region = null) + public Task EditServer(string serverId, string name = null, string region = null, ImageType iconType = ImageType.Png, byte[] icon = null) { if (serverId == null) throw new ArgumentNullException(nameof(serverId)); - var request = new EditServerRequest { Name = name, Region = region }; + var request = new EditServerRequest { Name = name, Region = region, Icon = Base64Picture(iconType, icon) }; return _rest.Patch(Endpoints.Server(serverId), request); } //User public Task EditUser(string currentPassword = "", string username = null, string email = null, string password = null, - AvatarImageType avatarType = AvatarImageType.Png, byte[] avatar = null) + ImageType avatarType = ImageType.Png, byte[] avatar = null) { if (currentPassword == null) throw new ArgumentNullException(nameof(currentPassword)); - - string avatarBase64 = null; - if (avatarType == AvatarImageType.None) - avatarBase64 = ""; - else if (avatar != null) - { - string base64 = Convert.ToBase64String(avatar); - string type = avatarType == AvatarImageType.Jpeg ? "image/jpeg;base64" : "image/png;base64"; - avatarBase64 = $"data:{type},{base64}"; - } - var request = new EditUserRequest { CurrentPassword = currentPassword, Username = username, Email = email, Password = password, Avatar = avatarBase64 }; + + var request = new EditUserRequest { CurrentPassword = currentPassword, Username = username, Email = email, Password = password, Avatar = Base64Picture(avatarType, avatar) }; return _rest.Patch(Endpoints.UserMe, request); } @@ -312,5 +303,18 @@ namespace Discord => _rest.Get(Endpoints.VoiceRegions); /*public Task GetVoiceIce() => _rest.Get(Endpoints.VoiceIce);*/ + + private string Base64Picture(ImageType type, byte[] data) + { + if (type == ImageType.None) + return ""; + else if (data != null) + { + string base64 = Convert.ToBase64String(data); + string imageType = type == ImageType.Jpeg ? "image/jpeg;base64" : "image/png;base64"; + return $"data:{imageType},{base64}"; + } + return null; + } } } diff --git a/src/Discord.Net/DiscordClient.API.cs b/src/Discord.Net/DiscordClient.API.cs index a778f4a74..d26c4405d 100644 --- a/src/Discord.Net/DiscordClient.API.cs +++ b/src/Discord.Net/DiscordClient.API.cs @@ -596,7 +596,7 @@ namespace Discord //Profile public Task EditProfile(string currentPassword = "", string username = null, string email = null, string password = null, - AvatarImageType avatarType = AvatarImageType.Png, byte[] avatar = null) + ImageType avatarType = ImageType.Png, byte[] avatar = null) { if (currentPassword == null) throw new ArgumentNullException(nameof(currentPassword)); @@ -727,18 +727,16 @@ namespace Discord } /// Edits the provided server, changing only non-null attributes. - public Task EditServer(Server server) - => EditServer(server?.Id); + public Task EditServer(string serverId, string name = null, string region = null, ImageType iconType = ImageType.Png, byte[] icon = null) + => EditServer(_servers[serverId], name: name, region: region, iconType: iconType, icon: icon); /// Edits the provided server, changing only non-null attributes. - public async Task EditServer(string serverId, string name = null, string region = null) + public async Task EditServer(Server server, string name = null, string region = null, ImageType iconType = ImageType.Png, byte[] icon = null) { - CheckReady(); - if (serverId == null) throw new ArgumentNullException(nameof(serverId)); + CheckReady(); + if (server == null) throw new ArgumentNullException(nameof(server)); - var response = await _api.EditServer(serverId, name: name, region: region); - var server = _servers[response.Id]; - if (server != null) - server.Update(response); + var response = await _api.EditServer(server.Id, name: name ?? server.Name, region: region, iconType: iconType, icon: icon); + server.Update(response); } /// Leaves the provided server, destroying it if you are the owner. From ea59f72d69b228fd9480f3937cd95f2793d8359f Mon Sep 17 00:00:00 2001 From: RogueException Date: Tue, 20 Oct 2015 03:32:42 -0300 Subject: [PATCH 035/109] Assume EveryoneRole to be same Id as the server --- src/Discord.Net/API/Servers.cs | 4 ++++ src/Discord.Net/Collections/Roles.cs | 2 +- src/Discord.Net/DiscordClient.API.cs | 2 +- src/Discord.Net/DiscordClient.cs | 2 +- src/Discord.Net/Models/Role.cs | 7 +++---- src/Discord.Net/Models/Server.cs | 8 ++------ 6 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/Discord.Net/API/Servers.cs b/src/Discord.Net/API/Servers.cs index 925d5c923..8abea6fca 100644 --- a/src/Discord.Net/API/Servers.cs +++ b/src/Discord.Net/API/Servers.cs @@ -69,6 +69,10 @@ namespace Discord.API public string Region; [JsonProperty("icon", NullValueHandling = NullValueHandling.Ignore)] public string Icon; + [JsonProperty("afk_channel_id", NullValueHandling = NullValueHandling.Ignore)] + public string AFKChannelId; + [JsonProperty("afk_timeout", NullValueHandling = NullValueHandling.Ignore)] + public int AFKTimeout; } public sealed class EditServerResponse : GuildInfo { } diff --git a/src/Discord.Net/Collections/Roles.cs b/src/Discord.Net/Collections/Roles.cs index 5a3d1d41b..502c9b8d7 100644 --- a/src/Discord.Net/Collections/Roles.cs +++ b/src/Discord.Net/Collections/Roles.cs @@ -9,7 +9,7 @@ namespace Discord.Collections internal Roles(DiscordClient client, object writerLock) : base(client, writerLock) { } - internal Role GetOrAdd(string id, string serverId, bool isEveryone) => GetOrAdd(id, () => new Role(_client, id, serverId, isEveryone)); + 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) diff --git a/src/Discord.Net/DiscordClient.API.cs b/src/Discord.Net/DiscordClient.API.cs index d26c4405d..e99561ebd 100644 --- a/src/Discord.Net/DiscordClient.API.cs +++ b/src/Discord.Net/DiscordClient.API.cs @@ -632,7 +632,7 @@ namespace Discord if (serverId == null) throw new NullReferenceException(nameof(serverId)); var response = await _api.CreateRole(serverId).ConfigureAwait(false); - var role = _roles.GetOrAdd(response.Id, serverId, false); + var role = _roles.GetOrAdd(response.Id, serverId); role.Update(response); await EditRole(role, name: name); diff --git a/src/Discord.Net/DiscordClient.cs b/src/Discord.Net/DiscordClient.cs index 183b1a54b..21f8db3ec 100644 --- a/src/Discord.Net/DiscordClient.cs +++ b/src/Discord.Net/DiscordClient.cs @@ -508,7 +508,7 @@ namespace Discord case "GUILD_ROLE_CREATE": { var data = e.Payload.ToObject(_serializer); - var role = _roles.GetOrAdd(data.Data.Id, data.GuildId, false); + var role = _roles.GetOrAdd(data.Data.Id, data.GuildId); role.Update(data.Data); var server = _servers[data.GuildId]; if (server != null) diff --git a/src/Discord.Net/Models/Role.cs b/src/Discord.Net/Models/Role.cs index ebd40a146..43215b8f8 100644 --- a/src/Discord.Net/Models/Role.cs +++ b/src/Discord.Net/Models/Role.cs @@ -32,7 +32,7 @@ namespace Discord public Server Server => _client.Servers[ServerId]; /// Returns true if this is the role representing all users in a server. - public bool IsEveryone { get; } + public bool IsEveryone => Id == ServerId; /// Returns a list of the ids of all members in this role. [JsonIgnore] public IEnumerable MemberIds => IsEveryone ? Server.UserIds : Server.Members.Where(x => x.RoleIds.Contains(Id)).Select(x => x.UserId); @@ -40,18 +40,17 @@ namespace Discord [JsonIgnore] public IEnumerable Members => IsEveryone ? Server.Members : Server.Members.Where(x => x.RoleIds.Contains(Id)); - internal Role(DiscordClient client, string id, string serverId, bool isEveryone) + internal Role(DiscordClient client, string id, string serverId) { _client = client; Id = id; ServerId = serverId; - IsEveryone = isEveryone; Permissions = new PackedServerPermissions(0); Permissions.Lock(); Color = new PackedColor(0); Color.Lock(); - if (isEveryone) + if (IsEveryone) Position = int.MinValue; } diff --git a/src/Discord.Net/Models/Server.cs b/src/Discord.Net/Models/Server.cs index c5dbff92c..aada16b20 100644 --- a/src/Discord.Net/Models/Server.cs +++ b/src/Discord.Net/Models/Server.cs @@ -83,7 +83,7 @@ namespace Discord public IEnumerable Users => _members.Select(x => _client.Users[x.Key]); /// Return the id of the role representing all users in a server. - public string EveryoneRoleId { get; private set; } + public string EveryoneRoleId => Id; /// Return the the role representing all users in a server. [JsonIgnore] public Role EveryoneRole => _client.Roles[EveryoneRoleId]; @@ -116,14 +116,10 @@ namespace Discord Region = model.Region; var roles = _client.Roles; - bool isEveryone = true; //Assumes first role is always everyone foreach (var subModel in model.Roles) { - var role = roles.GetOrAdd(subModel.Id, Id, isEveryone); + var role = roles.GetOrAdd(subModel.Id, Id); role.Update(subModel); - if (isEveryone) - EveryoneRoleId = subModel.Id; - isEveryone = false; } } internal void Update(ExtendedGuildInfo model) From 90398959936d653d62d7f79aaf5527d5e57e1302 Mon Sep 17 00:00:00 2001 From: RogueException Date: Tue, 20 Oct 2015 03:32:55 -0300 Subject: [PATCH 036/109] Minor adjustment to websocketsharp errors --- src/Discord.Net/Net/WebSockets/WebSocketSharpEngine.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Discord.Net/Net/WebSockets/WebSocketSharpEngine.cs b/src/Discord.Net/Net/WebSockets/WebSocketSharpEngine.cs index 37dc68d8a..7841f4e4b 100644 --- a/src/Discord.Net/Net/WebSockets/WebSocketSharpEngine.cs +++ b/src/Discord.Net/Net/WebSockets/WebSocketSharpEngine.cs @@ -39,7 +39,7 @@ namespace Discord.Net.WebSockets _webSocket.OnMessage += (s, e) => RaiseProcessMessage(e.Data); _webSocket.OnError += async (s, e) => { - _parent.RaiseOnLog(LogMessageSeverity.Error, $"Websocket Error: {e.Message}"); + _parent.RaiseOnLog(LogMessageSeverity.Error, e.Exception.GetBaseException().Message); await _parent.DisconnectInternal(e.Exception, skipAwait: true); }; _webSocket.OnClose += async (s, e) => From 3cb5169e37bc50a9a1eb8a15f89d0a871aabb96f Mon Sep 17 00:00:00 2001 From: RogueException Date: Tue, 20 Oct 2015 03:33:42 -0300 Subject: [PATCH 037/109] Fix logging for ServerAvailable --- src/Discord.Net/DiscordClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Discord.Net/DiscordClient.cs b/src/Discord.Net/DiscordClient.cs index 21f8db3ec..6fca3fdbe 100644 --- a/src/Discord.Net/DiscordClient.cs +++ b/src/Discord.Net/DiscordClient.cs @@ -103,7 +103,7 @@ namespace Discord $"Updated Server: {e.Server?.Name}" + (showIDs ? $" ({e.ServerId})" : "")); ServerAvailable += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, - $"Server Unavailable: {e.Server?.Name}" + + $"Server Available: {e.Server?.Name}" + (showIDs ? $" ({e.ServerId})" : "")); ServerUnavailable += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, $"Server Unavailable: {e.Server?.Name}" + From 02876f25425990aa4f60ac5880f130851f747658 Mon Sep 17 00:00:00 2001 From: RogueException Date: Thu, 22 Oct 2015 01:00:27 -0300 Subject: [PATCH 038/109] Re-partialed DiscordClient by object type and moved Finds from collections. --- src/Discord.Net.Net45/Discord.Net.csproj | 30 +- src/Discord.Net/Collections/AsyncCollection.cs | 4 +- src/Discord.Net/Collections/Channels.cs | 26 +- src/Discord.Net/Collections/Members.cs | 46 -- src/Discord.Net/Collections/Messages.cs | 17 +- src/Discord.Net/Collections/Roles.cs | 21 +- src/Discord.Net/Collections/Servers.cs | 10 +- src/Discord.Net/Collections/Users.cs | 20 +- src/Discord.Net/DiscordClient.API.cs | 756 ------------------------- src/Discord.Net/DiscordClient.Bans.cs | 68 +++ src/Discord.Net/DiscordClient.Cache.cs | 59 -- src/Discord.Net/DiscordClient.Channels.cs | 182 ++++++ src/Discord.Net/DiscordClient.Events.cs | 278 --------- src/Discord.Net/DiscordClient.Invites.cs | 96 ++++ src/Discord.Net/DiscordClient.Members.cs | 135 +++++ src/Discord.Net/DiscordClient.Messages.cs | 296 ++++++++++ src/Discord.Net/DiscordClient.Permissions.cs | 132 +++++ src/Discord.Net/DiscordClient.Roles.cs | 149 +++++ src/Discord.Net/DiscordClient.Servers.cs | 102 ++++ src/Discord.Net/DiscordClient.Users.cs | 130 +++++ src/Discord.Net/DiscordClient.cs | 114 ++-- 21 files changed, 1392 insertions(+), 1279 deletions(-) delete mode 100644 src/Discord.Net/DiscordClient.API.cs create mode 100644 src/Discord.Net/DiscordClient.Bans.cs delete mode 100644 src/Discord.Net/DiscordClient.Cache.cs create mode 100644 src/Discord.Net/DiscordClient.Channels.cs delete mode 100644 src/Discord.Net/DiscordClient.Events.cs create mode 100644 src/Discord.Net/DiscordClient.Invites.cs create mode 100644 src/Discord.Net/DiscordClient.Members.cs create mode 100644 src/Discord.Net/DiscordClient.Messages.cs create mode 100644 src/Discord.Net/DiscordClient.Permissions.cs create mode 100644 src/Discord.Net/DiscordClient.Roles.cs create mode 100644 src/Discord.Net/DiscordClient.Servers.cs create mode 100644 src/Discord.Net/DiscordClient.Users.cs diff --git a/src/Discord.Net.Net45/Discord.Net.csproj b/src/Discord.Net.Net45/Discord.Net.csproj index d92fa3858..1d54ee4b0 100644 --- a/src/Discord.Net.Net45/Discord.Net.csproj +++ b/src/Discord.Net.Net45/Discord.Net.csproj @@ -154,17 +154,35 @@ DiscordAPIClientConfig.cs - - DiscordClient.API.cs + + DiscordClient.Bans.cs - - DiscordClient.Cache.cs + + DiscordClient.Channels.cs DiscordClient.cs - - DiscordClient.Events.cs + + DiscordClient.Invites.cs + + + DiscordClient.Members.cs + + + DiscordClient.Messages.cs + + + DiscordClient.Permissions.cs + + + DiscordClient.Roles.cs + + + DiscordClient.Servers.cs + + + DiscordClient.Users.cs DiscordClientConfig.cs diff --git a/src/Discord.Net/Collections/AsyncCollection.cs b/src/Discord.Net/Collections/AsyncCollection.cs index 052988535..9d4a2d79b 100644 --- a/src/Discord.Net/Collections/AsyncCollection.cs +++ b/src/Discord.Net/Collections/AsyncCollection.cs @@ -127,8 +127,8 @@ namespace Discord.Collections } } - protected abstract void OnCreated(TValue item); - protected abstract void OnRemoved(TValue item); + protected virtual void OnCreated(TValue item) { } + protected virtual void OnRemoved(TValue item) { } public IEnumerator GetEnumerator() { diff --git a/src/Discord.Net/Collections/Channels.cs b/src/Discord.Net/Collections/Channels.cs index 4b9a88cff..a2fb81b24 100644 --- a/src/Discord.Net/Collections/Channels.cs +++ b/src/Discord.Net/Collections/Channels.cs @@ -1,6 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; namespace Discord.Collections { @@ -46,29 +44,13 @@ namespace Discord.Collections } } - internal Channel this[string id] => Get(id); - - internal IEnumerable Find(string serverId, string name, string type = null) + internal Channel this[string id] { - if (serverId == null) throw new ArgumentNullException(nameof(serverId)); - - IEnumerable result; - if (name.StartsWith("#")) - { - string name2 = name.Substring(1); - result = this.Where(x => x.ServerId == serverId && - string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase) || string.Equals(x.Name, name2, StringComparison.OrdinalIgnoreCase)); - } - else + get { - result = this.Where(x => x.ServerId == serverId && - string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase)); + if (id == null) throw new ArgumentNullException(nameof(id)); + return Get(id); } - - if (type != null) - result = result.Where(x => x.Type == type); - - return result; } } } diff --git a/src/Discord.Net/Collections/Members.cs b/src/Discord.Net/Collections/Members.cs index c8c4deae1..363a5d37c 100644 --- a/src/Discord.Net/Collections/Members.cs +++ b/src/Discord.Net/Collections/Members.cs @@ -1,6 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; namespace Discord.Collections { @@ -45,52 +43,8 @@ namespace Discord.Collections { 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); - }); - } - } - - internal Member Find(string username, string discriminator) - { - if (username == null) throw new ArgumentNullException(nameof(username)); - if (discriminator == null) throw new ArgumentNullException(nameof(discriminator)); - - if (username.StartsWith("@")) - username = username.Substring(1); - - return this.Where(x => - string.Equals(x.Name, username, StringComparison.OrdinalIgnoreCase) && - x.Discriminator == discriminator - ) - .FirstOrDefault(); - } } } diff --git a/src/Discord.Net/Collections/Messages.cs b/src/Discord.Net/Collections/Messages.cs index 0943c8a9d..16b2d32a7 100644 --- a/src/Discord.Net/Collections/Messages.cs +++ b/src/Discord.Net/Collections/Messages.cs @@ -1,11 +1,11 @@ -namespace Discord.Collections +using System; + +namespace Discord.Collections { public sealed class Messages : AsyncCollection { internal Messages(DiscordClient client, object writerLock) - : base(client, writerLock) - { - } + : base(client, writerLock) { } internal Message GetOrAdd(string id, string channelId, string userId) => GetOrAdd(id, () => new Message(_client, id, channelId, userId)); internal new Message TryRemove(string id) => base.TryRemove(id); @@ -26,6 +26,13 @@ user.RemoveRef(); } - internal Message this[string id] => Get(id); + internal Message this[string id] + { + get + { + if (id == null) throw new ArgumentNullException(nameof(id)); + return Get(id); + } + } } } diff --git a/src/Discord.Net/Collections/Roles.cs b/src/Discord.Net/Collections/Roles.cs index 502c9b8d7..fd83a7c1e 100644 --- a/src/Discord.Net/Collections/Roles.cs +++ b/src/Discord.Net/Collections/Roles.cs @@ -1,6 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; namespace Discord.Collections { @@ -23,23 +21,12 @@ namespace Discord.Collections item.Server.RemoveRole(item.Id); } - internal Role this[string id] => Get(id); - - internal IEnumerable Find(string serverId, string name) + internal Role this[string id] { - 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 + get { - return this.Where(x => x.ServerId == serverId && - string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase)); + if (id == null) throw new ArgumentNullException(nameof(id)); + return Get(id); } } } diff --git a/src/Discord.Net/Collections/Servers.cs b/src/Discord.Net/Collections/Servers.cs index d1f6b9245..ddd44071a 100644 --- a/src/Discord.Net/Collections/Servers.cs +++ b/src/Discord.Net/Collections/Servers.cs @@ -11,8 +11,7 @@ namespace Discord.Collections 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; @@ -29,12 +28,5 @@ namespace Discord.Collections } 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 index 11f301aaf..90317b65b 100644 --- a/src/Discord.Net/Collections/Users.cs +++ b/src/Discord.Net/Collections/Users.cs @@ -1,6 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; namespace Discord.Collections { @@ -15,22 +13,12 @@ namespace Discord.Collections protected override void OnCreated(User item) { } protected override void OnRemoved(User item) { } - internal User this[string id] => Get(id); - - internal IEnumerable Find(string name) + internal User this[string id] { - 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 + get { - return this.Where(x => - string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase)); + if (id == null) throw new ArgumentNullException(nameof(id)); + return Get(id); } } } diff --git a/src/Discord.Net/DiscordClient.API.cs b/src/Discord.Net/DiscordClient.API.cs deleted file mode 100644 index e99561ebd..000000000 --- a/src/Discord.Net/DiscordClient.API.cs +++ /dev/null @@ -1,756 +0,0 @@ -using Discord.API; -using Discord.Net; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Threading.Tasks; - -namespace Discord -{ - public partial class DiscordClient - { - public const int MaxMessageSize = 2000; - - //Bans - /// Bans a user from the provided server. - public Task Ban(Member member) - => Ban(member?.ServerId, member?.UserId); - /// Bans a user from the provided server. - public Task Ban(Server server, User user) - => Ban(server?.Id, user?.Id); - /// Bans a user from the provided server. - public Task Ban(Server server, string userId) - => Ban(server?.Id, userId); - /// Bans a user from the provided server. - public Task Ban(string server, User user) - => Ban(server, user?.Id); - /// Bans a user from the provided server. - public Task Ban(string serverId, string userId) - { - CheckReady(); - if (serverId == null) throw new ArgumentNullException(nameof(serverId)); - if (userId == null) throw new ArgumentNullException(nameof(userId)); - - return _api.Ban(serverId, userId); - } - - /// Unbans a user from the provided server. - public Task Unban(Member member) - => Unban(member?.ServerId, member?.UserId); - /// Unbans a user from the provided server. - public Task Unban(Server server, User user) - => Unban(server?.Id, user?.Id); - /// Unbans a user from the provided server. - public Task Unban(Server server, string userId) - => Unban(server?.Id, userId); - /// Unbans a user from the provided server. - public Task Unban(string server, User user) - => Unban(server, user?.Id); - /// Unbans a user from the provided server. - public async Task Unban(string serverId, string userId) - { - CheckReady(); - if (serverId == null) throw new ArgumentNullException(nameof(serverId)); - if (userId == null) throw new ArgumentNullException(nameof(userId)); - - try { await _api.Unban(serverId, userId).ConfigureAwait(false); } - catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } - } - - //Channels - /// Creates a new channel with the provided name and type (see ChannelTypes). - public Task CreateChannel(Server server, string name, string type = ChannelTypes.Text) - => CreateChannel(server?.Id, name, type); - /// Creates a new channel with the provided name and type (see ChannelTypes). - public async Task CreateChannel(string serverId, string name, string type = ChannelTypes.Text) - { - CheckReady(); - if (serverId == null) throw new ArgumentNullException(nameof(serverId)); - if (name == null) throw new ArgumentNullException(nameof(name)); - if (type == null) throw new ArgumentNullException(nameof(type)); - - var response = await _api.CreateChannel(serverId, name, type).ConfigureAwait(false); - 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); - /// Returns the private channel with the provided user, creating one if it does not currently exist. - public Task CreatePMChannel(Member member) => CreatePMChannel(member.User, member.UserId); - 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); - user = _users.GetOrAdd(response.Recipient?.Id); - user.Update(response.Recipient); - channel = _channels.GetOrAdd(response.Id, response.GuildId, response.Recipient?.Id); - channel.Update(response); - } - return channel; - } - - /// Edits the provided channel, changing only non-null attributes. - public Task EditChannel(string channelId, string name = null, string topic = null, int? position = null) - => EditChannel(_channels[channelId], name: name, topic: topic, position: position); - /// Edits the provided channel, changing only non-null attributes. - public async Task EditChannel(Channel channel, string name = null, string topic = null, int? position = null) - { - CheckReady(); - if (channel == null) throw new ArgumentNullException(nameof(channel)); - - await _api.EditChannel(channel.Id, name: name, topic: topic); - - if (position != null) - { - int oldPos = channel.Position; - int newPos = position.Value; - int minPos; - Channel[] channels = channel.Server.Channels.OrderBy(x => x.Position).ToArray(); - - if (oldPos < newPos) //Moving Down - { - minPos = oldPos; - for (int i = oldPos; i < newPos; i++) - channels[i] = channels[i + 1]; - channels[newPos] = channel; - } - else //(oldPos > newPos) Moving Up - { - minPos = newPos; - for (int i = oldPos; i > newPos; i--) - channels[i] = channels[i - 1]; - channels[newPos] = channel; - } - await _api.ReorderChannels(channel.ServerId, channels.Skip(minPos).Select(x => x.Id), minPos); - } - } - - public Task ReorderChannels(Server server, IEnumerable channels, int startPos = 0) - => ReorderChannels(server.Id, channels, startPos); - public Task ReorderChannels(string serverId, IEnumerable channels, int startPos = 0) - { - if (serverId == null) throw new ArgumentNullException(nameof(serverId)); - if (channels == null) throw new ArgumentNullException(nameof(channels)); - if (startPos < 0) throw new ArgumentOutOfRangeException(nameof(startPos), "startPos must be a positive integer."); - - var channelIds = CollectionHelper.FlattenChannels(channels); - return _api.ReorderChannels(serverId, channelIds, startPos); - } - - /// Destroys the provided channel. - public Task DestroyChannel(Channel channel) - => DestroyChannel(channel?.Id); - /// Destroys the provided channel. - public async Task DestroyChannel(string channelId) - { - CheckReady(); - if (channelId == null) throw new ArgumentNullException(nameof(channelId)); - - try { await _api.DestroyChannel(channelId).ConfigureAwait(false); } - catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } - return _channels.TryRemove(channelId); - } - - //Invites - /// Creates a new invite to the default channel of the provided server. - /// Time (in seconds) until the invite expires. Set to 0 to never expire. - /// If true, a user accepting this invite will be kicked from the server after closing their client. - /// If true, creates a human-readable link. Not supported if maxAge is set to 0. - /// The max amount of times this invite may be used. Set to 0 to have unlimited uses. - public Task CreateInvite(Server server, int maxAge = 1800, int maxUses = 0, bool tempMembership = false, bool hasXkcd = false) - => CreateInvite(server?.DefaultChannelId, maxAge, maxUses, tempMembership, hasXkcd); - /// Creates a new invite to the provided channel. - /// Time (in seconds) until the invite expires. Set to 0 to never expire. - /// If true, a user accepting this invite will be kicked from the server after closing their client. - /// If true, creates a human-readable link. Not supported if maxAge is set to 0. - /// The max amount of times this invite may be used. Set to 0 to have unlimited uses. - public Task CreateInvite(Channel channel, int maxAge = 1800, int maxUses = 0, bool tempMembership = false, bool hasXkcd = false) - => CreateInvite(channel?.Id, maxAge, maxUses, tempMembership, hasXkcd); - /// Creates a new invite to the provided channel. - /// Time (in seconds) until the invite expires. Set to 0 to never expire. - /// If true, a user accepting this invite will be kicked from the server after closing their client. - /// If true, creates a human-readable link. Not supported if maxAge is set to 0. - /// The max amount of times this invite may be used. Set to 0 to have unlimited uses. - public async Task CreateInvite(string serverOrChannelId, int maxAge = 1800, int maxUses = 0, bool tempMembership = false, bool hasXkcd = false) - { - CheckReady(); - if (serverOrChannelId == null) throw new ArgumentNullException(nameof(serverOrChannelId)); - if (maxAge <= 0) throw new ArgumentOutOfRangeException(nameof(maxAge)); - if (maxUses <= 0) throw new ArgumentOutOfRangeException(nameof(maxUses)); - - var response = await _api.CreateInvite(serverOrChannelId, maxAge, maxUses, tempMembership, hasXkcd).ConfigureAwait(false); - var invite = new Invite(this, response.Code, response.XkcdPass, response.Guild.Id); - invite.Update(response); - return invite; - } - - /// Deletes the provided invite. - public async Task DestroyInvite(string inviteId) - { - CheckReady(); - if (inviteId == null) throw new ArgumentNullException(nameof(inviteId)); - - try - { - //Check if this is a human-readable link and get its ID - var response = await _api.GetInvite(inviteId).ConfigureAwait(false); - await _api.DeleteInvite(response.Code).ConfigureAwait(false); - } - catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } - } - - /// 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 inviteIdOrXkcd) - { - CheckReady(); - if (inviteIdOrXkcd == null) throw new ArgumentNullException(nameof(inviteIdOrXkcd)); - - var response = await _api.GetInvite(inviteIdOrXkcd).ConfigureAwait(false); - var invite = new Invite(this, response.Code, response.XkcdPass, response.Guild.Id); - invite.Update(response); - return invite; - } - - /// Accepts the provided invite. - public Task AcceptInvite(Invite invite) - { - CheckReady(); - if (invite == null) throw new ArgumentNullException(nameof(invite)); - - return _api.AcceptInvite(invite.Id); - } - /// Accepts the provided invite. - public async Task AcceptInvite(string inviteId) - { - CheckReady(); - if (inviteId == null) throw new ArgumentNullException(nameof(inviteId)); - - //Remove trailing slash and any non-code url parts - if (inviteId.Length > 0 && inviteId[inviteId.Length - 1] == '/') - inviteId = inviteId.Substring(0, inviteId.Length - 1); - int index = inviteId.LastIndexOf('/'); - if (index >= 0) - inviteId = inviteId.Substring(index + 1); - - //Check if this is a human-readable link and get its ID - var invite = await GetInvite(inviteId).ConfigureAwait(false); - await _api.AcceptInvite(invite.Id).ConfigureAwait(false); - } - - //Members - public Task EditMember(Member member, bool? mute = null, bool? deaf = null, IEnumerable roles = null) - => EditMember(member?.ServerId, member?.UserId, mute, deaf, roles); - public Task EditMember(Server server, User user, bool? mute = null, bool? deaf = null, IEnumerable roles = null) - => EditMember(server?.Id, user?.Id, mute, deaf, roles); - public Task EditMember(Server server, string userId, bool? mute = null, bool? deaf = null, IEnumerable roles = null) - => EditMember(server?.Id, userId, mute, deaf, roles); - public Task EditMember(string serverId, User user, bool? mute = null, bool? deaf = null, IEnumerable roles = null) - => EditMember(serverId, user?.Id, mute, deaf, roles); - public Task EditMember(string serverId, string userId, bool? mute = null, bool? deaf = null, IEnumerable roles = null) - { - CheckReady(); - if (serverId == null) throw new NullReferenceException(nameof(serverId)); - if (userId == null) throw new NullReferenceException(nameof(userId)); - - var newRoles = CollectionHelper.FlattenRoles(roles); - return _api.EditMember(serverId, userId, mute: mute, deaf: deaf, roles: newRoles); - } - - //Messages - /// Sends a message to the provided channel. To include a mention, see the Mention static helper class. - public Task SendMessage(Channel channel, string text) - => SendMessage(channel, text, MentionHelper.GetUserIds(text), false); - /// Sends a message to the provided channel. To include a mention, see the Mention static helper class. - public Task SendMessage(string channelId, string text) - => SendMessage(_channels[channelId], text, MentionHelper.GetUserIds(text), false); - private async Task SendMessage(Channel channel, string text, IEnumerable mentionedUsers = null, bool isTextToSpeech = false) - { - CheckReady(); - if (channel == null) throw new ArgumentNullException(nameof(channel)); - if (text == null) throw new ArgumentNullException(nameof(text)); - var mentionedUserIds = CollectionHelper.FlattenUsers(mentionedUsers); - - int blockCount = (int)Math.Ceiling(text.Length / (double)MaxMessageSize); - Message[] result = new Message[blockCount]; - for (int i = 0; i < blockCount; i++) - { - int index = i * MaxMessageSize; - string blockText = text.Substring(index, Math.Min(2000, text.Length - index)); - var nonce = GenerateNonce(); - if (Config.UseMessageQueue) - { - var msg = _messages.GetOrAdd("nonce_" + nonce, channel.Id, CurrentUserId); - var currentUser = msg.User; - msg.Update(new MessageInfo - { - Content = blockText, - Timestamp = DateTime.UtcNow, - Author = new UserReference { Avatar = currentUser.AvatarId, Discriminator = currentUser.Discriminator, Id = CurrentUserId, Username = currentUser.Name }, - ChannelId = channel.Id, - IsTextToSpeech = isTextToSpeech - }); - msg.IsQueued = true; - msg.Nonce = nonce; - result[i] = msg; - _pendingMessages.Enqueue(msg); - } - else - { - var model = await _api.SendMessage(channel.Id, blockText, mentionedUserIds, nonce, isTextToSpeech).ConfigureAwait(false); - var msg = _messages.GetOrAdd(model.Id, channel.Id, model.Author.Id); - msg.Update(model); - RaiseMessageSent(msg); - result[i] = msg; - } - await Task.Delay(1000).ConfigureAwait(false); - } - return result; - } - - /// Sends a private message to the provided user. - public Task SendPrivateMessage(Member member, string text) - => SendPrivateMessage(member?.UserId, text); - /// Sends a private message to the provided user. - public Task SendPrivateMessage(User user, string text) - => SendPrivateMessage(user?.Id, text); - /// Sends a private message to the provided user. - public async Task SendPrivateMessage(string userId, string text) - { - var channel = await CreatePMChannel(userId).ConfigureAwait(false); - return await SendMessage(channel, text, new string[0]).ConfigureAwait(false); - } - - /// Sends a file to the provided channel. - public Task SendFile(Channel channel, string filePath) - => SendFile(channel?.Id, filePath); - /// Sends a file to the provided channel. - public Task SendFile(string channelId, string filePath) - { - CheckReady(); - if (channelId == null) throw new ArgumentNullException(nameof(channelId)); - if (filePath == null) throw new ArgumentNullException(nameof(filePath)); - - return _api.SendFile(channelId, filePath); - } - - /// Edits the provided message, changing only non-null attributes. - /// While not required, it is recommended to include a mention reference in the text (see Mention.User). - public Task EditMessage(Message message, string text = null, IEnumerable mentionedUsers = null) - => EditMessage(message?.ChannelId, message?.Id, text, mentionedUsers); - /// Edits the provided message, changing only non-null attributes. - /// While not required, it is recommended to include a mention reference in the text (see Mention.User). - public Task EditMessage(Channel channel, string messageId, string text = null, IEnumerable mentionedUsers = null) - => EditMessage(channel?.Id, messageId, text, mentionedUsers); - /// Edits the provided message, changing only non-null attributes. - /// While not required, it is recommended to include a mention reference in the text (see Mention.User). - public async Task EditMessage(string channelId, string messageId, string text = null, IEnumerable mentionedUsers = null) - { - CheckReady(); - if (channelId == null) throw new ArgumentNullException(nameof(channelId)); - if (messageId == null) throw new ArgumentNullException(nameof(messageId)); - var mentionedUserIds = CollectionHelper.FlattenUsers(mentionedUsers); - - if (text != null && text.Length > MaxMessageSize) - text = text.Substring(0, MaxMessageSize); - - var model = await _api.EditMessage(messageId, channelId, text, mentionedUserIds).ConfigureAwait(false); - var msg = _messages[messageId]; - if (msg != null) - msg.Update(model); - } - - /// Deletes the provided message. - public Task DeleteMessage(Message msg) - => DeleteMessage(msg?.ChannelId, msg?.Id); - /// Deletes the provided message. - public async Task DeleteMessage(string channelId, string msgId) - { - CheckReady(); - if (channelId == null) throw new ArgumentNullException(nameof(channelId)); - if (msgId == null) throw new ArgumentNullException(nameof(msgId)); - - try - { - await _api.DeleteMessage(msgId, channelId).ConfigureAwait(false); - _messages.TryRemove(msgId); - } - catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } - } - public async Task DeleteMessages(IEnumerable msgs) - { - CheckReady(); - if (msgs == null) throw new ArgumentNullException(nameof(msgs)); - - foreach (var msg in msgs) - { - try - { - await _api.DeleteMessage(msg.Id, msg.ChannelId).ConfigureAwait(false); - } - catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } - } - } - public async Task DeleteMessages(string channelId, IEnumerable msgIds) - { - CheckReady(); - if (msgIds == null) throw new ArgumentNullException(nameof(msgIds)); - - foreach (var msgId in msgIds) - { - try - { - await _api.DeleteMessage(msgId, channelId).ConfigureAwait(false); - } - catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } - } - } - - /// Downloads last count messages from the server, starting at beforeMessageId if it's provided. - public Task DownloadMessages(Channel channel, int count, string beforeMessageId = null, bool cache = true) - => DownloadMessages(channel.Id, count, beforeMessageId, cache); - /// Downloads last count messages from the server, starting at beforeMessageId if it's provided. - public async Task DownloadMessages(string channelId, int count, string beforeMessageId = null, bool cache = true) - { - CheckReady(); - if (channelId == null) throw new NullReferenceException(nameof(channelId)); - if (count < 0) throw new ArgumentOutOfRangeException(nameof(count)); - if (count == 0) return new Message[0]; - - Channel channel = _channels[channelId]; - if (channel != null && channel.Type == ChannelTypes.Text) - { - try - { - var msgs = await _api.GetMessages(channel.Id, count).ConfigureAwait(false); - return msgs.Select(x => - { - Message msg; - if (cache) - msg = _messages.GetOrAdd(x.Id, x.ChannelId, x.Author.Id); - else - msg = _messages[x.Id] ?? new Message(this, x.Id, x.ChannelId, x.Author.Id); - if (msg != null) - { - msg.Update(x); - if (Config.TrackActivity) - { - /*if (channel.IsPrivate) - { - var user = msg.User; - if (user != null) - user.UpdateActivity(msg.EditedTimestamp ?? msg.Timestamp); - } - else*/ - if (!channel.IsPrivate) - { - var member = msg.Member; - if (member != null) - member.UpdateActivity(msg.EditedTimestamp ?? msg.Timestamp); - } - } - } - return msg; - }) - .ToArray(); - } - catch (HttpException) { } //Bad Permissions? - } - return null; - } - - //Permissions - public Task SetChannelUserPermissions(Channel channel, Member member, PackedChannelPermissions allow = null, PackedChannelPermissions deny = null) - => SetChannelPermissions(channel, member?.UserId, PermissionTarget.Member, allow, deny); - public Task SetChannelUserPermissions(string channelId, Member member, PackedChannelPermissions allow = null, PackedChannelPermissions deny = null) - => SetChannelPermissions(_channels[channelId], member?.UserId, PermissionTarget.Member, allow, deny); - public Task SetChannelUserPermissions(Channel channel, User user, PackedChannelPermissions allow = null, PackedChannelPermissions deny = null) - => SetChannelPermissions(channel, user?.Id, PermissionTarget.Member, allow, deny); - public Task SetChannelUserPermissions(string channelId, User user, PackedChannelPermissions allow = null, PackedChannelPermissions deny = null) - => SetChannelPermissions(_channels[channelId], user?.Id, PermissionTarget.Member, allow, deny); - public Task SetChannelUserPermissions(Channel channel, string userId, PackedChannelPermissions allow = null, PackedChannelPermissions deny = null) - => SetChannelPermissions(channel, userId, PermissionTarget.Member, allow, deny); - public Task SetChannelUserPermissions(string channelId, string userId, PackedChannelPermissions allow = null, PackedChannelPermissions deny = null) - => SetChannelPermissions(_channels[channelId], userId, PermissionTarget.Member, allow, deny); - - public Task SetChannelRolePermissions(Channel channel, Role role, PackedChannelPermissions allow = null, PackedChannelPermissions deny = null) - => SetChannelPermissions(channel, role?.Id, PermissionTarget.Role, allow, deny); - public Task SetChannelRolePermissions(string channelId, Role role, PackedChannelPermissions allow = null, PackedChannelPermissions deny = null) - => SetChannelPermissions(_channels[channelId], role?.Id, PermissionTarget.Role, allow, deny); - public Task SetChannelRolePermissions(Channel channel, string userId, PackedChannelPermissions allow = null, PackedChannelPermissions deny = null) - => SetChannelPermissions(channel, userId, PermissionTarget.Role, allow, deny); - public Task SetChannelRolePermissions(string channelId, string userId, PackedChannelPermissions allow = null, PackedChannelPermissions deny = null) - => SetChannelPermissions(_channels[channelId], userId, PermissionTarget.Role, allow, deny); - - private async Task SetChannelPermissions(Channel channel, string targetId, string targetType, PackedChannelPermissions allow = null, PackedChannelPermissions deny = null) - { - CheckReady(); - if (channel == null) throw new NullReferenceException(nameof(channel)); - if (targetId == null) throw new NullReferenceException(nameof(targetId)); - if (targetType == null) throw new NullReferenceException(nameof(targetType)); - - uint allowValue = allow?.RawValue ?? 0; - uint denyValue = deny?.RawValue ?? 0; - bool changed = false; - - var perms = channel.PermissionOverwrites.Where(x => x.TargetType != targetType || x.TargetId != targetId).FirstOrDefault(); - if (allowValue != 0 || denyValue != 0) - { - await _api.SetChannelPermissions(channel.Id, targetId, targetType, allowValue, denyValue); - if (perms != null) - { - perms.Allow.SetRawValueInternal(allowValue); - perms.Deny.SetRawValueInternal(denyValue); - } - else - { - var oldPerms = channel._permissionOverwrites; - var newPerms = new Channel.PermissionOverwrite[oldPerms.Length + 1]; - Array.Copy(oldPerms, newPerms, oldPerms.Length); - newPerms[oldPerms.Length] = new Channel.PermissionOverwrite(targetType, targetId, allowValue, denyValue); - channel._permissionOverwrites = newPerms; - } - changed = true; - } - else - { - try - { - await _api.DeleteChannelPermissions(channel.Id, targetId); - if (perms != null) - { - channel._permissionOverwrites = channel.PermissionOverwrites.Where(x => x.TargetType != targetType || x.TargetId != targetId).ToArray(); - changed = true; - } - } - catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } - } - - if (changed) - { - if (targetType == PermissionTarget.Role) - channel.InvalidatePermissionsCache(); - else if (targetType == PermissionTarget.Member) - channel.InvalidatePermissionsCache(targetId); - } - } - - public Task RemoveChannelUserPermissions(Channel channel, Member member) - => RemoveChannelPermissions(channel, member?.UserId, PermissionTarget.Member); - public Task RemoveChannelUserPermissions(string channelId, Member member) - => RemoveChannelPermissions(_channels[channelId], member?.UserId, PermissionTarget.Member); - public Task RemoveChannelUserPermissions(Channel channel, User user) - => RemoveChannelPermissions(channel, user?.Id, PermissionTarget.Member); - public Task RemoveChannelUserPermissions(string channelId, User user) - => RemoveChannelPermissions(_channels[channelId], user?.Id, PermissionTarget.Member); - public Task RemoveChannelUserPermissions(Channel channel, string userId) - => RemoveChannelPermissions(channel, userId, PermissionTarget.Member); - public Task RemoveChannelUserPermissions(string channelId, string userId) - => RemoveChannelPermissions(_channels[channelId], userId, PermissionTarget.Member); - - public Task RemoveChannelRolePermissions(Channel channel, Role role) - => RemoveChannelPermissions(channel, role?.Id, PermissionTarget.Role); - public Task RemoveChannelRolePermissions(string channelId, Role role) - => RemoveChannelPermissions(_channels[channelId], role?.Id, PermissionTarget.Role); - public Task RemoveChannelRolePermissions(Channel channel, string roleId) - => RemoveChannelPermissions(channel, roleId, PermissionTarget.Role); - public Task RemoveChannelRolePermissions(string channelId, string roleId) - => RemoveChannelPermissions(_channels[channelId], roleId, PermissionTarget.Role); - - private async Task RemoveChannelPermissions(Channel channel, string userOrRoleId, string idType) - { - CheckReady(); - if (channel == null) throw new NullReferenceException(nameof(channel)); - if (userOrRoleId == null) throw new NullReferenceException(nameof(userOrRoleId)); - if (idType == null) throw new NullReferenceException(nameof(idType)); - - try - { - var perms = channel.PermissionOverwrites.Where(x => x.TargetType != idType || x.TargetId != userOrRoleId).FirstOrDefault(); - await _api.DeleteChannelPermissions(channel.Id, userOrRoleId).ConfigureAwait(false); - if (perms != null) - { - channel.PermissionOverwrites.Where(x => x.TargetType != idType || x.TargetId != userOrRoleId).ToArray(); - - if (idType == PermissionTarget.Role) - channel.InvalidatePermissionsCache(); - else if (idType == PermissionTarget.Member) - channel.InvalidatePermissionsCache(userOrRoleId); - } - } - catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } - } - - //Profile - public Task EditProfile(string currentPassword = "", - string username = null, string email = null, string password = null, - ImageType avatarType = ImageType.Png, byte[] avatar = null) - { - if (currentPassword == null) throw new ArgumentNullException(nameof(currentPassword)); - - return _api.EditUser(currentPassword: currentPassword, username: username ?? _currentUser?.Name, email: email ?? _currentUser?.Email, password: password, - avatarType: avatarType, avatar: avatar); - } - public Task SetStatus(string status) - { - if (status != UserStatus.Online && status != UserStatus.Idle) - throw new ArgumentException($"Invalid status, must be {UserStatus.Online} or {UserStatus.Idle}"); - _status = status; - return SendStatus(); - } - public Task SetGame(int? gameId) - { - _gameId = gameId; - return SendStatus(); - } - private Task SendStatus() - { - _dataSocket.SendStatus(_status == UserStatus.Idle ? EpochTime.GetMilliseconds() - (10 * 60 * 1000) : (ulong?)null, _gameId); - return TaskHelper.CompletedTask; - } - - //Roles - /// Note: due to current API limitations, the created role cannot be returned. - public Task CreateRole(Server server, string name) - => CreateRole(server?.Id, name); - /// Note: due to current API limitations, the created role cannot be returned. - public async Task CreateRole(string serverId, string name) - { - CheckReady(); - if (serverId == null) throw new NullReferenceException(nameof(serverId)); - - var response = await _api.CreateRole(serverId).ConfigureAwait(false); - var role = _roles.GetOrAdd(response.Id, serverId); - role.Update(response); - - await EditRole(role, name: name); - - return role; - } - - public Task EditRole(Role role, string name = null, PackedServerPermissions permissions = null, PackedColor color = null, bool? hoist = null, int? position = null) - => EditRole(role.ServerId, role.Id, name: name, permissions: permissions, color: color, hoist: hoist, position: position); - public async Task EditRole(string serverId, string roleId, string name = null, PackedServerPermissions permissions = null, PackedColor color = null, bool? hoist = null, int? position = null) - { - CheckReady(); - if (serverId == null) throw new NullReferenceException(nameof(serverId)); - if (roleId == null) throw new NullReferenceException(nameof(roleId)); - - var response = await _api.EditRole(serverId, roleId, name: name, - permissions: permissions?.RawValue, color: color?.RawValue, hoist: hoist); - - var role = _roles[response.Id]; - if (role != null) - role.Update(response); - - if (position != null) - { - int oldPos = role.Position; - int newPos = position.Value; - int minPos; - Role[] roles = role.Server.Roles.OrderBy(x => x.Position).ToArray(); - - if (oldPos < newPos) //Moving Down - { - minPos = oldPos; - for (int i = oldPos; i < newPos; i++) - roles[i] = roles[i + 1]; - roles[newPos] = role; - } - else //(oldPos > newPos) Moving Up - { - minPos = newPos; - for (int i = oldPos; i > newPos; i--) - roles[i] = roles[i - 1]; - roles[newPos] = role; - } - await _api.ReorderRoles(role.ServerId, roles.Skip(minPos).Select(x => x.Id), minPos); - } - } - - public Task DeleteRole(Role role) - => DeleteRole(role?.ServerId, role?.Id); - public Task DeleteRole(string serverId, string roleId) - { - CheckReady(); - if (serverId == null) throw new NullReferenceException(nameof(serverId)); - if (roleId == null) throw new NullReferenceException(nameof(roleId)); - - return _api.DeleteRole(serverId, roleId); - } - - public Task ReorderRoles(Server server, IEnumerable roles, int startPos = 0) - => ReorderChannels(server.Id, roles, startPos); - public Task ReorderRoles(string serverId, IEnumerable roles, int startPos = 0) - { - if (serverId == null) throw new ArgumentNullException(nameof(serverId)); - if (roles == null) throw new ArgumentNullException(nameof(roles)); - if (startPos < 0) throw new ArgumentOutOfRangeException(nameof(startPos), "startPos must be a positive integer."); - - var roleIds = roles.Select(x => - { - if (x is string) - return x as string; - else if (x is Role) - return (x as Role).Id; - else - throw new ArgumentException("Channels must be a collection of string or Role.", nameof(roles)); - }); - - return _api.ReorderRoles(serverId, roleIds, startPos); - } - - //Servers - /// Creates a new server with the provided name and region (see Regions). - public async Task CreateServer(string name, string region) - { - CheckReady(); - if (name == null) throw new ArgumentNullException(nameof(name)); - if (region == null) throw new ArgumentNullException(nameof(region)); - - var response = await _api.CreateServer(name, region).ConfigureAwait(false); - var server = _servers.GetOrAdd(response.Id); - server.Update(response); - return server; - } - - /// Edits the provided server, changing only non-null attributes. - public Task EditServer(string serverId, string name = null, string region = null, ImageType iconType = ImageType.Png, byte[] icon = null) - => EditServer(_servers[serverId], name: name, region: region, iconType: iconType, icon: icon); - /// Edits the provided server, changing only non-null attributes. - public async Task EditServer(Server server, string name = null, string region = null, ImageType iconType = ImageType.Png, byte[] icon = null) - { - CheckReady(); - if (server == null) throw new ArgumentNullException(nameof(server)); - - var response = await _api.EditServer(server.Id, name: name ?? server.Name, region: region, iconType: iconType, icon: icon); - server.Update(response); - } - - /// Leaves the provided server, destroying it if you are the owner. - public Task LeaveServer(Server server) - => LeaveServer(server?.Id); - /// Leaves the provided server, destroying it if you are the owner. - public async Task LeaveServer(string serverId) - { - CheckReady(); - if (serverId == null) throw new ArgumentNullException(nameof(serverId)); - - try { await _api.LeaveServer(serverId).ConfigureAwait(false); } - catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } - return _servers.TryRemove(serverId); - } - } -} \ No newline at end of file diff --git a/src/Discord.Net/DiscordClient.Bans.cs b/src/Discord.Net/DiscordClient.Bans.cs new file mode 100644 index 000000000..2c0d98ad0 --- /dev/null +++ b/src/Discord.Net/DiscordClient.Bans.cs @@ -0,0 +1,68 @@ +using Discord.Net; +using System; +using System.Net; +using System.Threading.Tasks; + +namespace Discord +{ + public partial class DiscordClient + { + public event EventHandler BanAdded; + private void RaiseBanAdded(string userId, Server server) + { + if (BanAdded != null) + RaiseEvent(nameof(BanAdded), () => BanAdded(this, new BanEventArgs(_users[userId], userId, server))); + } + public event EventHandler BanRemoved; + private void RaiseBanRemoved(string userId, Server server) + { + if (BanRemoved != null) + RaiseEvent(nameof(BanRemoved), () => BanRemoved(this, new BanEventArgs(_users[userId], userId, server))); + } + + /// Bans a user from the provided server. + public Task Ban(Member member) + => Ban(member?.ServerId, member?.UserId); + /// Bans a user from the provided server. + public Task Ban(Server server, User user) + => Ban(server?.Id, user?.Id); + /// Bans a user from the provided server. + public Task Ban(Server server, string userId) + => Ban(server?.Id, userId); + /// Bans a user from the provided server. + public Task Ban(string server, User user) + => Ban(server, user?.Id); + /// Bans a user from the provided server. + public Task Ban(string serverId, string userId) + { + CheckReady(); + if (serverId == null) throw new ArgumentNullException(nameof(serverId)); + if (userId == null) throw new ArgumentNullException(nameof(userId)); + + return _api.Ban(serverId, userId); + } + + /// Unbans a user from the provided server. + public Task Unban(Member member) + => Unban(member?.ServerId, member?.UserId); + /// Unbans a user from the provided server. + public Task Unban(Server server, User user) + => Unban(server?.Id, user?.Id); + /// Unbans a user from the provided server. + public Task Unban(Server server, string userId) + => Unban(server?.Id, userId); + /// Unbans a user from the provided server. + public Task Unban(string server, User user) + => Unban(server, user?.Id); + /// Unbans a user from the provided server. + public async Task Unban(string serverId, string userId) + { + CheckReady(); + if (serverId == null) throw new ArgumentNullException(nameof(serverId)); + if (userId == null) throw new ArgumentNullException(nameof(userId)); + + try { await _api.Unban(serverId, userId).ConfigureAwait(false); } + catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } + } + } +} \ No newline at end of file diff --git a/src/Discord.Net/DiscordClient.Cache.cs b/src/Discord.Net/DiscordClient.Cache.cs deleted file mode 100644 index ea72e881f..000000000 --- a/src/Discord.Net/DiscordClient.Cache.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System.Collections.Generic; - -namespace Discord -{ - public partial class DiscordClient - { - /// 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, string type = null) => _channels.Find(server?.Id, name, type); - /// 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, string type = null) => _channels.Find(serverId, name, type); - - /// 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) => _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(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, 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(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(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(string serverId, string name) => _members.Find(_servers[serverId], name); - - /// 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]; - /// 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) => _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) => _roles.Find(serverId, name); - - /// 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) => _members[name, discriminator]?.User; - /// 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); - - } -} \ No newline at end of file diff --git a/src/Discord.Net/DiscordClient.Channels.cs b/src/Discord.Net/DiscordClient.Channels.cs new file mode 100644 index 000000000..fa1902d7f --- /dev/null +++ b/src/Discord.Net/DiscordClient.Channels.cs @@ -0,0 +1,182 @@ +using Discord.Collections; +using Discord.Net; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Threading.Tasks; + +namespace Discord +{ + public sealed class ChannelEventArgs : EventArgs + { + public Channel Channel { get; } + public string ChannelId => Channel.Id; + public Server Server => Channel.Server; + public string ServerId => Channel.ServerId; + + internal ChannelEventArgs(Channel channel) { Channel = channel; } + } + + public partial class DiscordClient + { + /// Returns a collection of all channels this client is a member of. + public Channels Channels => _channels; + private readonly Channels _channels; + + public event EventHandler ChannelCreated; + private void RaiseChannelCreated(Channel channel) + { + if (ChannelCreated != null) + RaiseEvent(nameof(ChannelCreated), () => ChannelCreated(this, new ChannelEventArgs(channel))); + } + public event EventHandler ChannelDestroyed; + private void RaiseChannelDestroyed(Channel channel) + { + if (ChannelDestroyed != null) + RaiseEvent(nameof(ChannelDestroyed), () => ChannelDestroyed(this, new ChannelEventArgs(channel))); + } + public event EventHandler ChannelUpdated; + private void RaiseChannelUpdated(Channel channel) + { + if (ChannelUpdated != null) + RaiseEvent(nameof(ChannelUpdated), () => ChannelUpdated(this, new ChannelEventArgs(channel))); + } + + + /// 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, string type = null) => FindChannels(server?.Id, name, type); + /// 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, string type = null) + { + if (serverId == null) throw new ArgumentNullException(nameof(serverId)); + + IEnumerable result; + if (name.StartsWith("#")) + { + string name2 = name.Substring(1); + result = _channels.Where(x => x.ServerId == serverId && + string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase) || + string.Equals(x.Name, name2, StringComparison.OrdinalIgnoreCase)); + } + else + { + result = _channels.Where(x => x.ServerId == serverId && + string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase)); + } + + if (type != null) + result = result.Where(x => x.Type == type); + + return result; + } + + /// Creates a new channel with the provided name and type (see ChannelTypes). + public Task CreateChannel(Server server, string name, string type = ChannelTypes.Text) + => CreateChannel(server?.Id, name, type); + /// Creates a new channel with the provided name and type (see ChannelTypes). + public async Task CreateChannel(string serverId, string name, string type = ChannelTypes.Text) + { + CheckReady(); + if (serverId == null) throw new ArgumentNullException(nameof(serverId)); + if (name == null) throw new ArgumentNullException(nameof(name)); + if (type == null) throw new ArgumentNullException(nameof(type)); + + var response = await _api.CreateChannel(serverId, name, type).ConfigureAwait(false); + 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); + /// Returns the private channel with the provided user, creating one if it does not currently exist. + public Task CreatePMChannel(Member member) => CreatePMChannel(member.User, member.UserId); + 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); + user = _users.GetOrAdd(response.Recipient?.Id); + user.Update(response.Recipient); + channel = _channels.GetOrAdd(response.Id, response.GuildId, response.Recipient?.Id); + channel.Update(response); + } + return channel; + } + + /// Edits the provided channel, changing only non-null attributes. + public Task EditChannel(string channelId, string name = null, string topic = null, int? position = null) + => EditChannel(_channels[channelId], name: name, topic: topic, position: position); + /// Edits the provided channel, changing only non-null attributes. + public async Task EditChannel(Channel channel, string name = null, string topic = null, int? position = null) + { + CheckReady(); + if (channel == null) throw new ArgumentNullException(nameof(channel)); + + await _api.EditChannel(channel.Id, name: name, topic: topic); + + if (position != null) + { + int oldPos = channel.Position; + int newPos = position.Value; + int minPos; + Channel[] channels = channel.Server.Channels.OrderBy(x => x.Position).ToArray(); + + if (oldPos < newPos) //Moving Down + { + minPos = oldPos; + for (int i = oldPos; i < newPos; i++) + channels[i] = channels[i + 1]; + channels[newPos] = channel; + } + else //(oldPos > newPos) Moving Up + { + minPos = newPos; + for (int i = oldPos; i > newPos; i--) + channels[i] = channels[i - 1]; + channels[newPos] = channel; + } + await _api.ReorderChannels(channel.ServerId, channels.Skip(minPos).Select(x => x.Id), minPos); + } + } + + public Task ReorderChannels(Server server, IEnumerable channels, int startPos = 0) + => ReorderChannels(server.Id, channels, startPos); + public Task ReorderChannels(string serverId, IEnumerable channels, int startPos = 0) + { + if (serverId == null) throw new ArgumentNullException(nameof(serverId)); + if (channels == null) throw new ArgumentNullException(nameof(channels)); + if (startPos < 0) throw new ArgumentOutOfRangeException(nameof(startPos), "startPos must be a positive integer."); + + var channelIds = CollectionHelper.FlattenChannels(channels); + return _api.ReorderChannels(serverId, channelIds, startPos); + } + + /// Destroys the provided channel. + public Task DestroyChannel(Channel channel) + => DestroyChannel(channel?.Id); + /// Destroys the provided channel. + public async Task DestroyChannel(string channelId) + { + CheckReady(); + if (channelId == null) throw new ArgumentNullException(nameof(channelId)); + + try { await _api.DestroyChannel(channelId).ConfigureAwait(false); } + catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } + return _channels.TryRemove(channelId); + } + } +} \ No newline at end of file diff --git a/src/Discord.Net/DiscordClient.Events.cs b/src/Discord.Net/DiscordClient.Events.cs deleted file mode 100644 index 9b7f8e712..000000000 --- a/src/Discord.Net/DiscordClient.Events.cs +++ /dev/null @@ -1,278 +0,0 @@ -using System; - -namespace Discord -{ - public sealed class ServerEventArgs : EventArgs - { - public Server Server { get; } - public string ServerId => Server.Id; - - internal ServerEventArgs(Server server) { Server = server; } - } - public sealed class ChannelEventArgs : EventArgs - { - public Channel Channel { get; } - public string ChannelId => Channel.Id; - public Server Server => Channel.Server; - public string ServerId => Channel.ServerId; - - internal ChannelEventArgs(Channel channel) { Channel = channel; } - } - public sealed class UserEventArgs : EventArgs - { - public User User { get; } - public string UserId => User.Id; - - internal UserEventArgs(User user) { User = user; } - } - public sealed class MessageEventArgs : EventArgs - { - public Message Message { get; } - public string MessageId => Message.Id; - public Member Member => Message.Member; - public Channel Channel => Message.Channel; - public string ChannelId => Message.ChannelId; - public Server Server => Message.Server; - public string ServerId => Message.ServerId; - public User User => Member.User; - public string UserId => Message.UserId; - - internal MessageEventArgs(Message msg) { Message = msg; } - } - public sealed class RoleEventArgs : EventArgs - { - public Role Role { get; } - public string RoleId => Role.Id; - public Server Server => Role.Server; - public string ServerId => Role.ServerId; - - internal RoleEventArgs(Role role) { Role = role; } - } - public sealed class BanEventArgs : EventArgs - { - public User User { get; } - public string UserId { get; } - public Server Server { get; } - public string ServerId => Server.Id; - - internal BanEventArgs(User user, string userId, Server server) - { - User = user; - UserId = userId; - Server = server; - } - } - public sealed class MemberEventArgs : EventArgs - { - public Member Member { get; } - public User User => Member.User; - public string UserId => Member.UserId; - public Server Server => Member.Server; - public string ServerId => Member.ServerId; - - internal MemberEventArgs(Member member) { Member = member; } - } - public sealed class UserTypingEventArgs : EventArgs - { - public Channel Channel { get; } - public string ChannelId => Channel.Id; - public Server Server => Channel.Server; - public string ServerId => Channel.ServerId; - public User User { get; } - public string UserId => User.Id; - - internal UserTypingEventArgs(User user, Channel channel) - { - User = user; - Channel = channel; - } - } - public sealed class UserIsSpeakingEventArgs : EventArgs - { - public Channel Channel => Member.VoiceChannel; - public string ChannelId => Member.VoiceChannelId; - public Server Server => Member.Server; - public string ServerId => Member.ServerId; - public User User => Member.User; - public string UserId => Member.UserId; - public Member Member { get; } - public bool IsSpeaking { get; } - - internal UserIsSpeakingEventArgs(Member member, bool isSpeaking) - { - Member = member; - IsSpeaking = isSpeaking; - } - } - - public partial class DiscordClient - { - //Server - public event EventHandler ServerCreated; - private void RaiseServerCreated(Server server) - { - if (ServerCreated != null) - RaiseEvent(nameof(ServerCreated), () => ServerCreated(this, new ServerEventArgs(server))); - } - public event EventHandler ServerDestroyed; - private void RaiseServerDestroyed(Server server) - { - if (ServerDestroyed != null) - RaiseEvent(nameof(ServerDestroyed), () => ServerDestroyed(this, new ServerEventArgs(server))); - } - public event EventHandler ServerUpdated; - private void RaiseServerUpdated(Server server) - { - if (ServerUpdated != null) - RaiseEvent(nameof(ServerUpdated), () => ServerUpdated(this, new ServerEventArgs(server))); - } - public event EventHandler ServerUnavailable; - private void RaiseServerUnavailable(Server server) - { - if (ServerUnavailable != null) - RaiseEvent(nameof(ServerUnavailable), () => ServerUnavailable(this, new ServerEventArgs(server))); - } - public event EventHandler ServerAvailable; - private void RaiseServerAvailable(Server server) - { - if (ServerAvailable != null) - RaiseEvent(nameof(ServerAvailable), () => ServerAvailable(this, new ServerEventArgs(server))); - } - - //Channel - public event EventHandler ChannelCreated; - private void RaiseChannelCreated(Channel channel) - { - if (ChannelCreated != null) - RaiseEvent(nameof(ChannelCreated), () => ChannelCreated(this, new ChannelEventArgs(channel))); - } - public event EventHandler ChannelDestroyed; - private void RaiseChannelDestroyed(Channel channel) - { - if (ChannelDestroyed != null) - RaiseEvent(nameof(ChannelDestroyed), () => ChannelDestroyed(this, new ChannelEventArgs(channel))); - } - public event EventHandler ChannelUpdated; - private void RaiseChannelUpdated(Channel channel) - { - if (ChannelUpdated != null) - RaiseEvent(nameof(ChannelUpdated), () => ChannelUpdated(this, new ChannelEventArgs(channel))); - } - - //Message - public event EventHandler MessageCreated; - private void RaiseMessageCreated(Message msg) - { - if (MessageCreated != null) - RaiseEvent(nameof(MessageCreated), () => MessageCreated(this, new MessageEventArgs(msg))); - } - public event EventHandler MessageDeleted; - private void RaiseMessageDeleted(Message msg) - { - if (MessageDeleted != null) - RaiseEvent(nameof(MessageDeleted), () => MessageDeleted(this, new MessageEventArgs(msg))); - } - public event EventHandler MessageUpdated; - private void RaiseMessageUpdated(Message msg) - { - if (MessageUpdated != null) - RaiseEvent(nameof(MessageUpdated), () => MessageUpdated(this, new MessageEventArgs(msg))); - } - public event EventHandler MessageReadRemotely; - private void RaiseMessageReadRemotely(Message msg) - { - if (MessageReadRemotely != null) - RaiseEvent(nameof(MessageReadRemotely), () => MessageReadRemotely(this, new MessageEventArgs(msg))); - } - public event EventHandler MessageSent; - private void RaiseMessageSent(Message msg) - { - if (MessageSent != null) - RaiseEvent(nameof(MessageSent), () => MessageSent(this, new MessageEventArgs(msg))); - } - - //Role - public event EventHandler RoleCreated; - private void RaiseRoleCreated(Role role) - { - if (RoleCreated != null) - RaiseEvent(nameof(RoleCreated), () => RoleCreated(this, new RoleEventArgs(role))); - } - public event EventHandler RoleUpdated; - private void RaiseRoleDeleted(Role role) - { - if (RoleDeleted != null) - RaiseEvent(nameof(RoleDeleted), () => RoleDeleted(this, new RoleEventArgs(role))); - } - public event EventHandler RoleDeleted; - private void RaiseRoleUpdated(Role role) - { - if (RoleUpdated != null) - RaiseEvent(nameof(RoleUpdated), () => RoleUpdated(this, new RoleEventArgs(role))); - } - - //Ban - public event EventHandler BanAdded; - private void RaiseBanAdded(string userId, Server server) - { - if (BanAdded != null) - RaiseEvent(nameof(BanAdded), () => BanAdded(this, new BanEventArgs(_users[userId], userId, server))); - } - public event EventHandler BanRemoved; - private void RaiseBanRemoved(string userId, Server server) - { - if (BanRemoved != null) - RaiseEvent(nameof(BanRemoved), () => BanRemoved(this, new BanEventArgs(_users[userId], userId, server))); - } - - //User - public event EventHandler UserAdded; - private void RaiseUserAdded(Member member) - { - if (UserAdded != null) - RaiseEvent(nameof(UserAdded), () => UserAdded(this, new MemberEventArgs(member))); - } - public event EventHandler UserRemoved; - private void RaiseUserRemoved(Member member) - { - if (UserRemoved != null) - RaiseEvent(nameof(UserRemoved), () => UserRemoved(this, new MemberEventArgs(member))); - } - public event EventHandler UserUpdated; - private void RaiseUserUpdated(User user) - { - if (UserUpdated != null) - RaiseEvent(nameof(UserUpdated), () => UserUpdated(this, new UserEventArgs(user))); - } - public event EventHandler MemberUpdated; - private void RaiseMemberUpdated(Member member) - { - if (MemberUpdated != null) - RaiseEvent(nameof(MemberUpdated), () => MemberUpdated(this, new MemberEventArgs(member))); - } - public event EventHandler UserPresenceUpdated; - private void RaiseUserPresenceUpdated(Member member) - { - if (UserPresenceUpdated != null) - RaiseEvent(nameof(UserPresenceUpdated), () => UserPresenceUpdated(this, new MemberEventArgs(member))); - } - public event EventHandler UserVoiceStateUpdated; - private void RaiseUserVoiceStateUpdated(Member member) - { - if (UserVoiceStateUpdated != null) - RaiseEvent(nameof(UserVoiceStateUpdated), () => UserVoiceStateUpdated(this, new MemberEventArgs(member))); - } - public event EventHandler UserIsTyping; - private void RaiseUserIsTyping(User user, Channel channel) - { - if (UserIsTyping != null) - RaiseEvent(nameof(UserIsTyping), () => UserIsTyping(this, new UserTypingEventArgs(user, channel))); - } - public event EventHandler UserIsSpeaking; - private void RaiseUserIsSpeaking(Member member, bool isSpeaking) - { - if (UserIsSpeaking != null) - RaiseEvent(nameof(UserIsSpeaking), () => UserIsSpeaking(this, new UserIsSpeakingEventArgs(member, isSpeaking))); - } - } -} \ No newline at end of file diff --git a/src/Discord.Net/DiscordClient.Invites.cs b/src/Discord.Net/DiscordClient.Invites.cs new file mode 100644 index 000000000..115d9b7ce --- /dev/null +++ b/src/Discord.Net/DiscordClient.Invites.cs @@ -0,0 +1,96 @@ +using Discord.Net; +using System; +using System.Net; +using System.Threading.Tasks; + +namespace Discord +{ + public partial class DiscordClient + { + /// Creates a new invite to the default channel of the provided server. + /// Time (in seconds) until the invite expires. Set to 0 to never expire. + /// If true, a user accepting this invite will be kicked from the server after closing their client. + /// If true, creates a human-readable link. Not supported if maxAge is set to 0. + /// The max amount of times this invite may be used. Set to 0 to have unlimited uses. + public Task CreateInvite(Server server, int maxAge = 1800, int maxUses = 0, bool tempMembership = false, bool hasXkcd = false) + => CreateInvite(server?.DefaultChannelId, maxAge, maxUses, tempMembership, hasXkcd); + /// Creates a new invite to the provided channel. + /// Time (in seconds) until the invite expires. Set to 0 to never expire. + /// If true, a user accepting this invite will be kicked from the server after closing their client. + /// If true, creates a human-readable link. Not supported if maxAge is set to 0. + /// The max amount of times this invite may be used. Set to 0 to have unlimited uses. + public Task CreateInvite(Channel channel, int maxAge = 1800, int maxUses = 0, bool tempMembership = false, bool hasXkcd = false) + => CreateInvite(channel?.Id, maxAge, maxUses, tempMembership, hasXkcd); + /// Creates a new invite to the provided channel. + /// Time (in seconds) until the invite expires. Set to 0 to never expire. + /// If true, a user accepting this invite will be kicked from the server after closing their client. + /// If true, creates a human-readable link. Not supported if maxAge is set to 0. + /// The max amount of times this invite may be used. Set to 0 to have unlimited uses. + public async Task CreateInvite(string serverOrChannelId, int maxAge = 1800, int maxUses = 0, bool tempMembership = false, bool hasXkcd = false) + { + CheckReady(); + if (serverOrChannelId == null) throw new ArgumentNullException(nameof(serverOrChannelId)); + if (maxAge <= 0) throw new ArgumentOutOfRangeException(nameof(maxAge)); + if (maxUses <= 0) throw new ArgumentOutOfRangeException(nameof(maxUses)); + + var response = await _api.CreateInvite(serverOrChannelId, maxAge, maxUses, tempMembership, hasXkcd).ConfigureAwait(false); + var invite = new Invite(this, response.Code, response.XkcdPass, response.Guild.Id); + invite.Update(response); + return invite; + } + + /// Deletes the provided invite. + public async Task DestroyInvite(string inviteId) + { + CheckReady(); + if (inviteId == null) throw new ArgumentNullException(nameof(inviteId)); + + try + { + //Check if this is a human-readable link and get its ID + var response = await _api.GetInvite(inviteId).ConfigureAwait(false); + await _api.DeleteInvite(response.Code).ConfigureAwait(false); + } + catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } + } + + /// 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 inviteIdOrXkcd) + { + CheckReady(); + if (inviteIdOrXkcd == null) throw new ArgumentNullException(nameof(inviteIdOrXkcd)); + + var response = await _api.GetInvite(inviteIdOrXkcd).ConfigureAwait(false); + var invite = new Invite(this, response.Code, response.XkcdPass, response.Guild.Id); + invite.Update(response); + return invite; + } + + /// Accepts the provided invite. + public Task AcceptInvite(Invite invite) + { + CheckReady(); + if (invite == null) throw new ArgumentNullException(nameof(invite)); + + return _api.AcceptInvite(invite.Id); + } + /// Accepts the provided invite. + public async Task AcceptInvite(string inviteId) + { + CheckReady(); + if (inviteId == null) throw new ArgumentNullException(nameof(inviteId)); + + //Remove trailing slash and any non-code url parts + if (inviteId.Length > 0 && inviteId[inviteId.Length - 1] == '/') + inviteId = inviteId.Substring(0, inviteId.Length - 1); + int index = inviteId.LastIndexOf('/'); + if (index >= 0) + inviteId = inviteId.Substring(index + 1); + + //Check if this is a human-readable link and get its ID + var invite = await GetInvite(inviteId).ConfigureAwait(false); + await _api.AcceptInvite(invite.Id).ConfigureAwait(false); + } + } +} \ No newline at end of file diff --git a/src/Discord.Net/DiscordClient.Members.cs b/src/Discord.Net/DiscordClient.Members.cs new file mode 100644 index 000000000..562deb744 --- /dev/null +++ b/src/Discord.Net/DiscordClient.Members.cs @@ -0,0 +1,135 @@ +using Discord.Collections; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Discord +{ + public sealed class MemberTypingEventArgs : EventArgs + { + public Channel Channel { get; } + public string ChannelId => Channel.Id; + public Server Server => Channel.Server; + public string ServerId => Channel.ServerId; + public Member Member { get; } + public string UserId => User.Id; + public User User => Member.User; + + internal MemberTypingEventArgs(Member member, Channel channel) + { + Member = member; + Channel = channel; + } + } + + public sealed class MemberIsSpeakingEventArgs : EventArgs + { + public Channel Channel => Member.VoiceChannel; + public string ChannelId => Member.VoiceChannelId; + public Server Server => Member.Server; + public string ServerId => Member.ServerId; + public User User => Member.User; + public string UserId => Member.UserId; + public Member Member { get; } + public bool IsSpeaking { get; } + + internal MemberIsSpeakingEventArgs(Member member, bool isSpeaking) + { + Member = member; + IsSpeaking = isSpeaking; + } + } + + public partial class DiscordClient + { + public event EventHandler UserIsTyping; + private void RaiseUserIsTyping(Member member, Channel channel) + { + if (UserIsTyping != null) + RaiseEvent(nameof(UserIsTyping), () => UserIsTyping(this, new MemberTypingEventArgs(member, channel))); + } + public event EventHandler UserIsSpeaking; + private void RaiseUserIsSpeaking(Member member, bool isSpeaking) + { + if (UserIsSpeaking != null) + RaiseEvent(nameof(UserIsSpeaking), () => UserIsSpeaking(this, new MemberIsSpeakingEventArgs(member, isSpeaking))); + } + + /// Returns a collection of all user-server pairs this client can currently see. + public Members Members => _members; + private readonly Members _members; + + /// 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) => _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, 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(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) => _members[userId, serverId]; + /// Returns the user with the specified name and discriminator, along withtheir server-specific data, or null if they couldn't be found. + /// Name formats supported: Name and @Name. Search is case-insensitive. + public Member GetMember(Server server, string username, string discriminator) + => GetMember(server?.Id, username, discriminator); + /// Returns the user with the specified name and discriminator, along withtheir server-specific data, or null if they couldn't be found. + /// Name formats supported: Name and @Name. Search is case-insensitive. + public Member GetMember(string serverId, string username, string discriminator) + { + User user = GetUser(username, discriminator); + return _members[user?.Id, 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(_servers[serverId], 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) 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); + }); + } + } + + public Task EditMember(Member member, bool? mute = null, bool? deaf = null, IEnumerable roles = null) + => EditMember(member?.ServerId, member?.UserId, mute, deaf, roles); + public Task EditMember(Server server, User user, bool? mute = null, bool? deaf = null, IEnumerable roles = null) + => EditMember(server?.Id, user?.Id, mute, deaf, roles); + public Task EditMember(Server server, string userId, bool? mute = null, bool? deaf = null, IEnumerable roles = null) + => EditMember(server?.Id, userId, mute, deaf, roles); + public Task EditMember(string serverId, User user, bool? mute = null, bool? deaf = null, IEnumerable roles = null) + => EditMember(serverId, user?.Id, mute, deaf, roles); + public Task EditMember(string serverId, string userId, bool? mute = null, bool? deaf = null, IEnumerable roles = null) + { + CheckReady(); + if (serverId == null) throw new NullReferenceException(nameof(serverId)); + if (userId == null) throw new NullReferenceException(nameof(userId)); + + var newRoles = CollectionHelper.FlattenRoles(roles); + return _api.EditMember(serverId, userId, mute: mute, deaf: deaf, roles: newRoles); + } + } +} \ No newline at end of file diff --git a/src/Discord.Net/DiscordClient.Messages.cs b/src/Discord.Net/DiscordClient.Messages.cs new file mode 100644 index 000000000..697233c85 --- /dev/null +++ b/src/Discord.Net/DiscordClient.Messages.cs @@ -0,0 +1,296 @@ +using Discord.API; +using Discord.Collections; +using Discord.Net; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Threading.Tasks; + +namespace Discord +{ + public partial class DiscordClient + { + public const int MaxMessageSize = 2000; + + /// Returns a collection of all messages this client has seen since logging in and currently has in cache. + public Messages Messages => _messages; + private readonly Messages _messages; + + public event EventHandler MessageCreated; + private void RaiseMessageCreated(Message msg) + { + if (MessageCreated != null) + RaiseEvent(nameof(MessageCreated), () => MessageCreated(this, new MessageEventArgs(msg))); + } + public event EventHandler MessageDeleted; + private void RaiseMessageDeleted(Message msg) + { + if (MessageDeleted != null) + RaiseEvent(nameof(MessageDeleted), () => MessageDeleted(this, new MessageEventArgs(msg))); + } + public event EventHandler MessageUpdated; + private void RaiseMessageUpdated(Message msg) + { + if (MessageUpdated != null) + RaiseEvent(nameof(MessageUpdated), () => MessageUpdated(this, new MessageEventArgs(msg))); + } + public event EventHandler MessageReadRemotely; + private void RaiseMessageReadRemotely(Message msg) + { + if (MessageReadRemotely != null) + RaiseEvent(nameof(MessageReadRemotely), () => MessageReadRemotely(this, new MessageEventArgs(msg))); + } + public event EventHandler MessageSent; + private void RaiseMessageSent(Message msg) + { + if (MessageSent != null) + RaiseEvent(nameof(MessageSent), () => MessageSent(this, new MessageEventArgs(msg))); + } + + /// Returns the message with the specified id, or null if none was found. + public Message GetMessage(string id) => _messages[id]; + + /// Sends a message to the provided channel. To include a mention, see the Mention static helper class. + public Task SendMessage(Channel channel, string text) + => SendMessage(channel, text, MentionHelper.GetUserIds(text), false); + /// Sends a message to the provided channel. To include a mention, see the Mention static helper class. + public Task SendMessage(string channelId, string text) + => SendMessage(_channels[channelId], text, MentionHelper.GetUserIds(text), false); + private async Task SendMessage(Channel channel, string text, IEnumerable mentionedUsers = null, bool isTextToSpeech = false) + { + CheckReady(); + if (channel == null) throw new ArgumentNullException(nameof(channel)); + if (text == null) throw new ArgumentNullException(nameof(text)); + var mentionedUserIds = CollectionHelper.FlattenUsers(mentionedUsers); + + int blockCount = (int)Math.Ceiling(text.Length / (double)MaxMessageSize); + Message[] result = new Message[blockCount]; + for (int i = 0; i < blockCount; i++) + { + int index = i * MaxMessageSize; + string blockText = text.Substring(index, Math.Min(2000, text.Length - index)); + var nonce = GenerateNonce(); + if (Config.UseMessageQueue) + { + var msg = _messages.GetOrAdd("nonce_" + nonce, channel.Id, CurrentUserId); + var currentUser = msg.User; + msg.Update(new MessageInfo + { + Content = blockText, + Timestamp = DateTime.UtcNow, + Author = new UserReference { Avatar = currentUser.AvatarId, Discriminator = currentUser.Discriminator, Id = CurrentUserId, Username = currentUser.Name }, + ChannelId = channel.Id, + IsTextToSpeech = isTextToSpeech + }); + msg.IsQueued = true; + msg.Nonce = nonce; + result[i] = msg; + _pendingMessages.Enqueue(msg); + } + else + { + var model = await _api.SendMessage(channel.Id, blockText, mentionedUserIds, nonce, isTextToSpeech).ConfigureAwait(false); + var msg = _messages.GetOrAdd(model.Id, channel.Id, model.Author.Id); + msg.Update(model); + RaiseMessageSent(msg); + result[i] = msg; + } + await Task.Delay(1000).ConfigureAwait(false); + } + return result; + } + + /// Sends a private message to the provided user. + public Task SendPrivateMessage(Member member, string text) + => SendPrivateMessage(member?.UserId, text); + /// Sends a private message to the provided user. + public Task SendPrivateMessage(User user, string text) + => SendPrivateMessage(user?.Id, text); + /// Sends a private message to the provided user. + public async Task SendPrivateMessage(string userId, string text) + { + var channel = await CreatePMChannel(userId).ConfigureAwait(false); + return await SendMessage(channel, text, new string[0]).ConfigureAwait(false); + } + + /// Sends a file to the provided channel. + public Task SendFile(Channel channel, string filePath) + => SendFile(channel?.Id, filePath); + /// Sends a file to the provided channel. + public Task SendFile(string channelId, string filePath) + { + CheckReady(); + if (channelId == null) throw new ArgumentNullException(nameof(channelId)); + if (filePath == null) throw new ArgumentNullException(nameof(filePath)); + + return _api.SendFile(channelId, filePath); + } + + /// Edits the provided message, changing only non-null attributes. + /// While not required, it is recommended to include a mention reference in the text (see Mention.User). + public Task EditMessage(Message message, string text = null, IEnumerable mentionedUsers = null) + => EditMessage(message?.ChannelId, message?.Id, text, mentionedUsers); + /// Edits the provided message, changing only non-null attributes. + /// While not required, it is recommended to include a mention reference in the text (see Mention.User). + public Task EditMessage(Channel channel, string messageId, string text = null, IEnumerable mentionedUsers = null) + => EditMessage(channel?.Id, messageId, text, mentionedUsers); + /// Edits the provided message, changing only non-null attributes. + /// While not required, it is recommended to include a mention reference in the text (see Mention.User). + public async Task EditMessage(string channelId, string messageId, string text = null, IEnumerable mentionedUsers = null) + { + CheckReady(); + if (channelId == null) throw new ArgumentNullException(nameof(channelId)); + if (messageId == null) throw new ArgumentNullException(nameof(messageId)); + var mentionedUserIds = CollectionHelper.FlattenUsers(mentionedUsers); + + if (text != null && text.Length > MaxMessageSize) + text = text.Substring(0, MaxMessageSize); + + var model = await _api.EditMessage(messageId, channelId, text, mentionedUserIds).ConfigureAwait(false); + var msg = _messages[messageId]; + if (msg != null) + msg.Update(model); + } + + /// Deletes the provided message. + public Task DeleteMessage(Message msg) + => DeleteMessage(msg?.ChannelId, msg?.Id); + /// Deletes the provided message. + public async Task DeleteMessage(string channelId, string msgId) + { + CheckReady(); + if (channelId == null) throw new ArgumentNullException(nameof(channelId)); + if (msgId == null) throw new ArgumentNullException(nameof(msgId)); + + try + { + await _api.DeleteMessage(msgId, channelId).ConfigureAwait(false); + _messages.TryRemove(msgId); + } + catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } + } + public async Task DeleteMessages(IEnumerable msgs) + { + CheckReady(); + if (msgs == null) throw new ArgumentNullException(nameof(msgs)); + + foreach (var msg in msgs) + { + try + { + await _api.DeleteMessage(msg.Id, msg.ChannelId).ConfigureAwait(false); + } + catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } + } + } + public async Task DeleteMessages(string channelId, IEnumerable msgIds) + { + CheckReady(); + if (msgIds == null) throw new ArgumentNullException(nameof(msgIds)); + + foreach (var msgId in msgIds) + { + try + { + await _api.DeleteMessage(msgId, channelId).ConfigureAwait(false); + } + catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } + } + } + + /// Downloads last count messages from the server, starting at beforeMessageId if it's provided. + public Task DownloadMessages(Channel channel, int count, string beforeMessageId = null, bool cache = true) + => DownloadMessages(channel.Id, count, beforeMessageId, cache); + /// Downloads last count messages from the server, starting at beforeMessageId if it's provided. + public async Task DownloadMessages(string channelId, int count, string beforeMessageId = null, bool cache = true) + { + CheckReady(); + if (channelId == null) throw new NullReferenceException(nameof(channelId)); + if (count < 0) throw new ArgumentOutOfRangeException(nameof(count)); + if (count == 0) return new Message[0]; + + Channel channel = _channels[channelId]; + if (channel != null && channel.Type == ChannelTypes.Text) + { + try + { + var msgs = await _api.GetMessages(channel.Id, count).ConfigureAwait(false); + return msgs.Select(x => + { + Message msg; + if (cache) + msg = _messages.GetOrAdd(x.Id, x.ChannelId, x.Author.Id); + else + msg = _messages[x.Id] ?? new Message(this, x.Id, x.ChannelId, x.Author.Id); + if (msg != null) + { + msg.Update(x); + if (Config.TrackActivity) + { + /*if (channel.IsPrivate) + { + var user = msg.User; + if (user != null) + user.UpdateActivity(msg.EditedTimestamp ?? msg.Timestamp); + } + else*/ + if (!channel.IsPrivate) + { + var member = msg.Member; + if (member != null) + member.UpdateActivity(msg.EditedTimestamp ?? msg.Timestamp); + } + } + } + return msg; + }) + .ToArray(); + } + catch (HttpException) { } //Bad Permissions? + } + return null; + } + + private Task MessageQueueLoop() + { + var cancelToken = CancelToken; + int interval = Config.MessageQueueInterval; + + return Task.Run(async () => + { + Message msg; + while (!cancelToken.IsCancellationRequested) + { + while (_pendingMessages.TryDequeue(out msg)) + { + bool hasFailed = false; + SendMessageResponse response = null; + try + { + response = await _api.SendMessage(msg.ChannelId, msg.RawText, msg.MentionIds, msg.Nonce, msg.IsTTS).ConfigureAwait(false); + } + catch (WebException) { break; } + catch (HttpException) { hasFailed = true; } + + if (!hasFailed) + { + _messages.Remap(msg.Id, response.Id); + msg.Id = response.Id; + msg.Update(response); + } + msg.IsQueued = false; + msg.HasFailed = hasFailed; + RaiseMessageSent(msg); + } + await Task.Delay(interval).ConfigureAwait(false); + } + }); + } + private string GenerateNonce() + { + lock (_rand) + return _rand.Next().ToString(); + } + } +} \ No newline at end of file diff --git a/src/Discord.Net/DiscordClient.Permissions.cs b/src/Discord.Net/DiscordClient.Permissions.cs new file mode 100644 index 000000000..e6b46b8ac --- /dev/null +++ b/src/Discord.Net/DiscordClient.Permissions.cs @@ -0,0 +1,132 @@ +using Discord.Net; +using System; +using System.Linq; +using System.Net; +using System.Threading.Tasks; + +namespace Discord +{ + public partial class DiscordClient + { + public Task SetChannelUserPermissions(Channel channel, Member member, PackedChannelPermissions allow = null, PackedChannelPermissions deny = null) + => SetChannelPermissions(channel, member?.UserId, PermissionTarget.Member, allow, deny); + public Task SetChannelUserPermissions(string channelId, Member member, PackedChannelPermissions allow = null, PackedChannelPermissions deny = null) + => SetChannelPermissions(_channels[channelId], member?.UserId, PermissionTarget.Member, allow, deny); + public Task SetChannelUserPermissions(Channel channel, User user, PackedChannelPermissions allow = null, PackedChannelPermissions deny = null) + => SetChannelPermissions(channel, user?.Id, PermissionTarget.Member, allow, deny); + public Task SetChannelUserPermissions(string channelId, User user, PackedChannelPermissions allow = null, PackedChannelPermissions deny = null) + => SetChannelPermissions(_channels[channelId], user?.Id, PermissionTarget.Member, allow, deny); + public Task SetChannelUserPermissions(Channel channel, string userId, PackedChannelPermissions allow = null, PackedChannelPermissions deny = null) + => SetChannelPermissions(channel, userId, PermissionTarget.Member, allow, deny); + public Task SetChannelUserPermissions(string channelId, string userId, PackedChannelPermissions allow = null, PackedChannelPermissions deny = null) + => SetChannelPermissions(_channels[channelId], userId, PermissionTarget.Member, allow, deny); + + public Task SetChannelRolePermissions(Channel channel, Role role, PackedChannelPermissions allow = null, PackedChannelPermissions deny = null) + => SetChannelPermissions(channel, role?.Id, PermissionTarget.Role, allow, deny); + public Task SetChannelRolePermissions(string channelId, Role role, PackedChannelPermissions allow = null, PackedChannelPermissions deny = null) + => SetChannelPermissions(_channels[channelId], role?.Id, PermissionTarget.Role, allow, deny); + public Task SetChannelRolePermissions(Channel channel, string userId, PackedChannelPermissions allow = null, PackedChannelPermissions deny = null) + => SetChannelPermissions(channel, userId, PermissionTarget.Role, allow, deny); + public Task SetChannelRolePermissions(string channelId, string userId, PackedChannelPermissions allow = null, PackedChannelPermissions deny = null) + => SetChannelPermissions(_channels[channelId], userId, PermissionTarget.Role, allow, deny); + + private async Task SetChannelPermissions(Channel channel, string targetId, string targetType, PackedChannelPermissions allow = null, PackedChannelPermissions deny = null) + { + CheckReady(); + if (channel == null) throw new NullReferenceException(nameof(channel)); + if (targetId == null) throw new NullReferenceException(nameof(targetId)); + if (targetType == null) throw new NullReferenceException(nameof(targetType)); + + uint allowValue = allow?.RawValue ?? 0; + uint denyValue = deny?.RawValue ?? 0; + bool changed = false; + + var perms = channel.PermissionOverwrites.Where(x => x.TargetType != targetType || x.TargetId != targetId).FirstOrDefault(); + if (allowValue != 0 || denyValue != 0) + { + await _api.SetChannelPermissions(channel.Id, targetId, targetType, allowValue, denyValue); + if (perms != null) + { + perms.Allow.SetRawValueInternal(allowValue); + perms.Deny.SetRawValueInternal(denyValue); + } + else + { + var oldPerms = channel._permissionOverwrites; + var newPerms = new Channel.PermissionOverwrite[oldPerms.Length + 1]; + Array.Copy(oldPerms, newPerms, oldPerms.Length); + newPerms[oldPerms.Length] = new Channel.PermissionOverwrite(targetType, targetId, allowValue, denyValue); + channel._permissionOverwrites = newPerms; + } + changed = true; + } + else + { + try + { + await _api.DeleteChannelPermissions(channel.Id, targetId); + if (perms != null) + { + channel._permissionOverwrites = channel.PermissionOverwrites.Where(x => x.TargetType != targetType || x.TargetId != targetId).ToArray(); + changed = true; + } + } + catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } + } + + if (changed) + { + if (targetType == PermissionTarget.Role) + channel.InvalidatePermissionsCache(); + else if (targetType == PermissionTarget.Member) + channel.InvalidatePermissionsCache(targetId); + } + } + + public Task RemoveChannelUserPermissions(Channel channel, Member member) + => RemoveChannelPermissions(channel, member?.UserId, PermissionTarget.Member); + public Task RemoveChannelUserPermissions(string channelId, Member member) + => RemoveChannelPermissions(_channels[channelId], member?.UserId, PermissionTarget.Member); + public Task RemoveChannelUserPermissions(Channel channel, User user) + => RemoveChannelPermissions(channel, user?.Id, PermissionTarget.Member); + public Task RemoveChannelUserPermissions(string channelId, User user) + => RemoveChannelPermissions(_channels[channelId], user?.Id, PermissionTarget.Member); + public Task RemoveChannelUserPermissions(Channel channel, string userId) + => RemoveChannelPermissions(channel, userId, PermissionTarget.Member); + public Task RemoveChannelUserPermissions(string channelId, string userId) + => RemoveChannelPermissions(_channels[channelId], userId, PermissionTarget.Member); + + public Task RemoveChannelRolePermissions(Channel channel, Role role) + => RemoveChannelPermissions(channel, role?.Id, PermissionTarget.Role); + public Task RemoveChannelRolePermissions(string channelId, Role role) + => RemoveChannelPermissions(_channels[channelId], role?.Id, PermissionTarget.Role); + public Task RemoveChannelRolePermissions(Channel channel, string roleId) + => RemoveChannelPermissions(channel, roleId, PermissionTarget.Role); + public Task RemoveChannelRolePermissions(string channelId, string roleId) + => RemoveChannelPermissions(_channels[channelId], roleId, PermissionTarget.Role); + + private async Task RemoveChannelPermissions(Channel channel, string userOrRoleId, string idType) + { + CheckReady(); + if (channel == null) throw new NullReferenceException(nameof(channel)); + if (userOrRoleId == null) throw new NullReferenceException(nameof(userOrRoleId)); + if (idType == null) throw new NullReferenceException(nameof(idType)); + + try + { + var perms = channel.PermissionOverwrites.Where(x => x.TargetType != idType || x.TargetId != userOrRoleId).FirstOrDefault(); + await _api.DeleteChannelPermissions(channel.Id, userOrRoleId).ConfigureAwait(false); + if (perms != null) + { + channel.PermissionOverwrites.Where(x => x.TargetType != idType || x.TargetId != userOrRoleId).ToArray(); + + if (idType == PermissionTarget.Role) + channel.InvalidatePermissionsCache(); + else if (idType == PermissionTarget.Member) + channel.InvalidatePermissionsCache(userOrRoleId); + } + } + catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } + } + } +} \ No newline at end of file diff --git a/src/Discord.Net/DiscordClient.Roles.cs b/src/Discord.Net/DiscordClient.Roles.cs new file mode 100644 index 000000000..4fbdd3cd5 --- /dev/null +++ b/src/Discord.Net/DiscordClient.Roles.cs @@ -0,0 +1,149 @@ +using Discord.Collections; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Discord +{ + public partial class DiscordClient + { + public event EventHandler RoleCreated; + private void RaiseRoleCreated(Role role) + { + if (RoleCreated != null) + RaiseEvent(nameof(RoleCreated), () => RoleCreated(this, new RoleEventArgs(role))); + } + public event EventHandler RoleUpdated; + private void RaiseRoleDeleted(Role role) + { + if (RoleDeleted != null) + RaiseEvent(nameof(RoleDeleted), () => RoleDeleted(this, new RoleEventArgs(role))); + } + public event EventHandler RoleDeleted; + private void RaiseRoleUpdated(Role role) + { + if (RoleUpdated != null) + RaiseEvent(nameof(RoleUpdated), () => RoleUpdated(this, new RoleEventArgs(role))); + } + + /// Returns a collection of all role-server pairs this client can currently see. + public Roles Roles => _roles; + private readonly Roles _roles; + + /// Returns the role with the specified id, or null if none was found. + 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); + /// 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) throw new ArgumentNullException(nameof(serverId)); + if (name == null) throw new ArgumentNullException(nameof(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)); + } + } + + /// Note: due to current API limitations, the created role cannot be returned. + public Task CreateRole(Server server, string name) + => CreateRole(server?.Id, name); + /// Note: due to current API limitations, the created role cannot be returned. + public async Task CreateRole(string serverId, string name) + { + CheckReady(); + if (serverId == null) throw new NullReferenceException(nameof(serverId)); + + var response = await _api.CreateRole(serverId).ConfigureAwait(false); + var role = _roles.GetOrAdd(response.Id, serverId); + role.Update(response); + + await EditRole(role, name: name); + + return role; + } + + public Task EditRole(Role role, string name = null, PackedServerPermissions permissions = null, PackedColor color = null, bool? hoist = null, int? position = null) + => EditRole(role.ServerId, role.Id, name: name, permissions: permissions, color: color, hoist: hoist, position: position); + public async Task EditRole(string serverId, string roleId, string name = null, PackedServerPermissions permissions = null, PackedColor color = null, bool? hoist = null, int? position = null) + { + CheckReady(); + if (serverId == null) throw new NullReferenceException(nameof(serverId)); + if (roleId == null) throw new NullReferenceException(nameof(roleId)); + + var response = await _api.EditRole(serverId, roleId, name: name, + permissions: permissions?.RawValue, color: color?.RawValue, hoist: hoist); + + var role = _roles[response.Id]; + if (role != null) + role.Update(response); + + if (position != null) + { + int oldPos = role.Position; + int newPos = position.Value; + int minPos; + Role[] roles = role.Server.Roles.OrderBy(x => x.Position).ToArray(); + + if (oldPos < newPos) //Moving Down + { + minPos = oldPos; + for (int i = oldPos; i < newPos; i++) + roles[i] = roles[i + 1]; + roles[newPos] = role; + } + else //(oldPos > newPos) Moving Up + { + minPos = newPos; + for (int i = oldPos; i > newPos; i--) + roles[i] = roles[i - 1]; + roles[newPos] = role; + } + await _api.ReorderRoles(role.ServerId, roles.Skip(minPos).Select(x => x.Id), minPos); + } + } + + public Task DeleteRole(Role role) + => DeleteRole(role?.ServerId, role?.Id); + public Task DeleteRole(string serverId, string roleId) + { + CheckReady(); + if (serverId == null) throw new NullReferenceException(nameof(serverId)); + if (roleId == null) throw new NullReferenceException(nameof(roleId)); + + return _api.DeleteRole(serverId, roleId); + } + + public Task ReorderRoles(Server server, IEnumerable roles, int startPos = 0) + => ReorderChannels(server.Id, roles, startPos); + public Task ReorderRoles(string serverId, IEnumerable roles, int startPos = 0) + { + if (serverId == null) throw new ArgumentNullException(nameof(serverId)); + if (roles == null) throw new ArgumentNullException(nameof(roles)); + if (startPos < 0) throw new ArgumentOutOfRangeException(nameof(startPos), "startPos must be a positive integer."); + + var roleIds = roles.Select(x => + { + if (x is string) + return x as string; + else if (x is Role) + return (x as Role).Id; + else + throw new ArgumentException("Channels must be a collection of string or Role.", nameof(roles)); + }); + + return _api.ReorderRoles(serverId, roleIds, startPos); + } + } +} \ No newline at end of file diff --git a/src/Discord.Net/DiscordClient.Servers.cs b/src/Discord.Net/DiscordClient.Servers.cs new file mode 100644 index 000000000..ae3c6a5f9 --- /dev/null +++ b/src/Discord.Net/DiscordClient.Servers.cs @@ -0,0 +1,102 @@ +using Discord.Net; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Threading.Tasks; + +namespace Discord +{ + public sealed class ServerEventArgs : EventArgs + { + public Server Server { get; } + public string ServerId => Server.Id; + + internal ServerEventArgs(Server server) { Server = server; } + } + + public partial class DiscordClient + { + public event EventHandler ServerCreated; + private void RaiseServerCreated(Server server) + { + if (ServerCreated != null) + RaiseEvent(nameof(ServerCreated), () => ServerCreated(this, new ServerEventArgs(server))); + } + public event EventHandler ServerDestroyed; + private void RaiseServerDestroyed(Server server) + { + if (ServerDestroyed != null) + RaiseEvent(nameof(ServerDestroyed), () => ServerDestroyed(this, new ServerEventArgs(server))); + } + public event EventHandler ServerUpdated; + private void RaiseServerUpdated(Server server) + { + if (ServerUpdated != null) + RaiseEvent(nameof(ServerUpdated), () => ServerUpdated(this, new ServerEventArgs(server))); + } + public event EventHandler ServerUnavailable; + private void RaiseServerUnavailable(Server server) + { + if (ServerUnavailable != null) + RaiseEvent(nameof(ServerUnavailable), () => ServerUnavailable(this, new ServerEventArgs(server))); + } + public event EventHandler ServerAvailable; + private void RaiseServerAvailable(Server server) + { + if (ServerAvailable != null) + RaiseEvent(nameof(ServerAvailable), () => ServerAvailable(this, new ServerEventArgs(server))); + } + + /// 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) throw new ArgumentNullException(nameof(name)); + + return _servers.Where(x => string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase)); + } + + /// Creates a new server with the provided name and region (see Regions). + public async Task CreateServer(string name, string region) + { + CheckReady(); + if (name == null) throw new ArgumentNullException(nameof(name)); + if (region == null) throw new ArgumentNullException(nameof(region)); + + var response = await _api.CreateServer(name, region).ConfigureAwait(false); + var server = _servers.GetOrAdd(response.Id); + server.Update(response); + return server; + } + + /// Edits the provided server, changing only non-null attributes. + public Task EditServer(string serverId, string name = null, string region = null, ImageType iconType = ImageType.Png, byte[] icon = null) + => EditServer(_servers[serverId], name: name, region: region, iconType: iconType, icon: icon); + /// Edits the provided server, changing only non-null attributes. + public async Task EditServer(Server server, string name = null, string region = null, ImageType iconType = ImageType.Png, byte[] icon = null) + { + CheckReady(); + if (server == null) throw new ArgumentNullException(nameof(server)); + + var response = await _api.EditServer(server.Id, name: name ?? server.Name, region: region, iconType: iconType, icon: icon); + server.Update(response); + } + + /// Leaves the provided server, destroying it if you are the owner. + public Task LeaveServer(Server server) + => LeaveServer(server?.Id); + /// Leaves the provided server, destroying it if you are the owner. + public async Task LeaveServer(string serverId) + { + CheckReady(); + if (serverId == null) throw new ArgumentNullException(nameof(serverId)); + + try { await _api.LeaveServer(serverId).ConfigureAwait(false); } + catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } + return _servers.TryRemove(serverId); + } + } +} \ No newline at end of file diff --git a/src/Discord.Net/DiscordClient.Users.cs b/src/Discord.Net/DiscordClient.Users.cs new file mode 100644 index 000000000..829f60c80 --- /dev/null +++ b/src/Discord.Net/DiscordClient.Users.cs @@ -0,0 +1,130 @@ +using Discord.API; +using Discord.Collections; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Discord +{ + public sealed class UserEventArgs : EventArgs + { + public User User { get; } + public string UserId => User.Id; + + internal UserEventArgs(User user) { User = user; } + } + + public partial class DiscordClient + { + public event EventHandler UserAdded; + private void RaiseUserAdded(Member member) + { + if (UserAdded != null) + RaiseEvent(nameof(UserAdded), () => UserAdded(this, new MemberEventArgs(member))); + } + public event EventHandler UserRemoved; + private void RaiseUserRemoved(Member member) + { + if (UserRemoved != null) + RaiseEvent(nameof(UserRemoved), () => UserRemoved(this, new MemberEventArgs(member))); + } + public event EventHandler UserUpdated; + private void RaiseUserUpdated(User user) + { + if (UserUpdated != null) + RaiseEvent(nameof(UserUpdated), () => UserUpdated(this, new UserEventArgs(user))); + } + public event EventHandler MemberUpdated; + private void RaiseMemberUpdated(Member member) + { + if (MemberUpdated != null) + RaiseEvent(nameof(MemberUpdated), () => MemberUpdated(this, new MemberEventArgs(member))); + } + public event EventHandler UserPresenceUpdated; + private void RaiseUserPresenceUpdated(Member member) + { + if (UserPresenceUpdated != null) + RaiseEvent(nameof(UserPresenceUpdated), () => UserPresenceUpdated(this, new MemberEventArgs(member))); + } + public event EventHandler UserVoiceStateUpdated; + private void RaiseUserVoiceStateUpdated(Member member) + { + if (UserVoiceStateUpdated != null) + RaiseEvent(nameof(UserVoiceStateUpdated), () => UserVoiceStateUpdated(this, new MemberEventArgs(member))); + } + + /// Returns a collection of all users this client can currently see. + public Users Users => _users; + private readonly Users _users; + /// Returns the current logged-in user. + public User CurrentUser => _currentUser; + private User _currentUser; + + /// 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 username, string discriminator) + { + if (username == null) throw new ArgumentNullException(nameof(username)); + if (discriminator == null) throw new ArgumentNullException(nameof(discriminator)); + + if (username.StartsWith("@")) + username = username.Substring(1); + + return _users.Where(x => + string.Equals(x.Name, username, 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) throw new ArgumentNullException(nameof(name)); + + 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)); + } + } + + public Task EditProfile(string currentPassword = "", + string username = null, string email = null, string password = null, + ImageType avatarType = ImageType.Png, byte[] avatar = null) + { + if (currentPassword == null) throw new ArgumentNullException(nameof(currentPassword)); + + return _api.EditUser(currentPassword: currentPassword, username: username ?? _currentUser?.Name, email: email ?? _currentUser?.Email, password: password, + avatarType: avatarType, avatar: avatar); + } + + public Task SetStatus(string status) + { + if (status != UserStatus.Online && status != UserStatus.Idle) + throw new ArgumentException($"Invalid status, must be {UserStatus.Online} or {UserStatus.Idle}"); + _status = status; + return SendStatus(); + } + public Task SetGame(int? gameId) + { + _gameId = gameId; + return SendStatus(); + } + private Task SendStatus() + { + _dataSocket.SendStatus(_status == UserStatus.Idle ? EpochTime.GetMilliseconds() - (10 * 60 * 1000) : (ulong?)null, _gameId); + return TaskHelper.CompletedTask; + } + } +} \ No newline at end of file diff --git a/src/Discord.Net/DiscordClient.cs b/src/Discord.Net/DiscordClient.cs index 6fca3fdbe..6244b3962 100644 --- a/src/Discord.Net/DiscordClient.cs +++ b/src/Discord.Net/DiscordClient.cs @@ -13,6 +13,54 @@ using System.Threading.Tasks; namespace Discord { + public sealed class MessageEventArgs : EventArgs + { + public Message Message { get; } + public string MessageId => Message.Id; + public Member Member => Message.Member; + public Channel Channel => Message.Channel; + public string ChannelId => Message.ChannelId; + public Server Server => Message.Server; + public string ServerId => Message.ServerId; + public User User => Member.User; + public string UserId => Message.UserId; + + internal MessageEventArgs(Message msg) { Message = msg; } + } + public sealed class RoleEventArgs : EventArgs + { + public Role Role { get; } + public string RoleId => Role.Id; + public Server Server => Role.Server; + public string ServerId => Role.ServerId; + + internal RoleEventArgs(Role role) { Role = role; } + } + public sealed class BanEventArgs : EventArgs + { + public User User { get; } + public string UserId { get; } + public Server Server { get; } + public string ServerId => Server.Id; + + internal BanEventArgs(User user, string userId, Server server) + { + User = user; + UserId = userId; + Server = server; + } + } + public sealed class MemberEventArgs : EventArgs + { + public Member Member { get; } + public User User => Member.User; + public string UserId => Member.UserId; + public Server Server => Member.Server; + public string ServerId => Member.ServerId; + + internal MemberEventArgs(Member member) { Member = member; } + } + /// Provides a connection to the DiscordApp service. public partial class DiscordClient : DiscordWSClient { @@ -28,29 +76,10 @@ namespace Discord public new DiscordClientConfig Config => _config as DiscordClientConfig; - /// Returns the current logged-in user. - public User CurrentUser => _currentUser; - private User _currentUser; - - /// Returns a collection of all channels this client is a member of. - public Channels Channels => _channels; - private readonly Channels _channels; - /// Returns a collection of all user-server pairs this client can currently see. - public Members Members => _members; - private readonly Members _members; - /// Returns a collection of all messages this client has seen since logging in and currently has in cache. - public Messages Messages => _messages; - private readonly Messages _messages; - //TODO: Do we need the roles cache? - /// Returns a collection of all role-server pairs this client can currently see. - public Roles Roles => _roles; - private readonly Roles _roles; + /// Returns a collection of all servers this client is a member of. public Servers Servers => _servers; private readonly Servers _servers; - /// Returns a collection of all users this client can currently see. - public Users Users => _users; - private readonly Users _users; /// Initializes a new instance of the DiscordClient class. public DiscordClient(DiscordClientConfig config = null) @@ -69,8 +98,8 @@ namespace Discord _messages = new Messages(this, cacheLock); _roles = new Roles(this, cacheLock); _servers = new Servers(this, cacheLock); - _users = new Users(this, cacheLock); _status = UserStatus.Online; + _users = new Users(this, cacheLock); this.Connected += async (s, e) => { @@ -321,47 +350,6 @@ namespace Discord return base.GetTasks(); } - private Task MessageQueueLoop() - { - var cancelToken = CancelToken; - int interval = Config.MessageQueueInterval; - - return Task.Run(async () => - { - Message msg; - while (!cancelToken.IsCancellationRequested) - { - while (_pendingMessages.TryDequeue(out msg)) - { - bool hasFailed = false; - SendMessageResponse response = null; - try - { - response = await _api.SendMessage(msg.ChannelId, msg.RawText, msg.MentionIds, msg.Nonce, msg.IsTTS).ConfigureAwait(false); - } - catch (WebException) { break; } - catch (HttpException) { hasFailed = true; } - - if (!hasFailed) - { - _messages.Remap(msg.Id, response.Id); - msg.Id = response.Id; - msg.Update(response); - } - msg.IsQueued = false; - msg.HasFailed = hasFailed; - RaiseMessageSent(msg); - } - await Task.Delay(interval).ConfigureAwait(false); - } - }); - } - private string GenerateNonce() - { - lock (_rand) - return _rand.Next().ToString(); - } - internal override async Task OnReceivedEvent(WebSocketEventEventArgs e) { try @@ -656,7 +644,7 @@ namespace Discord { var data = e.Payload.ToObject(_serializer); var channel = _channels[data.ChannelId]; - var user = _users[data.UserId]; + var user = _members[data.UserId, channel.ServerId]; if (user != null) { From 4b79ec1df8acfe74561a7f17e58d5c4e28a94548 Mon Sep 17 00:00:00 2001 From: RogueException Date: Thu, 22 Oct 2015 01:34:04 -0300 Subject: [PATCH 039/109] Cleaned up the collection classes. --- src/Discord.Net/Collections/AsyncCollection.cs | 21 ++++++++++-------- src/Discord.Net/Collections/Channels.cs | 21 ++++++------------ src/Discord.Net/Collections/Members.cs | 30 ++++++++++---------------- src/Discord.Net/Collections/Messages.cs | 24 ++++++--------------- src/Discord.Net/Collections/Roles.cs | 21 +++++------------- src/Discord.Net/Collections/Servers.cs | 10 ++++----- src/Discord.Net/Collections/Users.cs | 23 ++++---------------- src/Discord.Net/DiscordClient.Channels.cs | 3 +-- src/Discord.Net/DiscordClient.Invites.cs | 28 ++++++++++++------------ src/Discord.Net/DiscordClient.Members.cs | 5 ++--- src/Discord.Net/DiscordClient.Messages.cs | 7 +++--- src/Discord.Net/DiscordClient.Roles.cs | 3 +-- src/Discord.Net/DiscordClient.Servers.cs | 7 ++++++ src/Discord.Net/DiscordClient.Users.cs | 3 ++- src/Discord.Net/DiscordClient.cs | 7 +----- test/Discord.Net.Tests/Tests.cs | 12 +++++------ 16 files changed, 85 insertions(+), 140 deletions(-) diff --git a/src/Discord.Net/Collections/AsyncCollection.cs b/src/Discord.Net/Collections/AsyncCollection.cs index 9d4a2d79b..63eb5513b 100644 --- a/src/Discord.Net/Collections/AsyncCollection.cs +++ b/src/Discord.Net/Collections/AsyncCollection.cs @@ -60,15 +60,18 @@ namespace Discord.Collections _dictionary = new ConcurrentDictionary(); } - protected TValue Get(string key) + public TValue this[string key] { - if (key == null) - return null; + get + { + if (key == null) + return null; - TValue result; - if (!_dictionary.TryGetValue(key, out result)) - return null; - return result; + TValue result; + if (!_dictionary.TryGetValue(key, out result)) + return null; + return result; + } } protected TValue GetOrAdd(string key, Func createFunc) { @@ -88,7 +91,7 @@ namespace Discord.Collections } return result; } - protected TValue TryRemove(string key) + public TValue TryRemove(string key) { if (_dictionary.ContainsKey(key)) { @@ -104,7 +107,7 @@ namespace Discord.Collections } return null; } - protected TValue Remap(string oldKey, string newKey) + public TValue Remap(string oldKey, string newKey) { if (_dictionary.ContainsKey(oldKey)) { diff --git a/src/Discord.Net/Collections/Channels.cs b/src/Discord.Net/Collections/Channels.cs index a2fb81b24..ec9d28c1c 100644 --- a/src/Discord.Net/Collections/Channels.cs +++ b/src/Discord.Net/Collections/Channels.cs @@ -2,13 +2,13 @@ namespace Discord.Collections { - public sealed class Channels : AsyncCollection - { - internal Channels(DiscordClient client, object writerLock) + internal sealed class Channels : AsyncCollection + { + public Channels(DiscordClient client, object writerLock) : base(client, writerLock) { } - - 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); + + public Channel GetOrAdd(string id, string serverId, string recipientId = null) + => GetOrAdd(id, () => new Channel(_client, id, serverId, recipientId)); protected override void OnCreated(Channel item) { @@ -43,14 +43,5 @@ namespace Discord.Collections } } } - - internal Channel this[string id] - { - get - { - if (id == null) throw new ArgumentNullException(nameof(id)); - return Get(id); - } - } } } diff --git a/src/Discord.Net/Collections/Members.cs b/src/Discord.Net/Collections/Members.cs index 363a5d37c..7801b8aa3 100644 --- a/src/Discord.Net/Collections/Members.cs +++ b/src/Discord.Net/Collections/Members.cs @@ -1,16 +1,18 @@ -using System; - -namespace Discord.Collections +namespace Discord.Collections { - public sealed class Members : AsyncCollection + internal sealed class Members : AsyncCollection { - internal Members(DiscordClient client, object writerLock) + public Members(DiscordClient client, object writerLock) : base(client, writerLock) { } + private string GetKey(string userId, string serverId) + => serverId + '_' + userId; - 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)); + public Member this[string userId, string serverId] + => this[GetKey(userId, serverId)]; + public Member GetOrAdd(string userId, string serverId) + => GetOrAdd(GetKey(userId, serverId), () => new Member(_client, userId, serverId)); + public Member TryRemove(string userId, string serverId) + => TryRemove(GetKey(userId, serverId)); protected override void OnCreated(Member item) { @@ -36,15 +38,5 @@ namespace Discord.Collections 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)); - } - } } } diff --git a/src/Discord.Net/Collections/Messages.cs b/src/Discord.Net/Collections/Messages.cs index 16b2d32a7..5e2b38ce3 100644 --- a/src/Discord.Net/Collections/Messages.cs +++ b/src/Discord.Net/Collections/Messages.cs @@ -1,15 +1,12 @@ -using System; - -namespace Discord.Collections +namespace Discord.Collections { - public sealed class Messages : AsyncCollection + internal sealed class Messages : AsyncCollection { - internal Messages(DiscordClient client, object writerLock) + public Messages(DiscordClient client, object writerLock) : base(client, writerLock) { } - - internal Message GetOrAdd(string id, string channelId, string userId) => GetOrAdd(id, () => new Message(_client, id, channelId, userId)); - internal new Message TryRemove(string id) => base.TryRemove(id); - internal new Message Remap(string oldKey, string newKey) => base.Remap(oldKey, newKey); + + public Message GetOrAdd(string id, string channelId, string userId) + => GetOrAdd(id, () => new Message(_client, id, channelId, userId)); protected override void OnCreated(Message item) { @@ -25,14 +22,5 @@ namespace Discord.Collections if (user != null) user.RemoveRef(); } - - internal Message this[string id] - { - get - { - if (id == null) throw new ArgumentNullException(nameof(id)); - return Get(id); - } - } } } diff --git a/src/Discord.Net/Collections/Roles.cs b/src/Discord.Net/Collections/Roles.cs index fd83a7c1e..4bc4bd1d8 100644 --- a/src/Discord.Net/Collections/Roles.cs +++ b/src/Discord.Net/Collections/Roles.cs @@ -1,14 +1,12 @@ -using System; - -namespace Discord.Collections +namespace Discord.Collections { - public sealed class Roles : AsyncCollection + internal sealed class Roles : AsyncCollection { - internal Roles(DiscordClient client, object writerLock) + public Roles(DiscordClient client, object writerLock) : base(client, writerLock) { } - internal Role GetOrAdd(string id, string serverId) => GetOrAdd(id, () => new Role(_client, id, serverId)); - internal new Role TryRemove(string id) => base.TryRemove(id); + public Role GetOrAdd(string id, string serverId) + => GetOrAdd(id, () => new Role(_client, id, serverId)); protected override void OnCreated(Role item) { @@ -20,14 +18,5 @@ namespace Discord.Collections if (server != null) item.Server.RemoveRole(item.Id); } - - internal Role this[string id] - { - get - { - if (id == null) throw new ArgumentNullException(nameof(id)); - return Get(id); - } - } } } diff --git a/src/Discord.Net/Collections/Servers.cs b/src/Discord.Net/Collections/Servers.cs index ddd44071a..977c3b99f 100644 --- a/src/Discord.Net/Collections/Servers.cs +++ b/src/Discord.Net/Collections/Servers.cs @@ -4,13 +4,13 @@ using System.Linq; namespace Discord.Collections { - public sealed class Servers : AsyncCollection + internal sealed class Servers : AsyncCollection { - internal Servers(DiscordClient client, object writerLock) + public Servers(DiscordClient client, object writerLock) : base(client, writerLock) { } - internal Server GetOrAdd(string id) => base.GetOrAdd(id, () => new Server(_client, id)); - internal new Server TryRemove(string id) => base.TryRemove(id); + public Server GetOrAdd(string id) + => base.GetOrAdd(id, () => new Server(_client, id)); protected override void OnRemoved(Server item) { @@ -26,7 +26,5 @@ namespace Discord.Collections foreach (var roleId in item.RoleIds) roles.TryRemove(roleId); } - - internal Server this[string id] => Get(id); } } diff --git a/src/Discord.Net/Collections/Users.cs b/src/Discord.Net/Collections/Users.cs index 90317b65b..31d12c1e2 100644 --- a/src/Discord.Net/Collections/Users.cs +++ b/src/Discord.Net/Collections/Users.cs @@ -1,25 +1,10 @@ -using System; - -namespace Discord.Collections +namespace Discord.Collections { - public sealed class Users : AsyncCollection + internal sealed class Users : AsyncCollection { - internal Users(DiscordClient client, object writerLock) + public Users(DiscordClient client, object writerLock) : base(client, writerLock) { } - 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) { } - - internal User this[string id] - { - get - { - if (id == null) throw new ArgumentNullException(nameof(id)); - return Get(id); - } - } + public User GetOrAdd(string id) => GetOrAdd(id, () => new User(_client, id)); } } diff --git a/src/Discord.Net/DiscordClient.Channels.cs b/src/Discord.Net/DiscordClient.Channels.cs index fa1902d7f..8b98cc395 100644 --- a/src/Discord.Net/DiscordClient.Channels.cs +++ b/src/Discord.Net/DiscordClient.Channels.cs @@ -20,8 +20,7 @@ namespace Discord public partial class DiscordClient { - /// Returns a collection of all channels this client is a member of. - public Channels Channels => _channels; + internal Channels Channels => _channels; private readonly Channels _channels; public event EventHandler ChannelCreated; diff --git a/src/Discord.Net/DiscordClient.Invites.cs b/src/Discord.Net/DiscordClient.Invites.cs index 115d9b7ce..cda080e19 100644 --- a/src/Discord.Net/DiscordClient.Invites.cs +++ b/src/Discord.Net/DiscordClient.Invites.cs @@ -7,6 +7,19 @@ namespace Discord { public partial class DiscordClient { + /// 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 inviteIdOrXkcd) + { + CheckReady(); + if (inviteIdOrXkcd == null) throw new ArgumentNullException(nameof(inviteIdOrXkcd)); + + var response = await _api.GetInvite(inviteIdOrXkcd).ConfigureAwait(false); + var invite = new Invite(this, response.Code, response.XkcdPass, response.Guild.Id); + invite.Update(response); + return invite; + } + /// Creates a new invite to the default channel of the provided server. /// Time (in seconds) until the invite expires. Set to 0 to never expire. /// If true, a user accepting this invite will be kicked from the server after closing their client. @@ -53,20 +66,7 @@ namespace Discord } catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } } - - /// 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 inviteIdOrXkcd) - { - CheckReady(); - if (inviteIdOrXkcd == null) throw new ArgumentNullException(nameof(inviteIdOrXkcd)); - - var response = await _api.GetInvite(inviteIdOrXkcd).ConfigureAwait(false); - var invite = new Invite(this, response.Code, response.XkcdPass, response.Guild.Id); - invite.Update(response); - return invite; - } - + /// Accepts the provided invite. public Task AcceptInvite(Invite invite) { diff --git a/src/Discord.Net/DiscordClient.Members.cs b/src/Discord.Net/DiscordClient.Members.cs index 562deb744..235eb704f 100644 --- a/src/Discord.Net/DiscordClient.Members.cs +++ b/src/Discord.Net/DiscordClient.Members.cs @@ -55,9 +55,8 @@ namespace Discord if (UserIsSpeaking != null) RaiseEvent(nameof(UserIsSpeaking), () => UserIsSpeaking(this, new MemberIsSpeakingEventArgs(member, isSpeaking))); } - - /// Returns a collection of all user-server pairs this client can currently see. - public Members Members => _members; + + internal Members Members => _members; private readonly Members _members; /// Returns the user with the specified id, along with their server-specific data, or null if none was found. diff --git a/src/Discord.Net/DiscordClient.Messages.cs b/src/Discord.Net/DiscordClient.Messages.cs index 697233c85..fe71dc233 100644 --- a/src/Discord.Net/DiscordClient.Messages.cs +++ b/src/Discord.Net/DiscordClient.Messages.cs @@ -13,10 +13,6 @@ namespace Discord { public const int MaxMessageSize = 2000; - /// Returns a collection of all messages this client has seen since logging in and currently has in cache. - public Messages Messages => _messages; - private readonly Messages _messages; - public event EventHandler MessageCreated; private void RaiseMessageCreated(Message msg) { @@ -47,6 +43,9 @@ namespace Discord if (MessageSent != null) RaiseEvent(nameof(MessageSent), () => MessageSent(this, new MessageEventArgs(msg))); } + + internal Messages Messages => _messages; + private readonly Messages _messages; /// 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.Roles.cs b/src/Discord.Net/DiscordClient.Roles.cs index 4fbdd3cd5..35802fb6d 100644 --- a/src/Discord.Net/DiscordClient.Roles.cs +++ b/src/Discord.Net/DiscordClient.Roles.cs @@ -27,8 +27,7 @@ namespace Discord RaiseEvent(nameof(RoleUpdated), () => RoleUpdated(this, new RoleEventArgs(role))); } - /// Returns a collection of all role-server pairs this client can currently see. - public Roles Roles => _roles; + internal Roles Roles => _roles; private readonly Roles _roles; /// Returns the role with the specified id, or null if none was found. diff --git a/src/Discord.Net/DiscordClient.Servers.cs b/src/Discord.Net/DiscordClient.Servers.cs index ae3c6a5f9..c52be5c42 100644 --- a/src/Discord.Net/DiscordClient.Servers.cs +++ b/src/Discord.Net/DiscordClient.Servers.cs @@ -1,3 +1,4 @@ +using Discord.Collections; using Discord.Net; using System; using System.Collections.Generic; @@ -48,8 +49,14 @@ namespace Discord RaiseEvent(nameof(ServerAvailable), () => ServerAvailable(this, new ServerEventArgs(server))); } + /// Returns a collection of all servers this client is a member of. + public IEnumerable AllServers => _servers; + internal Servers Servers => _servers; + private readonly Servers _servers; + /// 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) diff --git a/src/Discord.Net/DiscordClient.Users.cs b/src/Discord.Net/DiscordClient.Users.cs index 829f60c80..29b9a3bb5 100644 --- a/src/Discord.Net/DiscordClient.Users.cs +++ b/src/Discord.Net/DiscordClient.Users.cs @@ -55,8 +55,9 @@ namespace Discord } /// Returns a collection of all users this client can currently see. - public Users Users => _users; + internal Users Users => _users; private readonly Users _users; + /// Returns the current logged-in user. public User CurrentUser => _currentUser; private User _currentUser; diff --git a/src/Discord.Net/DiscordClient.cs b/src/Discord.Net/DiscordClient.cs index 6244b3962..a1fe71841 100644 --- a/src/Discord.Net/DiscordClient.cs +++ b/src/Discord.Net/DiscordClient.cs @@ -76,11 +76,6 @@ namespace Discord public new DiscordClientConfig Config => _config as DiscordClientConfig; - - /// Returns a collection of all servers this client is a member of. - public Servers Servers => _servers; - private readonly Servers _servers; - /// Initializes a new instance of the DiscordClient class. public DiscordClient(DiscordClientConfig config = null) : base(config ?? new DiscordClientConfig()) @@ -98,8 +93,8 @@ namespace Discord _messages = new Messages(this, cacheLock); _roles = new Roles(this, cacheLock); _servers = new Servers(this, cacheLock); - _status = UserStatus.Online; _users = new Users(this, cacheLock); + _status = UserStatus.Online; this.Connected += async (s, e) => { diff --git a/test/Discord.Net.Tests/Tests.cs b/test/Discord.Net.Tests/Tests.cs index 7a673431b..6b947662b 100644 --- a/test/Discord.Net.Tests/Tests.cs +++ b/test/Discord.Net.Tests/Tests.cs @@ -33,9 +33,9 @@ namespace Discord.Net.Tests //Cleanup existing servers WaitMany( - _hostClient.Servers.Select(x => _hostClient.LeaveServer(x)), - _targetBot.Servers.Select(x => _targetBot.LeaveServer(x)), - _observerBot.Servers.Select(x => _observerBot.LeaveServer(x))); + _hostClient.AllServers.Select(x => _hostClient.LeaveServer(x)), + _targetBot.AllServers.Select(x => _targetBot.LeaveServer(x)), + _observerBot.AllServers.Select(x => _observerBot.LeaveServer(x))); //Create new server and invite the other bots to it _testServer = _hostClient.CreateServer("Discord.Net Testing", Regions.US_East).Result; @@ -110,9 +110,9 @@ namespace Discord.Net.Tests public static void Cleanup() { WaitMany( - _hostClient.State == DiscordClientState.Connected ? _hostClient.Servers.Select(x => _hostClient.LeaveServer(x)) : null, - _targetBot.State == DiscordClientState.Connected ? _targetBot.Servers.Select(x => _targetBot.LeaveServer(x)) : null, - _observerBot.State == DiscordClientState.Connected ? _observerBot.Servers.Select(x => _observerBot.LeaveServer(x)) : null); + _hostClient.State == DiscordClientState.Connected ? _hostClient.AllServers.Select(x => _hostClient.LeaveServer(x)) : null, + _targetBot.State == DiscordClientState.Connected ? _targetBot.AllServers.Select(x => _targetBot.LeaveServer(x)) : null, + _observerBot.State == DiscordClientState.Connected ? _observerBot.AllServers.Select(x => _observerBot.LeaveServer(x)) : null); WaitAll( _hostClient.Disconnect(), From ea38efd67ba9ed6a2b171d87fdf46f52872c68bc Mon Sep 17 00:00:00 2001 From: RogueException Date: Thu, 22 Oct 2015 01:35:25 -0300 Subject: [PATCH 040/109] Made AsyncCollection internal --- src/Discord.Net/Collections/AsyncCollection.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Discord.Net/Collections/AsyncCollection.cs b/src/Discord.Net/Collections/AsyncCollection.cs index 63eb5513b..f206011e0 100644 --- a/src/Discord.Net/Collections/AsyncCollection.cs +++ b/src/Discord.Net/Collections/AsyncCollection.cs @@ -6,17 +6,17 @@ using System.Linq; namespace Discord.Collections { - public abstract class AsyncCollection : IEnumerable + internal abstract class AsyncCollection : IEnumerable where TValue : class { private readonly object _writerLock; - internal class CollectionItemEventArgs : EventArgs + public class CollectionItemEventArgs : EventArgs { public TValue Item { get; } public CollectionItemEventArgs(TValue item) { Item = item; } } - internal class CollectionItemRemappedEventArgs : EventArgs + public class CollectionItemRemappedEventArgs : EventArgs { public TValue Item { get; } public string OldId { get; } @@ -24,26 +24,26 @@ namespace Discord.Collections public CollectionItemRemappedEventArgs(TValue item, string oldId, string newId) { Item = item; OldId = oldId; NewId = newId; } } - internal EventHandler ItemCreated; + public EventHandler ItemCreated; private void RaiseItemCreated(TValue item) { if (ItemCreated != null) ItemCreated(this, new CollectionItemEventArgs(item)); } - internal EventHandler ItemDestroyed; + public EventHandler ItemDestroyed; private void RaiseItemDestroyed(TValue item) { if (ItemDestroyed != null) ItemDestroyed(this, new CollectionItemEventArgs(item)); } - internal EventHandler ItemRemapped; + public EventHandler ItemRemapped; private void RaiseItemRemapped(TValue item, string oldId, string newId) { if (ItemRemapped != null) ItemRemapped(this, new CollectionItemRemappedEventArgs(item, oldId, newId)); } - internal EventHandler Cleared; + public EventHandler Cleared; private void RaiseCleared() { if (Cleared != null) @@ -121,7 +121,7 @@ namespace Discord.Collections } return null; } - protected internal void Clear() + public void Clear() { lock (_writerLock) { From 5a8d3f5377b0b0aa9645be152993f9444832dc5b Mon Sep 17 00:00:00 2001 From: RogueException Date: Thu, 22 Oct 2015 01:47:15 -0300 Subject: [PATCH 041/109] Refactored Net.Voice and Interop namespaces --- src/Discord.Net.Net45/Discord.Net.csproj | 47 +++++++++++----------- .../{Net/Voice => Audio}/IDiscordVoiceBuffer.cs | 2 +- .../{Net/Voice => Audio}/IDiscordVoiceClient.cs | 2 +- src/Discord.Net/{Interop => Audio}/Opus.cs | 2 +- src/Discord.Net/{Interop => Audio}/OpusDecoder.cs | 2 +- src/Discord.Net/{Interop => Audio}/OpusEncoder.cs | 2 +- src/Discord.Net/{Interop => Audio}/Sodium.cs | 2 +- .../{Net/Voice => Audio}/VoiceBuffer.cs | 2 +- src/Discord.Net/DiscordClient.cs | 8 ++-- src/Discord.Net/DiscordWSClient.Voice.cs | 16 +------- src/Discord.Net/{Net => }/HttpException.cs | 2 +- src/Discord.Net/Net/WebSockets/VoiceWebSocket.cs | 3 +- src/Discord.Net/Net/WebSockets/WebSocket.cs | 2 +- 13 files changed, 39 insertions(+), 53 deletions(-) rename src/Discord.Net/{Net/Voice => Audio}/IDiscordVoiceBuffer.cs (83%) rename src/Discord.Net/{Net/Voice => Audio}/IDiscordVoiceClient.cs (90%) rename src/Discord.Net/{Interop => Audio}/Opus.cs (99%) rename src/Discord.Net/{Interop => Audio}/OpusDecoder.cs (99%) rename src/Discord.Net/{Interop => Audio}/OpusEncoder.cs (99%) rename src/Discord.Net/{Interop => Audio}/Sodium.cs (97%) rename src/Discord.Net/{Net/Voice => Audio}/VoiceBuffer.cs (99%) rename src/Discord.Net/{Net => }/HttpException.cs (93%) diff --git a/src/Discord.Net.Net45/Discord.Net.csproj b/src/Discord.Net.Net45/Discord.Net.csproj index 1d54ee4b0..adf26293a 100644 --- a/src/Discord.Net.Net45/Discord.Net.csproj +++ b/src/Discord.Net.Net45/Discord.Net.csproj @@ -127,6 +127,27 @@ API\WebSockets.cs + + Audio\IDiscordVoiceBuffer.cs + + + Audio\IDiscordVoiceClient.cs + + + Audio\Opus.cs + + + Audio\OpusDecoder.cs + + + Audio\OpusEncoder.cs + + + Audio\Sodium.cs + + + Audio\VoiceBuffer.cs + Collections\AsyncCollection.cs @@ -223,17 +244,8 @@ Helpers\TimeoutException.cs - - Interop\Opus.cs - - - Interop\OpusDecoder.cs - - - Interop\OpusEncoder.cs - - - Interop\Sodium.cs + + HttpException.cs Models\Channel.cs @@ -262,9 +274,6 @@ Models\User.cs - - Net\HttpException.cs - Net\Rest\IRestEngine.cs @@ -277,15 +286,6 @@ Net\Rest\SharpRestEngine.cs - - Net\Voice\IDiscordVoiceBuffer.cs - - - Net\Voice\IDiscordVoiceClient.cs - - - Net\Voice\VoiceBuffer.cs - Net\WebSockets\DataWebSocket.cs @@ -326,6 +326,7 @@ lib\opus.dll +