@@ -0,0 +1,28 @@ | |||||
| |||||
Microsoft Visual Studio Solution File, Format Version 12.00 | |||||
# Visual Studio 14 | |||||
VisualStudioVersion = 14.0.23107.0 | |||||
MinimumVisualStudioVersion = 10.0.40219.1 | |||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Discord.Net", "Discord.Net\Discord.Net.csproj", "{8D23F61B-723C-4966-859D-1119B28BCF19}" | |||||
EndProject | |||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{1DDC89B5-2A88-45E5-A743-7A43E6B5C4B3}" | |||||
ProjectSection(SolutionItems) = preProject | |||||
.gitignore = .gitignore | |||||
LICENSE = LICENSE | |||||
EndProjectSection | |||||
EndProject | |||||
Global | |||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution | |||||
Debug|Any CPU = Debug|Any CPU | |||||
Release|Any CPU = Release|Any CPU | |||||
EndGlobalSection | |||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution | |||||
{8D23F61B-723C-4966-859D-1119B28BCF19}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||||
{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 | |||||
EndGlobalSection | |||||
GlobalSection(SolutionProperties) = preSolution | |||||
HideSolutionNode = FALSE | |||||
EndGlobalSection | |||||
EndGlobal |
@@ -0,0 +1,61 @@ | |||||
using Discord.API.Models; | |||||
using Discord.Helpers; | |||||
using System.Threading.Tasks; | |||||
namespace Discord.API | |||||
{ | |||||
internal static class DiscordAPI | |||||
{ | |||||
public static async Task<AuthRegisterResponse> 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); | |||||
return registerResponse; | |||||
} | |||||
public static async Task<AuthLoginResponse> 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); | |||||
options.Token = response.Token; | |||||
return response; | |||||
} | |||||
public static Task Logout(HttpOptions options) | |||||
{ | |||||
return Http.Post(Endpoints.AuthLogout, options); | |||||
} | |||||
public static Task CreateServer(string name, Region region, HttpOptions options) | |||||
{ | |||||
var request = new CreateServerRequest { Name = name, Region = RegionConverter.Convert(region) }; | |||||
return Http.Post(Endpoints.Servers, request, options); | |||||
} | |||||
public static Task DeleteServer(string id, HttpOptions options) | |||||
{ | |||||
return Http.Delete(Endpoints.Server(id), options); | |||||
} | |||||
public static Task<GetInviteResponse> GetInvite(string id, HttpOptions options) | |||||
{ | |||||
return Http.Get<GetInviteResponse>(Endpoints.Invite(id), options); | |||||
} | |||||
public static Task AcceptInvite(string id, HttpOptions options) | |||||
{ | |||||
return Http.Post(Endpoints.Invite(id), options); | |||||
} | |||||
public static Task DeleteInvite(string id, HttpOptions options) | |||||
{ | |||||
return Http.Delete(Endpoints.Invite(id), options); | |||||
} | |||||
public static Task Typing(string channelId, HttpOptions options) | |||||
{ | |||||
return Http.Post(Endpoints.ChannelTyping(channelId), options); | |||||
} | |||||
public static Task SendMessage(string channelId, string message, string[] mentions, HttpOptions options) | |||||
{ | |||||
var request = new SendMessageRequest { Content = message, Mentions = mentions }; | |||||
return Http.Post(Endpoints.ChannelMessages(channelId), request, options); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,30 @@ | |||||
namespace Discord.API | |||||
{ | |||||
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 Auth = $"{BaseHttps}/api/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"; } | |||||
public static readonly string Invites = $"{BaseHttps}/api/invite"; | |||||
public static string Invite(string id) { return $"{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"; } | |||||
public static readonly string WebSocket_Hub = BaseWss + "hub"; | |||||
} | |||||
} |
@@ -0,0 +1,66 @@ | |||||
//Ignore unused/unassigned variable warnings | |||||
#pragma warning disable CS0649 | |||||
#pragma warning disable CS0169 | |||||
using Newtonsoft.Json; | |||||
namespace Discord.API.Models | |||||
{ | |||||
public class AuthFingerprintResponse | |||||
{ | |||||
[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 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 SendMessageRequest | |||||
{ | |||||
[JsonProperty(PropertyName = "content")] | |||||
public string Content; | |||||
[JsonProperty(PropertyName = "mentions")] | |||||
public string[] Mentions; | |||||
} | |||||
} |
@@ -0,0 +1,131 @@ | |||||
//Ignore unused/unassigned variable warnings | |||||
#pragma warning disable CS0649 | |||||
#pragma warning disable CS0169 | |||||
using Newtonsoft.Json; | |||||
using Newtonsoft.Json.Linq; | |||||
using System; | |||||
namespace Discord.API.Models | |||||
{ | |||||
internal class WebSocketMessage | |||||
{ | |||||
[JsonProperty(PropertyName = "op")] | |||||
public int Operation; | |||||
[JsonProperty(PropertyName = "t")] | |||||
public string Type; | |||||
[JsonProperty(PropertyName = "d")] | |||||
public object Payload; | |||||
} | |||||
internal abstract class WebSocketMessage<T> : WebSocketMessage | |||||
where T : new() | |||||
{ | |||||
public WebSocketMessage() { Payload = new T(); } | |||||
public WebSocketMessage(int op) { Operation = op; Payload = new T(); } | |||||
public WebSocketMessage(int op, T payload) { Operation = op; Payload = payload; } | |||||
[JsonIgnore] | |||||
public new T Payload | |||||
{ | |||||
get { if (base.Payload is JToken) { base.Payload = (base.Payload as JToken).ToObject<T>(); } return (T)base.Payload; } | |||||
set { base.Payload = value; } | |||||
} | |||||
} | |||||
public class UserInfo | |||||
{ | |||||
[JsonProperty(PropertyName = "username")] | |||||
public string Username; | |||||
[JsonProperty(PropertyName = "id")] | |||||
public string Id; | |||||
[JsonProperty(PropertyName = "discriminator")] | |||||
public string Discriminator; | |||||
[JsonProperty(PropertyName = "avatar")] | |||||
public string Avatar; | |||||
} | |||||
public class SelfUserInfo : UserInfo | |||||
{ | |||||
[JsonProperty(PropertyName = "email")] | |||||
public string Email; | |||||
[JsonProperty(PropertyName = "verified")] | |||||
public bool IsVerified; | |||||
} | |||||
public class PresenceUserInfo : UserInfo | |||||
{ | |||||
[JsonProperty(PropertyName = "game_id")] | |||||
public string GameId; | |||||
[JsonProperty(PropertyName = "status")] | |||||
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 | |||||
{ | |||||
[JsonProperty(PropertyName = "id")] | |||||
public string Id; | |||||
[JsonProperty(PropertyName = "name")] | |||||
public string Name; | |||||
[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 class ServerInfo | |||||
{ | |||||
[JsonProperty(PropertyName = "id")] | |||||
public string Id; | |||||
[JsonProperty(PropertyName = "name")] | |||||
public string Name; | |||||
} | |||||
public class ExtendedServerInfo : ServerInfo | |||||
{ | |||||
[JsonProperty(PropertyName = "afk_channel_id")] | |||||
public string AFKChannelId; | |||||
[JsonProperty(PropertyName = "afk_timeout")] | |||||
public int AFKTimeout; | |||||
[JsonProperty(PropertyName = "channels")] | |||||
public ChannelInfo[] Channels; | |||||
[JsonProperty(PropertyName = "joined_at")] | |||||
public DateTime JoinedAt; | |||||
[JsonProperty(PropertyName = "members")] | |||||
public MembershipInfo[] Members; | |||||
[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; | |||||
[JsonProperty(PropertyName = "voice_states")] | |||||
public object[] VoiceStates; | |||||
} | |||||
internal class MessageReference | |||||
{ | |||||
[JsonProperty(PropertyName = "message_id")] | |||||
public string MessageId; | |||||
[JsonProperty(PropertyName = "channel_id")] | |||||
public string ChannelId; | |||||
} | |||||
} |
@@ -0,0 +1,41 @@ | |||||
//Ignore unused/unassigned variable warnings | |||||
#pragma warning disable CS0649 | |||||
#pragma warning disable CS0169 | |||||
using Newtonsoft.Json; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
namespace Discord.API.Models | |||||
{ | |||||
internal static class WebSocketCommands | |||||
{ | |||||
internal sealed class KeepAlive : WebSocketMessage<int> | |||||
{ | |||||
private static DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); | |||||
public KeepAlive() : base(1, (int)(DateTime.UtcNow - epoch).TotalMilliseconds) { } | |||||
} | |||||
internal sealed class Login : WebSocketMessage<Login.Data> | |||||
{ | |||||
public Login() : base(2) { } | |||||
public class Data | |||||
{ | |||||
[JsonProperty(PropertyName = "token")] | |||||
public string Token; | |||||
[JsonProperty(PropertyName = "properties")] | |||||
public Dictionary<string, string> Properties = new Dictionary<string, string>(); | |||||
} | |||||
} | |||||
internal sealed class UpdateStatus : WebSocketMessage<UpdateStatus.Data> | |||||
{ | |||||
public UpdateStatus() : base(3) { } | |||||
public class Data | |||||
{ | |||||
[JsonProperty(PropertyName = "idle_since")] | |||||
public string IdleSince; | |||||
[JsonProperty(PropertyName = "game_id")] | |||||
public string GameId; | |||||
} | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,120 @@ | |||||
//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 WebSocketEvents | |||||
{ | |||||
internal sealed class Ready | |||||
{ | |||||
[JsonProperty(PropertyName = "user")] | |||||
public SelfUserInfo User; | |||||
[JsonProperty(PropertyName = "session_id")] | |||||
public string SessionId; | |||||
[JsonProperty(PropertyName = "read_state")] | |||||
public object[] ReadState; | |||||
[JsonProperty(PropertyName = "guilds")] | |||||
public ExtendedServerInfo[] Guilds; | |||||
[JsonProperty(PropertyName = "private_channels")] | |||||
public ChannelInfo[] PrivateChannels; | |||||
[JsonProperty(PropertyName = "heartbeat_interval")] | |||||
public int HeartbeatInterval; | |||||
} | |||||
internal sealed class GuildCreate : ExtendedServerInfo { } | |||||
internal sealed class GuildDelete : ExtendedServerInfo { } | |||||
internal sealed class ChannelCreate : ChannelInfo { } | |||||
internal sealed class ChannelDelete : ChannelInfo { } | |||||
internal sealed class GuildMemberAdd | |||||
{ | |||||
[JsonProperty(PropertyName = "user")] | |||||
public UserInfo User; | |||||
[JsonProperty(PropertyName = "roles")] | |||||
public object[] Roles; | |||||
[JsonProperty(PropertyName = "joined_at")] | |||||
public DateTime JoinedAt; | |||||
[JsonProperty(PropertyName = "guild_id")] | |||||
public string GuildId; | |||||
} | |||||
internal sealed class GuildMemberRemove | |||||
{ | |||||
[JsonProperty(PropertyName = "user")] | |||||
public UserInfo User; | |||||
[JsonProperty(PropertyName = "guild_id")] | |||||
public string GuildId; | |||||
} | |||||
internal sealed class UserUpdate : SelfUserInfo { } | |||||
internal sealed class PresenceUpdate : PresenceUserInfo { } | |||||
internal sealed class VoiceStateUpdate | |||||
{ | |||||
[JsonProperty(PropertyName = "user_id")] | |||||
public string UserId; | |||||
[JsonProperty(PropertyName = "guild_id")] | |||||
public string GuildId; | |||||
[JsonProperty(PropertyName = "channel_id")] | |||||
public string ChannelId; | |||||
[JsonProperty(PropertyName = "suppress")] | |||||
public bool IsSuppressed; | |||||
[JsonProperty(PropertyName = "session_id")] | |||||
public string SessionId; | |||||
[JsonProperty(PropertyName = "self_mute")] | |||||
public bool IsSelfMuted; | |||||
[JsonProperty(PropertyName = "self_deaf")] | |||||
public bool IsSelfDeafened; | |||||
[JsonProperty(PropertyName = "mute")] | |||||
public bool IsMuted; | |||||
[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 | |||||
{ | |||||
[JsonProperty(PropertyName = "id")] | |||||
public string Id; | |||||
[JsonProperty(PropertyName = "channel_id")] | |||||
public string ChannelId; | |||||
[JsonProperty(PropertyName = "embeds")] | |||||
public object[] Embeds; | |||||
} | |||||
internal sealed class MessageDelete : MessageReference { } | |||||
internal sealed class MessageAck : MessageReference { } | |||||
internal sealed class TypingStart | |||||
{ | |||||
[JsonProperty(PropertyName = "user_id")] | |||||
public string UserId; | |||||
[JsonProperty(PropertyName = "channel_id")] | |||||
public string ChannelId; | |||||
[JsonProperty(PropertyName = "timestamp")] | |||||
public int Timestamp; | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,78 @@ | |||||
<?xml version="1.0" encoding="utf-8"?> | |||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | |||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> | |||||
<PropertyGroup> | |||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> | |||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> | |||||
<ProjectGuid>{8D23F61B-723C-4966-859D-1119B28BCF19}</ProjectGuid> | |||||
<OutputType>Library</OutputType> | |||||
<AppDesignerFolder>Properties</AppDesignerFolder> | |||||
<RootNamespace>Discord</RootNamespace> | |||||
<AssemblyName>Discord.Net</AssemblyName> | |||||
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion> | |||||
<FileAlignment>512</FileAlignment> | |||||
<TargetFrameworkProfile /> | |||||
</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="Newtonsoft.Json, Version=7.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL"> | |||||
<HintPath>..\packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll</HintPath> | |||||
<Private>True</Private> | |||||
</Reference> | |||||
<Reference Include="System" /> | |||||
<Reference Include="System.Core" /> | |||||
<Reference Include="System.Xml.Linq" /> | |||||
<Reference Include="System.Data.DataSetExtensions" /> | |||||
<Reference Include="Microsoft.CSharp" /> | |||||
<Reference Include="System.Data" /> | |||||
<Reference Include="System.Net.Http" /> | |||||
<Reference Include="System.Xml" /> | |||||
</ItemGroup> | |||||
<ItemGroup> | |||||
<Compile Include="API\Models\General.cs" /> | |||||
<Compile Include="API\Models\ApiRequests.cs" /> | |||||
<Compile Include="API\Endpoints.cs" /> | |||||
<Compile Include="API\Models\WebSocketCommands.cs" /> | |||||
<Compile Include="Models\ChatMessageReference.cs" /> | |||||
<Compile Include="Models\ChatMessage.cs" /> | |||||
<Compile Include="Models\Channel.cs" /> | |||||
<Compile Include="DiscordWebSocket.Events.cs" /> | |||||
<Compile Include="Helpers\Http.cs" /> | |||||
<Compile Include="API\DiscordAPI.cs" /> | |||||
<Compile Include="API\Models\WebSocketEvents.cs" /> | |||||
<Compile Include="DiscordClient.cs" /> | |||||
<Compile Include="Region.cs" /> | |||||
<Compile Include="DiscordClient.Events.cs" /> | |||||
<Compile Include="Properties\AssemblyInfo.cs" /> | |||||
<Compile Include="DiscordWebSocket.cs" /> | |||||
<Compile Include="Models\Server.cs" /> | |||||
<Compile Include="Models\User.cs" /> | |||||
</ItemGroup> | |||||
<ItemGroup> | |||||
<None Include="packages.config" /> | |||||
</ItemGroup> | |||||
<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,145 @@ | |||||
using Discord.Models; | |||||
using System; | |||||
namespace Discord | |||||
{ | |||||
public partial class DiscordClient | |||||
{ | |||||
public sealed class ServerEventArgs : EventArgs | |||||
{ | |||||
public readonly Server Server; | |||||
internal ServerEventArgs(Server server) { Server = server; } | |||||
} | |||||
public sealed class ChannelEventArgs : EventArgs | |||||
{ | |||||
public readonly Channel Channel; | |||||
internal ChannelEventArgs(Channel channel) { Channel = channel; } | |||||
} | |||||
public sealed class UserEventArgs : EventArgs | |||||
{ | |||||
public readonly User User; | |||||
internal UserEventArgs(User user) { User = user; } | |||||
} | |||||
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 sealed class LogMessageEventArgs : EventArgs | |||||
{ | |||||
public readonly string Message; | |||||
internal LogMessageEventArgs(string msg) { Message = msg; } | |||||
} | |||||
public sealed class UserTypingEventArgs : EventArgs | |||||
{ | |||||
public readonly User User; | |||||
public readonly Channel Channel; | |||||
internal UserTypingEventArgs(User user, Channel channel) | |||||
{ | |||||
User = user; | |||||
Channel = channel; | |||||
} | |||||
} | |||||
public event EventHandler<LogMessageEventArgs> DebugMessage; | |||||
private void RaiseOnDebugMessage(string message) | |||||
{ | |||||
if (DebugMessage != null) | |||||
DebugMessage(this, new LogMessageEventArgs(message)); | |||||
} | |||||
public event EventHandler Connected; | |||||
private void RaiseConnected() | |||||
{ | |||||
if (Connected != null) | |||||
Connected(this, EventArgs.Empty); | |||||
} | |||||
public event EventHandler Disconnected; | |||||
private void RaiseDisconnected() | |||||
{ | |||||
if (Disconnected != null) | |||||
Disconnected(this, EventArgs.Empty); | |||||
} | |||||
public event EventHandler LoggedIn; | |||||
private void RaiseLoggedIn() | |||||
{ | |||||
if (LoggedIn != null) | |||||
LoggedIn(this, EventArgs.Empty); | |||||
} | |||||
public event EventHandler<ServerEventArgs> ServerCreated, ServerDestroyed; | |||||
private void RaiseServerCreated(Server server) | |||||
{ | |||||
if (ServerCreated != null) | |||||
ServerCreated(this, new ServerEventArgs(server)); | |||||
} | |||||
private void RaiseServerDestroyed(Server server) | |||||
{ | |||||
if (ServerDestroyed != null) | |||||
ServerDestroyed(this, new ServerEventArgs(server)); | |||||
} | |||||
public event EventHandler<ChannelEventArgs> ChannelCreated, ChannelDestroyed; | |||||
private void RaiseChannelCreated(Channel channel) | |||||
{ | |||||
if (ChannelCreated != null) | |||||
ChannelCreated(this, new ChannelEventArgs(channel)); | |||||
} | |||||
private void RaiseChannelDestroyed(Channel channel) | |||||
{ | |||||
if (ChannelDestroyed != null) | |||||
ChannelDestroyed(this, new ChannelEventArgs(channel)); | |||||
} | |||||
public event EventHandler<MessageCreateEventArgs> MessageCreated; | |||||
public event EventHandler<MessageEventArgs> MessageDeleted, MessageUpdated, MessageAcknowledged; | |||||
private void RaiseMessageCreated(ChatMessage msg) | |||||
{ | |||||
if (MessageCreated != null) | |||||
MessageCreated(this, new MessageCreateEventArgs(msg)); | |||||
} | |||||
private void RaiseMessageDeleted(ChatMessageReference msg) | |||||
{ | |||||
if (MessageDeleted != null) | |||||
MessageDeleted(this, new MessageEventArgs(msg)); | |||||
} | |||||
private void RaiseMessageUpdated(ChatMessageReference msg) | |||||
{ | |||||
if (MessageUpdated != null) | |||||
MessageUpdated(this, new MessageEventArgs(msg)); | |||||
} | |||||
private void RaiseMessageAcknowledged(ChatMessageReference msg) | |||||
{ | |||||
if (MessageAcknowledged != null) | |||||
MessageAcknowledged(this, new MessageEventArgs(msg)); | |||||
} | |||||
public event EventHandler<UserEventArgs> PresenceUpdated; | |||||
private void RaisePresenceUpdated(User user) | |||||
{ | |||||
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)); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,400 @@ | |||||
using Discord.API; | |||||
using Discord.API.Models; | |||||
using Discord.Helpers; | |||||
using Discord.Models; | |||||
using System; | |||||
using System.Collections.Concurrent; | |||||
using System.Collections.Generic; | |||||
using System.Reflection; | |||||
using System.Threading.Tasks; | |||||
namespace Discord | |||||
{ | |||||
public partial class DiscordClient | |||||
{ | |||||
private const int MaxMessageSize = 2000; | |||||
private DiscordWebSocket _webSocket; | |||||
private HttpOptions _httpOptions; | |||||
private bool _isClosing, _isReady; | |||||
public string SelfId { get; private set; } | |||||
public User Self { get { return GetUser(SelfId); } } | |||||
public IEnumerable<User> Users { get { return _users.Values; } } | |||||
private ConcurrentDictionary<string, User> _users; | |||||
public IEnumerable<Server> Servers { get { return _servers.Values; } } | |||||
private ConcurrentDictionary<string, Server> _servers; | |||||
public IEnumerable<Channel> Channels { get { return _channels.Values; } } | |||||
private ConcurrentDictionary<string, Channel> _channels; | |||||
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>(); | |||||
_webSocket = new DiscordWebSocket(); | |||||
_webSocket.Connected += (s,e) => RaiseConnected(); | |||||
_webSocket.Disconnected += async (s,e) => | |||||
{ | |||||
//Reconnect if we didn't cause the disconnect | |||||
RaiseDisconnected(); | |||||
if (!_isClosing) | |||||
{ | |||||
await Task.Delay(1000); | |||||
await _webSocket.ConnectAsync(Endpoints.WebSocket_Hub, _httpOptions); | |||||
} | |||||
}; | |||||
_webSocket.GotEvent += (s, e) => | |||||
{ | |||||
switch (e.Type) | |||||
{ | |||||
//Global | |||||
case "READY": //Resync | |||||
{ | |||||
var data = e.Event.ToObject<WebSocketEvents.Ready>(); | |||||
_servers.Clear(); | |||||
_channels.Clear(); | |||||
_users.Clear(); | |||||
SelfId = data.User.Id; | |||||
UpdateUser(data.User); | |||||
foreach (var server in data.Guilds) | |||||
UpdateServer(server); | |||||
foreach (var channel in data.PrivateChannels) | |||||
UpdateChannel(channel as ChannelInfo, null); | |||||
RaiseLoggedIn(); | |||||
} | |||||
break; | |||||
//Servers | |||||
case "GUILD_CREATE": | |||||
{ | |||||
var data = e.Event.ToObject<WebSocketEvents.GuildCreate>(); | |||||
var server = UpdateServer(data); | |||||
RaiseServerCreated(server); | |||||
} | |||||
break; | |||||
case "GUILD_DELETE": | |||||
{ | |||||
var data = e.Event.ToObject<WebSocketEvents.GuildDelete>(); | |||||
Server server; | |||||
if (_servers.TryRemove(data.Id, out server)) | |||||
RaiseServerDestroyed(server); | |||||
} | |||||
break; | |||||
//Channels | |||||
case "CHANNEL_CREATE": | |||||
{ | |||||
var data = e.Event.ToObject<WebSocketEvents.ChannelCreate>(); | |||||
var channel = UpdateChannel(data, null); | |||||
RaiseChannelCreated(channel); | |||||
} | |||||
break; | |||||
case "CHANNEL_DELETE": | |||||
{ | |||||
var data = e.Event.ToObject<WebSocketEvents.ChannelDelete>(); | |||||
var channel = DeleteChannel(data.Id); | |||||
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); | |||||
server._members[user.Id] = true; | |||||
} | |||||
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; | |||||
} | |||||
break; | |||||
//Users | |||||
case "PRESENCE_UPDATE": | |||||
{ | |||||
var data = e.Event.ToObject<WebSocketEvents.PresenceUpdate>(); | |||||
var user = UpdateUser(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 | |||||
RaiseVoiceStateUpdated(user); | |||||
} | |||||
break; | |||||
//Messages | |||||
case "MESSAGE_CREATE": | |||||
{ | |||||
var data = e.Event.ToObject<WebSocketEvents.MessageCreate>(); | |||||
var msg = UpdateMessage(data); | |||||
msg.User.UpdateActivity(data.Timestamp); | |||||
RaiseMessageCreated(msg); | |||||
} | |||||
break; | |||||
case "MESSAGE_UPDATE": | |||||
{ | |||||
var data = e.Event.ToObject<WebSocketEvents.MessageUpdate>(); | |||||
var msg = GetMessage(data.Id, data.ChannelId); | |||||
RaiseMessageUpdated(msg); | |||||
} | |||||
break; | |||||
case "MESSAGE_DELETE": | |||||
{ | |||||
var data = e.Event.ToObject<WebSocketEvents.MessageDelete>(); | |||||
var msg = GetMessage(data.MessageId, data.ChannelId); | |||||
RaiseMessageDeleted(msg); | |||||
} | |||||
break; | |||||
case "MESSAGE_ACK": | |||||
{ | |||||
var data = e.Event.ToObject<WebSocketEvents.MessageAck>(); | |||||
var msg = GetMessage(data.MessageId, data.ChannelId); | |||||
RaiseMessageAcknowledged(msg); | |||||
} | |||||
break; | |||||
case "TYPING_START": | |||||
{ | |||||
var data = e.Event.ToObject<WebSocketEvents.TypingStart>(); | |||||
var channel = GetChannel(data.ChannelId); | |||||
var user = GetUser(data.UserId); | |||||
RaiseUserTyping(user, channel); | |||||
} | |||||
break; | |||||
default: | |||||
RaiseOnDebugMessage("Unknown WebSocket message type: " + e.Type); | |||||
break; | |||||
} | |||||
}; | |||||
_webSocket.OnDebugMessage += (s, e) => RaiseOnDebugMessage(e.Message); | |||||
} | |||||
public async Task Connect(string email, string password) | |||||
{ | |||||
_isClosing = false; | |||||
var response = await DiscordAPI.Login(email, password, _httpOptions); | |||||
_httpOptions.Token = response.Token; | |||||
await _webSocket.ConnectAsync(Endpoints.WebSocket_Hub, _httpOptions); | |||||
_isReady = true; | |||||
} | |||||
public async Task ConnectAnonymous(string username) | |||||
{ | |||||
_isClosing = false; | |||||
var response = await DiscordAPI.LoginAnonymous(username, _httpOptions); | |||||
_httpOptions.Token = response.Token; | |||||
await _webSocket.ConnectAsync(Endpoints.WebSocket_Hub, _httpOptions); | |||||
_isReady = true; | |||||
} | |||||
public async Task Disconnect() | |||||
{ | |||||
_isReady = false; | |||||
_isClosing = true; | |||||
await _webSocket.DisconnectAsync(); | |||||
_isClosing = false; | |||||
} | |||||
public Task CreateServer(string name, Region region) | |||||
{ | |||||
CheckReady(); | |||||
return DiscordAPI.CreateServer(name, region, _httpOptions); | |||||
} | |||||
public Task DeleteServer(string id) | |||||
{ | |||||
CheckReady(); | |||||
return DiscordAPI.DeleteServer(id, _httpOptions); | |||||
} | |||||
public Task<GetInviteResponse> GetInvite(string id) | |||||
{ | |||||
CheckReady(); | |||||
return DiscordAPI.GetInvite(id, _httpOptions); | |||||
} | |||||
public async Task AcceptInvite(string id) | |||||
{ | |||||
CheckReady(); | |||||
//Check if this is a human-readable link and get its ID | |||||
var response = await DiscordAPI.GetInvite(id, _httpOptions); | |||||
await DiscordAPI.AcceptInvite(response.Code, _httpOptions); | |||||
} | |||||
public async Task DeleteInvite(string id) | |||||
{ | |||||
CheckReady(); | |||||
//Check if this is a human-readable link and get its ID | |||||
var response = await DiscordAPI.GetInvite(id, _httpOptions); | |||||
await DiscordAPI.DeleteInvite(response.Code, _httpOptions); | |||||
} | |||||
public Task SendMessage(string channelId, string text) | |||||
{ | |||||
return SendMessage(channelId, text, new string[0]); | |||||
} | |||||
public async Task SendMessage(string channelId, string text, string[] mentions) | |||||
{ | |||||
CheckReady(); | |||||
if (text.Length <= 2000) | |||||
await DiscordAPI.SendMessage(channelId, text, mentions, _httpOptions); | |||||
else | |||||
{ | |||||
int blockCount = (int)Math.Ceiling(text.Length / (double)MaxMessageSize); | |||||
for (int i = 0; i < blockCount; i++) | |||||
{ | |||||
int index = i * MaxMessageSize; | |||||
await DiscordAPI.SendMessage(channelId, text.Substring(index, Math.Min(2000, text.Length - index)), mentions, _httpOptions); | |||||
await Task.Delay(1000); | |||||
} | |||||
} | |||||
} | |||||
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) | |||||
{ | |||||
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; | |||||
} | |||||
_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) | |||||
{ | |||||
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); | |||||
server._channels[channel.Id] = true; | |||||
} | |||||
foreach (var membership in extendedModel.Members) | |||||
{ | |||||
UpdateUser(membership.User); | |||||
server._members[membership.User.Id] = true; | |||||
} | |||||
} | |||||
_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; | |||||
} | |||||
private Channel UpdateChannel(ChannelInfo model, string serverId) | |||||
{ | |||||
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; | |||||
_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) | |||||
{ | |||||
if (id == null || channelId == null) return null; | |||||
var msg = new ChatMessageReference(id, this); | |||||
msg.ChannelId = channelId; | |||||
return msg; | |||||
} | |||||
private ChatMessage UpdateMessage(WebSocketEvents.MessageCreate model) | |||||
{ | |||||
return new ChatMessage(model.Id, this) | |||||
{ | |||||
Attachments = model.Attachments, | |||||
ChannelId = model.ChannelId, | |||||
Text = model.Content, | |||||
Embeds = model.Embeds, | |||||
IsMentioningEveryone = model.IsMentioningEveryone, | |||||
IsTTS = model.IsTextToSpeech, | |||||
UserId = model.Author.Id, | |||||
Timestamp = model.Timestamp | |||||
}; | |||||
} | |||||
private void CheckReady() | |||||
{ | |||||
if (!_isReady) | |||||
throw new InvalidOperationException("The client is not currently connected to Discord"); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,46 @@ | |||||
using Newtonsoft.Json.Linq; | |||||
using System; | |||||
namespace Discord | |||||
{ | |||||
internal partial class DiscordWebSocket | |||||
{ | |||||
public event EventHandler Connected; | |||||
private void RaiseConnected() | |||||
{ | |||||
if (Connected != null) | |||||
Connected(this, EventArgs.Empty); | |||||
} | |||||
public event EventHandler Disconnected; | |||||
private void RaiseDisconnected() | |||||
{ | |||||
if (Disconnected != null) | |||||
Disconnected(this, EventArgs.Empty); | |||||
} | |||||
public event EventHandler<MessageEventArgs> GotEvent; | |||||
public sealed class MessageEventArgs : EventArgs | |||||
{ | |||||
public readonly string Type; | |||||
public readonly JToken Event; | |||||
internal MessageEventArgs(string type, JToken data) | |||||
{ | |||||
Type = type; | |||||
Event = data; | |||||
} | |||||
} | |||||
private void RaiseGotEvent(string type, JToken payload) | |||||
{ | |||||
if (GotEvent != null) | |||||
GotEvent(this, new MessageEventArgs(type, payload)); | |||||
} | |||||
public event EventHandler<DiscordClient.LogMessageEventArgs> OnDebugMessage; | |||||
private void RaiseOnDebugMessage(string message) | |||||
{ | |||||
if (OnDebugMessage != null) | |||||
OnDebugMessage(this, new DiscordClient.LogMessageEventArgs(message)); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,189 @@ | |||||
using Discord.API.Models; | |||||
using Discord.Helpers; | |||||
using Newtonsoft.Json; | |||||
using Newtonsoft.Json.Linq; | |||||
using System; | |||||
using System.Collections.Concurrent; | |||||
using System.Net.WebSockets; | |||||
using System.Text; | |||||
using System.Threading; | |||||
using System.Threading.Tasks; | |||||
namespace Discord | |||||
{ | |||||
internal sealed partial class DiscordWebSocket : IDisposable | |||||
{ | |||||
private const int ReceiveChunkSize = 4096; | |||||
private const int SendChunkSize = 4096; | |||||
private volatile ClientWebSocket _webSocket; | |||||
private volatile CancellationTokenSource _cancelToken; | |||||
private volatile Task _tasks; | |||||
private ConcurrentQueue<byte[]> _sendQueue; | |||||
private int _heartbeatInterval; | |||||
private DateTime _lastHeartbeat; | |||||
public async Task ConnectAsync(string url, HttpOptions options) | |||||
{ | |||||
await DisconnectAsync(); | |||||
_sendQueue = new ConcurrentQueue<byte[]>(); | |||||
_webSocket = new ClientWebSocket(); | |||||
_webSocket.Options.Cookies = options.Cookies; | |||||
_webSocket.Options.KeepAliveInterval = TimeSpan.Zero; | |||||
_cancelToken = new CancellationTokenSource(); | |||||
var cancelToken = _cancelToken.Token; | |||||
await _webSocket.ConnectAsync(new Uri(url), cancelToken); | |||||
_tasks = Task.WhenAll( | |||||
await Task.Factory.StartNew(ReceiveAsync, cancelToken, TaskCreationOptions.LongRunning, TaskScheduler.Default), | |||||
await Task.Factory.StartNew(SendAsync, cancelToken, TaskCreationOptions.LongRunning, TaskScheduler.Default) | |||||
).ContinueWith(x => | |||||
{ | |||||
//Do not clean up until both tasks have ended | |||||
_heartbeatInterval = 0; | |||||
_lastHeartbeat = DateTime.MinValue; | |||||
_webSocket.Dispose(); | |||||
_webSocket = null; | |||||
_cancelToken.Dispose(); | |||||
_cancelToken = null; | |||||
_tasks = null; | |||||
RaiseDisconnected(); | |||||
}); | |||||
WebSocketCommands.Login msg = new WebSocketCommands.Login(); | |||||
msg.Payload.Token = options.Token; | |||||
msg.Payload.Properties["$os"] = ""; | |||||
msg.Payload.Properties["$browser"] = ""; | |||||
msg.Payload.Properties["$device"] = "Discord.Net"; | |||||
msg.Payload.Properties["$referrer"] = ""; | |||||
msg.Payload.Properties["$referring_domain"] = ""; | |||||
SendMessage(msg, cancelToken); | |||||
} | |||||
public async Task DisconnectAsync() | |||||
{ | |||||
if (_webSocket != null) | |||||
{ | |||||
_cancelToken.Cancel(); | |||||
await _tasks; | |||||
} | |||||
} | |||||
private async Task ReceiveAsync() | |||||
{ | |||||
RaiseConnected(); | |||||
var cancelToken = _cancelToken.Token; | |||||
var buffer = new byte[ReceiveChunkSize]; | |||||
var builder = new StringBuilder(); | |||||
try | |||||
{ | |||||
while (_webSocket.State == WebSocketState.Open && !cancelToken.IsCancellationRequested) | |||||
{ | |||||
WebSocketReceiveResult result; | |||||
do | |||||
{ | |||||
result = await _webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), _cancelToken.Token); | |||||
if (result.MessageType == WebSocketMessageType.Close) | |||||
{ | |||||
await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None); | |||||
return; | |||||
} | |||||
else | |||||
builder.Append(Encoding.UTF8.GetString(buffer, 0, result.Count)); | |||||
} | |||||
while (!result.EndOfMessage); | |||||
var msg = JsonConvert.DeserializeObject<WebSocketMessage>(builder.ToString()); | |||||
switch (msg.Operation) | |||||
{ | |||||
case 0: | |||||
if (msg.Type == "READY") | |||||
{ | |||||
var payload = (msg.Payload as JToken).ToObject<WebSocketEvents.Ready>(); | |||||
_heartbeatInterval = payload.HeartbeatInterval; | |||||
SendMessage(new WebSocketCommands.UpdateStatus(), cancelToken); | |||||
SendMessage(new WebSocketCommands.KeepAlive(), cancelToken); | |||||
} | |||||
RaiseGotEvent(msg.Type, msg.Payload as JToken); | |||||
break; | |||||
default: | |||||
RaiseOnDebugMessage("Unknown WebSocket operation ID: " + msg.Operation); | |||||
break; | |||||
} | |||||
builder.Clear(); | |||||
} | |||||
} | |||||
catch { } | |||||
finally { _cancelToken.Cancel(); } | |||||
} | |||||
private async Task SendAsync() | |||||
{ | |||||
var cancelToken = _cancelToken.Token; | |||||
try | |||||
{ | |||||
byte[] bytes; | |||||
while (_webSocket.State == WebSocketState.Open && !cancelToken.IsCancellationRequested) | |||||
{ | |||||
if (_heartbeatInterval > 0) | |||||
{ | |||||
DateTime now = DateTime.UtcNow; | |||||
if ((now - _lastHeartbeat).TotalMilliseconds > _heartbeatInterval) | |||||
{ | |||||
SendMessage(new WebSocketCommands.KeepAlive(), cancelToken); | |||||
_lastHeartbeat = now; | |||||
} | |||||
} | |||||
while (_sendQueue.TryDequeue(out bytes)) | |||||
{ | |||||
var frameCount = (int)Math.Ceiling((double)bytes.Length / SendChunkSize); | |||||
int offset = 0; | |||||
for (var i = 0; i < frameCount; i++, offset += SendChunkSize) | |||||
{ | |||||
bool isLast = i == (frameCount - 1); | |||||
int count; | |||||
if (isLast) | |||||
count = bytes.Length - (i * SendChunkSize); | |||||
else | |||||
count = SendChunkSize; | |||||
await _webSocket.SendAsync(new ArraySegment<byte>(bytes, offset, count), WebSocketMessageType.Text, isLast, cancelToken); | |||||
} | |||||
} | |||||
await Task.Delay(100); | |||||
} | |||||
} | |||||
catch { } | |||||
finally { _cancelToken.Cancel(); } | |||||
} | |||||
private void SendMessage(object frame, CancellationToken token) | |||||
{ | |||||
var bytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(frame)); | |||||
_sendQueue.Enqueue(bytes); | |||||
} | |||||
#region IDisposable Support | |||||
private bool _isDisposed = false; | |||||
public void Dispose() | |||||
{ | |||||
if (!_isDisposed) | |||||
{ | |||||
DisconnectAsync().Wait(); | |||||
_isDisposed = true; | |||||
} | |||||
} | |||||
#endregion | |||||
} | |||||
} |
@@ -0,0 +1,142 @@ | |||||
using Newtonsoft.Json; | |||||
using System; | |||||
using System.IO; | |||||
using System.IO.Compression; | |||||
using System.Net; | |||||
using System.Net.Cache; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
namespace Discord.Helpers | |||||
{ | |||||
internal class HttpOptions | |||||
{ | |||||
public string UserAgent, Token; | |||||
public CookieContainer Cookies; | |||||
public HttpOptions(string userAgent = null) | |||||
{ | |||||
UserAgent = userAgent ?? "DiscordAPI"; | |||||
Cookies = new CookieContainer(1); | |||||
} | |||||
} | |||||
internal static class Http | |||||
{ | |||||
private static readonly RequestCachePolicy _cachePolicy = new RequestCachePolicy(RequestCacheLevel.NoCacheNoStore); | |||||
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); | |||||
} | |||||
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); | |||||
} | |||||
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); | |||||
} | |||||
internal static Task Post(string path, object data, HttpOptions options) | |||||
{ | |||||
string requestJson = JsonConvert.SerializeObject(data); | |||||
return SendRequest("POST", path, requestJson, options, false); | |||||
} | |||||
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); | |||||
} | |||||
internal static Task Post(string path, HttpOptions options) | |||||
{ | |||||
return SendRequest("POST", path, null, options, false); | |||||
} | |||||
internal static Task Delete(string path, HttpOptions options) | |||||
{ | |||||
return SendRequest("DELETE", path, null, options, false); | |||||
} | |||||
private static async Task<string> SendRequest(string method, string path, string data, HttpOptions options, bool hasResponse) | |||||
{ | |||||
options = options ?? new HttpOptions(); | |||||
//Create Request | |||||
HttpWebRequest request = WebRequest.CreateHttp(path); | |||||
request.Accept = "*/*"; | |||||
request.Headers[HttpRequestHeader.AcceptLanguage] = "en-US;q=0.8"; | |||||
request.Headers[HttpRequestHeader.AcceptEncoding] = "gzip, deflate"; | |||||
request.CachePolicy = _cachePolicy; | |||||
request.CookieContainer = options.Cookies; | |||||
request.Method = method; | |||||
request.UserAgent = options.UserAgent; | |||||
//Add Payload | |||||
if (data != null) | |||||
{ | |||||
byte[] buffer = Encoding.UTF8.GetBytes(data); | |||||
using (var payload = await request.GetRequestStreamAsync()) | |||||
payload.Write(buffer, 0, buffer.Length); | |||||
request.ContentType = "application/json"; | |||||
} | |||||
//Get Response | |||||
using (var response = (HttpWebResponse)(await request.GetResponseAsync())) | |||||
{ | |||||
if (hasResponse) | |||||
{ | |||||
using (var stream = response.GetResponseStream()) | |||||
using (var reader = new BinaryReader(stream)) | |||||
using (var largeBuffer = new MemoryStream()) | |||||
{ | |||||
//Read the response in small chunks and add them to a larger buffer. | |||||
//ContentLength isn't always provided, so this is safer. | |||||
int bytesRead = 0; | |||||
byte[] smallBuffer = new byte[4096]; | |||||
while ((bytesRead = reader.Read(smallBuffer, 0, smallBuffer.Length)) > 0) | |||||
largeBuffer.Write(smallBuffer, 0, bytesRead); | |||||
//Do we need to decompress? | |||||
if (!string.IsNullOrEmpty(response.ContentEncoding)) | |||||
{ | |||||
largeBuffer.Position = 0; | |||||
using (var decoder = GetDecoder(response.ContentEncoding, largeBuffer)) | |||||
using (var decodedStream = new MemoryStream()) | |||||
{ | |||||
decoder.CopyTo(decodedStream); | |||||
return Encoding.UTF8.GetString(decodedStream.GetBuffer(), 0, (int)decodedStream.Length); | |||||
} | |||||
} | |||||
else | |||||
return Encoding.UTF8.GetString(largeBuffer.GetBuffer(), 0, (int)largeBuffer.Length); | |||||
} | |||||
} | |||||
else | |||||
return null; | |||||
} | |||||
} | |||||
private static Stream GetDecoder(string contentEncoding, MemoryStream encodedStream) | |||||
{ | |||||
switch (contentEncoding) | |||||
{ | |||||
case "gzip": | |||||
return new GZipStream(encodedStream, CompressionMode.Decompress, true); | |||||
case "deflate": | |||||
return new DeflateStream(encodedStream, CompressionMode.Decompress, true); | |||||
default: | |||||
throw new ArgumentOutOfRangeException("Unknown encoding: " + contentEncoding); | |||||
} | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,41 @@ | |||||
using Newtonsoft.Json; | |||||
namespace Discord.Models | |||||
{ | |||||
public class Channel | |||||
{ | |||||
protected readonly DiscordClient _client; | |||||
private string _name; | |||||
public string Id { get; } | |||||
public string Name { get { return !IsPrivate ? _name : '@' + Recipient.Name; } internal set { _name = value; } } | |||||
public bool IsPrivate { get; internal set; } | |||||
public string Type { get; internal set; } | |||||
[JsonIgnore] | |||||
public string ServerId { get; } | |||||
[JsonIgnore] | |||||
public Server Server { get { return ServerId != null ? _client.GetServer(ServerId) : null; } } | |||||
[JsonIgnore] | |||||
public string RecipientId { get; internal set; } | |||||
public User Recipient { get { return _client.GetUser(RecipientId); } } | |||||
//Not Implemented | |||||
public object[] PermissionOverwrites { get; internal set; } | |||||
internal Channel(string id, string serverId, DiscordClient client) | |||||
{ | |||||
Id = id; | |||||
ServerId = serverId; | |||||
_client = client; | |||||
} | |||||
public override string ToString() | |||||
{ | |||||
return Name; | |||||
//return Name + " (" + Id + ")"; | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,31 @@ | |||||
using Newtonsoft.Json; | |||||
using System; | |||||
namespace Discord.Models | |||||
{ | |||||
public class ChatMessage : ChatMessageReference | |||||
{ | |||||
public bool IsMentioningEveryone { get; internal set; } | |||||
public bool IsTTS { get; internal set; } | |||||
public string Text { get; internal set; } | |||||
public DateTime Timestamp { get; internal set; } | |||||
[JsonIgnore] | |||||
public string UserId { get; internal set; } | |||||
public User User { get { return _client.GetUser(UserId); } } | |||||
//Not Implemented | |||||
public object[] Attachments { get; internal set; } | |||||
public object[] Embeds { get; internal set; } | |||||
internal ChatMessage(string id, DiscordClient client) | |||||
: base(id, client) | |||||
{ | |||||
} | |||||
public override string ToString() | |||||
{ | |||||
return User.ToString() + ": " + Text; | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,17 @@ | |||||
namespace Discord.Models | |||||
{ | |||||
public class ChatMessageReference | |||||
{ | |||||
protected readonly DiscordClient _client; | |||||
public string Id { get; } | |||||
public string ChannelId { get; internal set; } | |||||
public Channel Channel { get { return _client.GetChannel(ChannelId); } } | |||||
internal ChatMessageReference(string id, DiscordClient client) | |||||
{ | |||||
Id = id; | |||||
_client = client; | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,53 @@ | |||||
using Newtonsoft.Json; | |||||
using System; | |||||
using System.Collections.Concurrent; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
namespace Discord.Models | |||||
{ | |||||
public class Server | |||||
{ | |||||
protected readonly DiscordClient _client; | |||||
public string Id { get; } | |||||
public string Name { get; internal set; } | |||||
public string AFKChannelId { get; internal set; } | |||||
public int AFKTimeout { get; internal set; } | |||||
public DateTime JoinedAt { get; internal set; } | |||||
public string Region { get; internal set; } | |||||
public string OwnerId { get; internal set; } | |||||
public User Owner { get { return _client.GetUser(OwnerId); } } | |||||
internal ConcurrentDictionary<string, bool> _members; | |||||
[JsonIgnore] | |||||
public IEnumerable<string> MemberIds { get { return _members.Keys; } } | |||||
public IEnumerable<User> Members { get { return _members.Keys.Select(x => _client.GetUser(x)); } } | |||||
internal ConcurrentDictionary<string, bool> _channels; | |||||
[JsonIgnore] | |||||
public IEnumerable<string> ChannelIds { get { return _channels.Keys; } } | |||||
public IEnumerable<Channel> Channels { get { return _channels.Keys.Select(x => _client.GetChannel(x)); } } | |||||
//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) | |||||
{ | |||||
Id = id; | |||||
_client = client; | |||||
_members = new ConcurrentDictionary<string, bool>(); | |||||
_channels = new ConcurrentDictionary<string, bool>(); | |||||
} | |||||
public override string ToString() | |||||
{ | |||||
return Name; | |||||
//return Name + " (" + Id + ")"; | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,40 @@ | |||||
using System; | |||||
namespace Discord.Models | |||||
{ | |||||
public class User | |||||
{ | |||||
protected readonly DiscordClient _client; | |||||
public string Id { get; } | |||||
public string Name { get; internal set; } | |||||
public string Avatar { get; internal set; } | |||||
public string Discriminator { get; internal set; } | |||||
public string Email { get; internal set; } | |||||
public bool IsVerified { get; internal set; } = true; | |||||
public string GameId { get; internal set; } | |||||
public string Status { get; internal set; } | |||||
public DateTime LastActivity { get; private set; } | |||||
internal User(string id, DiscordClient client) | |||||
{ | |||||
Id = id; | |||||
_client = client; | |||||
LastActivity = DateTime.UtcNow; | |||||
} | |||||
internal void UpdateActivity(DateTime activity) | |||||
{ | |||||
if (activity > LastActivity) | |||||
LastActivity = activity; | |||||
} | |||||
public override string ToString() | |||||
{ | |||||
return Name; | |||||
//return Name + " (" + Id + ")"; | |||||
} | |||||
} | |||||
} |
@@ -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("DiscordAPI")] | |||||
[assembly: AssemblyDescription("")] | |||||
[assembly: AssemblyConfiguration("")] | |||||
[assembly: AssemblyCompany("")] | |||||
[assembly: AssemblyProduct("DiscordAPI")] | |||||
[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("8d23f61b-723c-4966-859d-1119b28bcf19")] | |||||
// 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")] |
@@ -0,0 +1,32 @@ | |||||
using System; | |||||
namespace Discord | |||||
{ | |||||
public enum Region | |||||
{ | |||||
US_West, | |||||
US_East, | |||||
Singapore, | |||||
London, | |||||
Sydney, | |||||
Amsterdam | |||||
} | |||||
internal static class RegionConverter | |||||
{ | |||||
public static string Convert(Region region) | |||||
{ | |||||
switch (region) | |||||
{ | |||||
case Region.US_West: return "us-west"; | |||||
case Region.US_East: return "us-east"; | |||||
case Region.Singapore: return "singapore"; | |||||
case Region.London: return "london"; | |||||
case Region.Sydney: return "sydney"; | |||||
case Region.Amsterdam: return "amsterdam"; | |||||
default: | |||||
throw new ArgumentOutOfRangeException("Unknown server region"); | |||||
} | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,4 @@ | |||||
<?xml version="1.0" encoding="utf-8"?> | |||||
<packages> | |||||
<package id="Newtonsoft.Json" version="7.0.1" targetFramework="net452" /> | |||||
</packages> |