Brandon Smith 10 years ago
parent
commit
8568d259fd
23 changed files with 1207 additions and 480 deletions
  1. +48
    -0
      Discord.Net.Tests/ChannelTests.cs
  2. +90
    -0
      Discord.Net.Tests/Discord.Net.Tests.csproj
  3. +36
    -0
      Discord.Net.Tests/Properties/AssemblyInfo.cs
  4. +7
    -0
      Discord.Net.sln
  5. +80
    -21
      Discord.Net/API/DiscordAPI.cs
  6. +31
    -15
      Discord.Net/API/Endpoints.cs
  7. +91
    -0
      Discord.Net/API/Models/APIResponses.cs
  8. +50
    -51
      Discord.Net/API/Models/ApiRequests.cs
  9. +72
    -34
      Discord.Net/API/Models/Common.cs
  10. +42
    -50
      Discord.Net/API/Models/WebSocketEvents.cs
  11. +6
    -4
      Discord.Net/Discord.Net.csproj
  12. +29
    -30
      Discord.Net/DiscordClient.Events.cs
  13. +331
    -200
      Discord.Net/DiscordClient.cs
  14. +7
    -1
      Discord.Net/DiscordWebSocket.cs
  15. +90
    -0
      Discord.Net/Helpers/AsyncCache.cs
  16. +99
    -24
      Discord.Net/Helpers/Http.cs
  17. +8
    -2
      Discord.Net/Models/Channel.cs
  18. +0
    -22
      Discord.Net/Models/ChatMessageReference.cs
  19. +32
    -0
      Discord.Net/Models/Invite.cs
  20. +15
    -5
      Discord.Net/Models/Message.cs
  21. +32
    -9
      Discord.Net/Models/Server.cs
  22. +3
    -3
      Discord.Net/Properties/AssemblyInfo.cs
  23. +8
    -9
      README.md

+ 48
- 0
Discord.Net.Tests/ChannelTests.cs View File

@@ -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();
}
}
}

+ 90
- 0
Discord.Net.Tests/Discord.Net.Tests.csproj View File

