@@ -1,15 +1,32 @@ | |||
using Newtonsoft.Json; | |||
using System; | |||
using System; | |||
using Newtonsoft.Json; | |||
using System.Collections.Generic; | |||
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) | |||
{ | |||
List<ulong> result = new List<ulong>(); | |||
@@ -36,14 +53,11 @@ namespace Discord.API.Converters | |||
writer.WriteEndArray(); | |||
} | |||
} | |||
} | |||
}*/ | |||
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) | |||
{ | |||
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.Rest; | |||
using Discord.Net.WebSockets; | |||
using Newtonsoft.Json; | |||
using System; | |||
using System.Collections.Concurrent; | |||
using System.Collections.Generic; | |||
using System.IO; | |||
using System.Reflection; | |||
using System.Runtime.ExceptionServices; | |||
using System.Security.Cryptography; | |||
using System.Text; | |||
using System.Threading; | |||
@@ -53,8 +53,6 @@ namespace Discord | |||
/// <summary> Provides a connection to the DiscordApp service. </summary> | |||
public partial class DiscordClient | |||
{ | |||
public static readonly string Version = typeof(DiscordClient).GetTypeInfo().Assembly.GetName().Version.ToString(3); | |||
private readonly LogService _log; | |||
private readonly Logger _logger, _restLogger, _cacheLogger; | |||
private readonly Dictionary<Type, object> _singletons; | |||
@@ -63,7 +61,6 @@ namespace Discord | |||
private readonly ManualResetEvent _disconnectedEvent; | |||
private readonly ManualResetEventSlim _connectedEvent; | |||
private readonly TaskManager _taskManager; | |||
private bool _sentInitialLog; | |||
private UserStatus _status; | |||
private int? _gameId; | |||
@@ -76,8 +73,8 @@ namespace Discord | |||
private ConnectionState _state; | |||
/// <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> | |||
public GatewaySocket WebSocket => _webSocket; | |||
@@ -145,8 +142,11 @@ namespace Discord | |||
_cacheLogger = CreateCacheLogger(); | |||
//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) => | |||
{ | |||
if (_state == ConnectionState.Connecting) | |||
@@ -156,18 +156,15 @@ namespace Discord | |||
{ | |||
RaiseDisconnected(e); | |||
}; | |||
_webSocket.ReceivedDispatch += (s, e) => OnReceivedEvent(e); | |||
_api = new DiscordAPIClient(_config); | |||
if (Config.UseMessageQueue) | |||
_pendingMessages = new ConcurrentQueue<MessageQueueItem>(); | |||
Connected += async (s, e) => | |||
{ | |||
_api.CancelToken = _cancelToken; | |||
_rest.SetCancelToken(_cancelToken); | |||
await SendStatus().ConfigureAwait(false); | |||
}; | |||
_restLogger = CreateRestLogger(); | |||
//Import/Export | |||
_messageImporter = new JsonSerializer(); | |||
@@ -216,7 +213,7 @@ namespace Discord | |||
if (_log.Level >= LogSeverity.Verbose) | |||
{ | |||
logger = _log.CreateLogger("Rest"); | |||
_api.RestClient.OnRequest += (s, e) => | |||
_rest.OnRequest += (s, e) => | |||
{ | |||
if (e.Payload != null) | |||
logger.Verbose( $"{e.Method} {e.Path}: {Math.Round(e.ElapsedMilliseconds, 2)} ms ({e.Payload})"); | |||
@@ -280,9 +277,6 @@ namespace Discord | |||
_lock.WaitOne(); | |||
try | |||
{ | |||
if (!_sentInitialLog) | |||
SendInitialLog(); | |||
if (State != ConnectionState.Disconnected) | |||
await Disconnect().ConfigureAwait(false); | |||
await _taskManager.Stop().ConfigureAwait(false); | |||
@@ -347,7 +341,8 @@ namespace Discord | |||
token = LoadToken(tokenPath, key); | |||
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; | |||
SaveToken(tokenPath, key, token); | |||
useCache = false; | |||
@@ -355,17 +350,18 @@ namespace Discord | |||
} | |||
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 = token; | |||
_api.Token = token; | |||
_rest.SetToken(token); | |||
//Get gateway and check token | |||
try | |||
{ | |||
var gatewayResponse = await _api.Gateway().ConfigureAwait(false); | |||
var gatewayResponse = await _rest.Send(new GatewayRequest()).ConfigureAwait(false); | |||
var gateway = gatewayResponse.Url; | |||
_gateway = gateway; | |||
if (_config.LogLevel >= LogSeverity.Verbose) | |||
@@ -399,7 +395,7 @@ namespace Discord | |||
while (_pendingMessages.TryDequeue(out ignored)) { } | |||
} | |||
await _api.Logout().ConfigureAwait(false); | |||
await _rest.Send(new LogoutRequest()).ConfigureAwait(false); | |||
_channels.Clear(); | |||
_users.Clear(); | |||
@@ -528,7 +524,7 @@ namespace Discord | |||
//Members | |||
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); | |||
user.Update(data); | |||
user.UpdateActivity(); | |||
@@ -537,7 +533,7 @@ namespace Discord | |||
break; | |||
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]; | |||
if (user != null) | |||
{ | |||
@@ -548,7 +544,7 @@ namespace Discord | |||
break; | |||
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); | |||
if (user != null) | |||
RaiseUserLeft(user); | |||
@@ -556,7 +552,7 @@ namespace Discord | |||
break; | |||
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) | |||
{ | |||
var user = _users.GetOrAdd(memberData.User.Id, memberData.GuildId); | |||
@@ -569,7 +565,7 @@ namespace Discord | |||
//Roles | |||
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); | |||
role.Update(data.Data); | |||
var server = _servers[data.GuildId]; | |||
@@ -580,7 +576,7 @@ namespace Discord | |||
break; | |||
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]; | |||
if (role != null) | |||
{ | |||
@@ -591,7 +587,7 @@ namespace Discord | |||
break; | |||
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); | |||
if (role != null) | |||
{ | |||
@@ -606,25 +602,23 @@ namespace Discord | |||
//Bans | |||
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]; | |||
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; | |||
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]; | |||
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; | |||
@@ -689,7 +683,7 @@ namespace Discord | |||
case "MESSAGE_ACK": | |||
{ | |||
var data = e.Payload.ToObject<MessageAckEvent>(_webSocket.Serializer); | |||
var msg = GetMessage(data.MessageId); | |||
var msg = _messages[data.MessageId]; | |||
if (msg != null) | |||
RaiseMessageAcknowledged(msg); | |||
} | |||
@@ -727,8 +721,8 @@ namespace Discord | |||
//Voice | |||
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) | |||
{ | |||
/*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 | |||
/// <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) | |||
@@ -946,5 +933,21 @@ namespace Discord | |||
_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 System; | |||
using System.Reflection; | |||
using System.Text; | |||
namespace Discord | |||
@@ -35,18 +36,28 @@ namespace Discord | |||
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; | |||
/// <summary> Version of your application. </summary> | |||
public string AppVersion { get { return _appVersion; } set { SetValue(ref _appVersion, value); UpdateUserAgent(); } } | |||
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> | |||
[JsonIgnore] | |||
public string UserAgent { get { return _userAgent; } } | |||
@@ -114,7 +125,7 @@ namespace Discord | |||
} | |||
builder.Append(' '); | |||
} | |||
builder.Append($"DiscordBot (https://github.com/RogueException/Discord.Net, v{DiscordClient.Version})"); | |||
builder.Append($"DiscordBot ({LibUrl}, v{LibVersion})"); | |||
_userAgent = builder.ToString(); | |||
} | |||
} | |||
@@ -106,13 +106,7 @@ namespace Discord.Net.WebSockets | |||
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) | |||
{ | |||
@@ -1 +0,0 @@ | |||
<StyleCopSettings Version="105" /> |