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..c1cd9bfcb 100644
--- a/Discord.Net.sln
+++ b/Discord.Net.sln
@@ -9,8 +9,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
ProjectSection(SolutionItems) = preProject
.gitignore = .gitignore
LICENSE = LICENSE
+ README.md = README.md
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 +24,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..e3f356508 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,95 @@ 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);
+ }
+
+ //Members
+ public static Task Kick(string serverId, string memberId, HttpOptions options)
+ {
+ return Http.Delete(Endpoints.ServerMember(serverId, memberId), options);
+ }
+ public static Task Ban(string serverId, string memberId, HttpOptions options)
+ {
+ return Http.Put(Endpoints.ServerBan(serverId, memberId), options);
+ }
+ public static Task Unban(string serverId, string memberId, HttpOptions options)
+ {
+ return Http.Delete(Endpoints.ServerBan(serverId, memberId), 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);
+ }
+ public static Task Mute(string serverId, string memberId, HttpOptions options)
+ {
+ var request = new APIRequests.SetMemberMute { Mute = true };
+ return Http.Patch(Endpoints.ServerMember(serverId, memberId), options);
+ }
+ public static Task Unmute(string serverId, string memberId, HttpOptions options)
+ {
+ var request = new APIRequests.SetMemberMute { Mute = false };
+ return Http.Patch(Endpoints.ServerMember(serverId, memberId), options);
+ }
+ public static Task Deafen(string serverId, string memberId, HttpOptions options)
+ {
+ var request = new APIRequests.SetMemberDeaf { Deaf = true };
+ return Http.Patch(Endpoints.ServerMember(serverId, memberId), options);
+ }
+ public static Task Undeafen(string serverId, string memberId, HttpOptions options)
+ {
+ var request = new APIRequests.SetMemberDeaf { Deaf = false };
+ return Http.Patch(Endpoints.ServerMember(serverId, memberId), options);
}
}
}
diff --git a/Discord.Net/API/Endpoints.cs b/Discord.Net/API/Endpoints.cs
index bbc8eea7a..9f1780935 100644
--- a/Discord.Net/API/Endpoints.cs
+++ b/Discord.Net/API/Endpoints.cs
@@ -2,29 +2,45 @@
{
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";
- public static string Server(string id) { return $"{Servers}/{id}"; }
- public static string ServerMessages(string id) { return $"{Servers}/{id}/messages?limit=50"; }
+ // /api/guilds
+ public static readonly string Servers = $"{BaseApi}/guilds";
+ public static string Server(string id) => $"{Servers}/{id}";
+ public static string ServerMember(string serverId, string userId) => $"{Servers}/{serverId}/members/{userId}";
+ public static string ServerBan(string serverId, string userId) => $"{Servers}/{serverId}/bans/{userId}";
- public static readonly string Invites = $"{BaseHttps}/api/invite";
- public static string Invite(string id) { return $"{Invites}/{id}"; }
+ // /api/invites
+ public static readonly string Invites = $"{BaseApi}/invite";
+ public static string Invite(string id) => $"{Invites}/{id}";
- public static readonly string Channels = $"{BaseHttps}/api/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"; }
+ // /api/channels
+ public static readonly string Channels = $"{BaseApi}/channels";
+ public static string Channel(string id) => $"{Channels}/{id}";
+ public static string ChannelTyping(string id) => $"{Channels}/{id}/typing";
+ public static string ChannelMessages(string id) => $"{Channels}/{id}/messages";
+ public static string ChannelMessages(string id, int limit) => $"{Channels}/{id}/messages?limit={limit}";
+ public static string ChannelInvites(string id) => $"{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..8f61f5796 100644
--- a/Discord.Net/API/Models/ApiRequests.cs
+++ b/Discord.Net/API/Models/ApiRequests.cs
@@ -6,61 +6,60 @@ 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 AuthLogin
+ {
+ [JsonProperty(PropertyName = "email")]
+ public string Email;
+ [JsonProperty(PropertyName = "password")]
+ public string Password;
+ }
- public class AuthRegisterRequest
- {
- [JsonProperty(PropertyName = "fingerprint")]
- public string Fingerprint;
- [JsonProperty(PropertyName = "username")]
- public string Username;
- }
- public class AuthRegisterResponse : AuthLoginResponse { }
+ public class CreateServer
+ {
+ [JsonProperty(PropertyName = "name")]
+ public string Name;
+ [JsonProperty(PropertyName = "region")]
+ public string Region;
+ }
- 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 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 CreateServerRequest
- {
- [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 SendMessage
+ {
+ [JsonProperty(PropertyName = "content")]
+ public string Content;
+ [JsonProperty(PropertyName = "mentions")]
+ public string[] Mentions;
+ }
- public class SendMessageRequest
- {
- [JsonProperty(PropertyName = "content")]
- public string Content;
- [JsonProperty(PropertyName = "mentions")]
- public string[] Mentions;
+ public class SetMemberMute
+ {
+ [JsonProperty(PropertyName = "mute")]
+ public bool Mute;
+ }
+ public class SetMemberDeaf
+ {
+ [JsonProperty(PropertyName = "deaf")]
+ public bool Deaf;
+ }
}
}
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..1c0200e8b 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,149 @@ 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 User GetUser(string id) => _users[id];
- public IEnumerable Servers { get { return _servers.Values; } }
- private ConcurrentDictionary _servers;
+ public IEnumerable Servers { get { return _servers; } }
+ private AsyncCache _servers;
+ public Server GetServer(string id) => _servers[id];
- public IEnumerable Channels { get { return _channels.Values; } }
- private ConcurrentDictionary _channels;
+ public IEnumerable Channels { get { return _channels; } }
+ private AsyncCache _channels;
+ public Channel GetChannel(string id) => _channels[id];
+
+ public IEnumerable Messages { get { return _messages; } }
+ private AsyncCache _messages;
+ public Message GetMessage(string id) => _messages[id];
+
+ public IEnumerable Roles { get { return _roles; } }
+ private AsyncCache _roles;
+ public Role GetRole(string id) => _roles[id];
+
+ 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 +190,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 +203,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,91 +220,94 @@ 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":
{
var data = e.Event.ToObject();
- var role = UpdateRole(data);
+ var role = _roles.Update(data.Role.Id, data.Role);
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();
- var role = UpdateRole(data);
+ var role = _roles.Update(data.Role.Id, data.Role);
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 +315,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 +323,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 +346,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 +384,7 @@ namespace Discord
_webSocket.OnDebugMessage += (s, e) => RaiseOnDebugMessage(e.Message);
}
+ //Auth
public async Task Connect(string email, string password)
{
_isClosing = false;
@@ -269,23 +407,98 @@ namespace Discord
_isClosing = true;
await _webSocket.DisconnectAsync();
_isClosing = false;
+
+ _channels.Clear();
+ _messages.Clear();
+ _roles.Clear();
+ _servers.Clear();
+ _users.Clear();
+ }
+
+ //Servers
+ public async Task CreateServer(string name, string region)
+ {
+ CheckReady();
+ var response = await DiscordAPI.CreateServer(name, region, _httpOptions);
+ return _servers.Update(response.Id, response);
+ }
+ public Task LeaveServer(Server server)
+ => LeaveServer(server.Id);
+ public async Task LeaveServer(string id)
+ {
+ CheckReady();
+ await DiscordAPI.LeaveServer(id, _httpOptions);
+ return _servers.Remove(id);
}
- public Task CreateServer(string name, string region)
+ //Bans
+ public Task Ban(Server server, User user)
+ => Ban(server.Id, user.Id);
+ public Task Ban(Server server, string userId)
+ => Ban(server.Id, userId);
+ public Task Ban(string server, User user)
+ => Ban(server, user.Id);
+ public Task Ban(string serverId, string userId)
{
CheckReady();
- return DiscordAPI.CreateServer(name, region, _httpOptions);
+ return DiscordAPI.Ban(serverId, userId, _httpOptions);
}
- public Task DeleteServer(string id)
+ public Task Unban(Server server, User user)
+ => Unban(server.Id, user.Id);
+ public Task Unban(Server server, string userId)
+ => Unban(server.Id, userId);
+ public Task Unban(string server, User user)
+ => Unban(server, user.Id);
+ public Task Unban(string serverId, string userId)
{
CheckReady();
- return DiscordAPI.DeleteServer(id, _httpOptions);
+ return DiscordAPI.Unban(serverId, userId, _httpOptions);
}
- 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 +515,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,143 +537,53 @@ 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;
- }
- private Server UpdateServer(ServerInfo model, bool addNew = true)
+ //Voice
+ public Task Mute(Server server, User user)
+ => Mute(server.Id, user.Id);
+ public Task Mute(Server server, string userId)
+ => Mute(server.Id, userId);
+ public Task Mute(string server, User user)
+ => Mute(server, user.Id);
+ public Task Mute(string serverId, string userId)
{
- 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;
+ CheckReady();
+ return DiscordAPI.Mute(serverId, userId, _httpOptions);
}
- public Channel GetChannel(string id)
+ public Task Unmute(Server server, User user)
+ => Unmute(server.Id, user.Id);
+ public Task Unmute(Server server, string userId)
+ => Unmute(server.Id, userId);
+ public Task Unmute(string server, User user)
+ => Unmute(server, user.Id);
+ public Task Unmute(string serverId, string userId)
{
- if (id == null) return null;
- Channel channel = null;
- _channels.TryGetValue(id, out channel);
- return channel;
- }
- 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;
+ CheckReady();
+ return DiscordAPI.Unmute(serverId, userId, _httpOptions);
}
- 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)
- {
- if (id == null || channelId == null) return null;
- return new ChatMessageReference(id, channelId, this);
- }
- private ChatMessage UpdateMessage(WebSocketEvents.MessageCreate model, bool addNew = true)
+ public Task Deafen(Server server, User user)
+ => Deafen(server.Id, user.Id);
+ public Task Deafen(Server server, string userId)
+ => Deafen(server.Id, userId);
+ public Task Deafen(string server, User user)
+ => Deafen(server, user.Id);
+ public Task Deafen(string serverId, string userId)
{
- 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
- };
+ CheckReady();
+ return DiscordAPI.Deafen(serverId, userId, _httpOptions);
}
- private Role GetRole(string id, string serverId)
+ public Task Undeafen(Server server, User user)
+ => Undeafen(server.Id, user.Id);
+ public Task Undeafen(Server server, string userId)
+ => Undeafen(server.Id, userId);
+ public Task Undeafen(string server, User user)
+ => Undeafen(server, user.Id);
+ public Task Undeafen(string serverId, string userId)
{
- if (id == null || serverId == null) return null;
- return new Role(id, serverId, this);
- }
- private Role UpdateRole(WebSocketEvents.GuildRoleCreateUpdate role, bool addNew = true)
- {
- return new Role(role.Role.Id, role.GuildId, this)
- {
- Name = role.Role.Name,
- Permissions = role.Role.Permissions
- };
+ CheckReady();
+ return DiscordAPI.Undeafen(serverId, userId, _httpOptions);
}
private void CheckReady()
@@ -467,5 +591,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..fe8e2cb22 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,104 @@ 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
+
+ internal static Task Get(string path, object data, HttpOptions options)
+ where ResponseT : class
+ => Send("GET", path, data, options);
+ internal static Task Get(string path, object data, HttpOptions options)
+ => Send("GET", path, data, options);
+ internal static Task Get(string path, HttpOptions options)
+ where ResponseT : class
+ => Send("GET", path, null, options);
+ internal static Task Get(string path, HttpOptions options)
+ => Send("GET", path, null, options);
+
+ internal static Task Post(string path, object data, HttpOptions options)
+ where ResponseT : class
+ => Send("POST", path, data, options);
+ internal static Task Post(string path, object data, HttpOptions options)
+ => Send("POST", path, data, options);
+ internal static Task Post(string path, HttpOptions options)
+ where ResponseT : class
+ => Send("POST", path, null, options);
+ internal static Task Post(string path, HttpOptions options)
+ => Send("POST", path, null, options);
+
+ internal static Task Put(string path, object data, HttpOptions options)
+ where ResponseT : class
+ => Send("PUT", path, data, options);
+ internal static Task Put(string path, object data, HttpOptions options)
+ => Send("PUT", path, data, options);
+ internal static Task Put(string path, HttpOptions options)
+ where ResponseT : class
+ => Send("PUT", path, null, options);
+ internal static Task Put(string path, HttpOptions options)
+ => Send("PUT", path, null, options);
- internal static async Task Get(string path, object data, HttpOptions options)
+ internal static Task Patch(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);
- }
- internal static async Task Get(string path, HttpOptions options)
+ => Send("PATCH", path, data, options);
+ internal static Task Patch(string path, object data, HttpOptions options)
+ => Send("PATCH", path, data, options);
+ internal static Task Patch(string path, HttpOptions options)
where ResponseT : class
- {
- string responseJson = await SendRequest("GET", path, null, options, true);
- return JsonConvert.DeserializeObject(responseJson);
- }
+ => Send("PATCH", path, null, options);
+ internal static Task Patch(string path, HttpOptions options)
+ => Send("PATCH", path, null, options);
+
+ internal static Task Delete(string path, object data, HttpOptions options)
+ where ResponseT : class
+ => Send("DELETE", path, data, options);
+ internal static Task Delete(string path, object data, HttpOptions options)
+ => Send("DELETE", path, data, options);
+ internal static Task Delete(string path, HttpOptions options)
+ where ResponseT : class
+ => Send("DELETE", path, null, options);
+ internal static Task Delete(string path, HttpOptions options)
+ => Send("DELETE", path, null, options);
- internal static async Task Post(string path, object data, HttpOptions options)
+ internal static async Task Send(string method, 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 Send(string method, 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)
+ internal static async Task Send(string method, string path, HttpOptions options)
where ResponseT : class
{
string responseJson = await SendRequest("POST", path, null, options, true);
- return JsonConvert.DeserializeObject(responseJson);
- }
- internal static Task Post(string path, HttpOptions options)
- {
- return SendRequest("POST", path, null, options, false);
+ var response = JsonConvert.DeserializeObject(responseJson);
+#if DEBUG
+ CheckResponse(responseJson, response);
+#endif
+ return response;
}
-
- internal static Task Delete(string path, HttpOptions options)
+ internal static async Task Send(string method, string path, HttpOptions options)
{
- return SendRequest("DELETE", path, null, options, false);
+ string responseJson = await SendRequest("POST", 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 +182,7 @@ namespace Discord.Helpers
else
return null;
}
+
}
private static Stream GetDecoder(string contentEncoding, MemoryStream encodedStream)
@@ -138,5 +197,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 62%
rename from Discord.Net/Models/ChatMessage.cs
rename to Discord.Net/Models/Message.cs
index 1b8d95d90..e8fc57e37 100644
--- a/Discord.Net/Models/ChatMessage.cs
+++ b/Discord.Net/Models/Message.cs
@@ -3,13 +3,21 @@ using System;
namespace Discord.Models
{
- public sealed class ChatMessage : ChatMessageReference
+ public sealed class Message
{
+ private readonly DiscordClient _client;
+
+ public string Id { get; }
+
public bool IsMentioningEveryone { get; internal set; }
public bool IsTTS { get; internal set; }
public string Text { get; internal set; }
public DateTime Timestamp { get; internal set; }
-
+
+ public string ChannelId { get; }
+ [JsonIgnore]
+ public Channel Channel { get { return _client.GetChannel(ChannelId); } }
+
public string UserId { get; internal set; }
[JsonIgnore]
public User User { get { return _client.GetUser(UserId); } }
@@ -18,10 +26,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..9db51651b 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")]
diff --git a/README.md b/README.md
index 721684fc8..90281fa07 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# Discord.Net
+# Discord.Net v0.2
A .Net API Wrapper for the Discord client (http://discordapp.com).
## This is an alpha!
@@ -6,19 +6,17 @@ The Discord API is still in active development, meaning this library may break a
Discord.Net is also in early development so several functions may be unstable or not work at all.
# Features
-- Login/Logout (account or anonymous)
-- Accepting Invites (standard or human readable)
-- Deleting Invites
+- Login/Logout (with credentials or anonymous)
+- Accepting/Creating/Deleting Invites (standard or human readable)
- Receiving/Sending Messages
- Creating/Destroying Servers
- Creating/Destroying Channels
+- Kick/Ban/Unban/Mute/Unmute/Deafen/Undeafen Users
- Several Discord Events
# Upcoming
- Modifying User/Channel/Server Settings
-- Creating Invites
-- Kick/Ban/Unban/Mute/Unmute/Deafen/Undeafen
-- Sending Private Messages
+- Sending Private Messages (Replies are supported, starting a new private chat currently is not)
# Example (Echo Client)
```
@@ -31,6 +29,7 @@ await client.Connect("discordtest@email.com", "Password123");
await client.AcceptInvite("channel-invite-code");
```
-# Remarks
+# Known Issues
-Due to current Discord restrictions, private messages are blocked unless both the sender and recipient are members of the same server.
+- Due to current Discord restrictions, private messages are blocked unless both the sender and recipient are members of the same server.
+- Caches do not currently clean up when their entries are no longer referenced, and there is no cap to the message cache. For now, connecting and disconnecting will clear all caches.