@@ -0,0 +1,90 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{855D6B1D-847B-42DA-BE6A-23683EA89511}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Discord.Net.Tests</RootNamespace>
<AssemblyName>Discord.Net.Tests</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
<ReferencePath>$(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages</ReferencePath>
<IsCodedUITest>False</IsCodedUITest>
<TestProjectType>UnitTest</TestProjectType>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
</ItemGroup>
<Choose>
<When Condition="('$(VisualStudioVersion)' == '10.0' or '$(VisualStudioVersion)' == '') and '$(TargetFrameworkVersion)' == 'v3.5'">
<ItemGroup>
<Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework, Version=10.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
</ItemGroup>
</When>
<Otherwise>
<ItemGroup>
<Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework" />
</ItemGroup>
</Otherwise>
</Choose>
<ItemGroup>
<Compile Include="ChannelTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Settings.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Discord.Net\Discord.Net.csproj">
<Project>{8d23f61b-723c-4966-859d-1119b28bcf19}</Project>
<Name>Discord.Net</Name>
</ProjectReference>
</ItemGroup>
<Choose>
<When Condition="'$(VisualStudioVersion)' == '10.0' And '$(IsCodedUITest)' == 'True'">
<ItemGroup>
<Reference Include="Microsoft.VisualStudio.QualityTools.CodedUITestFramework, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestTools.UITest.Common, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestTools.UITest.Extension, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestTools.UITesting, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
</ItemGroup>
</When>
</Choose>
<Import Project="$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets" Condition="Exists('$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets')" />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

+ 36
- 0
Discord.Net.Tests/Properties/AssemblyInfo.cs View File

@@ -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")]

+ 7
- 0
Discord.Net.sln View File

@@ -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


+ 80
- 21
Discord.Net/API/DiscordAPI.cs View File

@@ -6,17 +6,18 @@ namespace Discord.API
{
internal static class DiscordAPI
{
public static async Task<AuthRegisterResponse> LoginAnonymous(string username, HttpOptions options)
//Auth
public static async Task<APIResponses.AuthRegister> LoginAnonymous(string username, HttpOptions options)
{
var fingerprintResponse = await Http.Post<AuthFingerprintResponse>(Endpoints.AuthFingerprint, options);
var registerRequest = new AuthRegisterRequest { Fingerprint = fingerprintResponse.Fingerprint, Username = username };
var registerResponse = await Http.Post<AuthRegisterResponse>(Endpoints.AuthRegister, registerRequest, options);
var fingerprintResponse = await Http.Post<APIResponses.AuthFingerprint>(Endpoints.AuthFingerprint, options);
var registerRequest = new APIRequests.AuthRegisterRequest { Fingerprint = fingerprintResponse.Fingerprint, Username = username };
var registerResponse = await Http.Post<APIResponses.AuthRegister>(Endpoints.AuthRegister, registerRequest, options);
return registerResponse;
}
public static async Task<AuthLoginResponse> Login(string email, string password, HttpOptions options)
public static async Task<APIResponses.AuthLogin> Login(string email, string password, HttpOptions options)
{
var request = new AuthLoginRequest { Email = email, Password = password };
var response = await Http.Post<AuthLoginResponse>(Endpoints.AuthLogin, request, options);
var request = new APIRequests.AuthLogin { Email = email, Password = password };
var response = await Http.Post<APIResponses.AuthLogin>(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<APIResponses.CreateServer> 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<APIResponses.CreateServer>(Endpoints.Servers, request, options);
}
public static Task LeaveServer(string id, HttpOptions options)
{
return Http.Delete<APIResponses.DeleteServer>(Endpoints.Server(id), options);
}
public static Task DeleteServer(string id, HttpOptions options)

//Channels
public static Task<APIResponses.GetMessages[]> GetMessages(string channelId, HttpOptions options)
{
return Http.Delete(Endpoints.Server(id), options);
}
return Http.Get<APIResponses.GetMessages[]>(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<GetInviteResponse> GetInvite(string id, HttpOptions options)
//Invites
public static Task<APIResponses.CreateInvite> CreateInvite(string channelId, int maxAge, int maxUses, bool isTemporary, bool hasXkcdPass, HttpOptions options)
{
return Http.Get<GetInviteResponse>(Endpoints.Invite(id), options);
var request = new APIRequests.CreateInvite { MaxAge = maxAge, MaxUses = maxUses, IsTemporary = isTemporary, HasXkcdPass = hasXkcdPass };
return Http.Post<APIResponses.CreateInvite>(Endpoints.ChannelInvites(channelId), request, options);
}
public static Task<APIResponses.GetInvite> GetInvite(string id, HttpOptions options)
{
return Http.Get<APIResponses.GetInvite>(Endpoints.Invite(id), options);
}
public static Task AcceptInvite(string id, HttpOptions options)
{
return Http.Post(Endpoints.Invite(id), options);
return Http.Post<APIResponses.AcceptInvite>(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<APIResponses.GetRegions[]> GetVoiceRegions(HttpOptions options)
{
var request = new SendMessageRequest { Content = message, Mentions = mentions };
return Http.Post(Endpoints.ChannelMessages(channelId), request, options);
return Http.Get<APIResponses.GetRegions[]>(Endpoints.VoiceRegions, options);
}
public static Task<APIResponses.GetIce> GetVoiceIce(HttpOptions options)
{
return Http.Get<APIResponses.GetIce>(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);
}
}
}

+ 31
- 15
Discord.Net/API/Endpoints.cs View File

@@ -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";
}
}

+ 91
- 0
Discord.Net/API/Models/APIResponses.cs View File

@@ -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;
}
}
}
}

+ 50
- 51
Discord.Net/API/Models/ApiRequests.cs View File

@@ -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;
}
}
}

Discord.Net/API/Models/General.cs → Discord.Net/API/Models/Common.cs View File

@@ -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;

+ 42
- 50
Discord.Net/API/Models/WebSocketEvents.cs View File

@@ -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;
}
}
}

+ 6
- 4
Discord.Net/Discord.Net.csproj View File

