diff --git a/Discord.Net.Tests/ChannelTests.cs b/Discord.Net.Tests/ChannelTests.cs
new file mode 100644
index 000000000..82ca45c41
--- /dev/null
+++ b/Discord.Net.Tests/ChannelTests.cs
@@ -0,0 +1,48 @@
+using Discord.Models;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace Discord.Net.Tests
+{
+ [TestClass]
+ public class ChannelTests
+ {
+ private DiscordClient _bot1, _bot2;
+
+ [TestInitialize]
+ public void Initialize()
+ {
+ _bot1 = new DiscordClient();
+ _bot2 = new DiscordClient();
+
+ _bot1.Connect(Settings.Test1_Username, Settings.Test1_Password).Wait();
+ _bot2.Connect(Settings.Test2_Username, Settings.Test2_Password).Wait();
+
+ //Cleanup existing servers
+ Task.WaitAll(_bot1.Servers.Select(x => _bot1.LeaveServer(x)).ToArray());
+ Task.WaitAll(_bot2.Servers.Select(x => _bot2.LeaveServer(x)).ToArray());
+ }
+
+ [TestMethod]
+ public async Task DoNothing()
+ {
+ Server server = await _bot1.CreateServer("Discord.Net Testbed", Region.US_East);
+ Invite invite = await _bot1.CreateInvite(server, 60, 1, false, false);
+ await _bot2.AcceptInvite(invite);
+ await _bot2.LeaveServer(server);
+ }
+
+ [TestCleanup]
+ public void Cleanup()
+ {
+ if (_bot1.IsConnected)
+ Task.WaitAll(_bot1.Servers.Select(x => _bot1.LeaveServer(x)).ToArray());
+ if (_bot2.IsConnected)
+ Task.WaitAll(_bot2.Servers.Select(x => _bot2.LeaveServer(x)).ToArray());
+
+ _bot1.Disconnect().Wait();
+ _bot2.Disconnect().Wait();
+ }
+ }
+}
diff --git a/Discord.Net.Tests/Discord.Net.Tests.csproj b/Discord.Net.Tests/Discord.Net.Tests.csproj
new file mode 100644
index 000000000..15c96c217
--- /dev/null
+++ b/Discord.Net.Tests/Discord.Net.Tests.csproj
@@ -0,0 +1,90 @@
+
+
+
+ Debug
+ AnyCPU
+ {855D6B1D-847B-42DA-BE6A-23683EA89511}
+ Library
+ Properties
+ Discord.Net.Tests
+ Discord.Net.Tests
+ v4.5.2
+ 512
+ {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
+ 10.0
+ $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
+ $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages
+ False
+ UnitTest
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {8d23f61b-723c-4966-859d-1119b28bcf19}
+ Discord.Net
+
+
+
+
+
+
+ False
+
+
+ False
+
+
+ False
+
+
+ False
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Discord.Net.Tests/Properties/AssemblyInfo.cs b/Discord.Net.Tests/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000..5b1c7b125
--- /dev/null
+++ b/Discord.Net.Tests/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("Discord.Net.Tests")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("Discord.Net.Tests")]
+[assembly: AssemblyCopyright("Copyright © 2015")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("855d6b1d-847b-42da-be6a-23683ea89511")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/Discord.Net.sln b/Discord.Net.sln
index cdfa8301a..16724d0a9 100644
--- a/Discord.Net.sln
+++ b/Discord.Net.sln
@@ -11,6 +11,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
LICENSE = LICENSE
EndProjectSection
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Discord.Net.Tests", "Discord.Net.Tests\Discord.Net.Tests.csproj", "{855D6B1D-847B-42DA-BE6A-23683EA89511}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -21,6 +23,10 @@ Global
{8D23F61B-723C-4966-859D-1119B28BCF19}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8D23F61B-723C-4966-859D-1119B28BCF19}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8D23F61B-723C-4966-859D-1119B28BCF19}.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}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {855D6B1D-847B-42DA-BE6A-23683EA89511}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/Discord.Net/API/DiscordAPI.cs b/Discord.Net/API/DiscordAPI.cs
index 745875d17..dd50bcfe9 100644
--- a/Discord.Net/API/DiscordAPI.cs
+++ b/Discord.Net/API/DiscordAPI.cs
@@ -6,17 +6,18 @@ namespace Discord.API
{
internal static class DiscordAPI
{
- public static async Task LoginAnonymous(string username, HttpOptions options)
+ //Auth
+ public static async Task LoginAnonymous(string username, HttpOptions options)
{
- var fingerprintResponse = await Http.Post(Endpoints.AuthFingerprint, options);
- var registerRequest = new AuthRegisterRequest { Fingerprint = fingerprintResponse.Fingerprint, Username = username };
- var registerResponse = await Http.Post(Endpoints.AuthRegister, registerRequest, options);
+ var fingerprintResponse = await Http.Post(Endpoints.AuthFingerprint, options);
+ var registerRequest = new APIRequests.AuthRegisterRequest { Fingerprint = fingerprintResponse.Fingerprint, Username = username };
+ var registerResponse = await Http.Post(Endpoints.AuthRegister, registerRequest, options);
return registerResponse;
}
- public static async Task Login(string email, string password, HttpOptions options)
+ public static async Task Login(string email, string password, HttpOptions options)
{
- var request = new AuthLoginRequest { Email = email, Password = password };
- var response = await Http.Post(Endpoints.AuthLogin, request, options);
+ var request = new APIRequests.AuthLogin { Email = email, Password = password };
+ var response = await Http.Post(Endpoints.AuthLogin, request, options);
options.Token = response.Token;
return response;
}
@@ -25,37 +26,61 @@ namespace Discord.API
return Http.Post(Endpoints.AuthLogout, options);
}
- public static Task CreateServer(string name, string region, HttpOptions options)
+ //Servers
+ public static Task CreateServer(string name, string region, HttpOptions options)
{
- var request = new CreateServerRequest { Name = name, Region = region };
- return Http.Post(Endpoints.Servers, request, options);
+ var request = new APIRequests.CreateServer { Name = name, Region = region };
+ return Http.Post(Endpoints.Servers, request, options);
+ }
+ public static Task LeaveServer(string id, HttpOptions options)
+ {
+ return Http.Delete(Endpoints.Server(id), options);
}
- public static Task DeleteServer(string id, HttpOptions options)
+
+ //Channels
+ public static Task GetMessages(string channelId, HttpOptions options)
{
- return Http.Delete(Endpoints.Server(id), options);
- }
+ return Http.Get(Endpoints.ChannelMessages(channelId, 50), options);
+ }
- public static Task GetInvite(string id, HttpOptions options)
+ //Invites
+ public static Task CreateInvite(string channelId, int maxAge, int maxUses, bool isTemporary, bool hasXkcdPass, HttpOptions options)
{
- return Http.Get(Endpoints.Invite(id), options);
+ var request = new APIRequests.CreateInvite { MaxAge = maxAge, MaxUses = maxUses, IsTemporary = isTemporary, HasXkcdPass = hasXkcdPass };
+ return Http.Post(Endpoints.ChannelInvites(channelId), request, options);
+ }
+ public static Task GetInvite(string id, HttpOptions options)
+ {
+ return Http.Get(Endpoints.Invite(id), options);
}
public static Task AcceptInvite(string id, HttpOptions options)
{
- return Http.Post(Endpoints.Invite(id), options);
+ return Http.Post(Endpoints.Invite(id), options);
}
public static Task DeleteInvite(string id, HttpOptions options)
{
return Http.Delete(Endpoints.Invite(id), options);
}
-
- public static Task Typing(string channelId, HttpOptions options)
+
+ //Chat
+ public static Task SendMessage(string channelId, string message, string[] mentions, HttpOptions options)
+ {
+ var request = new APIRequests.SendMessage { Content = message, Mentions = mentions };
+ return Http.Post(Endpoints.ChannelMessages(channelId), request, options);
+ }
+ public static Task SendIsTyping(string channelId, HttpOptions options)
{
return Http.Post(Endpoints.ChannelTyping(channelId), options);
}
- public static Task SendMessage(string channelId, string message, string[] mentions, HttpOptions options)
+
+ //Voice
+ public static Task GetVoiceRegions(HttpOptions options)
{
- var request = new SendMessageRequest { Content = message, Mentions = mentions };
- return Http.Post(Endpoints.ChannelMessages(channelId), request, options);
+ return Http.Get(Endpoints.VoiceRegions, options);
+ }
+ public static Task GetVoiceIce(HttpOptions options)
+ {
+ return Http.Get(Endpoints.VoiceIce, options);
}
- }
+ }
}
diff --git a/Discord.Net/API/Endpoints.cs b/Discord.Net/API/Endpoints.cs
index bbc8eea7a..e505ceb46 100644
--- a/Discord.Net/API/Endpoints.cs
+++ b/Discord.Net/API/Endpoints.cs
@@ -2,29 +2,43 @@
{
internal static class Endpoints
{
- public static readonly string BaseUrl = "discordapp.com/";
- public static readonly string BaseHttps = "https://" + BaseUrl;
- public static readonly string BaseWss = "wss://" + BaseUrl;
+ public static readonly string BaseUrl = "discordapp.com";
+ public static readonly string BaseHttps = $"https://{BaseUrl}";
+
+ // /api
+ public static readonly string BaseApi = $"{BaseHttps}/api";
+ public static readonly string Track = $"{BaseApi}/track";
- public static readonly string Auth = $"{BaseHttps}/api/auth";
+ // /api/auth
+ public static readonly string Auth = $"{BaseApi}/auth";
public static readonly string AuthFingerprint = $"{Auth}fingerprint";
public static readonly string AuthRegister = $"{Auth}/register";
public static readonly string AuthLogin = $"{Auth}/login";
public static readonly string AuthLogout = $"{Auth}/logout";
- public static readonly string Servers = $"{BaseHttps}/api/guilds";
+ // /api/guilds
+ public static readonly string Servers = $"{BaseApi}/guilds";
public static string Server(string id) { return $"{Servers}/{id}"; }
- public static string ServerMessages(string id) { return $"{Servers}/{id}/messages?limit=50"; }
- public static readonly string Invites = $"{BaseHttps}/api/invite";
+ // /api/guilds
+ public static readonly string Invites = $"{BaseApi}/invite";
public static string Invite(string id) { return $"{Invites}/{id}"; }
- public static readonly string Channels = $"{BaseHttps}/api/channels";
+ // /api/channels
+ public static readonly string Channels = $"{BaseApi}/channels";
public static string Channel(string id) { return $"{Channels}/{id}"; }
public static string ChannelTyping(string id) { return $"{Channels}/{id}/typing"; }
public static string ChannelMessages(string id) { return $"{Channels}/{id}/messages"; }
+ public static string ChannelMessages(string id, int limit) { return $"{Channels}/{id}/messages?limit={limit}"; }
+ public static string ChannelInvites(string id) { return $"{Channels}/{id}/invites"; }
- public static readonly string WebSocket_Hub = BaseWss + "hub";
-
+ // /api/voice
+ public static readonly string Voice = $"{BaseApi}/voice";
+ public static readonly string VoiceRegions = $"{Voice}/regions";
+ public static readonly string VoiceIce = $"{Voice}/ice";
+
+ //Web Sockets
+ public static readonly string BaseWss = "wss://" + BaseUrl;
+ public static readonly string WebSocket_Hub = $"{BaseWss}/hub";
}
}
diff --git a/Discord.Net/API/Models/APIResponses.cs b/Discord.Net/API/Models/APIResponses.cs
new file mode 100644
index 000000000..70447f4e8
--- /dev/null
+++ b/Discord.Net/API/Models/APIResponses.cs
@@ -0,0 +1,91 @@
+//Ignore unused/unassigned variable warnings
+#pragma warning disable CS0649
+#pragma warning disable CS0169
+
+using Newtonsoft.Json;
+using System;
+
+namespace Discord.API.Models
+{
+
+ internal static class APIResponses
+ {
+ public class AuthFingerprint
+ {
+ [JsonProperty(PropertyName = "fingerprint")]
+ public string Fingerprint;
+ }
+ public class AuthRegister : AuthLogin { }
+ public class AuthLogin
+ {
+ [JsonProperty(PropertyName = "token")]
+ public string Token;
+ }
+
+ public class CreateServer : ServerInfo { }
+ public class DeleteServer : ServerInfo { }
+
+ public class CreateInvite : GetInvite
+ {
+ [JsonProperty(PropertyName = "max_age")]
+ public int MaxAge;
+ [JsonProperty(PropertyName = "max_uses")]
+ public int MaxUses;
+ [JsonProperty(PropertyName = "revoked")]
+ public bool IsRevoked;
+ [JsonProperty(PropertyName = "temporary")]
+ public bool IsTemporary;
+ [JsonProperty(PropertyName = "uses")]
+ public int Uses;
+ [JsonProperty(PropertyName = "created_at")]
+ public DateTime CreatedAt;
+ }
+
+ public class GetInvite
+ {
+ [JsonProperty(PropertyName = "inviter")]
+ public UserReference Inviter;
+ [JsonProperty(PropertyName = "guild")]
+ public ServerReference Server;
+ [JsonProperty(PropertyName = "channel")]
+ public ChannelReference Channel;
+ [JsonProperty(PropertyName = "code")]
+ public string Code;
+ [JsonProperty(PropertyName = "xkcdpass")]
+ public string XkcdPass;
+ }
+ public class AcceptInvite : GetInvite { }
+
+ public class GetMessages : Message { }
+
+ public class GetRegions
+ {
+ [JsonProperty(PropertyName = "sample_hostname")]
+ public string Hostname;
+ [JsonProperty(PropertyName = "sample_port")]
+ public int Port;
+ [JsonProperty(PropertyName = "id")]
+ public string Id;
+ [JsonProperty(PropertyName = "name")]
+ public string Name;
+ }
+
+ public class GetIce
+ {
+ [JsonProperty(PropertyName = "ttl")]
+ public string TTL;
+ [JsonProperty(PropertyName = "servers")]
+ public Server[] Servers;
+
+ public class Server
+ {
+ [JsonProperty(PropertyName = "url")]
+ public string URL;
+ [JsonProperty(PropertyName = "username")]
+ public string Username;
+ [JsonProperty(PropertyName = "credential")]
+ public string Credential;
+ }
+ }
+ }
+}
diff --git a/Discord.Net/API/Models/ApiRequests.cs b/Discord.Net/API/Models/ApiRequests.cs
index 882491300..96b0be614 100644
--- a/Discord.Net/API/Models/ApiRequests.cs
+++ b/Discord.Net/API/Models/ApiRequests.cs
@@ -6,61 +6,49 @@ using Newtonsoft.Json;
namespace Discord.API.Models
{
- public class AuthFingerprintResponse
+ internal static class APIRequests
{
- [JsonProperty(PropertyName = "fingerprint")]
- public string Fingerprint;
- }
-
- public class AuthRegisterRequest
- {
- [JsonProperty(PropertyName = "fingerprint")]
- public string Fingerprint;
- [JsonProperty(PropertyName = "username")]
- public string Username;
- }
- public class AuthRegisterResponse : AuthLoginResponse { }
-
- public class AuthLoginRequest
- {
- [JsonProperty(PropertyName = "email")]
- public string Email;
- [JsonProperty(PropertyName = "password")]
- public string Password;
- }
- public class AuthLoginResponse
- {
- [JsonProperty(PropertyName = "token")]
- public string Token;
- }
+ public class AuthRegisterRequest
+ {
+ [JsonProperty(PropertyName = "fingerprint")]
+ public string Fingerprint;
+ [JsonProperty(PropertyName = "username")]
+ public string Username;
+ }
+ public class AuthLogin
+ {
+ [JsonProperty(PropertyName = "email")]
+ public string Email;
+ [JsonProperty(PropertyName = "password")]
+ public string Password;
+ }
- public class CreateServerRequest
- {
- [JsonProperty(PropertyName = "name")]
- public string Name;
- [JsonProperty(PropertyName = "region")]
- public string Region;
- }
+ public class CreateServer
+ {
+ [JsonProperty(PropertyName = "name")]
+ public string Name;
+ [JsonProperty(PropertyName = "region")]
+ public string Region;
+ }
- public class GetInviteResponse
- {
- [JsonProperty(PropertyName = "inviter")]
- public UserInfo Inviter;
- [JsonProperty(PropertyName = "guild")]
- public ServerInfo Server;
- [JsonProperty(PropertyName = "channel")]
- public ChannelInfo Channel;
- [JsonProperty(PropertyName = "code")]
- public string Code;
- [JsonProperty(PropertyName = "xkcdpass")]
- public string XkcdPass;
- }
+ public class CreateInvite
+ {
+ [JsonProperty(PropertyName = "max_age")]
+ public int MaxAge;
+ [JsonProperty(PropertyName = "max_uses")]
+ public int MaxUses;
+ [JsonProperty(PropertyName = "temporary")]
+ public bool IsTemporary;
+ [JsonProperty(PropertyName = "xkcdpass")]
+ public bool HasXkcdPass;
+ }
- public class SendMessageRequest
- {
- [JsonProperty(PropertyName = "content")]
- public string Content;
- [JsonProperty(PropertyName = "mentions")]
- public string[] Mentions;
+ public class SendMessage
+ {
+ [JsonProperty(PropertyName = "content")]
+ public string Content;
+ [JsonProperty(PropertyName = "mentions")]
+ public string[] Mentions;
+ }
}
}
diff --git a/Discord.Net/API/Models/General.cs b/Discord.Net/API/Models/Common.cs
similarity index 62%
rename from Discord.Net/API/Models/General.cs
rename to Discord.Net/API/Models/Common.cs
index 46c487d8d..37cf91c22 100644
--- a/Discord.Net/API/Models/General.cs
+++ b/Discord.Net/API/Models/Common.cs
@@ -32,7 +32,8 @@ namespace Discord.API.Models
}
}
- public class UserInfo
+ //Users
+ internal class UserReference
{
[JsonProperty(PropertyName = "username")]
public string Username;
@@ -43,14 +44,14 @@ namespace Discord.API.Models
[JsonProperty(PropertyName = "avatar")]
public string Avatar;
}
- public class SelfUserInfo : UserInfo
+ internal class SelfUserInfo : UserReference
{
[JsonProperty(PropertyName = "email")]
public string Email;
[JsonProperty(PropertyName = "verified")]
public bool IsVerified;
}
- public class PresenceUserInfo : UserInfo
+ internal class PresenceUserInfo : UserReference
{
[JsonProperty(PropertyName = "game_id")]
public string GameId;
@@ -58,78 +59,115 @@ namespace Discord.API.Models
public string Status;
}
- public class MembershipInfo
- {
- [JsonProperty(PropertyName = "roles")]
- public object[] Roles;
- [JsonProperty(PropertyName = "mute")]
- public bool IsMuted;
- [JsonProperty(PropertyName = "deaf")]
- public bool IsDeaf;
- [JsonProperty(PropertyName = "joined_at")]
- public DateTime JoinedAt;
- [JsonProperty(PropertyName = "user")]
- public UserInfo User;
- }
-
- public class ChannelInfo
+ //Channels
+ internal class ChannelReference
{
[JsonProperty(PropertyName = "id")]
public string Id;
+ [JsonProperty(PropertyName = "guild_id")]
+ public string GuildId;
[JsonProperty(PropertyName = "name")]
public string Name;
+ [JsonProperty(PropertyName = "type")]
+ public string Type;
+ }
+ internal class ChannelInfo : ChannelReference
+ {
[JsonProperty(PropertyName = "last_message_id")]
public string LastMessageId;
[JsonProperty(PropertyName = "is_private")]
public bool IsPrivate;
- [JsonProperty(PropertyName = "type")]
- public string Type;
[JsonProperty(PropertyName = "permission_overwrites")]
public object[] PermissionOverwrites;
[JsonProperty(PropertyName = "recipient")]
- public UserInfo Recipient;
+ public UserReference Recipient;
}
- public class ServerInfo
+ //Servers
+ internal class ServerReference
{
[JsonProperty(PropertyName = "id")]
public string Id;
[JsonProperty(PropertyName = "name")]
public string Name;
}
- public class ExtendedServerInfo : ServerInfo
+ internal class ServerInfo : ServerReference
{
[JsonProperty(PropertyName = "afk_channel_id")]
public string AFKChannelId;
[JsonProperty(PropertyName = "afk_timeout")]
public int AFKTimeout;
- [JsonProperty(PropertyName = "channels")]
- public ChannelInfo[] Channels;
+ [JsonProperty(PropertyName = "embed_channel_id")]
+ public string EmbedChannelId;
+ [JsonProperty(PropertyName = "embed_enabled")]
+ public bool EmbedEnabled;
[JsonProperty(PropertyName = "joined_at")]
- public DateTime JoinedAt;
- [JsonProperty(PropertyName = "members")]
- public MembershipInfo[] Members;
+ public DateTime? JoinedAt;
[JsonProperty(PropertyName = "owner_id")]
public string OwnerId;
- [JsonProperty(PropertyName = "presence")]
- public object[] Presence;
[JsonProperty(PropertyName = "region")]
public string Region;
[JsonProperty(PropertyName = "roles")]
- public object[] Roles;
+ public Role[] Roles;
+ }
+ internal class ExtendedServerInfo : ServerInfo
+ {
+ public class Membership
+ {
+ [JsonProperty(PropertyName = "roles")]
+ public object[] Roles;
+ [JsonProperty(PropertyName = "mute")]
+ public bool IsMuted;
+ [JsonProperty(PropertyName = "deaf")]
+ public bool IsDeaf;
+ [JsonProperty(PropertyName = "joined_at")]
+ public DateTime JoinedAt;
+ [JsonProperty(PropertyName = "user")]
+ public UserReference User;
+ }
+
+ [JsonProperty(PropertyName = "channels")]
+ public ChannelInfo[] Channels;
+ [JsonProperty(PropertyName = "members")]
+ public Membership[] Members;
+ [JsonProperty(PropertyName = "presence")]
+ public object[] Presence;
[JsonProperty(PropertyName = "voice_states")]
public object[] VoiceStates;
}
+ //Messages
internal class MessageReference
{
- [JsonProperty(PropertyName = "message_id")]
- public string MessageId;
+ [JsonProperty(PropertyName = "id")]
+ public string Id;
[JsonProperty(PropertyName = "channel_id")]
public string ChannelId;
+ [JsonProperty(PropertyName = "message_id")]
+ public string MessageId { get { return Id; } set { Id = value; } }
+ }
+ internal class Message : MessageReference
+ {
+ [JsonProperty(PropertyName = "tts")]
+ public bool IsTextToSpeech;
+ [JsonProperty(PropertyName = "mention_everyone")]
+ public bool IsMentioningEveryone;
+ [JsonProperty(PropertyName = "timestamp")]
+ public DateTime Timestamp;
+ [JsonProperty(PropertyName = "mentions")]
+ public UserReference[] Mentions;
+ [JsonProperty(PropertyName = "embeds")]
+ public object[] Embeds;
+ [JsonProperty(PropertyName = "attachments")]
+ public object[] Attachments;
+ [JsonProperty(PropertyName = "content")]
+ public string Content;
+ [JsonProperty(PropertyName = "author")]
+ public UserReference Author;
}
- internal class Role
+ //Roles
+ internal class Role
{
[JsonProperty(PropertyName = "permissions")]
public int Permissions;
diff --git a/Discord.Net/API/Models/WebSocketEvents.cs b/Discord.Net/API/Models/WebSocketEvents.cs
index 827d87a87..d423226e0 100644
--- a/Discord.Net/API/Models/WebSocketEvents.cs
+++ b/Discord.Net/API/Models/WebSocketEvents.cs
@@ -25,65 +25,72 @@ namespace Discord.API.Models
public int HeartbeatInterval;
}
+ //Servers
internal sealed class GuildCreate : ExtendedServerInfo { }
internal sealed class GuildDelete : ExtendedServerInfo { }
+ //Channels
internal sealed class ChannelCreate : ChannelInfo { }
internal sealed class ChannelDelete : ChannelInfo { }
internal sealed class ChannelUpdate : ChannelInfo { }
- internal sealed class GuildMemberAdd : GuildMemberUpdate
+ //Memberships
+ internal abstract class GuildMemberEvent
+ {
+ [JsonProperty(PropertyName = "user")]
+ public UserReference User;
+ [JsonProperty(PropertyName = "guild_id")]
+ public string GuildId;
+ }
+ internal sealed class GuildMemberAdd : GuildMemberEvent
{
[JsonProperty(PropertyName = "joined_at")]
public DateTime JoinedAt;
+ [JsonProperty(PropertyName = "roles")]
+ public object[] Roles;
}
- internal class GuildMemberUpdate
+ internal sealed class GuildMemberUpdate : GuildMemberEvent
{
- [JsonProperty(PropertyName = "user")]
- public UserInfo User;
[JsonProperty(PropertyName = "roles")]
public object[] Roles;
- [JsonProperty(PropertyName = "guild_id")]
- public string GuildId;
}
- internal sealed class GuildMemberRemove
+ internal sealed class GuildMemberRemove : GuildMemberEvent { }
+
+ //Roles
+ internal abstract class GuildRoleEvent
{
- [JsonProperty(PropertyName = "user")]
- public UserInfo User;
[JsonProperty(PropertyName = "guild_id")]
public string GuildId;
}
-
- internal sealed class GuildRoleCreateUpdate
+ internal sealed class GuildRoleCreateUpdate : GuildRoleEvent
{
[JsonProperty(PropertyName = "role")]
public Role Role;
- [JsonProperty(PropertyName = "guild_id")]
- public string GuildId;
}
- internal sealed class GuildRoleDelete
+ internal sealed class GuildRoleDelete : GuildRoleEvent
{
[JsonProperty(PropertyName = "role_id")]
public string RoleId;
- [JsonProperty(PropertyName = "guild_id")]
- public string GuildId;
}
- internal sealed class GuildBanAddRemove
+ //Bans
+ internal abstract class GuildBanEvent
{
- [JsonProperty(PropertyName = "user")]
- public UserInfo User;
[JsonProperty(PropertyName = "guild_id")]
public string GuildId;
}
- internal sealed class GuildBanRemove
+ internal sealed class GuildBanAddRemove : GuildBanEvent
+ {
+ [JsonProperty(PropertyName = "user")]
+ public UserReference User;
+ }
+ internal sealed class GuildBanRemove : GuildBanEvent
{
[JsonProperty(PropertyName = "user_id")]
public string UserId;
- [JsonProperty(PropertyName = "guild_id")]
- public string GuildId;
}
+ //User
internal sealed class UserUpdate : SelfUserInfo { }
internal sealed class PresenceUpdate : PresenceUserInfo { }
internal sealed class VoiceStateUpdate
@@ -107,35 +114,11 @@ namespace Discord.API.Models
[JsonProperty(PropertyName = "deaf")]
public bool IsDeafened;
}
- internal sealed class MessageCreate
- {
- [JsonProperty(PropertyName = "id")]
- public string Id;
- [JsonProperty(PropertyName = "channel_id")]
- public string ChannelId;
- [JsonProperty(PropertyName = "tts")]
- public bool IsTextToSpeech;
- [JsonProperty(PropertyName = "mention_everyone")]
- public bool IsMentioningEveryone;
- [JsonProperty(PropertyName = "timestamp")]
- public DateTime Timestamp;
- [JsonProperty(PropertyName = "mentions")]
- public UserInfo[] Mentions;
- [JsonProperty(PropertyName = "embeds")]
- public object[] Embeds;
- [JsonProperty(PropertyName = "attachments")]
- public object[] Attachments;
- [JsonProperty(PropertyName = "content")]
- public string Content;
- [JsonProperty(PropertyName = "author")]
- public UserInfo Author;
- }
- internal sealed class MessageUpdate
+
+ //Chat
+ internal sealed class MessageCreate : Message { }
+ internal sealed class MessageUpdate : MessageReference
{
- [JsonProperty(PropertyName = "id")]
- public string Id;
- [JsonProperty(PropertyName = "channel_id")]
- public string ChannelId;
[JsonProperty(PropertyName = "embeds")]
public object[] Embeds;
}
@@ -150,5 +133,14 @@ namespace Discord.API.Models
[JsonProperty(PropertyName = "timestamp")]
public int Timestamp;
}
+
+ //Voice
+ internal sealed class VoiceServerUpdate
+ {
+ [JsonProperty(PropertyName = "guild_id")]
+ public string ServerId;
+ [JsonProperty(PropertyName = "endpoint")]
+ public string Endpoint;
+ }
}
}
diff --git a/Discord.Net/Discord.Net.csproj b/Discord.Net/Discord.Net.csproj
index eda49edcd..e0f31cce0 100644
--- a/Discord.Net/Discord.Net.csproj
+++ b/Discord.Net/Discord.Net.csproj
@@ -45,13 +45,15 @@
-
-
+
+
+
+
+
-
-
+
diff --git a/Discord.Net/DiscordClient.Events.cs b/Discord.Net/DiscordClient.Events.cs
index 62349c566..8ab4ea21c 100644
--- a/Discord.Net/DiscordClient.Events.cs
+++ b/Discord.Net/DiscordClient.Events.cs
@@ -11,7 +11,6 @@ namespace Discord
public readonly string Message;
internal LogMessageEventArgs(string msg) { Message = msg; }
}
-
public event EventHandler DebugMessage;
private void RaiseOnDebugMessage(string message)
{
@@ -26,7 +25,6 @@ namespace Discord
if (Connected != null)
Connected(this, EventArgs.Empty);
}
-
public event EventHandler Disconnected;
private void RaiseDisconnected()
{
@@ -34,12 +32,12 @@ namespace Discord
Disconnected(this, EventArgs.Empty);
}
- public event EventHandler LoggedIn;
+ /*public event EventHandler LoggedIn;
private void RaiseLoggedIn()
{
if (LoggedIn != null)
LoggedIn(this, EventArgs.Empty);
- }
+ }*/
//Server
public sealed class ServerEventArgs : EventArgs
@@ -54,7 +52,6 @@ namespace Discord
if (ServerCreated != null)
ServerCreated(this, new ServerEventArgs(server));
}
-
public event EventHandler ServerDestroyed;
private void RaiseServerDestroyed(Server server)
{
@@ -75,14 +72,12 @@ namespace Discord
if (ChannelCreated != null)
ChannelCreated(this, new ChannelEventArgs(channel));
}
-
public event EventHandler ChannelDestroyed;
private void RaiseChannelDestroyed(Channel channel)
{
if (ChannelDestroyed != null)
ChannelDestroyed(this, new ChannelEventArgs(channel));
}
-
public event EventHandler ChannelUpdated;
private void RaiseChannelUpdated(Channel channel)
{
@@ -98,40 +93,32 @@ namespace Discord
}
//Message
- public sealed class MessageCreateEventArgs : EventArgs
- {
- public readonly ChatMessage Message;
- internal MessageCreateEventArgs(ChatMessage msg) { Message = msg; }
- }
public sealed class MessageEventArgs : EventArgs
{
- public readonly ChatMessageReference Message;
- internal MessageEventArgs(ChatMessageReference msg) { Message = msg; }
+ public readonly Message Message;
+ internal MessageEventArgs(Message msg) { Message = msg; }
}
- public event EventHandler MessageCreated;
- private void RaiseMessageCreated(ChatMessage msg)
+ public event EventHandler MessageCreated;
+ private void RaiseMessageCreated(Message msg)
{
if (MessageCreated != null)
- MessageCreated(this, new MessageCreateEventArgs(msg));
+ MessageCreated(this, new MessageEventArgs(msg));
}
-
public event EventHandler MessageDeleted;
- private void RaiseMessageDeleted(ChatMessageReference msg)
+ private void RaiseMessageDeleted(Message msg)
{
if (MessageDeleted != null)
MessageDeleted(this, new MessageEventArgs(msg));
}
-
public event EventHandler MessageUpdated;
- private void RaiseMessageUpdated(ChatMessageReference msg)
+ private void RaiseMessageUpdated(Message msg)
{
if (MessageUpdated != null)
MessageUpdated(this, new MessageEventArgs(msg));
}
-
public event EventHandler MessageAcknowledged;
- private void RaiseMessageAcknowledged(ChatMessageReference msg)
+ private void RaiseMessageAcknowledged(Message msg)
{
if (MessageAcknowledged != null)
MessageAcknowledged(this, new MessageEventArgs(msg));
@@ -150,14 +137,12 @@ namespace Discord
if (RoleCreated != null)
RoleCreated(this, new RoleEventArgs(role));
}
-
public event EventHandler RoleUpdated;
private void RaiseRoleDeleted(Role role)
{
if (RoleDeleted != null)
RoleDeleted(this, new RoleEventArgs(role));
}
-
public event EventHandler RoleDeleted;
private void RaiseRoleUpdated(Role role)
{
@@ -183,7 +168,6 @@ namespace Discord
if (BanAdded != null)
BanAdded(this, new BanEventArgs(user, server));
}
-
public event EventHandler BanRemoved;
private void RaiseBanRemoved(User user, Server server)
{
@@ -209,14 +193,12 @@ namespace Discord
if (MemberAdded != null)
MemberAdded(this, new MemberEventArgs(user, server));
}
-
public event EventHandler MemberRemoved;
private void RaiseMemberRemoved(User user, Server server)
{
if (MemberRemoved != null)
MemberRemoved(this, new MemberEventArgs(user, server));
}
-
public event EventHandler MemberUpdated;
private void RaiseMemberUpdated(User user, Server server)
{
@@ -242,19 +224,36 @@ namespace Discord
if (PresenceUpdated != null)
PresenceUpdated(this, new UserEventArgs(user));
}
-
public event EventHandler VoiceStateUpdated;
private void RaiseVoiceStateUpdated(User user)
{
if (VoiceStateUpdated != null)
VoiceStateUpdated(this, new UserEventArgs(user));
}
-
public event EventHandler UserTyping;
private void RaiseUserTyping(User user, Channel channel)
{
if (UserTyping != null)
UserTyping(this, new UserTypingEventArgs(user, channel));
}
+
+ //Voice
+ public sealed class VoiceServerUpdatedEventArgs : EventArgs
+ {
+ public readonly Server Server;
+ public readonly string Endpoint;
+ internal VoiceServerUpdatedEventArgs(Server server, string endpoint)
+ {
+ Server = server;
+ Endpoint = endpoint;
+ }
+ }
+
+ public event EventHandler VoiceServerUpdated;
+ private void RaiseVoiceServerUpdated(Server server, string endpoint)
+ {
+ if (VoiceServerUpdated != null)
+ VoiceServerUpdated(this, new VoiceServerUpdatedEventArgs(server, endpoint));
+ }
}
}
diff --git a/Discord.Net/DiscordClient.cs b/Discord.Net/DiscordClient.cs
index ff51ae9f3..a8c86e343 100644
--- a/Discord.Net/DiscordClient.cs
+++ b/Discord.Net/DiscordClient.cs
@@ -3,10 +3,12 @@ using Discord.API.Models;
using Discord.Helpers;
using Discord.Models;
using System;
-using System.Collections.Concurrent;
using System.Collections.Generic;
+using System.Linq;
using System.Reflection;
+using System.Threading;
using System.Threading.Tasks;
+using Message = Discord.Models.Message;
using Role = Discord.Models.Role;
namespace Discord
@@ -19,26 +21,144 @@ namespace Discord
private HttpOptions _httpOptions;
private bool _isClosing, _isReady;
- public string SelfId { get; private set; }
- public User Self { get { return GetUser(SelfId); } }
+ public string UserId { get; private set; }
+ public User User { get { return _users[UserId]; } }
- public IEnumerable Users { get { return _users.Values; } }
- private ConcurrentDictionary _users;
+ public IEnumerable Users { get { return _users; } }
+ private AsyncCache _users;
- public IEnumerable Servers { get { return _servers.Values; } }
- private ConcurrentDictionary _servers;
+ public IEnumerable Servers { get { return _servers; } }
+ private AsyncCache _servers;
- public IEnumerable Channels { get { return _channels.Values; } }
- private ConcurrentDictionary _channels;
+ public IEnumerable Channels { get { return _channels; } }
+ private AsyncCache _channels;
+
+ public IEnumerable Messages { get { return _messages; } }
+ private AsyncCache _messages;
+
+ public IEnumerable Roles { get { return _roles; } }
+ private AsyncCache _roles;
+
+ public bool IsConnected { get { return _isReady; } }
public DiscordClient()
{
string version = typeof(DiscordClient).GetTypeInfo().Assembly.GetName().Version.ToString(2);
_httpOptions = new HttpOptions { UserAgent = $"Discord.Net/{version} (https://github.com/RogueException/Discord.Net)" };
- _users = new ConcurrentDictionary();
- _servers = new ConcurrentDictionary();
- _channels = new ConcurrentDictionary();
+ _servers = new AsyncCache(
+ (key, parentKey) => new Server(key, this),
+ (server, model) =>
+ {
+ server.Name = model.Name;
+ if (model is ExtendedServerInfo)
+ {
+ var extendedModel = model as ExtendedServerInfo;
+ server.AFKChannelId = extendedModel.AFKChannelId;
+ server.AFKTimeout = extendedModel.AFKTimeout;
+ server.JoinedAt = extendedModel.JoinedAt ?? DateTime.MinValue;
+ server.OwnerId = extendedModel.OwnerId;
+ server.Presence = extendedModel.Presence;
+ server.Region = extendedModel.Region;
+ server.VoiceStates = extendedModel.VoiceStates;
+
+ foreach (var role in extendedModel.Roles)
+ _roles.Update(role.Id, model.Id, role);
+ foreach (var channel in extendedModel.Channels)
+ {
+ _channels.Update(channel.Id, model.Id, channel);
+ if (channel.Type == "text")
+ {
+ try
+ {
+ var messages = DiscordAPI.GetMessages(channel.Id, _httpOptions).Result.OrderBy(x => x.Timestamp);
+ foreach (var message in messages)
+ {
+ var msg = _messages.Update(message.Id, message.ChannelId, message);
+ if (msg.User != null)
+ msg.User.UpdateActivity(message.Timestamp);
+ }
+ }
+ catch { } //Bad Permissions?
+ }
+ }
+ foreach (var membership in extendedModel.Members)
+ {
+ _users.Update(membership.User.Id, membership.User);
+ server.AddMember(membership.User.Id);
+ }
+ }
+ },
+ server => { }
+ );
+
+ _channels = new AsyncCache(
+ (key, parentKey) => new Channel(key, parentKey, this),
+ (channel, model) =>
+ {
+ channel.Name = model.Name;
+ channel.Type = model.Type;
+ if (model is ChannelInfo)
+ {
+ var extendedModel = model as ChannelInfo;
+ channel.PermissionOverwrites = extendedModel.PermissionOverwrites;
+ channel.RecipientId = extendedModel.Recipient?.Id;
+ }
+ },
+ channel => { });
+ _messages = new AsyncCache(
+ (key, parentKey) => new Message(key, parentKey, this),
+ (message, model) =>
+ {
+ if (model is API.Models.Message)
+ {
+ var extendedModel = model as API.Models.Message;
+ message.Attachments = extendedModel.Attachments;
+ message.Text = extendedModel.Content;
+ message.Embeds = extendedModel.Embeds;
+ message.IsMentioningEveryone = extendedModel.IsMentioningEveryone;
+ message.IsTTS = extendedModel.IsTextToSpeech;
+ message.UserId = extendedModel.Author.Id;
+ message.Timestamp = extendedModel.Timestamp;
+ }
+ if (model is WebSocketEvents.MessageUpdate)
+ {
+ var extendedModel = model as WebSocketEvents.MessageUpdate;
+ message.Embeds = extendedModel.Embeds;
+ }
+ },
+ message => { }
+ );
+ _roles = new AsyncCache(
+ (key, parentKey) => new Role(key, parentKey, this),
+ (role, model) =>
+ {
+ role.Permissions = model.Permissions;
+ },
+ role => { }
+ );
+ _users = new AsyncCache(
+ (key, parentKey) => new User(key, this),
+ (user, model) =>
+ {
+ user.Avatar = model.Avatar;
+ user.Discriminator = model.Discriminator;
+ user.Name = model.Username;
+ if (model is SelfUserInfo)
+ {
+ var extendedModel = model as SelfUserInfo;
+ user.Email = extendedModel.Email;
+ user.IsVerified = extendedModel.IsVerified;
+ }
+ if (model is PresenceUserInfo)
+ {
+ var extendedModel = model as PresenceUserInfo;
+ user.GameId = extendedModel.GameId;
+ user.Status = extendedModel.Status;
+ }
+ },
+ user => { }
+ );
_webSocket = new DiscordWebSocket();
_webSocket.Connected += (s,e) => RaiseConnected();
@@ -65,14 +185,12 @@ namespace Discord
_channels.Clear();
_users.Clear();
- SelfId = data.User.Id;
- UpdateUser(data.User);
+ UserId = data.User.Id;
+ _users.Update(data.User.Id, data.User);
foreach (var server in data.Guilds)
- UpdateServer(server);
+ _servers.Update(server.Id, server);
foreach (var channel in data.PrivateChannels)
- UpdateChannel(channel as ChannelInfo, null);
-
- RaiseLoggedIn();
+ _channels.Update(channel.Id, null, channel);
}
break;
@@ -80,15 +198,15 @@ namespace Discord
case "GUILD_CREATE":
{
var data = e.Event.ToObject();
- var server = UpdateServer(data);
+ var server = _servers.Update(data.Id, data);
RaiseServerCreated(server);
}
break;
case "GUILD_DELETE":
{
var data = e.Event.ToObject();
- Server server;
- if (_servers.TryRemove(data.Id, out server))
+ var server = _servers.Remove(data.Id);
+ if (server != null)
RaiseServerDestroyed(server);
}
break;
@@ -97,52 +215,53 @@ namespace Discord
case "CHANNEL_CREATE":
{
var data = e.Event.ToObject();
- var channel = UpdateChannel(data, null);
+ var channel = _channels.Update(data.Id, data.GuildId, data);
RaiseChannelCreated(channel);
}
break;
- case "CHANNEL_DELETE":
- {
- var data = e.Event.ToObject();
- var channel = DeleteChannel(data.Id);
- RaiseChannelDestroyed(channel);
- }
- break;
case "CHANNEL_UPDATE":
{
var data = e.Event.ToObject();
- var channel = DeleteChannel(data.Id);
+ var channel = _channels.Update(data.Id, data.GuildId, data);
RaiseChannelUpdated(channel);
}
break;
+ case "CHANNEL_DELETE":
+ {
+ var data = e.Event.ToObject();
+ var channel = _channels.Remove(data.Id);
+ if (channel != null)
+ RaiseChannelDestroyed(channel);
+ }
+ break;
//Members
case "GUILD_MEMBER_ADD":
{
var data = e.Event.ToObject();
- var user = UpdateUser(data.User);
- var server = GetServer(data.GuildId);
+ var user = _users.Update(data.User.Id, data.User);
+ var server = _servers[data.GuildId];
server._members[user.Id] = true;
RaiseMemberAdded(user, server);
}
break;
- case "GUILD_MEMBER_REMOVE":
- {
- var data = e.Event.ToObject();
- var user = UpdateUser(data.User);
- var server = GetServer(data.GuildId);
- server._members[user.Id] = true;
- RaiseMemberRemoved(user, server);
- }
- break;
case "GUILD_MEMBER_UPDATE":
{
var data = e.Event.ToObject();
- var user = UpdateUser(data.User);
- var server = GetServer(data.GuildId);
+ var user = _users.Update(data.User.Id, data.User);
+ var server = _servers[data.GuildId];
RaiseMemberUpdated(user, server);
}
break;
+ case "GUILD_MEMBER_REMOVE":
+ {
+ var data = e.Event.ToObject();
+ var user = _users.Update(data.User.Id, data.User);
+ var server = _servers[data.GuildId];
+ if (server != null && server.RemoveMember(user.Id))
+ RaiseMemberRemoved(user, server);
+ }
+ break;
//Roles
case "GUILD_ROLE_CREATE":
@@ -152,13 +271,6 @@ namespace Discord
RaiseRoleCreated(role);
}
break;
- case "GUILD_ROLE_DELETE":
- {
- var data = e.Event.ToObject();
- var role = GetRole(data.RoleId, data.GuildId);
- RaiseRoleDeleted(role);
- }
- break;
case "GUILD_ROLE_UPDATE":
{
var data = e.Event.ToObject();
@@ -166,22 +278,31 @@ namespace Discord
RaiseRoleUpdated(role);
}
break;
+ case "GUILD_ROLE_DELETE":
+ {
+ var data = e.Event.ToObject();
+ var role = _roles.Remove(data.RoleId);
+ if (role != null)
+ RaiseRoleDeleted(role);
+ }
+ break;
- //Roles
+ //Bans
case "GUILD_BAN_ADD":
{
var data = e.Event.ToObject();
- var user = UpdateUser(data.User);
- var server = GetServer(data.GuildId);
+ var user = _users.Update(data.User.Id, data.User);
+ var server = _servers[data.GuildId];
RaiseBanAdded(user, server);
}
break;
case "GUILD_BAN_REMOVE":
{
var data = e.Event.ToObject();
- var user = UpdateUser(data.User);
- var server = GetServer(data.GuildId);
- RaiseBanRemoved(user, server);
+ var user = _users.Update(data.User.Id, data.User);
+ var server = _servers[data.GuildId];
+ if (server != null && server.RemoveBan(user.Id))
+ RaiseBanRemoved(user, server);
}
break;
@@ -189,7 +310,7 @@ namespace Discord
case "MESSAGE_CREATE":
{
var data = e.Event.ToObject();
- var msg = UpdateMessage(data);
+ var msg = _messages.Update(data.Id, data.ChannelId, data);
msg.User.UpdateActivity(data.Timestamp);
RaiseMessageCreated(msg);
}
@@ -197,21 +318,21 @@ namespace Discord
case "MESSAGE_UPDATE":
{
var data = e.Event.ToObject();
- var msg = GetMessage(data.Id, data.ChannelId);
+ var msg = _messages.Update(data.Id, data);
RaiseMessageUpdated(msg);
}
break;
case "MESSAGE_DELETE":
{
var data = e.Event.ToObject();
- var msg = GetMessage(data.MessageId, data.ChannelId);
- RaiseMessageDeleted(msg);
+ var msg = GetMessage(data.MessageId);
+ _messages.Remove(msg.Id);
}
break;
case "MESSAGE_ACK":
{
var data = e.Event.ToObject();
- var msg = GetMessage(data.MessageId, data.ChannelId);
+ var msg = GetMessage(data.MessageId);
RaiseMessageAcknowledged(msg);
}
break;
@@ -220,25 +341,36 @@ namespace Discord
case "PRESENCE_UPDATE":
{
var data = e.Event.ToObject();
- var user = UpdateUser(data);
+ var user = _users.Update(data.Id, data);
RaisePresenceUpdated(user);
}
break;
case "VOICE_STATE_UPDATE":
{
var data = e.Event.ToObject();
- var user = GetUser(data.UserId); //TODO: Don't ignore this
+ var user = _users[data.UserId]; //TODO: Don't ignore this
RaiseVoiceStateUpdated(user);
}
break;
case "TYPING_START":
{
var data = e.Event.ToObject();
- var channel = GetChannel(data.ChannelId);
- var user = GetUser(data.UserId);
+ var channel = _channels[data.ChannelId];
+ var user = _users[data.UserId];
RaiseUserTyping(user, channel);
}
break;
+
+ //Voice
+ case "VOICE_SERVER_UPDATE":
+ {
+ var data = e.Event.ToObject();
+ var server = _servers[data.ServerId];
+ RaiseVoiceServerUpdated(server, data.Endpoint);
+ }
+ break;
+
+ //Others
default:
RaiseOnDebugMessage("Unknown WebSocket message type: " + e.Type);
break;
@@ -247,6 +379,7 @@ namespace Discord
_webSocket.OnDebugMessage += (s, e) => RaiseOnDebugMessage(e.Message);
}
+ //Auth
public async Task Connect(string email, string password)
{
_isClosing = false;
@@ -271,21 +404,67 @@ namespace Discord
_isClosing = false;
}
- public Task CreateServer(string name, string region)
+ //Servers
+ public async Task CreateServer(string name, string region)
{
CheckReady();
- return DiscordAPI.CreateServer(name, region, _httpOptions);
+ var response = await DiscordAPI.CreateServer(name, region, _httpOptions);
+ return _servers.Update(response.Id, response);
+ }
+ public Task LeaveServer(Server server)
+ {
+ return LeaveServer(server.Id);
}
- public Task DeleteServer(string id)
+ public async Task LeaveServer(string id)
{
CheckReady();
- return DiscordAPI.DeleteServer(id, _httpOptions);
+ await DiscordAPI.LeaveServer(id, _httpOptions);
+ return _servers.Remove(id);
}
- public Task GetInvite(string id)
+ //Invites
+ public Task CreateInvite(Server server, int maxAge, int maxUses, bool isTemporary, bool hasXkcdPass)
+ {
+ return CreateInvite(server.DefaultChannelId, maxAge, maxUses, isTemporary, hasXkcdPass);
+ }
+ public Task CreateInvite(Channel channel, int maxAge, int maxUses, bool isTemporary, bool hasXkcdPass)
+ {
+ return CreateInvite(channel, maxAge, maxUses, isTemporary, hasXkcdPass);
+ }
+ public async Task CreateInvite(string channelId, int maxAge, int maxUses, bool isTemporary, bool hasXkcdPass)
+ {
+ CheckReady();
+ var response = await DiscordAPI.CreateInvite(channelId, maxAge, maxUses, isTemporary, hasXkcdPass, _httpOptions);
+ _channels.Update(response.Channel.Id, response.Server.Id, response.Channel);
+ _servers.Update(response.Server.Id, response.Server);
+ _users.Update(response.Inviter.Id, response.Inviter);
+ return new Invite(response.Code, response.XkcdPass, this)
+ {
+ ChannelId = response.Channel.Id,
+ InviterId = response.Inviter.Id,
+ ServerId = response.Server.Id,
+ IsRevoked = response.IsRevoked,
+ IsTemporary = response.IsTemporary,
+ MaxAge = response.MaxAge,
+ MaxUses = response.MaxUses,
+ Uses = response.Uses
+ };
+ }
+ public async Task GetInvite(string id)
+ {
+ CheckReady();
+ var response = await DiscordAPI.GetInvite(id, _httpOptions);
+ return new Invite(response.Code, response.XkcdPass, this)
+ {
+ ChannelId = response.Channel.Id,
+ InviterId = response.Inviter.Id,
+ ServerId = response.Server.Id
+ };
+ }
+ public Task AcceptInvite(Invite invite)
{
CheckReady();
- return DiscordAPI.GetInvite(id, _httpOptions);
+ return DiscordAPI.AcceptInvite(invite.Code, _httpOptions);
}
public async Task AcceptInvite(string id)
{
@@ -302,6 +481,7 @@ namespace Discord
await DiscordAPI.DeleteInvite(response.Code, _httpOptions);
}
+ //Chat
public Task SendMessage(string channelId, string text)
{
return SendMessage(channelId, text, new string[0]);
@@ -323,136 +503,27 @@ namespace Discord
}
}
- public User GetUser(string id)
- {
- if (id == null) return null;
- User user = null;
- _users.TryGetValue(id, out user);
- return user;
- }
- private User UpdateUser(UserInfo model, bool addNew = true)
- {
- var user = GetUser(model.Id) ?? new User(model.Id, this);
-
- user.Avatar = model.Avatar;
- user.Discriminator = model.Discriminator;
- user.Name = model.Username;
- if (model is SelfUserInfo)
- {
- var extendedModel = model as SelfUserInfo;
- user.Email = extendedModel.Email;
- user.IsVerified = extendedModel.IsVerified;
- }
- if (model is PresenceUserInfo)
- {
- var extendedModel = model as PresenceUserInfo;
- user.GameId = extendedModel.GameId;
- user.Status = extendedModel.Status;
- }
-
- if (addNew)
- _users[model.Id] = user;
- return user;
- }
-
public Server GetServer(string id)
{
- if (id == null) return null;
- Server server = null;
- _servers.TryGetValue(id, out server);
- return server;
+ return _servers[id];
}
- private Server UpdateServer(ServerInfo model, bool addNew = true)
- {
- var server = GetServer(model.Id) ?? new Server(model.Id, this);
-
- server.Name = model.Name;
- if (model is ExtendedServerInfo)
- {
- var extendedModel = model as ExtendedServerInfo;
- server.AFKChannelId = extendedModel.AFKChannelId;
- server.AFKTimeout = extendedModel.AFKTimeout;
- server.JoinedAt = extendedModel.JoinedAt;
- server.OwnerId = extendedModel.OwnerId;
- server.Presence = extendedModel.Presence;
- server.Region = extendedModel.Region;
- server.Roles = extendedModel.Roles;
- server.VoiceStates = extendedModel.VoiceStates;
-
- foreach (var channel in extendedModel.Channels)
- {
- UpdateChannel(channel, model.Id, addNew);
- server._channels[channel.Id] = true;
- }
- foreach (var membership in extendedModel.Members)
- {
- UpdateUser(membership.User, addNew);
- server._members[membership.User.Id] = true;
- }
- }
-
- if (addNew)
- _servers[model.Id] = server;
- return server;
- }
-
public Channel GetChannel(string id)
{
- if (id == null) return null;
- Channel channel = null;
- _channels.TryGetValue(id, out channel);
- return channel;
+ return _channels[id];
}
- private Channel UpdateChannel(ChannelInfo model, string serverId, bool addNew = true)
- {
- var channel = GetChannel(model.Id) ?? new Channel(model.Id, serverId, this);
-
- channel.Name = model.Name;
- channel.IsPrivate = model.IsPrivate;
- channel.PermissionOverwrites = model.PermissionOverwrites;
- channel.RecipientId = model.Recipient?.Id;
- channel.Type = model.Type;
-
- if (addNew)
- _channels[model.Id] = channel;
- return channel;
- }
- private Channel DeleteChannel(string id)
- {
- Channel channel = null;
- if (_channels.TryRemove(id, out channel))
- {
- bool ignored;
- channel.Server._channels.TryRemove(id, out ignored);
- }
- return channel;
- }
-
- //TODO: Temporary measure, unsure if we want to store these or not.
- private ChatMessageReference GetMessage(string id, string channelId)
+ public User GetUser(string id)
{
- if (id == null || channelId == null) return null;
- return new ChatMessageReference(id, channelId, this);
+ return _users[id];
}
- private ChatMessage UpdateMessage(WebSocketEvents.MessageCreate model, bool addNew = true)
+ public Models.Message GetMessage(string id)
{
- return new ChatMessage(model.Id, model.ChannelId, this)
- {
- Attachments = model.Attachments,
- Text = model.Content,
- Embeds = model.Embeds,
- IsMentioningEveryone = model.IsMentioningEveryone,
- IsTTS = model.IsTextToSpeech,
- UserId = model.Author.Id,
- Timestamp = model.Timestamp
- };
+ return _messages[id];
}
-
- private Role GetRole(string id, string serverId)
+ public Role GetRole(string id)
{
- if (id == null || serverId == null) return null;
- return new Role(id, serverId, this);
+ return _roles[id];
}
+
private Role UpdateRole(WebSocketEvents.GuildRoleCreateUpdate role, bool addNew = true)
{
return new Role(role.Role.Id, role.GuildId, this)
@@ -467,5 +538,12 @@ namespace Discord
if (!_isReady)
throw new InvalidOperationException("The client is not currently connected to Discord");
}
+ public void Block()
+ {
+ //Blocking call for console apps
+ //TODO: Improve this
+ while (!_isClosing)
+ Thread.Sleep(1000);
+ }
}
}
diff --git a/Discord.Net/DiscordWebSocket.cs b/Discord.Net/DiscordWebSocket.cs
index 8bd2b6d80..566ad1980 100644
--- a/Discord.Net/DiscordWebSocket.cs
+++ b/Discord.Net/DiscordWebSocket.cs
@@ -22,11 +22,13 @@ namespace Discord
private ConcurrentQueue _sendQueue;
private int _heartbeatInterval;
private DateTime _lastHeartbeat;
+ private AutoResetEvent _connectWaitOnLogin;
public async Task ConnectAsync(string url, HttpOptions options)
{
await DisconnectAsync();
+ _connectWaitOnLogin = new AutoResetEvent(false);
_sendQueue = new ConcurrentQueue();
_webSocket = new ClientWebSocket();
@@ -62,7 +64,9 @@ namespace Discord
msg.Payload.Properties["$referrer"] = "";
msg.Payload.Properties["$referring_domain"] = "";
SendMessage(msg, cancelToken);
- }
+
+ _connectWaitOnLogin.WaitOne();
+ }
public async Task DisconnectAsync()
{
if (_webSocket != null)
@@ -112,6 +116,8 @@ namespace Discord
SendMessage(new WebSocketCommands.KeepAlive(), cancelToken);
}
RaiseGotEvent(msg.Type, msg.Payload as JToken);
+ if (msg.Type == "READY")
+ _connectWaitOnLogin.Set();
break;
default:
RaiseOnDebugMessage("Unknown WebSocket operation ID: " + msg.Operation);
diff --git a/Discord.Net/Helpers/AsyncCache.cs b/Discord.Net/Helpers/AsyncCache.cs
new file mode 100644
index 000000000..2fefbb985
--- /dev/null
+++ b/Discord.Net/Helpers/AsyncCache.cs
@@ -0,0 +1,90 @@
+using System;
+using System.Collections;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+
+namespace Discord.Helpers
+{
+ public class AsyncCache : IEnumerable
+ where TValue : class
+ where TModel : class
+ {
+ protected readonly ConcurrentDictionary _dictionary;
+ private readonly Func _onCreate;
+ private readonly Action _onUpdate;
+ private readonly Action _onRemove;
+
+ public AsyncCache(Func onCreate, Action onUpdate, Action onRemove)
+ {
+ _dictionary = new ConcurrentDictionary();
+ _onCreate = onCreate;
+ _onUpdate = onUpdate;
+ _onRemove = onRemove;
+ }
+
+ public TValue this[string key]
+ {
+ get
+ {
+ if (key == null)
+ return null;
+ TValue value = null;
+ _dictionary.TryGetValue(key, out value);
+ return value;
+ }
+ }
+
+ public TValue Update(string key, TModel model)
+ {
+ return Update(key, null, model);
+ }
+ public TValue Update(string key, string parentKey, TModel model)
+ {
+ if (key == null)
+ return null;
+ while (true)
+ {
+ bool isNew;
+ TValue value;
+ isNew = !_dictionary.TryGetValue(key, out value);
+ if (isNew)
+ value = _onCreate(key, parentKey);
+ _onUpdate(value, model);
+ if (isNew)
+ {
+ //If this fails, repeat as an update instead of an add
+ if (_dictionary.TryAdd(key, value))
+ return value;
+ }
+ else
+ {
+ _dictionary[key] = value;
+ return value;
+ }
+ }
+ }
+
+ public TValue Remove(string key)
+ {
+ TValue value = null;
+ if (_dictionary.TryRemove(key, out value))
+ return value;
+ else
+ return null;
+ }
+
+ public void Clear()
+ {
+ _dictionary.Clear();
+ }
+
+ public IEnumerator GetEnumerator()
+ {
+ return _dictionary.Values.GetEnumerator();
+ }
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return _dictionary.Values.GetEnumerator();
+ }
+ }
+}
diff --git a/Discord.Net/Helpers/Http.cs b/Discord.Net/Helpers/Http.cs
index 5a122c017..72775b231 100644
--- a/Discord.Net/Helpers/Http.cs
+++ b/Discord.Net/Helpers/Http.cs
@@ -1,4 +1,5 @@
using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
using System;
using System.IO;
using System.IO.Compression;
@@ -24,47 +25,93 @@ namespace Discord.Helpers
internal static class Http
{
private static readonly RequestCachePolicy _cachePolicy = new RequestCachePolicy(RequestCacheLevel.NoCacheNoStore);
+#if DEBUG
+ private const bool _isDebug = true;
+#else
+ private const bool _isDebug = false;
+#endif
+ //GET
internal static async Task Get(string path, object data, HttpOptions options)
where ResponseT : class
{
string requestJson = JsonConvert.SerializeObject(data);
string responseJson = await SendRequest("GET", path, requestJson, options, true);
- return JsonConvert.DeserializeObject(responseJson);
+ var response = JsonConvert.DeserializeObject(responseJson);
+#if DEBUG
+ CheckResponse(responseJson, response);
+#endif
+ return response;
}
internal static async Task Get(string path, HttpOptions options)
where ResponseT : class
{
string responseJson = await SendRequest("GET", path, null, options, true);
- return JsonConvert.DeserializeObject(responseJson);
+ var response = JsonConvert.DeserializeObject(responseJson);
+#if DEBUG
+ CheckResponse(responseJson, response);
+#endif
+ return response;
}
+ //POST
internal static async Task Post(string path, object data, HttpOptions options)
where ResponseT : class
{
string requestJson = JsonConvert.SerializeObject(data);
string responseJson = await SendRequest("POST", path, requestJson, options, true);
- return JsonConvert.DeserializeObject(responseJson);
+ var response = JsonConvert.DeserializeObject(responseJson);
+#if DEBUG
+ CheckResponse(responseJson, response);
+#endif
+ return response;
}
- internal static Task Post(string path, object data, HttpOptions options)
+ internal static async Task Post(string path, object data, HttpOptions options)
{
string requestJson = JsonConvert.SerializeObject(data);
- return SendRequest("POST", path, requestJson, options, false);
+ string responseJson = await SendRequest("POST", path, requestJson, options, _isDebug);
+#if DEBUG
+ CheckEmptyResponse(responseJson);
+#endif
+ return responseJson;
}
internal static async Task Post(string path, HttpOptions options)
where ResponseT : class
{
string responseJson = await SendRequest("POST", path, null, options, true);
- return JsonConvert.DeserializeObject(responseJson);
+ var response = JsonConvert.DeserializeObject(responseJson);
+#if DEBUG
+ CheckResponse(responseJson, response);
+#endif
+ return response;
}
- internal static Task Post(string path, HttpOptions options)
+ internal static async Task Post(string path, HttpOptions options)
{
- return SendRequest("POST", path, null, options, false);
+ string responseJson = await SendRequest("POST", path, null, options, _isDebug);
+#if DEBUG
+ CheckEmptyResponse(responseJson);
+#endif
+ return responseJson;
}
- internal static Task Delete(string path, HttpOptions options)
+ //DELETE
+ internal static async Task Delete(string path, HttpOptions options)
+ where ResponseT : class
+ {
+ string responseJson = await SendRequest("DELETE", path, null, options, true);
+ var response = JsonConvert.DeserializeObject(responseJson);
+#if DEBUG
+ CheckResponse(responseJson, response);
+#endif
+ return response;
+ }
+ internal static async Task Delete(string path, HttpOptions options)
{
- return SendRequest("DELETE", path, null, options, false);
+ string responseJson = await SendRequest("DELETE", path, null, options, _isDebug);
+#if DEBUG
+ CheckEmptyResponse(responseJson);
+#endif
+ return responseJson;
}
private static async Task SendRequest(string method, string path, string data, HttpOptions options, bool hasResponse)
@@ -124,6 +171,7 @@ namespace Discord.Helpers
else
return null;
}
+
}
private static Stream GetDecoder(string contentEncoding, MemoryStream encodedStream)
@@ -138,5 +186,21 @@ namespace Discord.Helpers
throw new ArgumentOutOfRangeException("Unknown encoding: " + contentEncoding);
}
}
+
+#if DEBUG
+ private static void CheckResponse(string json, T obj)
+ {
+ /*JToken token = JToken.Parse(json);
+ JToken token2 = JToken.FromObject(obj);
+ if (!JToken.DeepEquals(token, token2))
+ throw new Exception("API check failed: Objects do not match.");*/
+ }
+
+ private static void CheckEmptyResponse(string json)
+ {
+ if (!string.IsNullOrEmpty(json))
+ throw new Exception("API check failed: Response is not empty.");
+ }
+#endif
}
}
diff --git a/Discord.Net/Models/Channel.cs b/Discord.Net/Models/Channel.cs
index 23919e192..09e449a80 100644
--- a/Discord.Net/Models/Channel.cs
+++ b/Discord.Net/Models/Channel.cs
@@ -1,16 +1,19 @@
using Newtonsoft.Json;
+using System.Collections.Generic;
+using System.Linq;
namespace Discord.Models
{
public sealed class Channel
{
private readonly DiscordClient _client;
- private string _name;
public string Id { get; }
+
+ private string _name;
public string Name { get { return !IsPrivate ? _name : '@' + Recipient.Name; } internal set { _name = value; } }
- public bool IsPrivate { get; internal set; }
+ public bool IsPrivate { get; }
public string Type { get; internal set; }
public string ServerId { get; }
@@ -21,6 +24,8 @@ namespace Discord.Models
public string RecipientId { get; internal set; }
public User Recipient { get { return _client.GetUser(RecipientId); } }
+ public IEnumerable Messages { get { return _client.Messages.Where(x => x.ChannelId == Id); } }
+
//Not Implemented
public object[] PermissionOverwrites { get; internal set; }
@@ -28,6 +33,7 @@ namespace Discord.Models
{
Id = id;
ServerId = serverId;
+ IsPrivate = serverId == null;
_client = client;
}
diff --git a/Discord.Net/Models/ChatMessageReference.cs b/Discord.Net/Models/ChatMessageReference.cs
deleted file mode 100644
index d3c800cf3..000000000
--- a/Discord.Net/Models/ChatMessageReference.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-using Newtonsoft.Json;
-
-namespace Discord.Models
-{
- public class ChatMessageReference
- {
- protected readonly DiscordClient _client;
-
- public string Id { get; }
-
- public string ChannelId { get; }
- [JsonIgnore]
- public Channel Channel { get { return _client.GetChannel(ChannelId); } }
-
- internal ChatMessageReference(string id, string channelId, DiscordClient client)
- {
- Id = id;
- ChannelId = channelId;
- _client = client;
- }
- }
-}
diff --git a/Discord.Net/Models/Invite.cs b/Discord.Net/Models/Invite.cs
new file mode 100644
index 000000000..972b8f1fe
--- /dev/null
+++ b/Discord.Net/Models/Invite.cs
@@ -0,0 +1,32 @@
+using Newtonsoft.Json;
+
+namespace Discord.Models
+{
+ public sealed class Invite
+ {
+ private readonly DiscordClient _client;
+
+ public int MaxAge, Uses, MaxUses;
+ public bool IsRevoked, IsTemporary;
+ public readonly string Code, XkcdPass;
+
+ public string InviterId { get; internal set; }
+ [JsonIgnore]
+ public User Inviter { get { return _client.GetUser(InviterId); } }
+
+ public string ServerId { get; internal set; }
+ [JsonIgnore]
+ public Server Server { get { return _client.GetServer(ServerId); } }
+
+ public string ChannelId { get; internal set; }
+ [JsonIgnore]
+ public Channel Channel { get { return _client.GetChannel(ChannelId); } }
+
+ internal Invite(string code, string xkcdPass, DiscordClient client)
+ {
+ Code = code;
+ XkcdPass = xkcdPass;
+ _client = client;
+ }
+ }
+}
diff --git a/Discord.Net/Models/ChatMessage.cs b/Discord.Net/Models/Message.cs
similarity index 69%
rename from Discord.Net/Models/ChatMessage.cs
rename to Discord.Net/Models/Message.cs
index 1b8d95d90..462bcaa5f 100644
--- a/Discord.Net/Models/ChatMessage.cs
+++ b/Discord.Net/Models/Message.cs
@@ -3,8 +3,13 @@ using System;
namespace Discord.Models
{
- public sealed class ChatMessage : ChatMessageReference
+ public sealed class Message
{
+ private readonly DiscordClient _client;
+
+ public string Id { get; }
+ public string ChannelId { get; }
+
public bool IsMentioningEveryone { get; internal set; }
public bool IsTTS { get; internal set; }
public string Text { get; internal set; }
@@ -18,10 +23,12 @@ namespace Discord.Models
public object[] Attachments { get; internal set; }
public object[] Embeds { get; internal set; }
- internal ChatMessage(string id, string channelId, DiscordClient client)
- : base(id, channelId, client)
+ internal Message(string id, string channelId, DiscordClient client)
{
- }
+ Id = id;
+ ChannelId = channelId;
+ _client = client;
+ }
public override string ToString()
{
diff --git a/Discord.Net/Models/Server.cs b/Discord.Net/Models/Server.cs
index 667f0cc66..36840f8b1 100644
--- a/Discord.Net/Models/Server.cs
+++ b/Discord.Net/Models/Server.cs
@@ -1,4 +1,5 @@
-using Newtonsoft.Json;
+using Discord.Helpers;
+using Newtonsoft.Json;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
@@ -20,20 +21,22 @@ namespace Discord.Models
public string OwnerId { get; internal set; }
public User Owner { get { return _client.GetUser(OwnerId); } }
+ public bool IsOwner { get { return _client.UserId == OwnerId; } }
+
+ public string DefaultChannelId { get { return Id; } }
+ public Channel DefaultChannel { get { return _client.GetChannel(DefaultChannelId); } }
internal ConcurrentDictionary _members;
- public IEnumerable MemberIds { get { return _members.Keys; } }
- [JsonIgnore]
public IEnumerable Members { get { return _members.Keys.Select(x => _client.GetUser(x)); } }
- internal ConcurrentDictionary _channels;
- public IEnumerable ChannelIds { get { return _channels.Keys; } }
- [JsonIgnore]
- public IEnumerable Channels { get { return _channels.Keys.Select(x => _client.GetChannel(x)); } }
+ internal ConcurrentDictionary _bans;
+ public IEnumerable Bans { get { return _bans.Keys.Select(x => _client.GetUser(x)); } }
+
+ public IEnumerable Channels { get { return _client.Channels.Where(x => x.ServerId == Id); } }
+ public IEnumerable Roles { get { return _client.Roles.Where(x => x.ServerId == Id); } }
//Not Implemented
public object Presence { get; internal set; }
- public object[] Roles { get; internal set; }
public object[] VoiceStates { get; internal set; }
internal Server(string id, DiscordClient client)
@@ -41,12 +44,32 @@ namespace Discord.Models
Id = id;
_client = client;
_members = new ConcurrentDictionary();
- _channels = new ConcurrentDictionary();
+ _bans = new ConcurrentDictionary();
}
public override string ToString()
{
return Name;
}
+
+ internal void AddMember(string id)
+ {
+ _members.TryAdd(id, true);
+ }
+ internal bool RemoveMember(string id)
+ {
+ bool ignored;
+ return _members.TryRemove(id, out ignored);
+ }
+
+ internal void AddBan(string id)
+ {
+ _bans.TryAdd(id, true);
+ }
+ internal bool RemoveBan(string id)
+ {
+ bool ignored;
+ return _bans.TryRemove(id, out ignored);
+ }
}
}
diff --git a/Discord.Net/Properties/AssemblyInfo.cs b/Discord.Net/Properties/AssemblyInfo.cs
index db7498e6c..a375c505f 100644
--- a/Discord.Net/Properties/AssemblyInfo.cs
+++ b/Discord.Net/Properties/AssemblyInfo.cs
@@ -1,7 +1,7 @@
using System.Reflection;
[assembly: AssemblyTitle("Discord.Net")]
-[assembly: AssemblyDescription("A .Net API Wrapper for the Discord client")]
+[assembly: AssemblyDescription("A .Net API wrapper for the Discord client")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("RogueException")]
[assembly: AssemblyProduct("Discord.Net")]
@@ -9,5 +9,5 @@
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
-[assembly: AssemblyVersion("0.1.0.0")]
-[assembly: AssemblyFileVersion("0.1.0.0")]
+[assembly: AssemblyVersion("0.2.0.0")]
+[assembly: AssemblyFileVersion("0.2.0.0")]