@@ -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.RestClien t.OnRequest += (s, e) =>
_res t.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<Guild MemberAddEvent>(_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<Guild MemberUpdateEvent>(_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<Guild MemberRemoveEvent>(_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<Guild MembersChunkEvent>(_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<Guild RoleCreateEvent>(_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<Guild RoleUpdateEvent>(_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<Guild RoleDeleteEvent>(_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<Guild BanAddEvent>(_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<Guild BanRemoveEvent>(_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<Member VoiceStateUpdateEvent>(_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;
}
}
}
}
}