@@ -45,13 +45,15 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="API\Models\General.cs" />
<Compile Include="API\Models\ApiRequests.cs" />
<Compile Include="API\Models\Common.cs" />
<Compile Include="API\Models\APIRequests.cs" />
<Compile Include="API\Endpoints.cs" />
<Compile Include="API\Models\APIResponses.cs" />
<Compile Include="API\Models\WebSocketCommands.cs" />
<Compile Include="Helpers\AsyncCache.cs" />
<Compile Include="Models\Invite.cs" />
<Compile Include="Models\Role.cs" />
<Compile Include="Models\ChatMessageReference.cs" />
<Compile Include="Models\ChatMessage.cs" />
<Compile Include="Models\Message.cs" />
<Compile Include="Models\Channel.cs" />
<Compile Include="DiscordWebSocket.Events.cs" />
<Compile Include="Helpers\Http.cs" />


+ 29
- 30
Discord.Net/DiscordClient.Events.cs View File

@@ -11,7 +11,6 @@ namespace Discord
public readonly string Message;
internal LogMessageEventArgs(string msg) { Message = msg; }
}

public event EventHandler<LogMessageEventArgs> 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<ServerEventArgs> ServerDestroyed;
private void RaiseServerDestroyed(Server server)
{
@@ -75,14 +72,12 @@ namespace Discord
if (ChannelCreated != null)
ChannelCreated(this, new ChannelEventArgs(channel));
}

public event EventHandler<ChannelEventArgs> ChannelDestroyed;
private void RaiseChannelDestroyed(Channel channel)
{
if (ChannelDestroyed != null)
ChannelDestroyed(this, new ChannelEventArgs(channel));
}

public event EventHandler<ChannelEventArgs> 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<MessageCreateEventArgs> MessageCreated;
private void RaiseMessageCreated(ChatMessage msg)
public event EventHandler<MessageEventArgs> MessageCreated;
private void RaiseMessageCreated(Message msg)
{
if (MessageCreated != null)
MessageCreated(this, new MessageCreateEventArgs(msg));
MessageCreated(this, new MessageEventArgs(msg));
}

public event EventHandler<MessageEventArgs> MessageDeleted;
private void RaiseMessageDeleted(ChatMessageReference msg)
private void RaiseMessageDeleted(Message msg)
{
if (MessageDeleted != null)
MessageDeleted(this, new MessageEventArgs(msg));
}

public event EventHandler<MessageEventArgs> MessageUpdated;
private void RaiseMessageUpdated(ChatMessageReference msg)
private void RaiseMessageUpdated(Message msg)
{
if (MessageUpdated != null)
MessageUpdated(this, new MessageEventArgs(msg));
}

public event EventHandler<MessageEventArgs> 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<RoleEventArgs> RoleUpdated;
private void RaiseRoleDeleted(Role role)
{
if (RoleDeleted != null)
RoleDeleted(this, new RoleEventArgs(role));
}

public event EventHandler<RoleEventArgs> RoleDeleted;
private void RaiseRoleUpdated(Role role)
{
@@ -183,7 +168,6 @@ namespace Discord
if (BanAdded != null)
BanAdded(this, new BanEventArgs(user, server));
}

public event EventHandler<BanEventArgs> 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<MemberEventArgs> MemberRemoved;
private void RaiseMemberRemoved(User user, Server server)
{
if (MemberRemoved != null)
MemberRemoved(this, new MemberEventArgs(user, server));
}

public event EventHandler<MemberEventArgs> 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<UserEventArgs> VoiceStateUpdated;
private void RaiseVoiceStateUpdated(User user)
{
if (VoiceStateUpdated != null)
VoiceStateUpdated(this, new UserEventArgs(user));
}

public event EventHandler<UserTypingEventArgs> 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<VoiceServerUpdatedEventArgs> VoiceServerUpdated;
private void RaiseVoiceServerUpdated(Server server, string endpoint)
{
if (VoiceServerUpdated != null)
VoiceServerUpdated(this, new VoiceServerUpdatedEventArgs(server, endpoint));
}
}
}

+ 331
- 200
Discord.Net/DiscordClient.cs View File

