@@ -1,15 +1,32 @@ | |||||
using Newtonsoft.Json; | |||||
using System; | |||||
using System; | |||||
using Newtonsoft.Json; | |||||
using System.Collections.Generic; | using System.Collections.Generic; | ||||
namespace Discord.API.Converters | namespace Discord.API.Converters | ||||
{ | { | ||||
public class LongStringEnumerableConverter : JsonConverter | |||||
public class LongStringConverter : JsonConverter | |||||
{ | { | ||||
public override bool CanConvert(Type objectType) | |||||
{ | |||||
return objectType == typeof(IEnumerable<ulong>); | |||||
} | |||||
public override bool CanConvert(Type objectType) | |||||
=> objectType == typeof(ulong); | |||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) | |||||
=> IdConvert.ToLong((string)reader.Value); | |||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) | |||||
=> writer.WriteValue(IdConvert.ToString((ulong)value)); | |||||
} | |||||
public class NullableLongStringConverter : JsonConverter | |||||
{ | |||||
public override bool CanConvert(Type objectType) | |||||
=> objectType == typeof(ulong?); | |||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) | |||||
=> IdConvert.ToNullableLong((string)reader.Value); | |||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) | |||||
=> writer.WriteValue(IdConvert.ToString((ulong?)value)); | |||||
} | |||||
/*public class LongStringEnumerableConverter : JsonConverter | |||||
{ | |||||
public override bool CanConvert(Type objectType) => objectType == typeof(IEnumerable<ulong>); | |||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) | public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) | ||||
{ | { | ||||
List<ulong> result = new List<ulong>(); | List<ulong> result = new List<ulong>(); | ||||
@@ -36,14 +53,11 @@ namespace Discord.API.Converters | |||||
writer.WriteEndArray(); | writer.WriteEndArray(); | ||||
} | } | ||||
} | } | ||||
} | |||||
}*/ | |||||
internal class LongStringArrayConverter : JsonConverter | internal class LongStringArrayConverter : JsonConverter | ||||
{ | { | ||||
public override bool CanConvert(Type objectType) | |||||
{ | |||||
return objectType == typeof(IEnumerable<ulong[]>); | |||||
} | |||||
public override bool CanConvert(Type objectType) => objectType == typeof(IEnumerable<ulong[]>); | |||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) | public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) | ||||
{ | { | ||||
var result = new List<ulong>(); | var result = new List<ulong>(); |
@@ -1,37 +0,0 @@ | |||||
using System; | |||||
using Newtonsoft.Json; | |||||
namespace Discord.API.Converters | |||||
{ | |||||
public class LongStringConverter : JsonConverter | |||||
{ | |||||
public override bool CanConvert(Type objectType) | |||||
{ | |||||
return objectType == typeof(ulong); | |||||
} | |||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) | |||||
{ | |||||
return IdConvert.ToLong((string)reader.Value); | |||||
} | |||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) | |||||
{ | |||||
writer.WriteValue(IdConvert.ToString((ulong)value)); | |||||
} | |||||
} | |||||
public class NullableLongStringConverter : JsonConverter | |||||
{ | |||||
public override bool CanConvert(Type objectType) | |||||
{ | |||||
return objectType == typeof(ulong?); | |||||
} | |||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) | |||||
{ | |||||
return IdConvert.ToNullableLong((string)reader.Value); | |||||
} | |||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) | |||||
{ | |||||
writer.WriteValue(IdConvert.ToString((ulong?)value)); | |||||
} | |||||
} | |||||
} |
@@ -1,54 +0,0 @@ | |||||
namespace Discord.API | |||||
{ | |||||
public static class Endpoints | |||||
{ | |||||
public const string BaseStatusApi = "https://status.discordapp.com/api/v2/"; | |||||
public const string BaseApi = "https://discordapp.com/api/"; | |||||
public const string BaseCdn = "https://cdn.discordapp.com/"; | |||||
public const string Gateway = "gateway"; | |||||
public const string Auth = "auth"; | |||||
public const string AuthLogin = "auth/login"; | |||||
public const string AuthLogout = "auth/logout"; | |||||
public const string Channels = "channels"; | |||||
public static string Channel(ulong channelId) => $"channels/{channelId}"; | |||||
public static string ChannelInvites(ulong channelId) => $"channels/{channelId}/invites"; | |||||
public static string ChannelMessages(ulong channelId) => $"channels/{channelId}/messages"; | |||||
public static string ChannelMessages(ulong channelId, int limit) => $"channels/{channelId}/messages?limit={limit}"; | |||||
public static string ChannelMessages(ulong channelId, int limit, ulong relativeId, string relativeDir) => $"channels/{channelId}/messages?limit={limit}&{relativeDir}={relativeId}"; | |||||
public static string ChannelMessage(ulong channelId, ulong msgId) => $"channels/{channelId}/messages/{msgId}"; | |||||
public static string ChannelMessageAck(ulong channelId, ulong msgId) => $"channels/{channelId}/messages/{msgId}/ack"; | |||||
public static string ChannelPermission(ulong channelId, ulong userOrRoleId) => $"channels/{channelId}/permissions/{userOrRoleId}"; | |||||
public static string ChannelTyping(ulong channelId) => $"channels/{channelId}/typing"; | |||||
public const string Servers = "guilds"; | |||||
public static string Server(ulong serverId) => $"guilds/{serverId}"; | |||||
public static string ServerBan(ulong serverId, ulong userId) => $"guilds/{serverId}/bans/{userId}"; | |||||
public static string ServerChannels(ulong serverId) => $"guilds/{serverId}/channels"; | |||||
public static string ServerInvites(ulong serverId) => $"guilds/{serverId}/invites"; | |||||
public static string ServerMember(ulong serverId, ulong userId) => $"guilds/{serverId}/members/{userId}"; | |||||
public static string ServerPrune(ulong serverId, int days) => $"guilds/{serverId}/prune?days={days}"; | |||||
public static string ServerRoles(ulong serverId) => $"guilds/{serverId}/roles"; | |||||
public static string ServerRole(ulong serverId, ulong roleId) => $"guilds/{serverId}/roles/{roleId}"; | |||||
public static string ServerIcon(ulong serverId, string iconId) => BaseCdn + $"icons/{serverId}/{iconId}.jpg"; | |||||
public const string Invites = "invite"; | |||||
public static string Invite(ulong inviteId) => $"invite/{inviteId}"; | |||||
public static string Invite(string inviteIdOrXkcd) => $"invite/{inviteIdOrXkcd}"; | |||||
public static string InviteUrl(ulong inviteId) => $"https://discord.gg/{inviteId}"; | |||||
public static string InviteUrl(string inviteIdOrXkcd) => $"https://discord.gg/{inviteIdOrXkcd}"; | |||||
public const string Users = "users"; | |||||
public static string UserMe => $"users/@me"; | |||||
public static string UserChannels(ulong userId) => $"users/{userId}/channels"; | |||||
public static string UserAvatar(ulong serverId, string avatarId) => BaseCdn + $"avatars/{serverId}/{avatarId}.jpg"; | |||||
public const string Voice = "voice"; | |||||
public const string VoiceRegions = "voice/regions"; | |||||
public const string StatusActiveMaintenance = "scheduled-maintenances/active.json"; | |||||
public const string StatusUpcomingMaintenance = "scheduled-maintenances/upcoming.json"; | |||||
} | |||||
} |
@@ -1,293 +0,0 @@ | |||||
using Discord.API; | |||||
using Discord.Net.Rest; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.IO; | |||||
using System.Linq; | |||||
using System.Threading; | |||||
using System.Threading.Tasks; | |||||
namespace Discord | |||||
{ | |||||
public enum RelativeDirection { Before, After } | |||||
/// <summary> A lightweight wrapper around the Discord API. </summary> | |||||
public class DiscordAPIClient | |||||
{ | |||||
public static readonly string Version = DiscordClient.Version; | |||||
private readonly DiscordConfig _config; | |||||
internal RestClient RestClient => _rest; | |||||
private readonly RestClient _rest; | |||||
public DiscordAPIClient(DiscordConfig config = null, Logger logger = null) | |||||
{ | |||||
_config = config ?? new DiscordConfig(); | |||||
_rest = new RestClient(_config, logger); | |||||
} | |||||
private string _token; | |||||
public string Token | |||||
{ | |||||
get { return _token; } | |||||
set { _token = value; _rest.SetToken(value); } | |||||
} | |||||
private CancellationToken _cancelToken; | |||||
public CancellationToken CancelToken | |||||
{ | |||||
get { return _cancelToken; } | |||||
set { _cancelToken = value; _rest.SetCancelToken(value); } | |||||
} | |||||
//Auth | |||||
public Task<GatewayResponse> Gateway() | |||||
=> _rest.Get<GatewayResponse>(Endpoints.Gateway); | |||||
public async Task<LoginResponse> Login(string email, string password) | |||||
{ | |||||
if (email == null) throw new ArgumentNullException(nameof(email)); | |||||
if (password == null) throw new ArgumentNullException(nameof(password)); | |||||
var request = new LoginRequest { Email = email, Password = password }; | |||||
return await _rest.Post<LoginResponse>(Endpoints.AuthLogin, request).ConfigureAwait(false); | |||||
} | |||||
public Task Logout() | |||||
=> _rest.Post(Endpoints.AuthLogout); | |||||
//Channels | |||||
public Task<CreateChannelResponse> CreateChannel(ulong serverId, string name, string channelType) | |||||
{ | |||||
if (name == null) throw new ArgumentNullException(nameof(name)); | |||||
if (channelType == null) throw new ArgumentNullException(nameof(channelType)); | |||||
var request = new CreateChannelRequest { Name = name, Type = channelType }; | |||||
return _rest.Post<CreateChannelResponse>(Endpoints.ServerChannels(serverId), request); | |||||
} | |||||
public Task<CreateChannelResponse> CreatePMChannel(ulong myId, ulong recipientId) | |||||
{ | |||||
var request = new CreatePMChannelRequest { RecipientId = recipientId }; | |||||
return _rest.Post<CreateChannelResponse>(Endpoints.UserChannels(myId), request); | |||||
} | |||||
public Task<DestroyChannelResponse> DestroyChannel(ulong channelId) | |||||
{ | |||||
return _rest.Delete<DestroyChannelResponse>(Endpoints.Channel(channelId)); | |||||
} | |||||
public Task<EditChannelResponse> EditChannel(ulong channelId, string name = null, string topic = null) | |||||
{ | |||||
var request = new EditChannelRequest { Name = name, Topic = topic }; | |||||
return _rest.Patch<EditChannelResponse>(Endpoints.Channel(channelId), request); | |||||
} | |||||
public Task ReorderChannels(ulong serverId, IEnumerable<ulong> channelIds, int startPos = 0) | |||||
{ | |||||
if (channelIds == null) throw new ArgumentNullException(nameof(channelIds)); | |||||
if (startPos < 0) throw new ArgumentOutOfRangeException(nameof(startPos), "startPos must be a positive integer."); | |||||
uint pos = (uint)startPos; | |||||
var channels = channelIds.Select(x => new ReorderChannelsRequest.Channel { Id = x, Position = pos++ }); | |||||
var request = new ReorderChannelsRequest(channels); | |||||
return _rest.Patch(Endpoints.ServerChannels(serverId), request); | |||||
} | |||||
public Task<GetMessagesResponse> GetMessages(ulong channelId, int count, ulong? relativeMessageId = null, RelativeDirection relativeDir = RelativeDirection.Before) | |||||
{ | |||||
if (relativeMessageId != null) | |||||
return _rest.Get<GetMessagesResponse>(Endpoints.ChannelMessages(channelId, count, relativeMessageId.Value, relativeDir == RelativeDirection.Before ? "before" : "after")); | |||||
else | |||||
return _rest.Get<GetMessagesResponse>(Endpoints.ChannelMessages(channelId, count)); | |||||
} | |||||
//Incidents | |||||
public Task<GetIncidentsResponse> GetActiveIncidents() | |||||
{ | |||||
return _rest.Get<GetIncidentsResponse>(Endpoints.StatusActiveMaintenance); | |||||
} | |||||
public Task<GetIncidentsResponse> GetUpcomingIncidents() | |||||
{ | |||||
return _rest.Get<GetIncidentsResponse>(Endpoints.StatusUpcomingMaintenance); | |||||
} | |||||
//Invites | |||||
public Task<CreateInviteResponse> CreateInvite(ulong channelId, int maxAge, int maxUses, bool tempMembership, bool hasXkcd) | |||||
{ | |||||
var request = new CreateInviteRequest { MaxAge = maxAge, MaxUses = maxUses, IsTemporary = tempMembership, WithXkcdPass = hasXkcd }; | |||||
return _rest.Post<CreateInviteResponse>(Endpoints.ChannelInvites(channelId), request); | |||||
} | |||||
public Task<GetInviteResponse> GetInvite(string inviteIdOrXkcd) | |||||
{ | |||||
if (inviteIdOrXkcd == null) throw new ArgumentNullException(nameof(inviteIdOrXkcd)); | |||||
return _rest.Get<GetInviteResponse>(Endpoints.Invite(inviteIdOrXkcd)); | |||||
} | |||||
public Task<GetInvitesResponse> GetInvites(ulong serverId) | |||||
{ | |||||
return _rest.Get<GetInvitesResponse>(Endpoints.ServerInvites(serverId)); | |||||
} | |||||
public Task<AcceptInviteResponse> AcceptInvite(string inviteId) | |||||
{ | |||||
if (inviteId == null) throw new ArgumentNullException(nameof(inviteId)); | |||||
return _rest.Post<AcceptInviteResponse>(Endpoints.Invite(inviteId)); | |||||
} | |||||
public Task DeleteInvite(string inviteId) | |||||
{ | |||||
if (inviteId == null) throw new ArgumentNullException(nameof(inviteId)); | |||||
return _rest.Delete(Endpoints.Invite(inviteId)); | |||||
} | |||||
//Users | |||||
public Task EditUser(ulong serverId, ulong userId, bool? mute = null, bool? deaf = null, ulong? voiceChannelId = null, IEnumerable<ulong> roleIds = null) | |||||
{ | |||||
var request = new EditMemberRequest { Mute = mute, Deaf = deaf, ChannelId = voiceChannelId, Roles = roleIds }; | |||||
return _rest.Patch(Endpoints.ServerMember(serverId, userId), request); | |||||
} | |||||
public Task KickUser(ulong serverId, ulong userId) | |||||
{ | |||||
return _rest.Delete(Endpoints.ServerMember(serverId, userId)); | |||||
} | |||||
public Task BanUser(ulong serverId, ulong userId) | |||||
{ | |||||
return _rest.Put(Endpoints.ServerBan(serverId, userId)); | |||||
} | |||||
public Task UnbanUser(ulong serverId, ulong userId) | |||||
{ | |||||
return _rest.Delete(Endpoints.ServerBan(serverId, userId)); | |||||
} | |||||
public Task<PruneUsersResponse> PruneUsers(ulong serverId, int days, bool simulate) | |||||
{ | |||||
if (simulate) | |||||
return _rest.Get<PruneUsersResponse>(Endpoints.ServerPrune(serverId, days)); | |||||
else | |||||
return _rest.Post<PruneUsersResponse>(Endpoints.ServerPrune(serverId, days)); | |||||
} | |||||
//Messages | |||||
public Task<SendMessageResponse> SendMessage(ulong channelId, string message, IEnumerable<ulong> mentionedUserIds = null, string nonce = null, bool isTTS = false) | |||||
{ | |||||
if (message == null) throw new ArgumentNullException(nameof(message)); | |||||
var request = new SendMessageRequest { Content = message, Mentions = mentionedUserIds ?? new ulong[0], Nonce = nonce, IsTTS = isTTS ? true : false }; | |||||
return _rest.Post<SendMessageResponse>(Endpoints.ChannelMessages(channelId), request); | |||||
} | |||||
public Task<SendMessageResponse> SendFile(ulong channelId, string filename, Stream stream) | |||||
{ | |||||
if (filename == null) throw new ArgumentNullException(nameof(filename)); | |||||
if (stream == null) throw new ArgumentNullException(nameof(stream)); | |||||
return _rest.PostFile<SendMessageResponse>(Endpoints.ChannelMessages(channelId), filename, stream); | |||||
} | |||||
public Task DeleteMessage(ulong messageId, ulong channelId) | |||||
{ | |||||
return _rest.Delete(Endpoints.ChannelMessage(channelId, messageId)); | |||||
} | |||||
public Task<EditMessageResponse> EditMessage(ulong messageId, ulong channelId, string message = null, IEnumerable<ulong> mentionedUserIds = null) | |||||
{ | |||||
var request = new EditMessageRequest { Content = message, Mentions = mentionedUserIds }; | |||||
return _rest.Patch<EditMessageResponse>(Endpoints.ChannelMessage(channelId, messageId), request); | |||||
} | |||||
public Task AckMessage(ulong messageId, ulong channelId) | |||||
{ | |||||
return _rest.Post(Endpoints.ChannelMessageAck(channelId, messageId)); | |||||
} | |||||
public Task SendIsTyping(ulong channelId) | |||||
{ | |||||
return _rest.Post(Endpoints.ChannelTyping(channelId)); | |||||
} | |||||
//Permissions | |||||
public Task SetChannelPermissions(ulong channelId, ulong userOrRoleId, string idType, uint allow = 0, uint deny = 0) | |||||
{ | |||||
if (idType == null) throw new ArgumentNullException(nameof(idType)); | |||||
var request = new SetChannelPermissionsRequest { Id = userOrRoleId, Type = idType, Allow = allow, Deny = deny }; | |||||
return _rest.Put(Endpoints.ChannelPermission(channelId, userOrRoleId), request); | |||||
} | |||||
public Task DeleteChannelPermissions(ulong channelId, ulong userOrRoleId) | |||||
{ | |||||
return _rest.Delete(Endpoints.ChannelPermission(channelId, userOrRoleId), null); | |||||
} | |||||
//Roles | |||||
public Task<RoleInfo> CreateRole(ulong serverId) | |||||
{ | |||||
return _rest.Post<RoleInfo>(Endpoints.ServerRoles(serverId)); | |||||
} | |||||
public Task DeleteRole(ulong serverId, ulong roleId) | |||||
{ | |||||
return _rest.Delete(Endpoints.ServerRole(serverId, roleId)); | |||||
} | |||||
public Task<RoleInfo> EditRole(ulong serverId, ulong roleId, string name = null, uint? permissions = null, uint? color = null, bool? hoist = null) | |||||
{ | |||||
var request = new EditRoleRequest { Name = name, Permissions = permissions, Hoist = hoist, Color = color }; | |||||
return _rest.Patch<RoleInfo>(Endpoints.ServerRole(serverId, roleId), request); | |||||
} | |||||
public Task ReorderRoles(ulong serverId, IEnumerable<ulong> roleIds, int startPos = 0) | |||||
{ | |||||
if (roleIds == null) throw new ArgumentNullException(nameof(roleIds)); | |||||
if (startPos < 0) throw new ArgumentOutOfRangeException(nameof(startPos), "startPos must be a positive integer."); | |||||
uint pos = (uint)startPos; | |||||
var roles = roleIds.Select(x => new ReorderRolesRequest.Role { Id = x, Position = pos++ }); | |||||
var request = new ReorderRolesRequest(roles); | |||||
return _rest.Patch(Endpoints.ServerRoles(serverId), request); | |||||
} | |||||
//Servers | |||||
public Task<CreateServerResponse> CreateServer(string name, string region) | |||||
{ | |||||
if (name == null) throw new ArgumentNullException(nameof(name)); | |||||
if (region == null) throw new ArgumentNullException(nameof(region)); | |||||
var request = new CreateServerRequest { Name = name, Region = region }; | |||||
return _rest.Post<CreateServerResponse>(Endpoints.Servers, request); | |||||
} | |||||
public Task LeaveServer(ulong serverId) | |||||
{ | |||||
return _rest.Delete<DeleteServerResponse>(Endpoints.Server(serverId)); | |||||
} | |||||
public Task<EditServerResponse> EditServer(ulong serverId, string name, string region, | |||||
Stream icon, ImageType iconType, string existingIcon, | |||||
ulong? afkChannelId, int afkTimeout) | |||||
{ | |||||
var request = new EditServerRequest { | |||||
Name = name, | |||||
Region = region, | |||||
Icon = Base64Picture(icon, iconType, existingIcon), | |||||
AFKChannelId = afkChannelId, | |||||
AFKTimeout = afkTimeout | |||||
}; | |||||
return _rest.Patch<EditServerResponse>(Endpoints.Server(serverId), request); | |||||
} | |||||
//User | |||||
public Task<EditUserResponse> EditProfile(string currentPassword, | |||||
string username, string email, string password, | |||||
Stream avatar, ImageType avatarType, string existingAvatar) | |||||
{ | |||||
if (currentPassword == null) throw new ArgumentNullException(nameof(currentPassword)); | |||||
var request = new EditUserRequest { CurrentPassword = currentPassword, Username = username, | |||||
Email = email, Password = password, Avatar = Base64Picture(avatar, avatarType, existingAvatar) }; | |||||
return _rest.Patch<EditUserResponse>(Endpoints.UserMe, request); | |||||
} | |||||
//Voice | |||||
public Task<GetRegionsResponse> GetVoiceRegions() | |||||
=> _rest.Get<GetRegionsResponse>(Endpoints.VoiceRegions); | |||||
private string Base64Picture(Stream stream, ImageType type, string existingId) | |||||
{ | |||||
if (type == ImageType.None) | |||||
return null; | |||||
else if (stream != null) | |||||
{ | |||||
byte[] bytes = new byte[stream.Length - stream.Position]; | |||||
stream.Read(bytes, 0, bytes.Length); | |||||
string base64 = Convert.ToBase64String(bytes); | |||||
string imageType = type == ImageType.Jpeg ? "image/jpeg;base64" : "image/png;base64"; | |||||
return $"data:{imageType},{base64}"; | |||||
} | |||||
return existingId; | |||||
} | |||||
} | |||||
} |
@@ -1,13 +1,13 @@ | |||||
using Discord.API; | |||||
using Discord.API.Client.GatewaySocket; | |||||
using Discord.API.Client.Rest; | |||||
using Discord.Net; | using Discord.Net; | ||||
using Discord.Net.Rest; | |||||
using Discord.Net.WebSockets; | using Discord.Net.WebSockets; | ||||
using Newtonsoft.Json; | using Newtonsoft.Json; | ||||
using System; | using System; | ||||
using System.Collections.Concurrent; | using System.Collections.Concurrent; | ||||
using System.Collections.Generic; | using System.Collections.Generic; | ||||
using System.IO; | using System.IO; | ||||
using System.Reflection; | |||||
using System.Runtime.ExceptionServices; | |||||
using System.Security.Cryptography; | using System.Security.Cryptography; | ||||
using System.Text; | using System.Text; | ||||
using System.Threading; | using System.Threading; | ||||
@@ -53,8 +53,6 @@ namespace Discord | |||||
/// <summary> Provides a connection to the DiscordApp service. </summary> | /// <summary> Provides a connection to the DiscordApp service. </summary> | ||||
public partial class DiscordClient | public partial class DiscordClient | ||||
{ | { | ||||
public static readonly string Version = typeof(DiscordClient).GetTypeInfo().Assembly.GetName().Version.ToString(3); | |||||
private readonly LogService _log; | private readonly LogService _log; | ||||
private readonly Logger _logger, _restLogger, _cacheLogger; | private readonly Logger _logger, _restLogger, _cacheLogger; | ||||
private readonly Dictionary<Type, object> _singletons; | private readonly Dictionary<Type, object> _singletons; | ||||
@@ -63,7 +61,6 @@ namespace Discord | |||||
private readonly ManualResetEvent _disconnectedEvent; | private readonly ManualResetEvent _disconnectedEvent; | ||||
private readonly ManualResetEventSlim _connectedEvent; | private readonly ManualResetEventSlim _connectedEvent; | ||||
private readonly TaskManager _taskManager; | private readonly TaskManager _taskManager; | ||||
private bool _sentInitialLog; | |||||
private UserStatus _status; | private UserStatus _status; | ||||
private int? _gameId; | private int? _gameId; | ||||
@@ -76,8 +73,8 @@ namespace Discord | |||||
private ConnectionState _state; | private ConnectionState _state; | ||||
/// <summary> Gives direct access to the underlying DiscordAPIClient. This can be used to modify objects not in cache. </summary> | /// <summary> Gives direct access to the underlying DiscordAPIClient. This can be used to modify objects not in cache. </summary> | ||||
public DiscordAPIClient APIClient => _api; | |||||
private readonly DiscordAPIClient _api; | |||||
public RestClient Rest => _rest; | |||||
private readonly RestClient _rest; | |||||
/// <summary> Returns the internal websocket object. </summary> | /// <summary> Returns the internal websocket object. </summary> | ||||
public GatewaySocket WebSocket => _webSocket; | public GatewaySocket WebSocket => _webSocket; | ||||
@@ -145,8 +142,11 @@ namespace Discord | |||||
_cacheLogger = CreateCacheLogger(); | _cacheLogger = CreateCacheLogger(); | ||||
//Networking | //Networking | ||||
_webSocket = new GatewaySocket(this, _log.CreateLogger("WebSocket")); | |||||
var settings = new JsonSerializerSettings(); | |||||
_restLogger = CreateRestLogger(); | |||||
_rest = new RestClient(_config, _restLogger); | |||||
var webSocketLogger = _log.CreateLogger("WebSocket"); | |||||
_webSocket = new GatewaySocket(this, webSocketLogger); | |||||
_webSocket.Connected += (s, e) => | _webSocket.Connected += (s, e) => | ||||
{ | { | ||||
if (_state == ConnectionState.Connecting) | if (_state == ConnectionState.Connecting) | ||||
@@ -156,18 +156,15 @@ namespace Discord | |||||
{ | { | ||||
RaiseDisconnected(e); | RaiseDisconnected(e); | ||||
}; | }; | ||||
_webSocket.ReceivedDispatch += (s, e) => OnReceivedEvent(e); | _webSocket.ReceivedDispatch += (s, e) => OnReceivedEvent(e); | ||||
_api = new DiscordAPIClient(_config); | |||||
if (Config.UseMessageQueue) | if (Config.UseMessageQueue) | ||||
_pendingMessages = new ConcurrentQueue<MessageQueueItem>(); | _pendingMessages = new ConcurrentQueue<MessageQueueItem>(); | ||||
Connected += async (s, e) => | Connected += async (s, e) => | ||||
{ | { | ||||
_api.CancelToken = _cancelToken; | |||||
_rest.SetCancelToken(_cancelToken); | |||||
await SendStatus().ConfigureAwait(false); | await SendStatus().ConfigureAwait(false); | ||||
}; | }; | ||||
_restLogger = CreateRestLogger(); | |||||
//Import/Export | //Import/Export | ||||
_messageImporter = new JsonSerializer(); | _messageImporter = new JsonSerializer(); | ||||
@@ -216,7 +213,7 @@ namespace Discord | |||||
if (_log.Level >= LogSeverity.Verbose) | if (_log.Level >= LogSeverity.Verbose) | ||||
{ | { | ||||
logger = _log.CreateLogger("Rest"); | logger = _log.CreateLogger("Rest"); | ||||
_api.RestClient.OnRequest += (s, e) => | |||||
_rest.OnRequest += (s, e) => | |||||
{ | { | ||||
if (e.Payload != null) | if (e.Payload != null) | ||||
logger.Verbose( $"{e.Method} {e.Path}: {Math.Round(e.ElapsedMilliseconds, 2)} ms ({e.Payload})"); | logger.Verbose( $"{e.Method} {e.Path}: {Math.Round(e.ElapsedMilliseconds, 2)} ms ({e.Payload})"); | ||||
@@ -280,9 +277,6 @@ namespace Discord | |||||
_lock.WaitOne(); | _lock.WaitOne(); | ||||
try | try | ||||
{ | { | ||||
if (!_sentInitialLog) | |||||
SendInitialLog(); | |||||
if (State != ConnectionState.Disconnected) | if (State != ConnectionState.Disconnected) | ||||
await Disconnect().ConfigureAwait(false); | await Disconnect().ConfigureAwait(false); | ||||
await _taskManager.Stop().ConfigureAwait(false); | await _taskManager.Stop().ConfigureAwait(false); | ||||
@@ -347,7 +341,8 @@ namespace Discord | |||||
token = LoadToken(tokenPath, key); | token = LoadToken(tokenPath, key); | ||||
if (token == null) | if (token == null) | ||||
{ | { | ||||
var response = await _api.Login(email, password).ConfigureAwait(false); | |||||
var request = new LoginRequest() { Email = email, Password = password }; | |||||
var response = await _rest.Send(request).ConfigureAwait(false); | |||||
token = response.Token; | token = response.Token; | ||||
SaveToken(tokenPath, key, token); | SaveToken(tokenPath, key, token); | ||||
useCache = false; | useCache = false; | ||||
@@ -355,17 +350,18 @@ namespace Discord | |||||
} | } | ||||
else | else | ||||
{ | { | ||||
var response = await _api.Login(email, password).ConfigureAwait(false); | |||||
var request = new LoginRequest() { Email = email, Password = password }; | |||||
var response = await _rest.Send(request).ConfigureAwait(false); | |||||
token = response.Token; | token = response.Token; | ||||
} | } | ||||
} | } | ||||
_token = token; | _token = token; | ||||
_api.Token = token; | |||||
_rest.SetToken(token); | |||||
//Get gateway and check token | //Get gateway and check token | ||||
try | try | ||||
{ | { | ||||
var gatewayResponse = await _api.Gateway().ConfigureAwait(false); | |||||
var gatewayResponse = await _rest.Send(new GatewayRequest()).ConfigureAwait(false); | |||||
var gateway = gatewayResponse.Url; | var gateway = gatewayResponse.Url; | ||||
_gateway = gateway; | _gateway = gateway; | ||||
if (_config.LogLevel >= LogSeverity.Verbose) | if (_config.LogLevel >= LogSeverity.Verbose) | ||||
@@ -399,7 +395,7 @@ namespace Discord | |||||
while (_pendingMessages.TryDequeue(out ignored)) { } | while (_pendingMessages.TryDequeue(out ignored)) { } | ||||
} | } | ||||
await _api.Logout().ConfigureAwait(false); | |||||
await _rest.Send(new LogoutRequest()).ConfigureAwait(false); | |||||
_channels.Clear(); | _channels.Clear(); | ||||
_users.Clear(); | _users.Clear(); | ||||
@@ -528,7 +524,7 @@ namespace Discord | |||||
//Members | //Members | ||||
case "GUILD_MEMBER_ADD": | case "GUILD_MEMBER_ADD": | ||||
{ | { | ||||
var data = e.Payload.ToObject<MemberAddEvent>(_webSocket.Serializer); | |||||
var data = e.Payload.ToObject<GuildMemberAddEvent>(_webSocket.Serializer); | |||||
var user = _users.GetOrAdd(data.User.Id, data.GuildId); | var user = _users.GetOrAdd(data.User.Id, data.GuildId); | ||||
user.Update(data); | user.Update(data); | ||||
user.UpdateActivity(); | user.UpdateActivity(); | ||||
@@ -537,7 +533,7 @@ namespace Discord | |||||
break; | break; | ||||
case "GUILD_MEMBER_UPDATE": | case "GUILD_MEMBER_UPDATE": | ||||
{ | { | ||||
var data = e.Payload.ToObject<MemberUpdateEvent>(_webSocket.Serializer); | |||||
var data = e.Payload.ToObject<GuildMemberUpdateEvent>(_webSocket.Serializer); | |||||
var user = _users[data.User.Id, data.GuildId]; | var user = _users[data.User.Id, data.GuildId]; | ||||
if (user != null) | if (user != null) | ||||
{ | { | ||||
@@ -548,7 +544,7 @@ namespace Discord | |||||
break; | break; | ||||
case "GUILD_MEMBER_REMOVE": | case "GUILD_MEMBER_REMOVE": | ||||
{ | { | ||||
var data = e.Payload.ToObject<MemberRemoveEvent>(_webSocket.Serializer); | |||||
var data = e.Payload.ToObject<GuildMemberRemoveEvent>(_webSocket.Serializer); | |||||
var user = _users.TryRemove(data.User.Id, data.GuildId); | var user = _users.TryRemove(data.User.Id, data.GuildId); | ||||
if (user != null) | if (user != null) | ||||
RaiseUserLeft(user); | RaiseUserLeft(user); | ||||
@@ -556,7 +552,7 @@ namespace Discord | |||||
break; | break; | ||||
case "GUILD_MEMBERS_CHUNK": | case "GUILD_MEMBERS_CHUNK": | ||||
{ | { | ||||
var data = e.Payload.ToObject<MembersChunkEvent>(_webSocket.Serializer); | |||||
var data = e.Payload.ToObject<GuildMembersChunkEvent>(_webSocket.Serializer); | |||||
foreach (var memberData in data.Members) | foreach (var memberData in data.Members) | ||||
{ | { | ||||
var user = _users.GetOrAdd(memberData.User.Id, memberData.GuildId); | var user = _users.GetOrAdd(memberData.User.Id, memberData.GuildId); | ||||
@@ -569,7 +565,7 @@ namespace Discord | |||||
//Roles | //Roles | ||||
case "GUILD_ROLE_CREATE": | case "GUILD_ROLE_CREATE": | ||||
{ | { | ||||
var data = e.Payload.ToObject<RoleCreateEvent>(_webSocket.Serializer); | |||||
var data = e.Payload.ToObject<GuildRoleCreateEvent>(_webSocket.Serializer); | |||||
var role = _roles.GetOrAdd(data.Data.Id, data.GuildId); | var role = _roles.GetOrAdd(data.Data.Id, data.GuildId); | ||||
role.Update(data.Data); | role.Update(data.Data); | ||||
var server = _servers[data.GuildId]; | var server = _servers[data.GuildId]; | ||||
@@ -580,7 +576,7 @@ namespace Discord | |||||
break; | break; | ||||
case "GUILD_ROLE_UPDATE": | case "GUILD_ROLE_UPDATE": | ||||
{ | { | ||||
var data = e.Payload.ToObject<RoleUpdateEvent>(_webSocket.Serializer); | |||||
var data = e.Payload.ToObject<GuildRoleUpdateEvent>(_webSocket.Serializer); | |||||
var role = _roles[data.Data.Id]; | var role = _roles[data.Data.Id]; | ||||
if (role != null) | if (role != null) | ||||
{ | { | ||||
@@ -591,7 +587,7 @@ namespace Discord | |||||
break; | break; | ||||
case "GUILD_ROLE_DELETE": | case "GUILD_ROLE_DELETE": | ||||
{ | { | ||||
var data = e.Payload.ToObject<RoleDeleteEvent>(_webSocket.Serializer); | |||||
var data = e.Payload.ToObject<GuildRoleDeleteEvent>(_webSocket.Serializer); | |||||
var role = _roles.TryRemove(data.RoleId); | var role = _roles.TryRemove(data.RoleId); | ||||
if (role != null) | if (role != null) | ||||
{ | { | ||||
@@ -606,25 +602,23 @@ namespace Discord | |||||
//Bans | //Bans | ||||
case "GUILD_BAN_ADD": | case "GUILD_BAN_ADD": | ||||
{ | { | ||||
var data = e.Payload.ToObject<BanAddEvent>(_webSocket.Serializer); | |||||
var data = e.Payload.ToObject<GuildBanAddEvent>(_webSocket.Serializer); | |||||
var server = _servers[data.GuildId]; | var server = _servers[data.GuildId]; | ||||
if (server != null) | if (server != null) | ||||
{ | { | ||||
var id = data.User?.Id ?? data.UserId; | |||||
server.AddBan(id); | |||||
RaiseUserBanned(id, server); | |||||
server.AddBan(data.UserId); | |||||
RaiseUserBanned(data.UserId, server); | |||||
} | } | ||||
} | } | ||||
break; | break; | ||||
case "GUILD_BAN_REMOVE": | case "GUILD_BAN_REMOVE": | ||||
{ | { | ||||
var data = e.Payload.ToObject<BanRemoveEvent>(_webSocket.Serializer); | |||||
var data = e.Payload.ToObject<GuildBanRemoveEvent>(_webSocket.Serializer); | |||||
var server = _servers[data.GuildId]; | var server = _servers[data.GuildId]; | ||||
if (server != null) | if (server != null) | ||||
{ | { | ||||
var id = data.User?.Id ?? data.UserId; | |||||
if (server.RemoveBan(id)) | |||||
RaiseUserUnbanned(id, server); | |||||
if (server.RemoveBan(data.UserId)) | |||||
RaiseUserUnbanned(data.UserId, server); | |||||
} | } | ||||
} | } | ||||
break; | break; | ||||
@@ -689,7 +683,7 @@ namespace Discord | |||||
case "MESSAGE_ACK": | case "MESSAGE_ACK": | ||||
{ | { | ||||
var data = e.Payload.ToObject<MessageAckEvent>(_webSocket.Serializer); | var data = e.Payload.ToObject<MessageAckEvent>(_webSocket.Serializer); | ||||
var msg = GetMessage(data.MessageId); | |||||
var msg = _messages[data.MessageId]; | |||||
if (msg != null) | if (msg != null) | ||||
RaiseMessageAcknowledged(msg); | RaiseMessageAcknowledged(msg); | ||||
} | } | ||||
@@ -727,8 +721,8 @@ namespace Discord | |||||
//Voice | //Voice | ||||
case "VOICE_STATE_UPDATE": | case "VOICE_STATE_UPDATE": | ||||
{ | { | ||||
var data = e.Payload.ToObject<MemberVoiceStateUpdateEvent>(_webSocket.Serializer); | |||||
var user = _users[data.UserId, data.GuildId]; | |||||
var data = e.Payload.ToObject<VoiceStateUpdateEvent>(_webSocket.Serializer); | |||||
var user = _users[data.User.Id, data.GuildId]; | |||||
if (user != null) | if (user != null) | ||||
{ | { | ||||
/*var voiceChannel = user.VoiceChannel; | /*var voiceChannel = user.VoiceChannel; | ||||
@@ -779,13 +773,6 @@ namespace Discord | |||||
} | } | ||||
} | } | ||||
private void SendInitialLog() | |||||
{ | |||||
if (_config.LogLevel >= LogSeverity.Verbose) | |||||
_logger.Verbose( $"Config: {JsonConvert.SerializeObject(_config)}"); | |||||
_sentInitialLog = true; | |||||
} | |||||
#region Async Wrapper | #region Async Wrapper | ||||
/// <summary> Blocking call that will not return until client has been stopped. This is mainly intended for use in console applications. </summary> | /// <summary> Blocking call that will not return until client has been stopped. This is mainly intended for use in console applications. </summary> | ||||
public void Run(Func<Task> asyncAction) | public void Run(Func<Task> asyncAction) | ||||
@@ -946,5 +933,21 @@ namespace Discord | |||||
_logger.Warning("Failed to cache token", ex); | _logger.Warning("Failed to cache token", ex); | ||||
} | } | ||||
} | } | ||||
private static string Base64Image(ImageType type, Stream stream, string existingId) | |||||
{ | |||||
if (type == ImageType.None) | |||||
return null; | |||||
else if (stream != null) | |||||
{ | |||||
byte[] bytes = new byte[stream.Length - stream.Position]; | |||||
stream.Read(bytes, 0, bytes.Length); | |||||
string base64 = Convert.ToBase64String(bytes); | |||||
string imageType = type == ImageType.Jpeg ? "image/jpeg;base64" : "image/png;base64"; | |||||
return $"data:{imageType},{base64}"; | |||||
} | |||||
return existingId; | |||||
} | |||||
} | } | ||||
} | } |
@@ -1,5 +1,6 @@ | |||||
using Newtonsoft.Json; | using Newtonsoft.Json; | ||||
using System; | using System; | ||||
using System.Reflection; | |||||
using System.Text; | using System.Text; | ||||
namespace Discord | namespace Discord | ||||
@@ -35,18 +36,28 @@ namespace Discord | |||||
public class DiscordConfig : BaseConfig<DiscordConfig> | public class DiscordConfig : BaseConfig<DiscordConfig> | ||||
{ | { | ||||
//Global | |||||
public static string LibName => "Discord.Net"; | |||||
public static string LibVersion => typeof(DiscordClient).GetTypeInfo().Assembly.GetName().Version.ToString(3); | |||||
public static string LibUrl => "https://github.com/RogueException/Discord.Net"; | |||||
/// <summary> Specifies the minimum log level severity that will be sent to the LogMessage event. Warning: setting this to debug will really hurt performance but should help investigate any internal issues. </summary> | |||||
public LogSeverity LogLevel { get { return _logLevel; } set { SetValue(ref _logLevel, value); } } | |||||
private LogSeverity _logLevel = LogSeverity.Info; | |||||
public static string ClientAPIUrl => "https://discordapp.com/api/"; | |||||
public static string StatusAPIUrl => "https://status.discordapp.com/api/v2/"; | |||||
public static string CDNUrl => "https://cdn.discordapp.com/"; | |||||
public static string InviteUrl => "https://discord.gg/"; | |||||
/// <summary> Name of your application. </summary> | |||||
public string AppName { get { return _appName; } set { SetValue(ref _appName, value); UpdateUserAgent(); } } | |||||
//Global | |||||
/// <summary> Name of your application. </summary> | |||||
public string AppName { get { return _appName; } set { SetValue(ref _appName, value); UpdateUserAgent(); } } | |||||
private string _appName = null; | private string _appName = null; | ||||
/// <summary> Version of your application. </summary> | /// <summary> Version of your application. </summary> | ||||
public string AppVersion { get { return _appVersion; } set { SetValue(ref _appVersion, value); UpdateUserAgent(); } } | public string AppVersion { get { return _appVersion; } set { SetValue(ref _appVersion, value); UpdateUserAgent(); } } | ||||
private string _appVersion = null; | private string _appVersion = null; | ||||
/// <summary> Specifies the minimum log level severity that will be sent to the LogMessage event. Warning: setting this to debug will really hurt performance but should help investigate any internal issues. </summary> | |||||
public LogSeverity LogLevel { get { return _logLevel; } set { SetValue(ref _logLevel, value); } } | |||||
private LogSeverity _logLevel = LogSeverity.Info; | |||||
/// <summary> User Agent string to use when connecting to Discord. </summary> | /// <summary> User Agent string to use when connecting to Discord. </summary> | ||||
[JsonIgnore] | [JsonIgnore] | ||||
public string UserAgent { get { return _userAgent; } } | public string UserAgent { get { return _userAgent; } } | ||||
@@ -114,7 +125,7 @@ namespace Discord | |||||
} | } | ||||
builder.Append(' '); | builder.Append(' '); | ||||
} | } | ||||
builder.Append($"DiscordBot (https://github.com/RogueException/Discord.Net, v{DiscordClient.Version})"); | |||||
builder.Append($"DiscordBot ({LibUrl}, v{LibVersion})"); | |||||
_userAgent = builder.ToString(); | _userAgent = builder.ToString(); | ||||
} | } | ||||
} | } | ||||
@@ -106,13 +106,7 @@ namespace Discord.Net.WebSockets | |||||
RaiseBinaryMessage(e.Data); | RaiseBinaryMessage(e.Data); | ||||
} | } | ||||
public IEnumerable<Task> GetTasks(CancellationToken cancelToken) | |||||
{ | |||||
return new Task[] | |||||
{ | |||||
SendAsync(cancelToken) | |||||
}; | |||||
} | |||||
public IEnumerable<Task> GetTasks(CancellationToken cancelToken) => new Task[] { SendAsync(cancelToken) }; | |||||
private Task SendAsync(CancellationToken cancelToken) | private Task SendAsync(CancellationToken cancelToken) | ||||
{ | { | ||||
@@ -1 +0,0 @@ | |||||
<StyleCopSettings Version="105" /> |