@@ -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(); | |||
} | |||
} | |||
} |
@@ -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> |
@@ -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")] |
@@ -11,6 +11,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution | |||
LICENSE = LICENSE | |||
EndProjectSection | |||
EndProject | |||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Discord.Net.Tests", "Discord.Net.Tests\Discord.Net.Tests.csproj", "{855D6B1D-847B-42DA-BE6A-23683EA89511}" | |||
EndProject | |||
Global | |||
GlobalSection(SolutionConfigurationPlatforms) = preSolution | |||
Debug|Any CPU = Debug|Any CPU | |||
@@ -21,6 +23,10 @@ Global | |||
{8D23F61B-723C-4966-859D-1119B28BCF19}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||
{8D23F61B-723C-4966-859D-1119B28BCF19}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||
{8D23F61B-723C-4966-859D-1119B28BCF19}.Release|Any CPU.Build.0 = Release|Any CPU | |||
{855D6B1D-847B-42DA-BE6A-23683EA89511}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||
{855D6B1D-847B-42DA-BE6A-23683EA89511}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||
{855D6B1D-847B-42DA-BE6A-23683EA89511}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||
{855D6B1D-847B-42DA-BE6A-23683EA89511}.Release|Any CPU.Build.0 = Release|Any CPU | |||
EndGlobalSection | |||
GlobalSection(SolutionProperties) = preSolution | |||
HideSolutionNode = FALSE | |||
@@ -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,61 @@ 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); | |||
} | |||
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); | |||
} | |||
} | |||
} | |||
} |
@@ -2,29 +2,43 @@ | |||
{ | |||
internal static class Endpoints | |||
{ | |||
public static readonly string BaseUrl = "discordapp.com/"; | |||
public static readonly string BaseHttps = "https://" + BaseUrl; | |||
public static readonly string BaseWss = "wss://" + BaseUrl; | |||
public static readonly string BaseUrl = "discordapp.com"; | |||
public static readonly string BaseHttps = $"https://{BaseUrl}"; | |||
// /api | |||
public static readonly string BaseApi = $"{BaseHttps}/api"; | |||
public static readonly string Track = $"{BaseApi}/track"; | |||
public static readonly string Auth = $"{BaseHttps}/api/auth"; | |||
// /api/auth | |||
public static readonly string Auth = $"{BaseApi}/auth"; | |||
public static readonly string AuthFingerprint = $"{Auth}fingerprint"; | |||
public static readonly string AuthRegister = $"{Auth}/register"; | |||
public static readonly string AuthLogin = $"{Auth}/login"; | |||
public static readonly string AuthLogout = $"{Auth}/logout"; | |||
public static readonly string Servers = $"{BaseHttps}/api/guilds"; | |||
// /api/guilds | |||
public static readonly string Servers = $"{BaseApi}/guilds"; | |||
public static string Server(string id) { return $"{Servers}/{id}"; } | |||
public static string ServerMessages(string id) { return $"{Servers}/{id}/messages?limit=50"; } | |||
public static readonly string Invites = $"{BaseHttps}/api/invite"; | |||
// /api/guilds | |||
public static readonly string Invites = $"{BaseApi}/invite"; | |||
public static string Invite(string id) { return $"{Invites}/{id}"; } | |||
public static readonly string Channels = $"{BaseHttps}/api/channels"; | |||
// /api/channels | |||
public static readonly string Channels = $"{BaseApi}/channels"; | |||
public static string Channel(string id) { return $"{Channels}/{id}"; } | |||
public static string ChannelTyping(string id) { return $"{Channels}/{id}/typing"; } | |||
public static string ChannelMessages(string id) { return $"{Channels}/{id}/messages"; } | |||
public static string ChannelMessages(string id, int limit) { return $"{Channels}/{id}/messages?limit={limit}"; } | |||
public static string ChannelInvites(string id) { return $"{Channels}/{id}/invites"; } | |||
public static readonly string WebSocket_Hub = BaseWss + "hub"; | |||
// /api/voice | |||
public static readonly string Voice = $"{BaseApi}/voice"; | |||
public static readonly string VoiceRegions = $"{Voice}/regions"; | |||
public static readonly string VoiceIce = $"{Voice}/ice"; | |||
//Web Sockets | |||
public static readonly string BaseWss = "wss://" + BaseUrl; | |||
public static readonly string WebSocket_Hub = $"{BaseWss}/hub"; | |||
} | |||
} |
@@ -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; | |||
} | |||
} | |||
} | |||
} |
@@ -6,61 +6,49 @@ using Newtonsoft.Json; | |||
namespace Discord.API.Models | |||
{ | |||
public class AuthFingerprintResponse | |||
internal static class APIRequests | |||
{ | |||
[JsonProperty(PropertyName = "fingerprint")] | |||
public string Fingerprint; | |||
} | |||
public class AuthRegisterRequest | |||
{ | |||
[JsonProperty(PropertyName = "fingerprint")] | |||
public string Fingerprint; | |||
[JsonProperty(PropertyName = "username")] | |||
public string Username; | |||
} | |||
public class AuthRegisterResponse : AuthLoginResponse { } | |||
public class AuthLoginRequest | |||
{ | |||
[JsonProperty(PropertyName = "email")] | |||
public string Email; | |||
[JsonProperty(PropertyName = "password")] | |||
public string Password; | |||
} | |||
public class AuthLoginResponse | |||
{ | |||
[JsonProperty(PropertyName = "token")] | |||
public string Token; | |||
} | |||
public class AuthRegisterRequest | |||
{ | |||
[JsonProperty(PropertyName = "fingerprint")] | |||
public string Fingerprint; | |||
[JsonProperty(PropertyName = "username")] | |||
public string Username; | |||
} | |||
public class AuthLogin | |||
{ | |||
[JsonProperty(PropertyName = "email")] | |||
public string Email; | |||
[JsonProperty(PropertyName = "password")] | |||
public string Password; | |||
} | |||
public class CreateServerRequest | |||
{ | |||
[JsonProperty(PropertyName = "name")] | |||
public string Name; | |||
[JsonProperty(PropertyName = "region")] | |||
public string Region; | |||
} | |||
public class CreateServer | |||
{ | |||
[JsonProperty(PropertyName = "name")] | |||
public string Name; | |||
[JsonProperty(PropertyName = "region")] | |||
public string Region; | |||
} | |||
public class GetInviteResponse | |||
{ | |||
[JsonProperty(PropertyName = "inviter")] | |||
public UserInfo Inviter; | |||
[JsonProperty(PropertyName = "guild")] | |||
public ServerInfo Server; | |||
[JsonProperty(PropertyName = "channel")] | |||
public ChannelInfo Channel; | |||
[JsonProperty(PropertyName = "code")] | |||
public string Code; | |||
[JsonProperty(PropertyName = "xkcdpass")] | |||
public string XkcdPass; | |||
} | |||
public class CreateInvite | |||
{ | |||
[JsonProperty(PropertyName = "max_age")] | |||
public int MaxAge; | |||
[JsonProperty(PropertyName = "max_uses")] | |||
public int MaxUses; | |||
[JsonProperty(PropertyName = "temporary")] | |||
public bool IsTemporary; | |||
[JsonProperty(PropertyName = "xkcdpass")] | |||
public bool HasXkcdPass; | |||
} | |||
public class SendMessageRequest | |||
{ | |||
[JsonProperty(PropertyName = "content")] | |||
public string Content; | |||
[JsonProperty(PropertyName = "mentions")] | |||
public string[] Mentions; | |||
public class SendMessage | |||
{ | |||
[JsonProperty(PropertyName = "content")] | |||
public string Content; | |||
[JsonProperty(PropertyName = "mentions")] | |||
public string[] Mentions; | |||
} | |||
} | |||
} |
@@ -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; |
@@ -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; | |||
} | |||
} | |||
} |
@@ -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" /> | |||
@@ -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)); | |||
} | |||
} | |||
} |
@@ -3,10 +3,12 @@ using Discord.API.Models; | |||
using Discord.Helpers; | |||
using Discord.Models; | |||
using System; | |||
using System.Collections.Concurrent; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Reflection; | |||
using System.Threading; | |||
using System.Threading.Tasks; | |||
using Message = Discord.Models.Message; | |||
using Role = Discord.Models.Role; | |||
namespace Discord | |||
@@ -19,26 +21,144 @@ namespace Discord | |||
private HttpOptions _httpOptions; | |||
private bool _isClosing, _isReady; | |||
public string SelfId { get; private set; } | |||
public User Self { get { return GetUser(SelfId); } } | |||
public string UserId { get; private set; } | |||
public User User { get { return _users[UserId]; } } | |||
public IEnumerable<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 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 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 IEnumerable<Message> Messages { get { return _messages; } } | |||
private AsyncCache<Message, API.Models.MessageReference> _messages; | |||
public IEnumerable<Role> Roles { get { return _roles; } } | |||
private AsyncCache<Role, API.Models.Role> _roles; | |||
public bool IsConnected { get { return _isReady; } } | |||
public DiscordClient() | |||
{ | |||
string version = typeof(DiscordClient).GetTypeInfo().Assembly.GetName().Version.ToString(2); | |||
_httpOptions = new HttpOptions { UserAgent = $"Discord.Net/{version} (https://github.com/RogueException/Discord.Net)" }; | |||
_users = new ConcurrentDictionary<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 +185,12 @@ namespace Discord | |||
_channels.Clear(); | |||
_users.Clear(); | |||
SelfId = data.User.Id; | |||
UpdateUser(data.User); | |||
UserId = data.User.Id; | |||
_users.Update(data.User.Id, data.User); | |||
foreach (var server in data.Guilds) | |||
UpdateServer(server); | |||
_servers.Update(server.Id, server); | |||
foreach (var channel in data.PrivateChannels) | |||
UpdateChannel(channel as ChannelInfo, null); | |||
RaiseLoggedIn(); | |||
_channels.Update(channel.Id, null, channel); | |||
} | |||
break; | |||
@@ -80,15 +198,15 @@ namespace Discord | |||
case "GUILD_CREATE": | |||
{ | |||
var data = e.Event.ToObject<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,52 +215,53 @@ 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": | |||
@@ -152,13 +271,6 @@ namespace Discord | |||
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>(); | |||
@@ -166,22 +278,31 @@ namespace Discord | |||
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 +310,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 +318,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 +341,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 +379,7 @@ namespace Discord | |||
_webSocket.OnDebugMessage += (s, e) => RaiseOnDebugMessage(e.Message); | |||
} | |||
//Auth | |||
public async Task Connect(string email, string password) | |||
{ | |||
_isClosing = false; | |||
@@ -271,21 +404,67 @@ namespace Discord | |||
_isClosing = false; | |||
} | |||
public Task CreateServer(string name, string region) | |||
//Servers | |||
public async Task<Server> CreateServer(string name, string region) | |||
{ | |||
CheckReady(); | |||
return DiscordAPI.CreateServer(name, region, _httpOptions); | |||
var response = await DiscordAPI.CreateServer(name, region, _httpOptions); | |||
return _servers.Update(response.Id, response); | |||
} | |||
public Task<Server> LeaveServer(Server server) | |||
{ | |||
return LeaveServer(server.Id); | |||
} | |||
public Task DeleteServer(string id) | |||
public async Task<Server> LeaveServer(string id) | |||
{ | |||
CheckReady(); | |||
return DiscordAPI.DeleteServer(id, _httpOptions); | |||
await DiscordAPI.LeaveServer(id, _httpOptions); | |||
return _servers.Remove(id); | |||
} | |||
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 +481,7 @@ namespace Discord | |||
await DiscordAPI.DeleteInvite(response.Code, _httpOptions); | |||
} | |||
//Chat | |||
public Task SendMessage(string channelId, string text) | |||
{ | |||
return SendMessage(channelId, text, new string[0]); | |||
@@ -323,136 +503,27 @@ namespace Discord | |||
} | |||
} | |||
public User GetUser(string id) | |||
{ | |||
if (id == null) return null; | |||
User user = null; | |||
_users.TryGetValue(id, out user); | |||
return user; | |||
} | |||
private User UpdateUser(UserInfo model, bool addNew = true) | |||
{ | |||
var user = GetUser(model.Id) ?? new User(model.Id, this); | |||
user.Avatar = model.Avatar; | |||
user.Discriminator = model.Discriminator; | |||
user.Name = model.Username; | |||
if (model is SelfUserInfo) | |||
{ | |||
var extendedModel = model as SelfUserInfo; | |||
user.Email = extendedModel.Email; | |||
user.IsVerified = extendedModel.IsVerified; | |||
} | |||
if (model is PresenceUserInfo) | |||
{ | |||
var extendedModel = model as PresenceUserInfo; | |||
user.GameId = extendedModel.GameId; | |||
user.Status = extendedModel.Status; | |||
} | |||
if (addNew) | |||
_users[model.Id] = user; | |||
return user; | |||
} | |||
public Server GetServer(string id) | |||
{ | |||
if (id == null) return null; | |||
Server server = null; | |||
_servers.TryGetValue(id, out server); | |||
return server; | |||
return _servers[id]; | |||
} | |||
private Server UpdateServer(ServerInfo model, bool addNew = true) | |||
{ | |||
var server = GetServer(model.Id) ?? new Server(model.Id, this); | |||
server.Name = model.Name; | |||
if (model is ExtendedServerInfo) | |||
{ | |||
var extendedModel = model as ExtendedServerInfo; | |||
server.AFKChannelId = extendedModel.AFKChannelId; | |||
server.AFKTimeout = extendedModel.AFKTimeout; | |||
server.JoinedAt = extendedModel.JoinedAt; | |||
server.OwnerId = extendedModel.OwnerId; | |||
server.Presence = extendedModel.Presence; | |||
server.Region = extendedModel.Region; | |||
server.Roles = extendedModel.Roles; | |||
server.VoiceStates = extendedModel.VoiceStates; | |||
foreach (var channel in extendedModel.Channels) | |||
{ | |||
UpdateChannel(channel, model.Id, addNew); | |||
server._channels[channel.Id] = true; | |||
} | |||
foreach (var membership in extendedModel.Members) | |||
{ | |||
UpdateUser(membership.User, addNew); | |||
server._members[membership.User.Id] = true; | |||
} | |||
} | |||
if (addNew) | |||
_servers[model.Id] = server; | |||
return server; | |||
} | |||
public Channel GetChannel(string id) | |||
{ | |||
if (id == null) return null; | |||
Channel channel = null; | |||
_channels.TryGetValue(id, out channel); | |||
return channel; | |||
return _channels[id]; | |||
} | |||
private Channel UpdateChannel(ChannelInfo model, string serverId, bool addNew = true) | |||
{ | |||
var channel = GetChannel(model.Id) ?? new Channel(model.Id, serverId, this); | |||
channel.Name = model.Name; | |||
channel.IsPrivate = model.IsPrivate; | |||
channel.PermissionOverwrites = model.PermissionOverwrites; | |||
channel.RecipientId = model.Recipient?.Id; | |||
channel.Type = model.Type; | |||
if (addNew) | |||
_channels[model.Id] = channel; | |||
return channel; | |||
} | |||
private Channel DeleteChannel(string id) | |||
{ | |||
Channel channel = null; | |||
if (_channels.TryRemove(id, out channel)) | |||
{ | |||
bool ignored; | |||
channel.Server._channels.TryRemove(id, out ignored); | |||
} | |||
return channel; | |||
} | |||
//TODO: Temporary measure, unsure if we want to store these or not. | |||
private ChatMessageReference GetMessage(string id, string channelId) | |||
public User GetUser(string id) | |||
{ | |||
if (id == null || channelId == null) return null; | |||
return new ChatMessageReference(id, channelId, this); | |||
return _users[id]; | |||
} | |||
private ChatMessage UpdateMessage(WebSocketEvents.MessageCreate model, bool addNew = true) | |||
public Models.Message GetMessage(string id) | |||
{ | |||
return new ChatMessage(model.Id, model.ChannelId, this) | |||
{ | |||
Attachments = model.Attachments, | |||
Text = model.Content, | |||
Embeds = model.Embeds, | |||
IsMentioningEveryone = model.IsMentioningEveryone, | |||
IsTTS = model.IsTextToSpeech, | |||
UserId = model.Author.Id, | |||
Timestamp = model.Timestamp | |||
}; | |||
return _messages[id]; | |||
} | |||
private Role GetRole(string id, string serverId) | |||
public Role GetRole(string id) | |||
{ | |||
if (id == null || serverId == null) return null; | |||
return new Role(id, serverId, this); | |||
return _roles[id]; | |||
} | |||
private Role UpdateRole(WebSocketEvents.GuildRoleCreateUpdate role, bool addNew = true) | |||
{ | |||
return new Role(role.Role.Id, role.GuildId, this) | |||
@@ -467,5 +538,12 @@ namespace Discord | |||
if (!_isReady) | |||
throw new InvalidOperationException("The client is not currently connected to Discord"); | |||
} | |||
public void Block() | |||
{ | |||
//Blocking call for console apps | |||
//TODO: Improve this | |||
while (!_isClosing) | |||
Thread.Sleep(1000); | |||
} | |||
} | |||
} |
@@ -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); | |||
@@ -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(); | |||
} | |||
} | |||
} |
@@ -1,4 +1,5 @@ | |||
using Newtonsoft.Json; | |||
using Newtonsoft.Json.Linq; | |||
using System; | |||
using System.IO; | |||
using System.IO.Compression; | |||
@@ -24,47 +25,93 @@ namespace Discord.Helpers | |||
internal static class Http | |||
{ | |||
private static readonly RequestCachePolicy _cachePolicy = new RequestCachePolicy(RequestCacheLevel.NoCacheNoStore); | |||
#if DEBUG | |||
private const bool _isDebug = true; | |||
#else | |||
private const bool _isDebug = false; | |||
#endif | |||
//GET | |||
internal static async Task<ResponseT> Get<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); | |||
var response = JsonConvert.DeserializeObject<ResponseT>(responseJson); | |||
#if DEBUG | |||
CheckResponse(responseJson, response); | |||
#endif | |||
return response; | |||
} | |||
internal static async Task<ResponseT> Get<ResponseT>(string path, HttpOptions options) | |||
where ResponseT : class | |||
{ | |||
string responseJson = await SendRequest("GET", path, null, options, true); | |||
return JsonConvert.DeserializeObject<ResponseT>(responseJson); | |||
var response = JsonConvert.DeserializeObject<ResponseT>(responseJson); | |||
#if DEBUG | |||
CheckResponse(responseJson, response); | |||
#endif | |||
return response; | |||
} | |||
//POST | |||
internal static async Task<ResponseT> Post<ResponseT>(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> Post(string path, object data, HttpOptions options) | |||
{ | |||
string requestJson = JsonConvert.SerializeObject(data); | |||
return SendRequest("POST", path, requestJson, options, false); | |||
string responseJson = await SendRequest("POST", path, requestJson, options, _isDebug); | |||
#if DEBUG | |||
CheckEmptyResponse(responseJson); | |||
#endif | |||
return responseJson; | |||
} | |||
internal static async Task<ResponseT> Post<ResponseT>(string path, HttpOptions options) | |||
where ResponseT : class | |||
{ | |||
string responseJson = await SendRequest("POST", path, null, 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, HttpOptions options) | |||
internal static async Task<string> Post(string path, HttpOptions options) | |||
{ | |||
return SendRequest("POST", path, null, options, false); | |||
string responseJson = await SendRequest("POST", path, null, options, _isDebug); | |||
#if DEBUG | |||
CheckEmptyResponse(responseJson); | |||
#endif | |||
return responseJson; | |||
} | |||
internal static Task Delete(string path, HttpOptions options) | |||
//DELETE | |||
internal static async Task<ResponseT> Delete<ResponseT>(string path, HttpOptions options) | |||
where ResponseT : class | |||
{ | |||
string responseJson = await SendRequest("DELETE", path, null, options, true); | |||
var response = JsonConvert.DeserializeObject<ResponseT>(responseJson); | |||
#if DEBUG | |||
CheckResponse(responseJson, response); | |||
#endif | |||
return response; | |||
} | |||
internal static async Task<string> Delete(string path, HttpOptions options) | |||
{ | |||
return SendRequest("DELETE", path, null, options, false); | |||
string responseJson = await SendRequest("DELETE", path, null, options, _isDebug); | |||
#if DEBUG | |||
CheckEmptyResponse(responseJson); | |||
#endif | |||
return responseJson; | |||
} | |||
private static async Task<string> SendRequest(string method, string path, string data, HttpOptions options, bool hasResponse) | |||
@@ -124,6 +171,7 @@ namespace Discord.Helpers | |||
else | |||
return null; | |||
} | |||
} | |||
private static Stream GetDecoder(string contentEncoding, MemoryStream encodedStream) | |||
@@ -138,5 +186,21 @@ namespace Discord.Helpers | |||
throw new ArgumentOutOfRangeException("Unknown encoding: " + contentEncoding); | |||
} | |||
} | |||
#if DEBUG | |||
private static void CheckResponse<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 | |||
} | |||
} |
@@ -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; | |||
} | |||
@@ -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; | |||
} | |||
} | |||
} |
@@ -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; | |||
} | |||
} | |||
} |
@@ -3,8 +3,13 @@ using System; | |||
namespace Discord.Models | |||
{ | |||
public sealed class ChatMessage : ChatMessageReference | |||
public sealed class Message | |||
{ | |||
private readonly DiscordClient _client; | |||
public string Id { get; } | |||
public string ChannelId { get; } | |||
public bool IsMentioningEveryone { get; internal set; } | |||
public bool IsTTS { get; internal set; } | |||
public string Text { get; internal set; } | |||
@@ -18,10 +23,12 @@ namespace Discord.Models | |||
public object[] Attachments { get; internal set; } | |||
public object[] Embeds { get; internal set; } | |||
internal ChatMessage(string id, string channelId, DiscordClient client) | |||
: base(id, channelId, client) | |||
internal Message(string id, string channelId, DiscordClient client) | |||
{ | |||
} | |||
Id = id; | |||
ChannelId = channelId; | |||
_client = client; | |||
} | |||
public override string ToString() | |||
{ |
@@ -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); | |||
} | |||
} | |||
} |
@@ -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")] |