@@ -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<User> Users { get { return _users.Values; } }
private ConcurrentDictionary<string, User> _users;
public IEnumerable<User> Users { get { return _users; } }
private AsyncCache<User, API.Models.UserReference> _users;
public User GetUser(string id) => _users[id];

public IEnumerable<Server> Servers { get { return _servers.Values; } }
private ConcurrentDictionary<string, Server> _servers;
public IEnumerable<Server> Servers { get { return _servers; } }
private AsyncCache<Server, API.Models.ServerReference> _servers;
public Server GetServer(string id) => _servers[id];

public IEnumerable<Channel> Channels { get { return _channels.Values; } }
private ConcurrentDictionary<string, Channel> _channels;
public IEnumerable<Channel> Channels { get { return _channels; } }
private AsyncCache<Channel, API.Models.ChannelReference> _channels;
public Channel GetChannel(string id) => _channels[id];

public IEnumerable<Message> Messages { get { return _messages; } }
private AsyncCache<Message, API.Models.MessageReference> _messages;
public Message GetMessage(string id) => _messages[id];

public IEnumerable<Role> Roles { get { return _roles; } }
private AsyncCache<Role, API.Models.Role> _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<string, User>();
_servers = new ConcurrentDictionary<string, Server>();
_channels = new ConcurrentDictionary<string, Channel>();
_servers = new AsyncCache<Server, API.Models.ServerReference>(
(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<Channel, API.Models.ChannelReference>(
(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<Message, API.Models.MessageReference>(
(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<Role, API.Models.Role>(
(key, parentKey) => new Role(key, parentKey, this),
(role, model) =>
{
role.Permissions = model.Permissions;
},
role => { }
);
_users = new AsyncCache<User, API.Models.UserReference>(
(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<WebSocketEvents.GuildCreate>();
var server = UpdateServer(data);
var server = _servers.Update(data.Id, data);
RaiseServerCreated(server);
}
break;
case "GUILD_DELETE":
{
var data = e.Event.ToObject<WebSocketEvents.GuildDelete>();
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<WebSocketEvents.ChannelCreate>();
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<WebSocketEvents.ChannelDelete>();
var channel = DeleteChannel(data.Id);
RaiseChannelDestroyed(channel);
}
break;
case "CHANNEL_UPDATE":
{
var data = e.Event.ToObject<WebSocketEvents.ChannelUpdate>();
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<WebSocketEvents.ChannelDelete>();
var channel = _channels.Remove(data.Id);
if (channel != null)
RaiseChannelDestroyed(channel);
}
break;

//Members
case "GUILD_MEMBER_ADD":
{
var data = e.Event.ToObject<WebSocketEvents.GuildMemberAdd>();
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<WebSocketEvents.GuildMemberRemove>();
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<WebSocketEvents.GuildMemberUpdate>();
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<WebSocketEvents.GuildMemberRemove>();
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<WebSocketEvents.GuildRoleCreateUpdate>();
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<WebSocketEvents.GuildRoleDelete>();
var role = GetRole(data.RoleId, data.GuildId);
RaiseRoleDeleted(role);
}
break;
case "GUILD_ROLE_UPDATE":
{
var data = e.Event.ToObject<WebSocketEvents.GuildRoleCreateUpdate>();
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<WebSocketEvents.GuildRoleDelete>();
var role = _roles.Remove(data.RoleId);
if (role != null)
RaiseRoleDeleted(role);
}
break;

//Roles
//Bans
case "GUILD_BAN_ADD":
{
var data = e.Event.ToObject<WebSocketEvents.GuildBanAddRemove>();
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<WebSocketEvents.GuildBanAddRemove>();
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<WebSocketEvents.MessageCreate>();
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<WebSocketEvents.MessageUpdate>();
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<WebSocketEvents.MessageDelete>();
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<WebSocketEvents.MessageAck>();
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<WebSocketEvents.PresenceUpdate>();
var user = UpdateUser(data);
var user = _users.Update(data.Id, data);
RaisePresenceUpdated(user);
}
break;
case "VOICE_STATE_UPDATE":
{
var data = e.Event.ToObject<WebSocketEvents.VoiceStateUpdate>();
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<WebSocketEvents.TypingStart>();
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<WebSocketEvents.VoiceServerUpdate>();
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<Server> CreateServer(string name, string region)
{
CheckReady();
var response = await DiscordAPI.CreateServer(name, region, _httpOptions);
return _servers.Update(response.Id, response);
}
public Task<Server> LeaveServer(Server server)
=> LeaveServer(server.Id);
public async Task<Server> 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<GetInviteResponse> GetInvite(string id)

//Invites
public Task<Invite> CreateInvite(Server server, int maxAge, int maxUses, bool isTemporary, bool hasXkcdPass)
{
return CreateInvite(server.DefaultChannelId, maxAge, maxUses, isTemporary, hasXkcdPass);
}
public Task<Invite> CreateInvite(Channel channel, int maxAge, int maxUses, bool isTemporary, bool hasXkcdPass)
{
return CreateInvite(channel, maxAge, maxUses, isTemporary, hasXkcdPass);
}
public async Task<Invite> 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<Invite> 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);
}
}
}

+ 7
- 1
Discord.Net/DiscordWebSocket.cs View File

@@ -22,11 +22,13 @@ namespace Discord
private ConcurrentQueue<byte[]> _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<byte[]>();

_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);


+ 90
- 0
Discord.Net/Helpers/AsyncCache.cs View File

@@ -0,0 +1,90 @@
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;

namespace Discord.Helpers
{
public class AsyncCache<TValue, TModel> : IEnumerable<TValue>
where TValue : class
where TModel : class
{
protected readonly ConcurrentDictionary<string, TValue> _dictionary;
private readonly Func<string, string, TValue> _onCreate;
private readonly Action<TValue, TModel> _onUpdate;
private readonly Action<TValue> _onRemove;

public AsyncCache(Func<string, string, TValue> onCreate, Action<TValue, TModel> onUpdate, Action<TValue> onRemove)
{
_dictionary = new ConcurrentDictionary<string, TValue>();
_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<TValue> GetEnumerator()
{
return _dictionary.Values.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return _dictionary.Values.GetEnumerator();
}
}
}

+ 99
- 24
Discord.Net/Helpers/Http.cs View File

@@ -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<ResponseT> Get<ResponseT>(string path, object data, HttpOptions options)
where ResponseT : class
=> Send<ResponseT>("GET", path, data, options);
internal static Task<string> Get(string path, object data, HttpOptions options)
=> Send("GET", path, data, options);
internal static Task<ResponseT> Get<ResponseT>(string path, HttpOptions options)
where ResponseT : class
=> Send<ResponseT>("GET", path, null, options);
internal static Task<string> Get(string path, HttpOptions options)
=> Send("GET", path, null, options);
internal static Task<ResponseT> Post<ResponseT>(string path, object data, HttpOptions options)
where ResponseT : class
=> Send<ResponseT>("POST", path, data, options);
internal static Task<string> Post(string path, object data, HttpOptions options)
=> Send("POST", path, data, options);
internal static Task<ResponseT> Post<ResponseT>(string path, HttpOptions options)
where ResponseT : class
=> Send<ResponseT>("POST", path, null, options);
internal static Task<string> Post(string path, HttpOptions options)
=> Send("POST", path, null, options);
internal static Task<ResponseT> Put<ResponseT>(string path, object data, HttpOptions options)
where ResponseT : class
=> Send<ResponseT>("PUT", path, data, options);
internal static Task<string> Put(string path, object data, HttpOptions options)
=> Send("PUT", path, data, options);
internal static Task<ResponseT> Put<ResponseT>(string path, HttpOptions options)
where ResponseT : class
=> Send<ResponseT>("PUT", path, null, options);
internal static Task<string> Put(string path, HttpOptions options)
=> Send("PUT", path, null, options);

internal static async Task<ResponseT> Get<ResponseT>(string path, object data, HttpOptions options)
internal static Task<ResponseT> Patch<ResponseT>(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<ResponseT>(responseJson);
}
internal static async Task<ResponseT> Get<ResponseT>(string path, HttpOptions options)
=> Send<ResponseT>("PATCH", path, data, options);
internal static Task<string> Patch(string path, object data, HttpOptions options)
=> Send("PATCH", path, data, options);
internal static Task<ResponseT> Patch<ResponseT>(string path, HttpOptions options)
where ResponseT : class
{
string responseJson = await SendRequest("GET", path, null, options, true);
return JsonConvert.DeserializeObject<ResponseT>(responseJson);
}
=> Send<ResponseT>("PATCH", path, null, options);
internal static Task<string> Patch(string path, HttpOptions options)
=> Send("PATCH", path, null, options);

internal static Task<ResponseT> Delete<ResponseT>(string path, object data, HttpOptions options)
where ResponseT : class
=> Send<ResponseT>("DELETE", path, data, options);
internal static Task<string> Delete(string path, object data, HttpOptions options)
=> Send("DELETE", path, data, options);
internal static Task<ResponseT> Delete<ResponseT>(string path, HttpOptions options)
where ResponseT : class
=> Send<ResponseT>("DELETE", path, null, options);
internal static Task<string> Delete(string path, HttpOptions options)
=> Send("DELETE", path, null, options);

internal static async Task<ResponseT> Post<ResponseT>(string path, object data, HttpOptions options)
internal static async Task<ResponseT> Send<ResponseT>(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<ResponseT>(responseJson);
var response = JsonConvert.DeserializeObject<ResponseT>(responseJson);
#if DEBUG
CheckResponse(responseJson, response);
#endif
return response;
}
internal static Task Post(string path, object data, HttpOptions options)
internal static async Task<string> 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<ResponseT> Post<ResponseT>(string path, HttpOptions options)
internal static async Task<ResponseT> Send<ResponseT>(string method, string path, HttpOptions options)
where ResponseT : class
{
string responseJson = await SendRequest("POST", path, null, options, true);
return JsonConvert.DeserializeObject<ResponseT>(responseJson);
}
internal static Task Post(string path, HttpOptions options)
{
return SendRequest("POST", path, null, options, false);
var response = JsonConvert.DeserializeObject<ResponseT>(responseJson);
#if DEBUG
CheckResponse(responseJson, response);
#endif
return response;
}

internal static Task Delete(string path, HttpOptions options)
internal static async Task<string> 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<string> 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<T>(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
}
}

+ 8
- 2
Discord.Net/Models/Channel.cs View File

@@ -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<Message> 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;
}



+ 0
- 22
Discord.Net/Models/ChatMessageReference.cs View File

@@ -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;
}
}
}

