| @@ -94,9 +94,6 @@ | |||
| <Compile Include="..\Discord.Net\API\Enums\PermissionTarget.cs"> | |||
| <Link>API\Enums\PermissionTarget.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\Discord.Net\API\Enums\Region.cs"> | |||
| <Link>API\Enums\Region.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\Discord.Net\API\Enums\StringEnum.cs"> | |||
| <Link>API\Enums\StringEnum.cs</Link> | |||
| </Compile> | |||
| @@ -259,6 +256,9 @@ | |||
| <Compile Include="..\Discord.Net\Models\Permissions.cs"> | |||
| <Link>Models\Permissions.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\Discord.Net\Models\Region.cs"> | |||
| <Link>Models\Region.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\Discord.Net\Models\Role.cs"> | |||
| <Link>Models\Role.cs</Link> | |||
| </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 | |||
| { | |||
| 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> | |||
| public LogMessageSeverity LogLevel { get { return _logLevel; } set { SetValue(ref _logLevel, value); } } | |||
| private LogMessageSeverity _logLevel = LogMessageSeverity.Info; | |||
| @@ -21,15 +23,6 @@ namespace Discord | |||
| public NetworkCredential ProxyCredentials { get { return _proxyCredentials; } set { SetValue(ref _proxyCredentials, value); } } | |||
| 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 | |||
| protected bool _isLocked; | |||
| 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> | |||
| public IEnumerable<Message> ImportMessages(string json) | |||
| public IEnumerable<Message> ImportMessages(Channel channel, string json) | |||
| { | |||
| if (json == null) throw new ArgumentNullException(nameof(json)); | |||
| @@ -269,8 +269,8 @@ namespace Discord | |||
| .Select(x => | |||
| { | |||
| var msg = new Message(this, | |||
| x["Id"].Value<long>(), | |||
| x["ChannelId"].Value<long>(), | |||
| x["Id"].Value<long>(), | |||
| channel.Id, | |||
| x["UserId"].Value<long>()); | |||
| var reader = x.CreateReader(); | |||
| @@ -86,19 +86,19 @@ namespace Discord | |||
| if (region == null) throw new ArgumentNullException(nameof(region)); | |||
| 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); | |||
| server.Update(response); | |||
| return server; | |||
| } | |||
| /// <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)); | |||
| 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); | |||
| } | |||
| @@ -112,5 +112,12 @@ namespace Discord | |||
| catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } | |||
| //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.Generic; | |||
| using System.Linq; | |||
| using System.Reflection; | |||
| using System.Threading.Tasks; | |||
| namespace Discord | |||
| @@ -12,12 +13,14 @@ namespace Discord | |||
| /// <summary> Provides a connection to the DiscordApp service. </summary> | |||
| 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 Random _rand; | |||
| private readonly JsonSerializer _socketSerializer, _messageImporter; | |||
| private readonly JsonSerializer _messageImporter; | |||
| private readonly ConcurrentQueue<Message> _pendingMessages; | |||
| private readonly ConcurrentDictionary<long, DiscordWSClient> _voiceClients; | |||
| private readonly Dictionary<Type, IService> _services; | |||
| private readonly Dictionary<Type, object> _singletons; | |||
| private bool _sentInitialLog; | |||
| private uint _nextVoiceClientId; | |||
| private UserStatus _status; | |||
| @@ -47,7 +50,7 @@ namespace Discord | |||
| _roles = new Roles(this, cacheLock); | |||
| _servers = new Servers(this, cacheLock); | |||
| _globalUsers = new GlobalUsers(this, cacheLock); | |||
| _services = new Dictionary<Type, IService>(); | |||
| _singletons = new Dictionary<Type, object>(); | |||
| _status = UserStatus.Online; | |||
| @@ -162,16 +165,9 @@ namespace Discord | |||
| if (Config.UseMessageQueue) | |||
| _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.ContractResolver = new MessageImporterResolver(); | |||
| _messageImporter.ContractResolver = new Message.ImportResolver(); | |||
| } | |||
| internal override VoiceWebSocket CreateVoiceSocket() | |||
| { | |||
| @@ -276,25 +272,34 @@ namespace Discord | |||
| _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) | |||
| where T : class, IService | |||
| { | |||
| _services.Add(typeof(T), obj); | |||
| AddSingleton(obj); | |||
| obj.Install(this); | |||
| return obj; | |||
| } | |||
| public T GetService<T>(bool required = true) | |||
| 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() | |||
| { | |||
| @@ -314,7 +319,7 @@ namespace Discord | |||
| case "READY": //Resync | |||
| { | |||
| 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.Update(data.User); | |||
| _privateUser.Global.Update(data.User); | |||
| @@ -339,7 +344,7 @@ namespace Discord | |||
| //Servers | |||
| case "GUILD_CREATE": | |||
| { | |||
| var data = e.Payload.ToObject<GuildCreateEvent>(_socketSerializer); | |||
| var data = e.Payload.ToObject<GuildCreateEvent>(_dataSocketSerializer); | |||
| if (data.Unavailable != true) | |||
| { | |||
| var server = _servers.GetOrAdd(data.Id); | |||
| @@ -353,7 +358,7 @@ namespace Discord | |||
| break; | |||
| case "GUILD_UPDATE": | |||
| { | |||
| var data = e.Payload.ToObject<GuildUpdateEvent>(_socketSerializer); | |||
| var data = e.Payload.ToObject<GuildUpdateEvent>(_dataSocketSerializer); | |||
| var server = _servers[data.Id]; | |||
| if (server != null) | |||
| { | |||
| @@ -364,7 +369,7 @@ namespace Discord | |||
| break; | |||
| case "GUILD_DELETE": | |||
| { | |||
| var data = e.Payload.ToObject<GuildDeleteEvent>(_socketSerializer); | |||
| var data = e.Payload.ToObject<GuildDeleteEvent>(_dataSocketSerializer); | |||
| var server = _servers.TryRemove(data.Id); | |||
| if (server != null) | |||
| { | |||
| @@ -379,7 +384,7 @@ namespace Discord | |||
| //Channels | |||
| case "CHANNEL_CREATE": | |||
| { | |||
| var data = e.Payload.ToObject<ChannelCreateEvent>(_socketSerializer); | |||
| var data = e.Payload.ToObject<ChannelCreateEvent>(_dataSocketSerializer); | |||
| Channel channel; | |||
| if (data.IsPrivate) | |||
| { | |||
| @@ -395,7 +400,7 @@ namespace Discord | |||
| break; | |||
| case "CHANNEL_UPDATE": | |||
| { | |||
| var data = e.Payload.ToObject<ChannelUpdateEvent>(_socketSerializer); | |||
| var data = e.Payload.ToObject<ChannelUpdateEvent>(_dataSocketSerializer); | |||
| var channel = _channels[data.Id]; | |||
| if (channel != null) | |||
| { | |||
| @@ -406,7 +411,7 @@ namespace Discord | |||
| break; | |||
| case "CHANNEL_DELETE": | |||
| { | |||
| var data = e.Payload.ToObject<ChannelDeleteEvent>(_socketSerializer); | |||
| var data = e.Payload.ToObject<ChannelDeleteEvent>(_dataSocketSerializer); | |||
| var channel = _channels.TryRemove(data.Id); | |||
| if (channel != null) | |||
| RaiseChannelDestroyed(channel); | |||
| @@ -416,7 +421,7 @@ namespace Discord | |||
| //Members | |||
| 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); | |||
| user.Update(data); | |||
| if (Config.TrackActivity) | |||
| @@ -426,7 +431,7 @@ namespace Discord | |||
| break; | |||
| 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]; | |||
| if (user != null) | |||
| { | |||
| @@ -437,7 +442,7 @@ namespace Discord | |||
| break; | |||
| 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); | |||
| if (user != null) | |||
| RaiseUserLeft(user); | |||
| @@ -445,7 +450,7 @@ namespace Discord | |||
| break; | |||
| case "GUILD_MEMBERS_CHUNK": | |||
| { | |||
| var data = e.Payload.ToObject<MembersChunkEvent>(_socketSerializer); | |||
| var data = e.Payload.ToObject<MembersChunkEvent>(_dataSocketSerializer); | |||
| foreach (var memberData in data.Members) | |||
| { | |||
| var user = _users.GetOrAdd(memberData.User.Id, memberData.GuildId); | |||
| @@ -458,7 +463,7 @@ namespace Discord | |||
| //Roles | |||
| 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); | |||
| role.Update(data.Data); | |||
| var server = _servers[data.GuildId]; | |||
| @@ -469,7 +474,7 @@ namespace Discord | |||
| break; | |||
| case "GUILD_ROLE_UPDATE": | |||
| { | |||
| var data = e.Payload.ToObject<RoleUpdateEvent>(_socketSerializer); | |||
| var data = e.Payload.ToObject<RoleUpdateEvent>(_dataSocketSerializer); | |||
| var role = _roles[data.Data.Id]; | |||
| if (role != null) | |||
| { | |||
| @@ -480,7 +485,7 @@ namespace Discord | |||
| break; | |||
| case "GUILD_ROLE_DELETE": | |||
| { | |||
| var data = e.Payload.ToObject<RoleDeleteEvent>(_socketSerializer); | |||
| var data = e.Payload.ToObject<RoleDeleteEvent>(_dataSocketSerializer); | |||
| var role = _roles.TryRemove(data.RoleId); | |||
| if (role != null) | |||
| { | |||
| @@ -495,7 +500,7 @@ namespace Discord | |||
| //Bans | |||
| case "GUILD_BAN_ADD": | |||
| { | |||
| var data = e.Payload.ToObject<BanAddEvent>(_socketSerializer); | |||
| var data = e.Payload.ToObject<BanAddEvent>(_dataSocketSerializer); | |||
| var server = _servers[data.GuildId]; | |||
| if (server != null) | |||
| { | |||
| @@ -507,7 +512,7 @@ namespace Discord | |||
| break; | |||
| case "GUILD_BAN_REMOVE": | |||
| { | |||
| var data = e.Payload.ToObject<BanRemoveEvent>(_socketSerializer); | |||
| var data = e.Payload.ToObject<BanRemoveEvent>(_dataSocketSerializer); | |||
| var server = _servers[data.GuildId]; | |||
| if (server != null) | |||
| { | |||
| @@ -521,7 +526,7 @@ namespace Discord | |||
| //Messages | |||
| case "MESSAGE_CREATE": | |||
| { | |||
| var data = e.Payload.ToObject<MessageCreateEvent>(_socketSerializer); | |||
| var data = e.Payload.ToObject<MessageCreateEvent>(_dataSocketSerializer); | |||
| Message msg = null; | |||
| bool isAuthor = data.Author.Id == _userId; | |||
| @@ -548,7 +553,7 @@ namespace Discord | |||
| break; | |||
| case "MESSAGE_UPDATE": | |||
| { | |||
| var data = e.Payload.ToObject<MessageUpdateEvent>(_socketSerializer); | |||
| var data = e.Payload.ToObject<MessageUpdateEvent>(_dataSocketSerializer); | |||
| var msg = _messages[data.Id]; | |||
| if (msg != null) | |||
| { | |||
| @@ -559,7 +564,7 @@ namespace Discord | |||
| break; | |||
| case "MESSAGE_DELETE": | |||
| { | |||
| var data = e.Payload.ToObject<MessageDeleteEvent>(_socketSerializer); | |||
| var data = e.Payload.ToObject<MessageDeleteEvent>(_dataSocketSerializer); | |||
| var msg = _messages.TryRemove(data.Id); | |||
| if (msg != null) | |||
| RaiseMessageDeleted(msg); | |||
| @@ -567,7 +572,7 @@ namespace Discord | |||
| break; | |||
| case "MESSAGE_ACK": | |||
| { | |||
| var data = e.Payload.ToObject<MessageAckEvent>(_socketSerializer); | |||
| var data = e.Payload.ToObject<MessageAckEvent>(_dataSocketSerializer); | |||
| var msg = GetMessage(data.MessageId); | |||
| if (msg != null) | |||
| RaiseMessageReadRemotely(msg); | |||
| @@ -577,7 +582,7 @@ namespace Discord | |||
| //Statuses | |||
| 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); | |||
| if (user != null) | |||
| { | |||
| @@ -588,7 +593,7 @@ namespace Discord | |||
| break; | |||
| case "TYPING_START": | |||
| { | |||
| var data = e.Payload.ToObject<TypingStartEvent>(_socketSerializer); | |||
| var data = e.Payload.ToObject<TypingStartEvent>(_dataSocketSerializer); | |||
| var channel = _channels[data.ChannelId]; | |||
| if (channel != null) | |||
| { | |||
| @@ -614,7 +619,7 @@ namespace Discord | |||
| //Voice | |||
| 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]; | |||
| if (user != null) | |||
| { | |||
| @@ -633,7 +638,7 @@ namespace Discord | |||
| //Settings | |||
| case "USER_UPDATE": | |||
| { | |||
| var data = e.Payload.ToObject<UserUpdateEvent>(_socketSerializer); | |||
| var data = e.Payload.ToObject<UserUpdateEvent>(_dataSocketSerializer); | |||
| var user = _globalUsers[data.Id]; | |||
| if (user != null) | |||
| { | |||
| @@ -1,5 +1,6 @@ | |||
| using Discord.Net; | |||
| using Discord.Net.WebSockets; | |||
| using Newtonsoft.Json; | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| @@ -23,9 +24,9 @@ namespace Discord | |||
| protected readonly DiscordWSClientConfig _config; | |||
| protected readonly ManualResetEvent _disconnectedEvent; | |||
| protected readonly ManualResetEventSlim _connectedEvent; | |||
| protected ExceptionDispatchInfo _disconnectReason; | |||
| internal readonly DataWebSocket _dataSocket; | |||
| internal readonly VoiceWebSocket _voiceSocket; | |||
| protected ExceptionDispatchInfo _disconnectReason; | |||
| protected string _gateway, _token; | |||
| protected long? _userId, _voiceServerId; | |||
| private Task _runTask; | |||
| @@ -44,6 +45,10 @@ namespace Discord | |||
| private CancellationTokenSource _cancelTokenSource; | |||
| 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> | |||
| public DiscordWSClient(DiscordWSClientConfig config = null) | |||
| { | |||
| @@ -55,6 +60,32 @@ namespace Discord | |||
| _disconnectedEvent = new ManualResetEvent(true); | |||
| _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(); | |||
| if (_config.EnableVoice) | |||
| _voiceSocket = CreateVoiceSocket(); | |||
| @@ -84,15 +84,21 @@ namespace Discord | |||
| 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) | |||
| { | |||
| @@ -101,9 +107,13 @@ namespace Discord | |||
| foreach (var pair in items) | |||
| { | |||
| 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; | |||
| } | |||
| internal void Cache() | |||
| internal bool Cache() | |||
| { | |||
| LoadReferences(); | |||
| _isCached = true; | |||
| if (LoadReferences()) | |||
| { | |||
| _isCached = true; | |||
| return true; | |||
| } | |||
| return false; | |||
| } | |||
| internal void Uncache() | |||
| { | |||
| @@ -44,7 +48,7 @@ namespace Discord | |||
| _isCached = false; | |||
| } | |||
| } | |||
| internal abstract void LoadReferences(); | |||
| internal abstract bool LoadReferences(); | |||
| 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() | |||
| @@ -124,12 +124,12 @@ namespace Discord | |||
| if (client.Config.MessageCacheLength > 0) | |||
| _messages = new ConcurrentDictionary<long, Message>(); | |||
| } | |||
| internal override void LoadReferences() | |||
| internal override bool LoadReferences() | |||
| { | |||
| if (IsPrivate) | |||
| _recipient.Load(); | |||
| return _recipient.Load(); | |||
| else | |||
| _server.Load(); | |||
| return _server.Load(); | |||
| } | |||
| internal override void UnloadReferences() | |||
| { | |||
| @@ -44,7 +44,7 @@ namespace Discord | |||
| { | |||
| _users = new ConcurrentDictionary<long, User>(); | |||
| } | |||
| internal override void LoadReferences() { } | |||
| internal override bool LoadReferences() { return true; } | |||
| internal override void UnloadReferences() | |||
| { | |||
| //Don't need to clean _users - they're considered owned by server | |||
| @@ -82,7 +82,7 @@ namespace Discord | |||
| { | |||
| XkcdCode = xkcdPass; | |||
| } | |||
| internal override void LoadReferences() { } | |||
| internal override bool LoadReferences() { return true; } | |||
| internal override void UnloadReferences() { } | |||
| internal void Update(InviteReference model) | |||
| @@ -15,24 +15,25 @@ namespace Discord | |||
| Queued, | |||
| 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 | |||
| { | |||
| /// <summary> Unique identifier for this file. </summary> | |||
| @@ -173,6 +174,8 @@ namespace Discord | |||
| x => | |||
| { | |||
| var channel = Channel; | |||
| if (channel == null) return null; | |||
| if (!channel.IsPrivate) | |||
| return _client.Users[x, channel.Server.Id]; | |||
| else | |||
| @@ -181,10 +184,9 @@ namespace Discord | |||
| Attachments = _initialAttachments; | |||
| Embeds = _initialEmbeds; | |||
| } | |||
| internal override void LoadReferences() | |||
| internal override bool LoadReferences() | |||
| { | |||
| _channel.Load(); | |||
| _user.Load(); | |||
| return _channel.Load() && _user.Load(); | |||
| } | |||
| 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.Lock(); | |||
| } | |||
| internal override void LoadReferences() | |||
| internal override bool LoadReferences() | |||
| { | |||
| _server.Load(); | |||
| return _server.Load(); | |||
| } | |||
| internal override void UnloadReferences() | |||
| { | |||
| @@ -103,9 +103,10 @@ namespace Discord | |||
| //Local Cache | |||
| _bans = new ConcurrentDictionary<long, bool>(); | |||
| } | |||
| internal override void LoadReferences() | |||
| internal override bool LoadReferences() | |||
| { | |||
| _afkChannel.Load(); | |||
| return true; | |||
| } | |||
| internal override void UnloadReferences() | |||
| { | |||
| @@ -148,10 +148,10 @@ namespace Discord | |||
| if (serverId == 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() | |||
| { | |||
| @@ -20,7 +20,7 @@ namespace Discord.Net.Rest | |||
| { | |||
| PreAuthenticate = false, | |||
| ReadWriteTimeout = _config.APITimeout, | |||
| UserAgent = _config.UserAgent | |||
| UserAgent = DiscordAPIClientConfig.UserAgent | |||
| }; | |||
| if (_config.ProxyUrl != null) | |||
| _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; | |||
| if (msg.Type == "READY") | |||
| { | |||
| var payload = token.ToObject<ReadyEvent>(); | |||
| var payload = token.ToObject<ReadyEvent>(_client.DataSocketSerializer); | |||
| _sessionId = payload.SessionId; | |||
| _heartbeatInterval = payload.HeartbeatInterval; | |||
| } | |||
| else if (msg.Type == "RESUMED") | |||
| { | |||
| var payload = token.ToObject<ResumedEvent>(); | |||
| var payload = token.ToObject<ResumedEvent>(_client.DataSocketSerializer); | |||
| _heartbeatInterval = payload.HeartbeatInterval; | |||
| } | |||
| RaiseReceivedEvent(msg.Type, token); | |||
| @@ -98,7 +98,7 @@ namespace Discord.Net.WebSockets | |||
| break; | |||
| 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) | |||
| { | |||
| Host = payload.Url; | |||
| @@ -443,7 +443,7 @@ namespace Discord.Net.WebSockets | |||
| { | |||
| 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; | |||
| _ssrc = payload.SSRC; | |||
| _endpoint = new IPEndPoint((await Dns.GetHostAddressesAsync(Host.Replace("wss://", "")).ConfigureAwait(false)).FirstOrDefault(), payload.Port); | |||
| @@ -486,7 +486,7 @@ namespace Discord.Net.WebSockets | |||
| break; | |||
| 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; | |||
| SendIsTalking(true); | |||
| EndConnect(); | |||
| @@ -494,7 +494,7 @@ namespace Discord.Net.WebSockets | |||
| break; | |||
| 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); | |||
| } | |||
| break; | |||