@@ -94,9 +94,6 @@ | |||||
<Compile Include="..\Discord.Net\API\Enums\PermissionTarget.cs"> | <Compile Include="..\Discord.Net\API\Enums\PermissionTarget.cs"> | ||||
<Link>API\Enums\PermissionTarget.cs</Link> | <Link>API\Enums\PermissionTarget.cs</Link> | ||||
</Compile> | </Compile> | ||||
<Compile Include="..\Discord.Net\API\Enums\Region.cs"> | |||||
<Link>API\Enums\Region.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net\API\Enums\StringEnum.cs"> | <Compile Include="..\Discord.Net\API\Enums\StringEnum.cs"> | ||||
<Link>API\Enums\StringEnum.cs</Link> | <Link>API\Enums\StringEnum.cs</Link> | ||||
</Compile> | </Compile> | ||||
@@ -259,6 +256,9 @@ | |||||
<Compile Include="..\Discord.Net\Models\Permissions.cs"> | <Compile Include="..\Discord.Net\Models\Permissions.cs"> | ||||
<Link>Models\Permissions.cs</Link> | <Link>Models\Permissions.cs</Link> | ||||
</Compile> | </Compile> | ||||
<Compile Include="..\Discord.Net\Models\Region.cs"> | |||||
<Link>Models\Region.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net\Models\Role.cs"> | <Compile Include="..\Discord.Net\Models\Role.cs"> | ||||
<Link>Models\Role.cs</Link> | <Link>Models\Role.cs</Link> | ||||
</Compile> | </Compile> | ||||
@@ -0,0 +1,11 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Threading.Tasks; | |||||
namespace Discord.API.Converters | |||||
{ | |||||
public class StringEnumConverter | |||||
{ | |||||
} | |||||
} |
@@ -6,6 +6,8 @@ namespace Discord | |||||
{ | { | ||||
public class DiscordAPIClientConfig | public class DiscordAPIClientConfig | ||||
{ | { | ||||
internal static readonly string UserAgent = $"Discord.Net/{DiscordClient.Version} (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> | /// <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 LogMessageSeverity LogLevel { get { return _logLevel; } set { SetValue(ref _logLevel, value); } } | public LogMessageSeverity LogLevel { get { return _logLevel; } set { SetValue(ref _logLevel, value); } } | ||||
private LogMessageSeverity _logLevel = LogMessageSeverity.Info; | private LogMessageSeverity _logLevel = LogMessageSeverity.Info; | ||||
@@ -21,15 +23,6 @@ namespace Discord | |||||
public NetworkCredential ProxyCredentials { get { return _proxyCredentials; } set { SetValue(ref _proxyCredentials, value); } } | public NetworkCredential ProxyCredentials { get { return _proxyCredentials; } set { SetValue(ref _proxyCredentials, value); } } | ||||
private NetworkCredential _proxyCredentials = null; | private NetworkCredential _proxyCredentials = null; | ||||
internal string UserAgent | |||||
{ | |||||
get | |||||
{ | |||||
string version = typeof(DiscordClientConfig).GetTypeInfo().Assembly.GetName().Version.ToString(2); | |||||
return $"Discord.Net/{version} (https://github.com/RogueException/Discord.Net)"; | |||||
} | |||||
} | |||||
//Lock | //Lock | ||||
protected bool _isLocked; | protected bool _isLocked; | ||||
internal void Lock() { _isLocked = true; } | internal void Lock() { _isLocked = true; } | ||||
@@ -261,7 +261,7 @@ namespace Discord | |||||
} | } | ||||
/// <summary> Deserializes messages from JSON format and imports them into the message cache.</summary> | /// <summary> Deserializes messages from JSON format and imports them into the message cache.</summary> | ||||
public IEnumerable<Message> ImportMessages(string json) | |||||
public IEnumerable<Message> ImportMessages(Channel channel, string json) | |||||
{ | { | ||||
if (json == null) throw new ArgumentNullException(nameof(json)); | if (json == null) throw new ArgumentNullException(nameof(json)); | ||||
@@ -269,8 +269,8 @@ namespace Discord | |||||
.Select(x => | .Select(x => | ||||
{ | { | ||||
var msg = new Message(this, | var msg = new Message(this, | ||||
x["Id"].Value<long>(), | |||||
x["ChannelId"].Value<long>(), | |||||
x["Id"].Value<long>(), | |||||
channel.Id, | |||||
x["UserId"].Value<long>()); | x["UserId"].Value<long>()); | ||||
var reader = x.CreateReader(); | var reader = x.CreateReader(); | ||||
@@ -86,19 +86,19 @@ namespace Discord | |||||
if (region == null) throw new ArgumentNullException(nameof(region)); | if (region == null) throw new ArgumentNullException(nameof(region)); | ||||
CheckReady(); | CheckReady(); | ||||
var response = await _api.CreateServer(name, region.Value).ConfigureAwait(false); | |||||
var response = await _api.CreateServer(name, region.Id).ConfigureAwait(false); | |||||
var server = _servers.GetOrAdd(response.Id); | var server = _servers.GetOrAdd(response.Id); | ||||
server.Update(response); | server.Update(response); | ||||
return server; | return server; | ||||
} | } | ||||
/// <summary> Edits the provided server, changing only non-null attributes. </summary> | /// <summary> Edits the provided server, changing only non-null attributes. </summary> | ||||
public async Task EditServer(Server server, string name = null, Region region = null, ImageType iconType = ImageType.Png, byte[] icon = null) | |||||
public async Task EditServer(Server server, string name = null, string region = null, ImageType iconType = ImageType.Png, byte[] icon = null) | |||||
{ | { | ||||
if (server == null) throw new ArgumentNullException(nameof(server)); | if (server == null) throw new ArgumentNullException(nameof(server)); | ||||
CheckReady(); | CheckReady(); | ||||
var response = await _api.EditServer(server.Id, name: name ?? server.Name, region: region.Value, iconType: iconType, icon: icon).ConfigureAwait(false); | |||||
var response = await _api.EditServer(server.Id, name: name ?? server.Name, region: region, iconType: iconType, icon: icon).ConfigureAwait(false); | |||||
server.Update(response); | server.Update(response); | ||||
} | } | ||||
@@ -112,5 +112,12 @@ namespace Discord | |||||
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } | catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } | ||||
//return _servers.TryRemove(server.Id); | //return _servers.TryRemove(server.Id); | ||||
} | } | ||||
public async Task<IEnumerable<Region>> GetVoiceRegions() | |||||
{ | |||||
CheckReady(); | |||||
return (await _api.GetVoiceRegions()).Select(x => new Region { Id = x.Id, Name = x.Name, Hostname = x.Hostname, Port = x.Port }); | |||||
} | |||||
} | } | ||||
} | } |
@@ -5,6 +5,7 @@ using System; | |||||
using System.Collections.Concurrent; | using System.Collections.Concurrent; | ||||
using System.Collections.Generic; | using System.Collections.Generic; | ||||
using System.Linq; | using System.Linq; | ||||
using System.Reflection; | |||||
using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
namespace Discord | namespace Discord | ||||
@@ -12,12 +13,14 @@ namespace Discord | |||||
/// <summary> Provides a connection to the DiscordApp service. </summary> | /// <summary> Provides a connection to the DiscordApp service. </summary> | ||||
public sealed partial class DiscordClient : DiscordWSClient | public sealed partial class DiscordClient : DiscordWSClient | ||||
{ | { | ||||
public static readonly string Version = typeof(DiscordClientConfig).GetTypeInfo().Assembly.GetName().Version.ToString(3); | |||||
private readonly DiscordAPIClient _api; | private readonly DiscordAPIClient _api; | ||||
private readonly Random _rand; | private readonly Random _rand; | ||||
private readonly JsonSerializer _socketSerializer, _messageImporter; | |||||
private readonly JsonSerializer _messageImporter; | |||||
private readonly ConcurrentQueue<Message> _pendingMessages; | private readonly ConcurrentQueue<Message> _pendingMessages; | ||||
private readonly ConcurrentDictionary<long, DiscordWSClient> _voiceClients; | private readonly ConcurrentDictionary<long, DiscordWSClient> _voiceClients; | ||||
private readonly Dictionary<Type, IService> _services; | |||||
private readonly Dictionary<Type, object> _singletons; | |||||
private bool _sentInitialLog; | private bool _sentInitialLog; | ||||
private uint _nextVoiceClientId; | private uint _nextVoiceClientId; | ||||
private UserStatus _status; | private UserStatus _status; | ||||
@@ -47,7 +50,7 @@ namespace Discord | |||||
_roles = new Roles(this, cacheLock); | _roles = new Roles(this, cacheLock); | ||||
_servers = new Servers(this, cacheLock); | _servers = new Servers(this, cacheLock); | ||||
_globalUsers = new GlobalUsers(this, cacheLock); | _globalUsers = new GlobalUsers(this, cacheLock); | ||||
_services = new Dictionary<Type, IService>(); | |||||
_singletons = new Dictionary<Type, object>(); | |||||
_status = UserStatus.Online; | _status = UserStatus.Online; | ||||
@@ -162,16 +165,9 @@ namespace Discord | |||||
if (Config.UseMessageQueue) | if (Config.UseMessageQueue) | ||||
_pendingMessages = new ConcurrentQueue<Message>(); | _pendingMessages = new ConcurrentQueue<Message>(); | ||||
_socketSerializer = new JsonSerializer(); | |||||
_socketSerializer.DateTimeZoneHandling = DateTimeZoneHandling.Utc; | |||||
#if TEST_RESPONSES | |||||
_serializer.CheckAdditionalContent = true; | |||||
_serializer.MissingMemberHandling = MissingMemberHandling.Error; | |||||
#endif | |||||
_messageImporter = new JsonSerializer(); | _messageImporter = new JsonSerializer(); | ||||
_messageImporter.ContractResolver = new MessageImporterResolver(); | |||||
_messageImporter.ContractResolver = new Message.ImportResolver(); | |||||
} | } | ||||
internal override VoiceWebSocket CreateVoiceSocket() | internal override VoiceWebSocket CreateVoiceSocket() | ||||
{ | { | ||||
@@ -276,25 +272,34 @@ namespace Discord | |||||
_privateUser = null; | _privateUser = null; | ||||
} | } | ||||
public T AddSingleton<T>(T obj) | |||||
where T : class | |||||
{ | |||||
_singletons.Add(typeof(T), obj); | |||||
return obj; | |||||
} | |||||
public T GetSingleton<T>(bool required = true) | |||||
where T : class | |||||
{ | |||||
object singleton; | |||||
T singletonT = null; | |||||
if (_singletons.TryGetValue(typeof(T), out singleton)) | |||||
singletonT = singleton as T; | |||||
if (singletonT == null && required) | |||||
throw new InvalidOperationException($"This operation requires {nameof(T)} to be added to {nameof(DiscordClient)}."); | |||||
return singletonT; | |||||
} | |||||
public T AddService<T>(T obj) | public T AddService<T>(T obj) | ||||
where T : class, IService | where T : class, IService | ||||
{ | { | ||||
_services.Add(typeof(T), obj); | |||||
AddSingleton(obj); | |||||
obj.Install(this); | obj.Install(this); | ||||
return obj; | return obj; | ||||
} | } | ||||
public T GetService<T>(bool required = true) | public T GetService<T>(bool required = true) | ||||
where T : class, IService | where T : class, IService | ||||
{ | |||||
IService service; | |||||
T serviceT = null; | |||||
if (_services.TryGetValue(typeof(T), out service)) | |||||
serviceT = service as T; | |||||
if (serviceT == null && required) | |||||
throw new InvalidOperationException($"This operation requires {nameof(T)} to be added to {nameof(DiscordClient)}."); | |||||
return serviceT; | |||||
} | |||||
=> GetSingleton<T>(required); | |||||
protected override IEnumerable<Task> GetTasks() | protected override IEnumerable<Task> GetTasks() | ||||
{ | { | ||||
@@ -314,7 +319,7 @@ namespace Discord | |||||
case "READY": //Resync | case "READY": //Resync | ||||
{ | { | ||||
base.OnReceivedEvent(e).Wait(); //This cannot be an await, or we'll get later messages before we're ready | base.OnReceivedEvent(e).Wait(); //This cannot be an await, or we'll get later messages before we're ready | ||||
var data = e.Payload.ToObject<ReadyEvent>(_socketSerializer); | |||||
var data = e.Payload.ToObject<ReadyEvent>(_dataSocketSerializer); | |||||
_privateUser = _users.GetOrAdd(data.User.Id, null); | _privateUser = _users.GetOrAdd(data.User.Id, null); | ||||
_privateUser.Update(data.User); | _privateUser.Update(data.User); | ||||
_privateUser.Global.Update(data.User); | _privateUser.Global.Update(data.User); | ||||
@@ -339,7 +344,7 @@ namespace Discord | |||||
//Servers | //Servers | ||||
case "GUILD_CREATE": | case "GUILD_CREATE": | ||||
{ | { | ||||
var data = e.Payload.ToObject<GuildCreateEvent>(_socketSerializer); | |||||
var data = e.Payload.ToObject<GuildCreateEvent>(_dataSocketSerializer); | |||||
if (data.Unavailable != true) | if (data.Unavailable != true) | ||||
{ | { | ||||
var server = _servers.GetOrAdd(data.Id); | var server = _servers.GetOrAdd(data.Id); | ||||
@@ -353,7 +358,7 @@ namespace Discord | |||||
break; | break; | ||||
case "GUILD_UPDATE": | case "GUILD_UPDATE": | ||||
{ | { | ||||
var data = e.Payload.ToObject<GuildUpdateEvent>(_socketSerializer); | |||||
var data = e.Payload.ToObject<GuildUpdateEvent>(_dataSocketSerializer); | |||||
var server = _servers[data.Id]; | var server = _servers[data.Id]; | ||||
if (server != null) | if (server != null) | ||||
{ | { | ||||
@@ -364,7 +369,7 @@ namespace Discord | |||||
break; | break; | ||||
case "GUILD_DELETE": | case "GUILD_DELETE": | ||||
{ | { | ||||
var data = e.Payload.ToObject<GuildDeleteEvent>(_socketSerializer); | |||||
var data = e.Payload.ToObject<GuildDeleteEvent>(_dataSocketSerializer); | |||||
var server = _servers.TryRemove(data.Id); | var server = _servers.TryRemove(data.Id); | ||||
if (server != null) | if (server != null) | ||||
{ | { | ||||
@@ -379,7 +384,7 @@ namespace Discord | |||||
//Channels | //Channels | ||||
case "CHANNEL_CREATE": | case "CHANNEL_CREATE": | ||||
{ | { | ||||
var data = e.Payload.ToObject<ChannelCreateEvent>(_socketSerializer); | |||||
var data = e.Payload.ToObject<ChannelCreateEvent>(_dataSocketSerializer); | |||||
Channel channel; | Channel channel; | ||||
if (data.IsPrivate) | if (data.IsPrivate) | ||||
{ | { | ||||
@@ -395,7 +400,7 @@ namespace Discord | |||||
break; | break; | ||||
case "CHANNEL_UPDATE": | case "CHANNEL_UPDATE": | ||||
{ | { | ||||
var data = e.Payload.ToObject<ChannelUpdateEvent>(_socketSerializer); | |||||
var data = e.Payload.ToObject<ChannelUpdateEvent>(_dataSocketSerializer); | |||||
var channel = _channels[data.Id]; | var channel = _channels[data.Id]; | ||||
if (channel != null) | if (channel != null) | ||||
{ | { | ||||
@@ -406,7 +411,7 @@ namespace Discord | |||||
break; | break; | ||||
case "CHANNEL_DELETE": | case "CHANNEL_DELETE": | ||||
{ | { | ||||
var data = e.Payload.ToObject<ChannelDeleteEvent>(_socketSerializer); | |||||
var data = e.Payload.ToObject<ChannelDeleteEvent>(_dataSocketSerializer); | |||||
var channel = _channels.TryRemove(data.Id); | var channel = _channels.TryRemove(data.Id); | ||||
if (channel != null) | if (channel != null) | ||||
RaiseChannelDestroyed(channel); | RaiseChannelDestroyed(channel); | ||||
@@ -416,7 +421,7 @@ namespace Discord | |||||
//Members | //Members | ||||
case "GUILD_MEMBER_ADD": | case "GUILD_MEMBER_ADD": | ||||
{ | { | ||||
var data = e.Payload.ToObject<MemberAddEvent>(_socketSerializer); | |||||
var data = e.Payload.ToObject<MemberAddEvent>(_dataSocketSerializer); | |||||
var user = _users.GetOrAdd(data.User.Id, data.GuildId); | var user = _users.GetOrAdd(data.User.Id, data.GuildId); | ||||
user.Update(data); | user.Update(data); | ||||
if (Config.TrackActivity) | if (Config.TrackActivity) | ||||
@@ -426,7 +431,7 @@ namespace Discord | |||||
break; | break; | ||||
case "GUILD_MEMBER_UPDATE": | case "GUILD_MEMBER_UPDATE": | ||||
{ | { | ||||
var data = e.Payload.ToObject<MemberUpdateEvent>(_socketSerializer); | |||||
var data = e.Payload.ToObject<MemberUpdateEvent>(_dataSocketSerializer); | |||||
var user = _users[data.User.Id, data.GuildId]; | var user = _users[data.User.Id, data.GuildId]; | ||||
if (user != null) | if (user != null) | ||||
{ | { | ||||
@@ -437,7 +442,7 @@ namespace Discord | |||||
break; | break; | ||||
case "GUILD_MEMBER_REMOVE": | case "GUILD_MEMBER_REMOVE": | ||||
{ | { | ||||
var data = e.Payload.ToObject<MemberRemoveEvent>(_socketSerializer); | |||||
var data = e.Payload.ToObject<MemberRemoveEvent>(_dataSocketSerializer); | |||||
var user = _users.TryRemove(data.UserId, data.GuildId); | var user = _users.TryRemove(data.UserId, data.GuildId); | ||||
if (user != null) | if (user != null) | ||||
RaiseUserLeft(user); | RaiseUserLeft(user); | ||||
@@ -445,7 +450,7 @@ namespace Discord | |||||
break; | break; | ||||
case "GUILD_MEMBERS_CHUNK": | case "GUILD_MEMBERS_CHUNK": | ||||
{ | { | ||||
var data = e.Payload.ToObject<MembersChunkEvent>(_socketSerializer); | |||||
var data = e.Payload.ToObject<MembersChunkEvent>(_dataSocketSerializer); | |||||
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); | ||||
@@ -458,7 +463,7 @@ namespace Discord | |||||
//Roles | //Roles | ||||
case "GUILD_ROLE_CREATE": | case "GUILD_ROLE_CREATE": | ||||
{ | { | ||||
var data = e.Payload.ToObject<RoleCreateEvent>(_socketSerializer); | |||||
var data = e.Payload.ToObject<RoleCreateEvent>(_dataSocketSerializer); | |||||
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]; | ||||
@@ -469,7 +474,7 @@ namespace Discord | |||||
break; | break; | ||||
case "GUILD_ROLE_UPDATE": | case "GUILD_ROLE_UPDATE": | ||||
{ | { | ||||
var data = e.Payload.ToObject<RoleUpdateEvent>(_socketSerializer); | |||||
var data = e.Payload.ToObject<RoleUpdateEvent>(_dataSocketSerializer); | |||||
var role = _roles[data.Data.Id]; | var role = _roles[data.Data.Id]; | ||||
if (role != null) | if (role != null) | ||||
{ | { | ||||
@@ -480,7 +485,7 @@ namespace Discord | |||||
break; | break; | ||||
case "GUILD_ROLE_DELETE": | case "GUILD_ROLE_DELETE": | ||||
{ | { | ||||
var data = e.Payload.ToObject<RoleDeleteEvent>(_socketSerializer); | |||||
var data = e.Payload.ToObject<RoleDeleteEvent>(_dataSocketSerializer); | |||||
var role = _roles.TryRemove(data.RoleId); | var role = _roles.TryRemove(data.RoleId); | ||||
if (role != null) | if (role != null) | ||||
{ | { | ||||
@@ -495,7 +500,7 @@ namespace Discord | |||||
//Bans | //Bans | ||||
case "GUILD_BAN_ADD": | case "GUILD_BAN_ADD": | ||||
{ | { | ||||
var data = e.Payload.ToObject<BanAddEvent>(_socketSerializer); | |||||
var data = e.Payload.ToObject<BanAddEvent>(_dataSocketSerializer); | |||||
var server = _servers[data.GuildId]; | var server = _servers[data.GuildId]; | ||||
if (server != null) | if (server != null) | ||||
{ | { | ||||
@@ -507,7 +512,7 @@ namespace Discord | |||||
break; | break; | ||||
case "GUILD_BAN_REMOVE": | case "GUILD_BAN_REMOVE": | ||||
{ | { | ||||
var data = e.Payload.ToObject<BanRemoveEvent>(_socketSerializer); | |||||
var data = e.Payload.ToObject<BanRemoveEvent>(_dataSocketSerializer); | |||||
var server = _servers[data.GuildId]; | var server = _servers[data.GuildId]; | ||||
if (server != null) | if (server != null) | ||||
{ | { | ||||
@@ -521,7 +526,7 @@ namespace Discord | |||||
//Messages | //Messages | ||||
case "MESSAGE_CREATE": | case "MESSAGE_CREATE": | ||||
{ | { | ||||
var data = e.Payload.ToObject<MessageCreateEvent>(_socketSerializer); | |||||
var data = e.Payload.ToObject<MessageCreateEvent>(_dataSocketSerializer); | |||||
Message msg = null; | Message msg = null; | ||||
bool isAuthor = data.Author.Id == _userId; | bool isAuthor = data.Author.Id == _userId; | ||||
@@ -548,7 +553,7 @@ namespace Discord | |||||
break; | break; | ||||
case "MESSAGE_UPDATE": | case "MESSAGE_UPDATE": | ||||
{ | { | ||||
var data = e.Payload.ToObject<MessageUpdateEvent>(_socketSerializer); | |||||
var data = e.Payload.ToObject<MessageUpdateEvent>(_dataSocketSerializer); | |||||
var msg = _messages[data.Id]; | var msg = _messages[data.Id]; | ||||
if (msg != null) | if (msg != null) | ||||
{ | { | ||||
@@ -559,7 +564,7 @@ namespace Discord | |||||
break; | break; | ||||
case "MESSAGE_DELETE": | case "MESSAGE_DELETE": | ||||
{ | { | ||||
var data = e.Payload.ToObject<MessageDeleteEvent>(_socketSerializer); | |||||
var data = e.Payload.ToObject<MessageDeleteEvent>(_dataSocketSerializer); | |||||
var msg = _messages.TryRemove(data.Id); | var msg = _messages.TryRemove(data.Id); | ||||
if (msg != null) | if (msg != null) | ||||
RaiseMessageDeleted(msg); | RaiseMessageDeleted(msg); | ||||
@@ -567,7 +572,7 @@ namespace Discord | |||||
break; | break; | ||||
case "MESSAGE_ACK": | case "MESSAGE_ACK": | ||||
{ | { | ||||
var data = e.Payload.ToObject<MessageAckEvent>(_socketSerializer); | |||||
var data = e.Payload.ToObject<MessageAckEvent>(_dataSocketSerializer); | |||||
var msg = GetMessage(data.MessageId); | var msg = GetMessage(data.MessageId); | ||||
if (msg != null) | if (msg != null) | ||||
RaiseMessageReadRemotely(msg); | RaiseMessageReadRemotely(msg); | ||||
@@ -577,7 +582,7 @@ namespace Discord | |||||
//Statuses | //Statuses | ||||
case "PRESENCE_UPDATE": | case "PRESENCE_UPDATE": | ||||
{ | { | ||||
var data = e.Payload.ToObject<PresenceUpdateEvent>(_socketSerializer); | |||||
var data = e.Payload.ToObject<PresenceUpdateEvent>(_dataSocketSerializer); | |||||
var user = _users.GetOrAdd(data.User.Id, data.GuildId); | var user = _users.GetOrAdd(data.User.Id, data.GuildId); | ||||
if (user != null) | if (user != null) | ||||
{ | { | ||||
@@ -588,7 +593,7 @@ namespace Discord | |||||
break; | break; | ||||
case "TYPING_START": | case "TYPING_START": | ||||
{ | { | ||||
var data = e.Payload.ToObject<TypingStartEvent>(_socketSerializer); | |||||
var data = e.Payload.ToObject<TypingStartEvent>(_dataSocketSerializer); | |||||
var channel = _channels[data.ChannelId]; | var channel = _channels[data.ChannelId]; | ||||
if (channel != null) | if (channel != null) | ||||
{ | { | ||||
@@ -614,7 +619,7 @@ namespace Discord | |||||
//Voice | //Voice | ||||
case "VOICE_STATE_UPDATE": | case "VOICE_STATE_UPDATE": | ||||
{ | { | ||||
var data = e.Payload.ToObject<MemberVoiceStateUpdateEvent>(_socketSerializer); | |||||
var data = e.Payload.ToObject<MemberVoiceStateUpdateEvent>(_dataSocketSerializer); | |||||
var user = _users[data.UserId, data.GuildId]; | var user = _users[data.UserId, data.GuildId]; | ||||
if (user != null) | if (user != null) | ||||
{ | { | ||||
@@ -633,7 +638,7 @@ namespace Discord | |||||
//Settings | //Settings | ||||
case "USER_UPDATE": | case "USER_UPDATE": | ||||
{ | { | ||||
var data = e.Payload.ToObject<UserUpdateEvent>(_socketSerializer); | |||||
var data = e.Payload.ToObject<UserUpdateEvent>(_dataSocketSerializer); | |||||
var user = _globalUsers[data.Id]; | var user = _globalUsers[data.Id]; | ||||
if (user != null) | if (user != null) | ||||
{ | { | ||||
@@ -1,5 +1,6 @@ | |||||
using Discord.Net; | using Discord.Net; | ||||
using Discord.Net.WebSockets; | using Discord.Net.WebSockets; | ||||
using Newtonsoft.Json; | |||||
using System; | using System; | ||||
using System.Collections.Generic; | using System.Collections.Generic; | ||||
using System.Linq; | using System.Linq; | ||||
@@ -23,9 +24,9 @@ namespace Discord | |||||
protected readonly DiscordWSClientConfig _config; | protected readonly DiscordWSClientConfig _config; | ||||
protected readonly ManualResetEvent _disconnectedEvent; | protected readonly ManualResetEvent _disconnectedEvent; | ||||
protected readonly ManualResetEventSlim _connectedEvent; | protected readonly ManualResetEventSlim _connectedEvent; | ||||
protected ExceptionDispatchInfo _disconnectReason; | |||||
internal readonly DataWebSocket _dataSocket; | internal readonly DataWebSocket _dataSocket; | ||||
internal readonly VoiceWebSocket _voiceSocket; | internal readonly VoiceWebSocket _voiceSocket; | ||||
protected ExceptionDispatchInfo _disconnectReason; | |||||
protected string _gateway, _token; | protected string _gateway, _token; | ||||
protected long? _userId, _voiceServerId; | protected long? _userId, _voiceServerId; | ||||
private Task _runTask; | private Task _runTask; | ||||
@@ -44,6 +45,10 @@ namespace Discord | |||||
private CancellationTokenSource _cancelTokenSource; | private CancellationTokenSource _cancelTokenSource; | ||||
protected CancellationToken _cancelToken; | protected CancellationToken _cancelToken; | ||||
internal JsonSerializer DataSocketSerializer => _dataSocketSerializer; | |||||
internal JsonSerializer VoiceSocketSerializer => _voiceSocketSerializer; | |||||
protected readonly JsonSerializer _dataSocketSerializer, _voiceSocketSerializer; | |||||
/// <summary> Initializes a new instance of the DiscordClient class. </summary> | /// <summary> Initializes a new instance of the DiscordClient class. </summary> | ||||
public DiscordWSClient(DiscordWSClientConfig config = null) | public DiscordWSClient(DiscordWSClientConfig config = null) | ||||
{ | { | ||||
@@ -55,6 +60,32 @@ namespace Discord | |||||
_disconnectedEvent = new ManualResetEvent(true); | _disconnectedEvent = new ManualResetEvent(true); | ||||
_connectedEvent = new ManualResetEventSlim(false); | _connectedEvent = new ManualResetEventSlim(false); | ||||
_dataSocketSerializer = new JsonSerializer(); | |||||
_dataSocketSerializer.DateTimeZoneHandling = DateTimeZoneHandling.Utc; | |||||
#if TEST_RESPONSES | |||||
_dataSocketSerializer.CheckAdditionalContent = true; | |||||
_dataSocketSerializer.MissingMemberHandling = MissingMemberHandling.Error; | |||||
#else | |||||
_dataSocketSerializer.Error += (s, e) => | |||||
{ | |||||
e.ErrorContext.Handled = true; | |||||
RaiseOnLog(LogMessageSeverity.Error, LogMessageSource.DataWebSocket, "Serialization Failed", e.ErrorContext.Error); | |||||
}; | |||||
#endif | |||||
_voiceSocketSerializer = new JsonSerializer(); | |||||
_voiceSocketSerializer.DateTimeZoneHandling = DateTimeZoneHandling.Utc; | |||||
#if TEST_RESPONSES | |||||
_voiceSocketSerializer.CheckAdditionalContent = true; | |||||
_voiceSocketSerializer.MissingMemberHandling = MissingMemberHandling.Error; | |||||
#else | |||||
_voiceSocketSerializer.Error += (s, e) => | |||||
{ | |||||
e.ErrorContext.Handled = true; | |||||
RaiseOnLog(LogMessageSeverity.Error, LogMessageSource.VoiceWebSocket, "Serialization Failed", e.ErrorContext.Error); | |||||
}; | |||||
#endif | |||||
_dataSocket = CreateDataSocket(); | _dataSocket = CreateDataSocket(); | ||||
if (_config.EnableVoice) | if (_config.EnableVoice) | ||||
_voiceSocket = CreateVoiceSocket(); | _voiceSocket = CreateVoiceSocket(); | ||||
@@ -84,15 +84,21 @@ namespace Discord | |||||
lock (_writerLock) | lock (_writerLock) | ||||
{ | { | ||||
TValue newItem = createFunc(); | |||||
result = _dictionary.GetOrAdd(key, newItem); | |||||
if (result == newItem) | |||||
if (!_dictionary.ContainsKey(key)) | |||||
{ | { | ||||
result.Cache(); | |||||
RaiseItemCreated(result); | |||||
result = createFunc(); | |||||
if (result.Cache()) | |||||
{ | |||||
_dictionary.TryAdd(key, result); | |||||
RaiseItemCreated(result); | |||||
} | |||||
else | |||||
result.Uncache(); | |||||
return result; | |||||
} | } | ||||
else | |||||
return _dictionary[key]; | |||||
} | } | ||||
return result; | |||||
} | } | ||||
protected void Import(IEnumerable<KeyValuePair<TKey, TValue>> items) | protected void Import(IEnumerable<KeyValuePair<TKey, TValue>> items) | ||||
{ | { | ||||
@@ -101,9 +107,13 @@ namespace Discord | |||||
foreach (var pair in items) | foreach (var pair in items) | ||||
{ | { | ||||
var value = pair.Value; | var value = pair.Value; | ||||
_dictionary.TryAdd(pair.Key, value); | |||||
value.Cache(); | |||||
RaiseItemCreated(value); | |||||
if (value.Cache()) | |||||
{ | |||||
_dictionary.TryAdd(pair.Key, value); | |||||
RaiseItemCreated(value); | |||||
} | |||||
else | |||||
value.Uncache(); | |||||
} | } | ||||
} | } | ||||
} | } | ||||
@@ -31,10 +31,14 @@ namespace Discord | |||||
_client = client; | _client = client; | ||||
} | } | ||||
internal void Cache() | |||||
internal bool Cache() | |||||
{ | { | ||||
LoadReferences(); | |||||
_isCached = true; | |||||
if (LoadReferences()) | |||||
{ | |||||
_isCached = true; | |||||
return true; | |||||
} | |||||
return false; | |||||
} | } | ||||
internal void Uncache() | internal void Uncache() | ||||
{ | { | ||||
@@ -44,7 +48,7 @@ namespace Discord | |||||
_isCached = false; | _isCached = false; | ||||
} | } | ||||
} | } | ||||
internal abstract void LoadReferences(); | |||||
internal abstract bool LoadReferences(); | |||||
internal abstract void UnloadReferences(); | internal abstract void UnloadReferences(); | ||||
} | } | ||||
} | } |
@@ -41,9 +41,9 @@ namespace Discord | |||||
} | } | ||||
} | } | ||||
public T Load() | |||||
public bool Load() | |||||
{ | { | ||||
return Value; //Used for precaching | |||||
return Value != null; //Used for precaching | |||||
} | } | ||||
public void Unload() | public void Unload() | ||||
@@ -124,12 +124,12 @@ namespace Discord | |||||
if (client.Config.MessageCacheLength > 0) | if (client.Config.MessageCacheLength > 0) | ||||
_messages = new ConcurrentDictionary<long, Message>(); | _messages = new ConcurrentDictionary<long, Message>(); | ||||
} | } | ||||
internal override void LoadReferences() | |||||
internal override bool LoadReferences() | |||||
{ | { | ||||
if (IsPrivate) | if (IsPrivate) | ||||
_recipient.Load(); | |||||
return _recipient.Load(); | |||||
else | else | ||||
_server.Load(); | |||||
return _server.Load(); | |||||
} | } | ||||
internal override void UnloadReferences() | internal override void UnloadReferences() | ||||
{ | { | ||||
@@ -44,7 +44,7 @@ namespace Discord | |||||
{ | { | ||||
_users = new ConcurrentDictionary<long, User>(); | _users = new ConcurrentDictionary<long, User>(); | ||||
} | } | ||||
internal override void LoadReferences() { } | |||||
internal override bool LoadReferences() { return true; } | |||||
internal override void UnloadReferences() | internal override void UnloadReferences() | ||||
{ | { | ||||
//Don't need to clean _users - they're considered owned by server | //Don't need to clean _users - they're considered owned by server | ||||
@@ -82,7 +82,7 @@ namespace Discord | |||||
{ | { | ||||
XkcdCode = xkcdPass; | XkcdCode = xkcdPass; | ||||
} | } | ||||
internal override void LoadReferences() { } | |||||
internal override bool LoadReferences() { return true; } | |||||
internal override void UnloadReferences() { } | internal override void UnloadReferences() { } | ||||
internal void Update(InviteReference model) | internal void Update(InviteReference model) | ||||
@@ -15,24 +15,25 @@ namespace Discord | |||||
Queued, | Queued, | ||||
Failed | Failed | ||||
} | } | ||||
internal class MessageImporterResolver : DefaultContractResolver | |||||
public sealed class Message : CachedObject<long> | |||||
{ | { | ||||
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) | |||||
internal class ImportResolver : DefaultContractResolver | |||||
{ | { | ||||
var property = base.CreateProperty(member, memberSerialization); | |||||
if (member is PropertyInfo) | |||||
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) | |||||
{ | { | ||||
if (!(member as PropertyInfo).CanWrite) | |||||
return null; | |||||
var property = base.CreateProperty(member, memberSerialization); | |||||
if (member is PropertyInfo) | |||||
{ | |||||
if (member.Name == nameof(ChannelId) || !(member as PropertyInfo).CanWrite) | |||||
return null; | |||||
property.Writable = true; //Handles private setters | |||||
property.Writable = true; //Handles private setters | |||||
} | |||||
return property; | |||||
} | } | ||||
return property; | |||||
} | } | ||||
} | |||||
public sealed class Message : CachedObject<long> | |||||
{ | |||||
public sealed class Attachment : File | public sealed class Attachment : File | ||||
{ | { | ||||
/// <summary> Unique identifier for this file. </summary> | /// <summary> Unique identifier for this file. </summary> | ||||
@@ -173,6 +174,8 @@ namespace Discord | |||||
x => | x => | ||||
{ | { | ||||
var channel = Channel; | var channel = Channel; | ||||
if (channel == null) return null; | |||||
if (!channel.IsPrivate) | if (!channel.IsPrivate) | ||||
return _client.Users[x, channel.Server.Id]; | return _client.Users[x, channel.Server.Id]; | ||||
else | else | ||||
@@ -181,10 +184,9 @@ namespace Discord | |||||
Attachments = _initialAttachments; | Attachments = _initialAttachments; | ||||
Embeds = _initialEmbeds; | Embeds = _initialEmbeds; | ||||
} | } | ||||
internal override void LoadReferences() | |||||
internal override bool LoadReferences() | |||||
{ | { | ||||
_channel.Load(); | |||||
_user.Load(); | |||||
return _channel.Load() && _user.Load(); | |||||
} | } | ||||
internal override void UnloadReferences() | internal override void UnloadReferences() | ||||
{ | { | ||||
@@ -0,0 +1,10 @@ | |||||
namespace Discord | |||||
{ | |||||
public sealed class Region | |||||
{ | |||||
public string Hostname; | |||||
public int Port; | |||||
public string Id; | |||||
public string Name; | |||||
} | |||||
} |
@@ -47,9 +47,9 @@ namespace Discord | |||||
Color = new Color(0); | Color = new Color(0); | ||||
Color.Lock(); | Color.Lock(); | ||||
} | } | ||||
internal override void LoadReferences() | |||||
internal override bool LoadReferences() | |||||
{ | { | ||||
_server.Load(); | |||||
return _server.Load(); | |||||
} | } | ||||
internal override void UnloadReferences() | internal override void UnloadReferences() | ||||
{ | { | ||||
@@ -103,9 +103,10 @@ namespace Discord | |||||
//Local Cache | //Local Cache | ||||
_bans = new ConcurrentDictionary<long, bool>(); | _bans = new ConcurrentDictionary<long, bool>(); | ||||
} | } | ||||
internal override void LoadReferences() | |||||
internal override bool LoadReferences() | |||||
{ | { | ||||
_afkChannel.Load(); | _afkChannel.Load(); | ||||
return true; | |||||
} | } | ||||
internal override void UnloadReferences() | internal override void UnloadReferences() | ||||
{ | { | ||||
@@ -148,10 +148,10 @@ namespace Discord | |||||
if (serverId == null) | if (serverId == null) | ||||
UpdateRoles(null); | UpdateRoles(null); | ||||
} | } | ||||
internal override void LoadReferences() | |||||
internal override bool LoadReferences() | |||||
{ | { | ||||
_globalUser.Load(); | |||||
_server.Load(); | |||||
return _globalUser.Load() && | |||||
(IsPrivate || _server.Load()); | |||||
} | } | ||||
internal override void UnloadReferences() | internal override void UnloadReferences() | ||||
{ | { | ||||
@@ -20,7 +20,7 @@ namespace Discord.Net.Rest | |||||
{ | { | ||||
PreAuthenticate = false, | PreAuthenticate = false, | ||||
ReadWriteTimeout = _config.APITimeout, | ReadWriteTimeout = _config.APITimeout, | ||||
UserAgent = _config.UserAgent | |||||
UserAgent = DiscordAPIClientConfig.UserAgent | |||||
}; | }; | ||||
if (_config.ProxyUrl != null) | if (_config.ProxyUrl != null) | ||||
_client.Proxy = new WebProxy(_config.ProxyUrl, true, new string[0], _config.ProxyCredentials); | _client.Proxy = new WebProxy(_config.ProxyUrl, true, new string[0], _config.ProxyCredentials); | ||||
@@ -82,13 +82,13 @@ namespace Discord.Net.WebSockets | |||||
JToken token = msg.Payload as JToken; | JToken token = msg.Payload as JToken; | ||||
if (msg.Type == "READY") | if (msg.Type == "READY") | ||||
{ | { | ||||
var payload = token.ToObject<ReadyEvent>(); | |||||
var payload = token.ToObject<ReadyEvent>(_client.DataSocketSerializer); | |||||
_sessionId = payload.SessionId; | _sessionId = payload.SessionId; | ||||
_heartbeatInterval = payload.HeartbeatInterval; | _heartbeatInterval = payload.HeartbeatInterval; | ||||
} | } | ||||
else if (msg.Type == "RESUMED") | else if (msg.Type == "RESUMED") | ||||
{ | { | ||||
var payload = token.ToObject<ResumedEvent>(); | |||||
var payload = token.ToObject<ResumedEvent>(_client.DataSocketSerializer); | |||||
_heartbeatInterval = payload.HeartbeatInterval; | _heartbeatInterval = payload.HeartbeatInterval; | ||||
} | } | ||||
RaiseReceivedEvent(msg.Type, token); | RaiseReceivedEvent(msg.Type, token); | ||||
@@ -98,7 +98,7 @@ namespace Discord.Net.WebSockets | |||||
break; | break; | ||||
case 7: //Redirect | case 7: //Redirect | ||||
{ | { | ||||
var payload = (msg.Payload as JToken).ToObject<RedirectEvent>(); | |||||
var payload = (msg.Payload as JToken).ToObject<RedirectEvent>(_client.DataSocketSerializer); | |||||
if (payload.Url != null) | if (payload.Url != null) | ||||
{ | { | ||||
Host = payload.Url; | Host = payload.Url; | ||||
@@ -443,7 +443,7 @@ namespace Discord.Net.WebSockets | |||||
{ | { | ||||
if (_state != (int)WebSocketState.Connected) | if (_state != (int)WebSocketState.Connected) | ||||
{ | { | ||||
var payload = (msg.Payload as JToken).ToObject<VoiceReadyEvent>(); | |||||
var payload = (msg.Payload as JToken).ToObject<VoiceReadyEvent>(_client.VoiceSocketSerializer); | |||||
_heartbeatInterval = payload.HeartbeatInterval; | _heartbeatInterval = payload.HeartbeatInterval; | ||||
_ssrc = payload.SSRC; | _ssrc = payload.SSRC; | ||||
_endpoint = new IPEndPoint((await Dns.GetHostAddressesAsync(Host.Replace("wss://", "")).ConfigureAwait(false)).FirstOrDefault(), payload.Port); | _endpoint = new IPEndPoint((await Dns.GetHostAddressesAsync(Host.Replace("wss://", "")).ConfigureAwait(false)).FirstOrDefault(), payload.Port); | ||||
@@ -486,7 +486,7 @@ namespace Discord.Net.WebSockets | |||||
break; | break; | ||||
case 4: //SESSION_DESCRIPTION | case 4: //SESSION_DESCRIPTION | ||||
{ | { | ||||
var payload = (msg.Payload as JToken).ToObject<JoinServerEvent>(); | |||||
var payload = (msg.Payload as JToken).ToObject<JoinServerEvent>(_client.VoiceSocketSerializer); | |||||
_secretKey = payload.SecretKey; | _secretKey = payload.SecretKey; | ||||
SendIsTalking(true); | SendIsTalking(true); | ||||
EndConnect(); | EndConnect(); | ||||
@@ -494,7 +494,7 @@ namespace Discord.Net.WebSockets | |||||
break; | break; | ||||
case 5: | case 5: | ||||
{ | { | ||||
var payload = (msg.Payload as JToken).ToObject<IsTalkingEvent>(); | |||||
var payload = (msg.Payload as JToken).ToObject<IsTalkingEvent>(_client.VoiceSocketSerializer); | |||||
RaiseIsSpeaking(payload.UserId, payload.IsSpeaking); | RaiseIsSpeaking(payload.UserId, payload.IsSpeaking); | ||||
} | } | ||||
break; | break; | ||||