+ 32
- 0
Discord.Net/Models/Invite.cs View File

@@ -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;
}
}
}

Discord.Net/Models/ChatMessage.cs → Discord.Net/Models/Message.cs View File

@@ -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()
{

+ 32
- 9
Discord.Net/Models/Server.cs View File

@@ -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<string, bool> _members;
public IEnumerable<string> MemberIds { get { return _members.Keys; } }
[JsonIgnore]
public IEnumerable<User> Members { get { return _members.Keys.Select(x => _client.GetUser(x)); } }

internal ConcurrentDictionary<string, bool> _channels;
public IEnumerable<string> ChannelIds { get { return _channels.Keys; } }
[JsonIgnore]
public IEnumerable<Channel> Channels { get { return _channels.Keys.Select(x => _client.GetChannel(x)); } }
internal ConcurrentDictionary<string, bool> _bans;
public IEnumerable<User> Bans { get { return _bans.Keys.Select(x => _client.GetUser(x)); } }

public IEnumerable<Channel> Channels { get { return _client.Channels.Where(x => x.ServerId == Id); } }
public IEnumerable<Role> 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<string, bool>();
_channels = new ConcurrentDictionary<string, bool>();
_bans = new ConcurrentDictionary<string, bool>();
}

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);
}
}
}

+ 3
- 3
Discord.Net/Properties/AssemblyInfo.cs View File

@@ -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")]

+ 8
- 9
README.md View File

@@ -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.

Loading…
Cancel
Save