| @@ -30,7 +30,9 @@ | |||||
| }, | }, | ||||
| "dependencies": { | "dependencies": { | ||||
| "Discord.Net": "1.0.0-*" | |||||
| "Discord.Net.Core": { | |||||
| "target": "project" | |||||
| } | |||||
| }, | }, | ||||
| "frameworks": { | "frameworks": { | ||||
| @@ -0,0 +1,8 @@ | |||||
| namespace Discord | |||||
| { | |||||
| public enum CacheMode | |||||
| { | |||||
| AllowDownload, | |||||
| CacheOnly | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,6 @@ | |||||
| namespace Discord | |||||
| { | |||||
| public interface IAudioChannel | |||||
| { | |||||
| } | |||||
| } | |||||
| @@ -5,13 +5,10 @@ namespace Discord | |||||
| { | { | ||||
| public interface IChannel : ISnowflakeEntity | public interface IChannel : ISnowflakeEntity | ||||
| { | { | ||||
| IReadOnlyCollection<IUser> CachedUsers { get; } | |||||
| /// <summary> Gets a collection of all users in this channel. </summary> | /// <summary> Gets a collection of all users in this channel. </summary> | ||||
| IAsyncEnumerable<IReadOnlyCollection<IUser>> GetUsersAsync(); | |||||
| IAsyncEnumerable<IReadOnlyCollection<IUser>> GetUsersAsync(CacheMode mode = CacheMode.AllowDownload); | |||||
| /// <summary> Gets a user in this channel with the provided id.</summary> | /// <summary> Gets a user in this channel with the provided id.</summary> | ||||
| Task<IUser> GetUserAsync(ulong id); | |||||
| IUser GetCachedUser(ulong id); | |||||
| Task<IUser> GetUserAsync(ulong id, CacheMode mode = CacheMode.AllowDownload); | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,15 +1,9 @@ | |||||
| using System.Collections.Generic; | |||||
| using System.Threading.Tasks; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord | namespace Discord | ||||
| { | { | ||||
| public interface IGroupChannel : IMessageChannel, IPrivateChannel | |||||
| public interface IGroupChannel : IMessageChannel, IPrivateChannel, IAudioChannel | |||||
| { | { | ||||
| ///// <summary> Adds a user to this group. </summary> | |||||
| //Task AddUserAsync(IUser user); | |||||
| //new IReadOnlyCollection<IGroupUser> CachedUsers { get; } | |||||
| /// <summary> Leaves this group. </summary> | /// <summary> Leaves this group. </summary> | ||||
| Task LeaveAsync(); | Task LeaveAsync(); | ||||
| } | } | ||||
| @@ -14,7 +14,8 @@ namespace Discord | |||||
| /// <summary> Gets the id of the guild this channel is a member of. </summary> | /// <summary> Gets the id of the guild this channel is a member of. </summary> | ||||
| ulong GuildId { get; } | ulong GuildId { get; } | ||||
| new IReadOnlyCollection<IGuildUser> CachedUsers { get; } | |||||
| /// <summary> Gets a collection of permission overwrites for this channel. </summary> | |||||
| IReadOnlyCollection<Overwrite> PermissionOverwrites { get; } | |||||
| /// <summary> Creates a new invite to this channel. </summary> | /// <summary> Creates a new invite to this channel. </summary> | ||||
| /// <param name="maxAge"> The time (in seconds) until the invite expires. Set to null to never expire. </param> | /// <param name="maxAge"> The time (in seconds) until the invite expires. Set to null to never expire. </param> | ||||
| @@ -23,9 +24,6 @@ namespace Discord | |||||
| Task<IInviteMetadata> CreateInviteAsync(int? maxAge = 1800, int? maxUses = default(int?), bool isTemporary = false); | Task<IInviteMetadata> CreateInviteAsync(int? maxAge = 1800, int? maxUses = default(int?), bool isTemporary = false); | ||||
| /// <summary> Returns a collection of all invites to this channel. </summary> | /// <summary> Returns a collection of all invites to this channel. </summary> | ||||
| Task<IReadOnlyCollection<IInviteMetadata>> GetInvitesAsync(); | Task<IReadOnlyCollection<IInviteMetadata>> GetInvitesAsync(); | ||||
| /// <summary> Gets a collection of permission overwrites for this channel. </summary> | |||||
| IReadOnlyCollection<Overwrite> PermissionOverwrites { get; } | |||||
| /// <summary> Modifies this guild channel. </summary> | /// <summary> Modifies this guild channel. </summary> | ||||
| Task ModifyAsync(Action<ModifyGuildChannelParams> func); | Task ModifyAsync(Action<ModifyGuildChannelParams> func); | ||||
| @@ -44,9 +42,8 @@ namespace Discord | |||||
| Task AddPermissionOverwriteAsync(IUser user, OverwritePermissions permissions); | Task AddPermissionOverwriteAsync(IUser user, OverwritePermissions permissions); | ||||
| /// <summary> Gets a collection of all users in this channel. </summary> | /// <summary> Gets a collection of all users in this channel. </summary> | ||||
| new IAsyncEnumerable<IReadOnlyCollection<IGuildUser>> GetUsersAsync(); | |||||
| new IAsyncEnumerable<IReadOnlyCollection<IGuildUser>> GetUsersAsync(CacheMode mode = CacheMode.AllowDownload); | |||||
| /// <summary> Gets a user in this channel with the provided id.</summary> | /// <summary> Gets a user in this channel with the provided id.</summary> | ||||
| new Task<IGuildUser> GetUserAsync(ulong id); | |||||
| new IGuildUser GetCachedUser(ulong id); | |||||
| new Task<IGuildUser> GetUserAsync(ulong id, CacheMode mode = CacheMode.AllowDownload); | |||||
| } | } | ||||
| } | } | ||||
| @@ -7,9 +7,6 @@ namespace Discord | |||||
| { | { | ||||
| public interface IMessageChannel : IChannel | public interface IMessageChannel : IChannel | ||||
| { | { | ||||
| /// <summary> Gets all messages in this channel's cache. </summary> | |||||
| IReadOnlyCollection<IMessage> CachedMessages { get; } | |||||
| /// <summary> Sends a message to this message channel. </summary> | /// <summary> Sends a message to this message channel. </summary> | ||||
| Task<IUserMessage> SendMessageAsync(string text, bool isTTS = false); | Task<IUserMessage> SendMessageAsync(string text, bool isTTS = false); | ||||
| /// <summary> Sends a file to this text channel, with an optional caption. </summary> | /// <summary> Sends a file to this text channel, with an optional caption. </summary> | ||||
| @@ -18,13 +15,11 @@ namespace Discord | |||||
| Task<IUserMessage> SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false); | Task<IUserMessage> SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false); | ||||
| /// <summary> Gets a message from this message channel with the given id, or null if not found. </summary> | /// <summary> Gets a message from this message channel with the given id, or null if not found. </summary> | ||||
| Task<IMessage> GetMessageAsync(ulong id); | |||||
| /// <summary> Gets the message from this channel's cache with the given id, or null if not found. </summary> | |||||
| IMessage GetCachedMessage(ulong id); | |||||
| Task<IMessage> GetMessageAsync(ulong id, CacheMode mode = CacheMode.AllowDownload); | |||||
| /// <summary> Gets the last N messages from this message channel. </summary> | /// <summary> Gets the last N messages from this message channel. </summary> | ||||
| IAsyncEnumerable<IReadOnlyCollection<IMessage>> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch); | |||||
| IAsyncEnumerable<IReadOnlyCollection<IMessage>> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, CacheMode mode = CacheMode.AllowDownload); | |||||
| /// <summary> Gets a collection of messages in this channel. </summary> | /// <summary> Gets a collection of messages in this channel. </summary> | ||||
| IAsyncEnumerable<IReadOnlyCollection<IMessage>> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch); | |||||
| IAsyncEnumerable<IReadOnlyCollection<IMessage>> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, CacheMode mode = CacheMode.AllowDownload); | |||||
| /// <summary> Gets a collection of pinned messages in this channel. </summary> | /// <summary> Gets a collection of pinned messages in this channel. </summary> | ||||
| Task<IReadOnlyCollection<IMessage>> GetPinnedMessagesAsync(); | Task<IReadOnlyCollection<IMessage>> GetPinnedMessagesAsync(); | ||||
| /// <summary> Bulk deletes multiple messages. </summary> | /// <summary> Bulk deletes multiple messages. </summary> | ||||
| @@ -5,7 +5,7 @@ using System.Threading.Tasks; | |||||
| namespace Discord | namespace Discord | ||||
| { | { | ||||
| public interface IVoiceChannel : IGuildChannel | |||||
| public interface IVoiceChannel : IGuildChannel, IAudioChannel | |||||
| { | { | ||||
| /// <summary> Gets the bitrate, in bits per second, clients in this voice channel are requested to use. </summary> | /// <summary> Gets the bitrate, in bits per second, clients in this voice channel are requested to use. </summary> | ||||
| int Bitrate { get; } | int Bitrate { get; } | ||||
| @@ -1,10 +1,5 @@ | |||||
| //using Discord.Rest; | |||||
| using System.Diagnostics; | |||||
| using Model = Discord.API.Ban; | |||||
| namespace Discord | |||||
| namespace Discord | |||||
| { | { | ||||
| public interface IBan | public interface IBan | ||||
| { | { | ||||
| IUser User { get; } | IUser User { get; } | ||||
| @@ -41,7 +41,6 @@ namespace Discord | |||||
| ulong OwnerId { get; } | ulong OwnerId { get; } | ||||
| /// <summary> Gets the id of the region hosting this guild's voice channels. </summary> | /// <summary> Gets the id of the region hosting this guild's voice channels. </summary> | ||||
| string VoiceRegionId { get; } | string VoiceRegionId { get; } | ||||
| /// <summary> Gets the IAudioClient currently associated with this guild. </summary> | /// <summary> Gets the IAudioClient currently associated with this guild. </summary> | ||||
| IAudioClient AudioClient { get; } | IAudioClient AudioClient { get; } | ||||
| /// <summary> Gets the built-in role containing all users in this guild. </summary> | /// <summary> Gets the built-in role containing all users in this guild. </summary> | ||||
| @@ -52,7 +51,6 @@ namespace Discord | |||||
| IReadOnlyCollection<string> Features { get; } | IReadOnlyCollection<string> Features { get; } | ||||
| /// <summary> Gets a collection of all roles in this guild. </summary> | /// <summary> Gets a collection of all roles in this guild. </summary> | ||||
| IReadOnlyCollection<IRole> Roles { get; } | IReadOnlyCollection<IRole> Roles { get; } | ||||
| IReadOnlyCollection<IGuildUser> CachedUsers { get; } | |||||
| /// <summary> Modifies this guild. </summary> | /// <summary> Modifies this guild. </summary> | ||||
| Task ModifyAsync(Action<ModifyGuildParams> func); | Task ModifyAsync(Action<ModifyGuildParams> func); | ||||
| @@ -77,10 +75,9 @@ namespace Discord | |||||
| Task RemoveBanAsync(ulong userId); | Task RemoveBanAsync(ulong userId); | ||||
| /// <summary> Gets a collection of all channels in this guild. </summary> | /// <summary> Gets a collection of all channels in this guild. </summary> | ||||
| Task<IReadOnlyCollection<IGuildChannel>> GetChannelsAsync(); | |||||
| Task<IReadOnlyCollection<IGuildChannel>> GetChannelsAsync(CacheMode mode = CacheMode.AllowDownload); | |||||
| /// <summary> Gets the channel in this guild with the provided id, or null if not found. </summary> | /// <summary> Gets the channel in this guild with the provided id, or null if not found. </summary> | ||||
| Task<IGuildChannel> GetChannelAsync(ulong id); | |||||
| IGuildChannel GetCachedChannel(ulong id); | |||||
| Task<IGuildChannel> GetChannelAsync(ulong id, CacheMode mode = CacheMode.AllowDownload); | |||||
| /// <summary> Creates a new text channel. </summary> | /// <summary> Creates a new text channel. </summary> | ||||
| Task<ITextChannel> CreateTextChannelAsync(string name); | Task<ITextChannel> CreateTextChannelAsync(string name); | ||||
| /// <summary> Creates a new voice channel. </summary> | /// <summary> Creates a new voice channel. </summary> | ||||
| @@ -98,12 +95,11 @@ namespace Discord | |||||
| Task<IRole> CreateRoleAsync(string name, GuildPermissions? permissions = null, Color? color = null, bool isHoisted = false); | Task<IRole> CreateRoleAsync(string name, GuildPermissions? permissions = null, Color? color = null, bool isHoisted = false); | ||||
| /// <summary> Gets a collection of all users in this guild. </summary> | /// <summary> Gets a collection of all users in this guild. </summary> | ||||
| Task<IReadOnlyCollection<IGuildUser>> GetUsersAsync(); | |||||
| Task<IReadOnlyCollection<IGuildUser>> GetUsersAsync(CacheMode mode = CacheMode.AllowDownload); //TODO: shouldnt this be paged? | |||||
| /// <summary> Gets the user in this guild with the provided id, or null if not found. </summary> | /// <summary> Gets the user in this guild with the provided id, or null if not found. </summary> | ||||
| Task<IGuildUser> GetUserAsync(ulong id); | |||||
| IGuildUser GetCachedUser(ulong id); | |||||
| Task<IGuildUser> GetUserAsync(ulong id, CacheMode mode = CacheMode.AllowDownload); | |||||
| /// <summary> Gets the current user for this guild. </summary> | /// <summary> Gets the current user for this guild. </summary> | ||||
| Task<IGuildUser> GetCurrentUserAsync(); | |||||
| Task<IGuildUser> GetCurrentUserAsync(CacheMode mode = CacheMode.AllowDownload); | |||||
| /// <summary> Downloads all users for this guild if the current list is incomplete. </summary> | /// <summary> Downloads all users for this guild if the current list is incomplete. </summary> | ||||
| Task DownloadUsersAsync(); | Task DownloadUsersAsync(); | ||||
| /// <summary> Removes all users from this guild if they have not logged on in a provided number of days or, if simulate is true, returns the number of users that would be removed. </summary> | /// <summary> Removes all users from this guild if they have not logged on in a provided number of days or, if simulate is true, returns the number of users that would be removed. </summary> | ||||
| @@ -17,8 +17,8 @@ namespace Discord | |||||
| /// <summary> Gets the username for this user. </summary> | /// <summary> Gets the username for this user. </summary> | ||||
| string Username { get; } | string Username { get; } | ||||
| /// <summary> Returns a private message channel to this user, returning null if one does not exist or is not cached. </summary> | |||||
| IDMChannel GetCachedDMChannel(); | |||||
| /// <summary> Returns a private message channel to this user, creating one if it does not already exist. </summary> | |||||
| Task<IDMChannel> GetDMChannelAsync(CacheMode mode = CacheMode.AllowDownload); | |||||
| /// <summary> Returns a private message channel to this user, creating one if it does not already exist. </summary> | /// <summary> Returns a private message channel to this user, creating one if it does not already exist. </summary> | ||||
| Task<IDMChannel> CreateDMChannelAsync(); | Task<IDMChannel> CreateDMChannelAsync(); | ||||
| } | } | ||||
| @@ -5,10 +5,18 @@ namespace Discord | |||||
| { | { | ||||
| internal static class TaskCompletionSourceExtensions | internal static class TaskCompletionSourceExtensions | ||||
| { | { | ||||
| public static Task SetResultAsync<T>(this TaskCompletionSource<T> source, T result) | |||||
| => Task.Run(() => source.SetResult(result)); | |||||
| public static Task<bool> TrySetResultAsync<T>(this TaskCompletionSource<T> source, T result) | public static Task<bool> TrySetResultAsync<T>(this TaskCompletionSource<T> source, T result) | ||||
| => Task.Run(() => source.TrySetResult(result)); | => Task.Run(() => source.TrySetResult(result)); | ||||
| public static Task SetExceptionAsync<T>(this TaskCompletionSource<T> source, Exception ex) | |||||
| => Task.Run(() => source.SetException(ex)); | |||||
| public static Task<bool> TrySetExceptionAsync<T>(this TaskCompletionSource<T> source, Exception ex) | public static Task<bool> TrySetExceptionAsync<T>(this TaskCompletionSource<T> source, Exception ex) | ||||
| => Task.Run(() => source.TrySetException(ex)); | => Task.Run(() => source.TrySetException(ex)); | ||||
| public static Task SetCanceledAsync<T>(this TaskCompletionSource<T> source) | |||||
| => Task.Run(() => source.SetCanceled()); | |||||
| public static Task<bool> TrySetCanceledAsync<T>(this TaskCompletionSource<T> source) | public static Task<bool> TrySetCanceledAsync<T>(this TaskCompletionSource<T> source) | ||||
| => Task.Run(() => source.TrySetCanceled()); | => Task.Run(() => source.TrySetCanceled()); | ||||
| } | } | ||||
| @@ -11,7 +11,7 @@ | |||||
| internal bool IgnoreState { get; set; } | internal bool IgnoreState { get; set; } | ||||
| public static RequestOptions CreateOrClone(RequestOptions options) | |||||
| internal static RequestOptions CreateOrClone(RequestOptions options) | |||||
| { | { | ||||
| if (options == null) | if (options == null) | ||||
| return new RequestOptions(); | return new RequestOptions(); | ||||
| @@ -9,6 +9,11 @@ namespace Discord | |||||
| { | { | ||||
| //Based on https://github.com/dotnet/corefx/blob/master/src/System.Collections.Concurrent/src/System/Collections/Concurrent/ConcurrentDictionary.cs | //Based on https://github.com/dotnet/corefx/blob/master/src/System.Collections.Concurrent/src/System/Collections/Concurrent/ConcurrentDictionary.cs | ||||
| //Copyright (c) .NET Foundation and Contributors | //Copyright (c) .NET Foundation and Contributors | ||||
| public static class ConcurrentHashSet | |||||
| { | |||||
| public static int DefaultConcurrencyLevel => PlatformHelper.ProcessorCount; | |||||
| } | |||||
| [DebuggerDisplay("Count = {Count}")] | [DebuggerDisplay("Count = {Count}")] | ||||
| internal class ConcurrentHashSet<T> : IReadOnlyCollection<T> | internal class ConcurrentHashSet<T> : IReadOnlyCollection<T> | ||||
| { | { | ||||
| @@ -1,152 +0,0 @@ | |||||
| using System.Runtime.CompilerServices; | |||||
| namespace Discord.Extensions | |||||
| { | |||||
| internal static class Permissions | |||||
| { | |||||
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | |||||
| public static PermValue GetValue(ulong allow, ulong deny, ChannelPermission bit) | |||||
| => GetValue(allow, deny, (byte)bit); | |||||
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | |||||
| public static PermValue GetValue(ulong allow, ulong deny, GuildPermission bit) | |||||
| => GetValue(allow, deny, (byte)bit); | |||||
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | |||||
| public static PermValue GetValue(ulong allow, ulong deny, byte bit) | |||||
| { | |||||
| if (HasBit(allow, bit)) | |||||
| return PermValue.Allow; | |||||
| else if (HasBit(deny, bit)) | |||||
| return PermValue.Deny; | |||||
| else | |||||
| return PermValue.Inherit; | |||||
| } | |||||
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | |||||
| public static bool GetValue(ulong value, ChannelPermission bit) | |||||
| => GetValue(value, (byte)bit); | |||||
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | |||||
| public static bool GetValue(ulong value, GuildPermission bit) | |||||
| => GetValue(value, (byte)bit); | |||||
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | |||||
| public static bool GetValue(ulong value, byte bit) => HasBit(value, bit); | |||||
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | |||||
| public static void SetValue(ref ulong rawValue, bool? value, ChannelPermission bit) | |||||
| => SetValue(ref rawValue, value, (byte)bit); | |||||
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | |||||
| public static void SetValue(ref ulong rawValue, bool? value, GuildPermission bit) | |||||
| => SetValue(ref rawValue, value, (byte)bit); | |||||
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | |||||
| public static void SetValue(ref ulong rawValue, bool? value, byte bit) | |||||
| { | |||||
| if (value.HasValue) | |||||
| { | |||||
| if (value == true) | |||||
| SetBit(ref rawValue, bit); | |||||
| else | |||||
| UnsetBit(ref rawValue, bit); | |||||
| } | |||||
| } | |||||
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | |||||
| public static void SetValue(ref ulong allow, ref ulong deny, PermValue? value, ChannelPermission bit) | |||||
| => SetValue(ref allow, ref deny, value, (byte)bit); | |||||
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | |||||
| public static void SetValue(ref ulong allow, ref ulong deny, PermValue? value, GuildPermission bit) | |||||
| => SetValue(ref allow, ref deny, value, (byte)bit); | |||||
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | |||||
| public static void SetValue(ref ulong allow, ref ulong deny, PermValue? value, byte bit) | |||||
| { | |||||
| if (value.HasValue) | |||||
| { | |||||
| switch (value) | |||||
| { | |||||
| case PermValue.Allow: | |||||
| SetBit(ref allow, bit); | |||||
| UnsetBit(ref deny, bit); | |||||
| break; | |||||
| case PermValue.Deny: | |||||
| UnsetBit(ref allow, bit); | |||||
| SetBit(ref deny, bit); | |||||
| break; | |||||
| default: | |||||
| UnsetBit(ref allow, bit); | |||||
| UnsetBit(ref deny, bit); | |||||
| break; | |||||
| } | |||||
| } | |||||
| } | |||||
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | |||||
| private static bool HasBit(ulong value, byte bit) => (value & (1U << bit)) != 0; | |||||
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | |||||
| public static void SetBit(ref ulong value, byte bit) => value |= (1U << bit); | |||||
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | |||||
| public static void UnsetBit(ref ulong value, byte bit) => value &= ~(1U << bit); | |||||
| public static ulong ResolveGuild(IGuild guild, IGuildUser user) | |||||
| { | |||||
| ulong resolvedPermissions = 0; | |||||
| if (user.Id == guild.OwnerId) | |||||
| resolvedPermissions = GuildPermissions.All.RawValue; //Owners always have all permissions | |||||
| else | |||||
| { | |||||
| foreach (var role in user.RoleIds) | |||||
| resolvedPermissions |= guild.GetRole(role).Permissions.RawValue; | |||||
| if (GetValue(resolvedPermissions, GuildPermission.Administrator)) | |||||
| resolvedPermissions = GuildPermissions.All.RawValue; //Administrators always have all permissions | |||||
| } | |||||
| return resolvedPermissions; | |||||
| } | |||||
| /*public static ulong ResolveChannel(IGuildUser user, IGuildChannel channel) | |||||
| { | |||||
| return ResolveChannel(user, channel, ResolveGuild(user)); | |||||
| }*/ | |||||
| public static ulong ResolveChannel(IGuild guild, IGuildChannel channel, IGuildUser user, ulong guildPermissions) | |||||
| { | |||||
| ulong resolvedPermissions = 0; | |||||
| ulong mask = ChannelPermissions.All(channel).RawValue; | |||||
| if (/*user.Id == user.Guild.OwnerId || */GetValue(guildPermissions, GuildPermission.Administrator)) | |||||
| resolvedPermissions = mask; //Owners and administrators always have all permissions | |||||
| else | |||||
| { | |||||
| //Start with this user's guild permissions | |||||
| resolvedPermissions = guildPermissions; | |||||
| OverwritePermissions? perms; | |||||
| var roleIds = user.RoleIds; | |||||
| if (roleIds.Count > 0) | |||||
| { | |||||
| ulong deniedPermissions = 0UL, allowedPermissions = 0UL; | |||||
| foreach (var roleId in roleIds) | |||||
| { | |||||
| perms = channel.GetPermissionOverwrite(guild.GetRole(roleId)); | |||||
| if (perms != null) | |||||
| { | |||||
| deniedPermissions |= perms.Value.DenyValue; | |||||
| allowedPermissions |= perms.Value.AllowValue; | |||||
| } | |||||
| } | |||||
| resolvedPermissions = (resolvedPermissions & ~deniedPermissions) | allowedPermissions; | |||||
| } | |||||
| perms = channel.GetPermissionOverwrite(user); | |||||
| if (perms != null) | |||||
| resolvedPermissions = (resolvedPermissions & ~perms.Value.DenyValue) | perms.Value.AllowValue; | |||||
| //TODO: C#7 Typeswitch candidate | |||||
| var textChannel = channel as ITextChannel; | |||||
| var voiceChannel = channel as IVoiceChannel; | |||||
| if (textChannel != null && !GetValue(resolvedPermissions, ChannelPermission.ReadMessages)) | |||||
| resolvedPermissions = 0; //No read permission on a text channel removes all other permissions | |||||
| else if (voiceChannel != null && !GetValue(resolvedPermissions, ChannelPermission.Connect)) | |||||
| resolvedPermissions = 0; //No connect permission on a voice channel removes all other permissions | |||||
| resolvedPermissions &= mask; //Ensure we didnt get any permissions this channel doesnt support (from guildPerms, for example) | |||||
| } | |||||
| return resolvedPermissions; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -3,6 +3,7 @@ using System.Collections.Immutable; | |||||
| using System.Globalization; | using System.Globalization; | ||||
| using System.Linq; | using System.Linq; | ||||
| using System.Text.RegularExpressions; | using System.Text.RegularExpressions; | ||||
| using System.Threading.Tasks; | |||||
| namespace Discord | namespace Discord | ||||
| { | { | ||||
| @@ -34,7 +35,7 @@ namespace Discord | |||||
| { | { | ||||
| if (userMention.Id == id) | if (userMention.Id == id) | ||||
| { | { | ||||
| user = channel?.GetCachedUser(id) as TUser; | |||||
| user = channel?.GetUserAsync(id, CacheMode.CacheOnly).GetAwaiter().GetResult() as TUser; | |||||
| if (user == null) //User not found, fallback to basic mention info | if (user == null) //User not found, fallback to basic mention info | ||||
| user = userMention; | user = userMention; | ||||
| break; | break; | ||||
| @@ -136,7 +137,7 @@ namespace Discord | |||||
| return ""; | return ""; | ||||
| case ChannelMentionHandling.Name: | case ChannelMentionHandling.Name: | ||||
| IGuildChannel channel = null; | IGuildChannel channel = null; | ||||
| channel = guild.GetCachedChannel(id); | |||||
| channel = guild.GetChannelAsync(id, CacheMode.CacheOnly).GetAwaiter().GetResult(); | |||||
| if (channel != null) | if (channel != null) | ||||
| return $"#{channel.Name}"; | return $"#{channel.Name}"; | ||||
| else | else | ||||
| @@ -0,0 +1,6 @@ | |||||
| namespace Discord.Rest | |||||
| { | |||||
| public interface IRestAudioChannel : IAudioChannel | |||||
| { | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,25 @@ | |||||
| using System.Collections.Generic; | |||||
| using System.IO; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord.Rest | |||||
| { | |||||
| public interface IRestMessageChannel : IMessageChannel | |||||
| { | |||||
| /// <summary> Sends a message to this message channel. </summary> | |||||
| new Task<RestUserMessage> SendMessageAsync(string text, bool isTTS = false); | |||||
| /// <summary> Sends a file to this text channel, with an optional caption. </summary> | |||||
| new Task<RestUserMessage> SendFileAsync(string filePath, string text = null, bool isTTS = false); | |||||
| /// <summary> Sends a file to this text channel, with an optional caption. </summary> | |||||
| new Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false); | |||||
| /// <summary> Gets a message from this message channel with the given id, or null if not found. </summary> | |||||
| Task<RestMessage> GetMessageAsync(ulong id); | |||||
| /// <summary> Gets the last N messages from this message channel. </summary> | |||||
| IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch); | |||||
| /// <summary> Gets a collection of messages in this channel. </summary> | |||||
| IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch); | |||||
| /// <summary> Gets a collection of pinned messages in this channel. </summary> | |||||
| new Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync(); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,9 @@ | |||||
| using System.Collections.Generic; | |||||
| namespace Discord.Rest | |||||
| { | |||||
| public interface IRestPrivateChannel : IPrivateChannel | |||||
| { | |||||
| new IReadOnlyCollection<RestUser> Recipients { get; } | |||||
| } | |||||
| } | |||||
| @@ -23,6 +23,17 @@ namespace Discord.Rest | |||||
| return RestTextChannel.Create(discord, model); | return RestTextChannel.Create(discord, model); | ||||
| case ChannelType.Voice: | case ChannelType.Voice: | ||||
| return RestVoiceChannel.Create(discord, model); | return RestVoiceChannel.Create(discord, model); | ||||
| case ChannelType.DM: | |||||
| case ChannelType.Group: | |||||
| return CreatePrivate(discord, model) as RestChannel; | |||||
| default: | |||||
| throw new InvalidOperationException($"Unexpected channel type: {model.Type}"); | |||||
| } | |||||
| } | |||||
| internal static IRestPrivateChannel CreatePrivate(BaseDiscordClient discord, Model model) | |||||
| { | |||||
| switch (model.Type) | |||||
| { | |||||
| case ChannelType.DM: | case ChannelType.DM: | ||||
| return RestDMChannel.Create(discord, model); | return RestDMChannel.Create(discord, model); | ||||
| case ChannelType.Group: | case ChannelType.Group: | ||||
| @@ -35,14 +46,10 @@ namespace Discord.Rest | |||||
| public abstract Task UpdateAsync(); | public abstract Task UpdateAsync(); | ||||
| //IChannel | |||||
| IReadOnlyCollection<IUser> IChannel.CachedUsers => ImmutableArray.Create<IUser>(); | |||||
| IUser IChannel.GetCachedUser(ulong id) | |||||
| => null; | |||||
| Task<IUser> IChannel.GetUserAsync(ulong id) | |||||
| => Task.FromResult<IUser>(null); | |||||
| IAsyncEnumerable<IReadOnlyCollection<IUser>> IChannel.GetUsersAsync() | |||||
| => ImmutableArray.Create<IReadOnlyCollection<IUser>>().ToAsyncEnumerable(); | |||||
| //IChannel | |||||
| Task<IUser> IChannel.GetUserAsync(ulong id, CacheMode mode) | |||||
| => Task.FromResult<IUser>(null); //Overriden | |||||
| IAsyncEnumerable<IReadOnlyCollection<IUser>> IChannel.GetUsersAsync(CacheMode mode) | |||||
| => ImmutableArray.Create<IReadOnlyCollection<IUser>>().ToAsyncEnumerable(); //Overriden | |||||
| } | } | ||||
| } | } | ||||
| @@ -10,7 +10,7 @@ using Model = Discord.API.Channel; | |||||
| namespace Discord.Rest | namespace Discord.Rest | ||||
| { | { | ||||
| [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | ||||
| public class RestDMChannel : RestChannel, IDMChannel, IUpdateable | |||||
| public class RestDMChannel : RestChannel, IDMChannel, IRestPrivateChannel, IRestMessageChannel, IUpdateable | |||||
| { | { | ||||
| public RestUser CurrentUser { get; private set; } | public RestUser CurrentUser { get; private set; } | ||||
| public RestUser Recipient { get; private set; } | public RestUser Recipient { get; private set; } | ||||
| @@ -75,23 +75,39 @@ namespace Discord.Rest | |||||
| public override string ToString() => $"@{Recipient}"; | public override string ToString() => $"@{Recipient}"; | ||||
| private string DebuggerDisplay => $"@{Recipient} ({Id}, DM)"; | private string DebuggerDisplay => $"@{Recipient} ({Id}, DM)"; | ||||
| //IDMChannel | //IDMChannel | ||||
| IUser IDMChannel.Recipient => Recipient; | IUser IDMChannel.Recipient => Recipient; | ||||
| //IRestPrivateChannel | |||||
| IReadOnlyCollection<RestUser> IRestPrivateChannel.Recipients => ImmutableArray.Create(Recipient); | |||||
| //IPrivateChannel | //IPrivateChannel | ||||
| IReadOnlyCollection<IUser> IPrivateChannel.Recipients => ImmutableArray.Create<IUser>(Recipient); | IReadOnlyCollection<IUser> IPrivateChannel.Recipients => ImmutableArray.Create<IUser>(Recipient); | ||||
| //IMessageChannel | //IMessageChannel | ||||
| IReadOnlyCollection<IMessage> IMessageChannel.CachedMessages => ImmutableArray.Create<IMessage>(); | |||||
| IMessage IMessageChannel.GetCachedMessage(ulong id) => null; | |||||
| async Task<IMessage> IMessageChannel.GetMessageAsync(ulong id) | |||||
| => await GetMessageAsync(id); | |||||
| IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(int limit) | |||||
| => GetMessagesAsync(limit); | |||||
| IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit) | |||||
| => GetMessagesAsync(fromMessageId, dir, limit); | |||||
| async Task<IMessage> IMessageChannel.GetMessageAsync(ulong id, CacheMode mode) | |||||
| { | |||||
| if (mode == CacheMode.AllowDownload) | |||||
| return await GetMessageAsync(id); | |||||
| else | |||||
| return null; | |||||
| } | |||||
| IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode) | |||||
| { | |||||
| if (mode == CacheMode.AllowDownload) | |||||
| return GetMessagesAsync(limit); | |||||
| else | |||||
| return ImmutableArray.Create<IReadOnlyCollection<IMessage>>().ToAsyncEnumerable(); | |||||
| } | |||||
| IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit, CacheMode mode) | |||||
| { | |||||
| if (mode == CacheMode.AllowDownload) | |||||
| return GetMessagesAsync(fromMessageId, dir, limit); | |||||
| else | |||||
| return ImmutableArray.Create<IReadOnlyCollection<IMessage>>().ToAsyncEnumerable(); | |||||
| } | |||||
| async Task<IReadOnlyCollection<IMessage>> IMessageChannel.GetPinnedMessagesAsync() | async Task<IReadOnlyCollection<IMessage>> IMessageChannel.GetPinnedMessagesAsync() | ||||
| => await GetPinnedMessagesAsync().ConfigureAwait(false); | => await GetPinnedMessagesAsync().ConfigureAwait(false); | ||||
| async Task<IUserMessage> IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS) | async Task<IUserMessage> IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS) | ||||
| @@ -103,14 +119,10 @@ namespace Discord.Rest | |||||
| IDisposable IMessageChannel.EnterTypingState() | IDisposable IMessageChannel.EnterTypingState() | ||||
| => EnterTypingState(); | => EnterTypingState(); | ||||
| //IChannel | |||||
| IReadOnlyCollection<IUser> IChannel.CachedUsers => Users; | |||||
| IUser IChannel.GetCachedUser(ulong id) | |||||
| => GetUser(id); | |||||
| Task<IUser> IChannel.GetUserAsync(ulong id) | |||||
| //IChannel | |||||
| Task<IUser> IChannel.GetUserAsync(ulong id, CacheMode mode) | |||||
| => Task.FromResult<IUser>(GetUser(id)); | => Task.FromResult<IUser>(GetUser(id)); | ||||
| IAsyncEnumerable<IReadOnlyCollection<IUser>> IChannel.GetUsersAsync() | |||||
| => ImmutableArray.Create<IReadOnlyCollection<IUser>>().ToAsyncEnumerable(); | |||||
| IAsyncEnumerable<IReadOnlyCollection<IUser>> IChannel.GetUsersAsync(CacheMode mode) | |||||
| => ImmutableArray.Create<IReadOnlyCollection<IUser>>(Users).ToAsyncEnumerable(); | |||||
| } | } | ||||
| } | } | ||||
| @@ -10,11 +10,11 @@ using Model = Discord.API.Channel; | |||||
| namespace Discord.Rest | namespace Discord.Rest | ||||
| { | { | ||||
| [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | ||||
| public class RestGroupChannel : RestChannel, IGroupChannel, IUpdateable | |||||
| public class RestGroupChannel : RestChannel, IGroupChannel, IRestPrivateChannel, IRestMessageChannel, IRestAudioChannel, IUpdateable | |||||
| { | { | ||||
| private string _iconId; | private string _iconId; | ||||
| private ImmutableDictionary<ulong, RestGroupUser> _users; | private ImmutableDictionary<ulong, RestGroupUser> _users; | ||||
| public string Name { get; private set; } | public string Name { get; private set; } | ||||
| public IReadOnlyCollection<RestGroupUser> Users => _users.ToReadOnlyCollection(); | public IReadOnlyCollection<RestGroupUser> Users => _users.ToReadOnlyCollection(); | ||||
| @@ -86,20 +86,34 @@ namespace Discord.Rest | |||||
| public IDisposable EnterTypingState() | public IDisposable EnterTypingState() | ||||
| => ChannelHelper.EnterTypingState(this, Discord); | => ChannelHelper.EnterTypingState(this, Discord); | ||||
| //ISocketPrivateChannel | |||||
| IReadOnlyCollection<RestUser> IRestPrivateChannel.Recipients => Recipients; | |||||
| //IPrivateChannel | //IPrivateChannel | ||||
| IReadOnlyCollection<IUser> IPrivateChannel.Recipients => Recipients; | IReadOnlyCollection<IUser> IPrivateChannel.Recipients => Recipients; | ||||
| //IMessageChannel | //IMessageChannel | ||||
| IReadOnlyCollection<IMessage> IMessageChannel.CachedMessages => ImmutableArray.Create<IMessage>(); | |||||
| IMessage IMessageChannel.GetCachedMessage(ulong id) | |||||
| => null; | |||||
| async Task<IMessage> IMessageChannel.GetMessageAsync(ulong id) | |||||
| => await GetMessageAsync(id); | |||||
| IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(int limit) | |||||
| => GetMessagesAsync(limit); | |||||
| IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit) | |||||
| => GetMessagesAsync(fromMessageId, dir, limit); | |||||
| async Task<IMessage> IMessageChannel.GetMessageAsync(ulong id, CacheMode mode) | |||||
| { | |||||
| if (mode == CacheMode.AllowDownload) | |||||
| return await GetMessageAsync(id); | |||||
| else | |||||
| return null; | |||||
| } | |||||
| IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode) | |||||
| { | |||||
| if (mode == CacheMode.AllowDownload) | |||||
| return GetMessagesAsync(limit); | |||||
| else | |||||
| return ImmutableArray.Create<IReadOnlyCollection<IMessage>>().ToAsyncEnumerable(); | |||||
| } | |||||
| IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit, CacheMode mode) | |||||
| { | |||||
| if (mode == CacheMode.AllowDownload) | |||||
| return GetMessagesAsync(fromMessageId, dir, limit); | |||||
| else | |||||
| return ImmutableArray.Create<IReadOnlyCollection<IMessage>>().ToAsyncEnumerable(); | |||||
| } | |||||
| async Task<IReadOnlyCollection<IMessage>> IMessageChannel.GetPinnedMessagesAsync() | async Task<IReadOnlyCollection<IMessage>> IMessageChannel.GetPinnedMessagesAsync() | ||||
| => await GetPinnedMessagesAsync(); | => await GetPinnedMessagesAsync(); | ||||
| @@ -112,14 +126,10 @@ namespace Discord.Rest | |||||
| IDisposable IMessageChannel.EnterTypingState() | IDisposable IMessageChannel.EnterTypingState() | ||||
| => EnterTypingState(); | => EnterTypingState(); | ||||
| //IChannel | |||||
| IReadOnlyCollection<IUser> IChannel.CachedUsers => Users; | |||||
| IUser IChannel.GetCachedUser(ulong id) | |||||
| => GetUser(id); | |||||
| Task<IUser> IChannel.GetUserAsync(ulong id) | |||||
| //IChannel | |||||
| Task<IUser> IChannel.GetUserAsync(ulong id, CacheMode mode) | |||||
| => Task.FromResult(GetUser(id)); | => Task.FromResult(GetUser(id)); | ||||
| IAsyncEnumerable<IReadOnlyCollection<IUser>> IChannel.GetUsersAsync() | |||||
| IAsyncEnumerable<IReadOnlyCollection<IUser>> IChannel.GetUsersAsync(CacheMode mode) | |||||
| => ImmutableArray.Create<IReadOnlyCollection<IUser>>(Users).ToAsyncEnumerable(); | => ImmutableArray.Create<IReadOnlyCollection<IUser>>(Users).ToAsyncEnumerable(); | ||||
| } | } | ||||
| } | } | ||||
| @@ -135,24 +135,16 @@ namespace Discord.Rest | |||||
| => await RemovePermissionOverwriteAsync(role); | => await RemovePermissionOverwriteAsync(role); | ||||
| async Task IGuildChannel.RemovePermissionOverwriteAsync(IUser user) | async Task IGuildChannel.RemovePermissionOverwriteAsync(IUser user) | ||||
| => await RemovePermissionOverwriteAsync(user); | => await RemovePermissionOverwriteAsync(user); | ||||
| IReadOnlyCollection<IGuildUser> IGuildChannel.CachedUsers | |||||
| => ImmutableArray.Create<IGuildUser>(); | |||||
| IAsyncEnumerable<IReadOnlyCollection<IGuildUser>> IGuildChannel.GetUsersAsync() | |||||
| IAsyncEnumerable<IReadOnlyCollection<IGuildUser>> IGuildChannel.GetUsersAsync(CacheMode mode) | |||||
| => ImmutableArray.Create<IReadOnlyCollection<IGuildUser>>().ToAsyncEnumerable(); //Overriden in Text/Voice //TODO: Does this actually override? | => ImmutableArray.Create<IReadOnlyCollection<IGuildUser>>().ToAsyncEnumerable(); //Overriden in Text/Voice //TODO: Does this actually override? | ||||
| Task<IGuildUser> IGuildChannel.GetUserAsync(ulong id) | |||||
| Task<IGuildUser> IGuildChannel.GetUserAsync(ulong id, CacheMode mode) | |||||
| => Task.FromResult<IGuildUser>(null); //Overriden in Text/Voice //TODO: Does this actually override? | => Task.FromResult<IGuildUser>(null); //Overriden in Text/Voice //TODO: Does this actually override? | ||||
| IGuildUser IGuildChannel.GetCachedUser(ulong id) | |||||
| => null; | |||||
| //IChannel | //IChannel | ||||
| IReadOnlyCollection<IUser> IChannel.CachedUsers | |||||
| => ImmutableArray.Create<IUser>(); | |||||
| IUser IChannel.GetCachedUser(ulong id) | |||||
| => null; | |||||
| IAsyncEnumerable<IReadOnlyCollection<IUser>> IChannel.GetUsersAsync() | |||||
| IAsyncEnumerable<IReadOnlyCollection<IUser>> IChannel.GetUsersAsync(CacheMode mode) | |||||
| => ImmutableArray.Create<IReadOnlyCollection<IUser>>().ToAsyncEnumerable(); //Overriden in Text/Voice //TODO: Does this actually override? | => ImmutableArray.Create<IReadOnlyCollection<IUser>>().ToAsyncEnumerable(); //Overriden in Text/Voice //TODO: Does this actually override? | ||||
| Task<IUser> IChannel.GetUserAsync(ulong id) | |||||
| Task<IUser> IChannel.GetUserAsync(ulong id, CacheMode mode) | |||||
| => Task.FromResult<IUser>(null); //Overriden in Text/Voice //TODO: Does this actually override? | => Task.FromResult<IUser>(null); //Overriden in Text/Voice //TODO: Does this actually override? | ||||
| } | } | ||||
| } | } | ||||
| @@ -4,13 +4,14 @@ using System.Collections.Generic; | |||||
| using System.Collections.Immutable; | using System.Collections.Immutable; | ||||
| using System.Diagnostics; | using System.Diagnostics; | ||||
| using System.IO; | using System.IO; | ||||
| using System.Linq; | |||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| using Model = Discord.API.Channel; | using Model = Discord.API.Channel; | ||||
| namespace Discord.Rest | namespace Discord.Rest | ||||
| { | { | ||||
| [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | ||||
| public class RestTextChannel : RestGuildChannel, ITextChannel | |||||
| public class RestTextChannel : RestGuildChannel, IRestMessageChannel, ITextChannel | |||||
| { | { | ||||
| public string Topic { get; private set; } | public string Topic { get; private set; } | ||||
| @@ -67,22 +68,43 @@ namespace Discord.Rest | |||||
| => ChannelHelper.EnterTypingState(this, Discord); | => ChannelHelper.EnterTypingState(this, Discord); | ||||
| //IGuildChannel | //IGuildChannel | ||||
| async Task<IGuildUser> IGuildChannel.GetUserAsync(ulong id) | |||||
| => await GetUserAsync(id); | |||||
| IAsyncEnumerable<IReadOnlyCollection<IGuildUser>> IGuildChannel.GetUsersAsync() | |||||
| => GetUsersAsync(); | |||||
| async Task<IGuildUser> IGuildChannel.GetUserAsync(ulong id, CacheMode mode) | |||||
| { | |||||
| if (mode == CacheMode.AllowDownload) | |||||
| return await GetUserAsync(id); | |||||
| else | |||||
| return null; | |||||
| } | |||||
| IAsyncEnumerable<IReadOnlyCollection<IGuildUser>> IGuildChannel.GetUsersAsync(CacheMode mode) | |||||
| { | |||||
| if (mode == CacheMode.AllowDownload) | |||||
| return GetUsersAsync(); | |||||
| else | |||||
| return ImmutableArray.Create<IReadOnlyCollection<IGuildUser>>().ToAsyncEnumerable(); | |||||
| } | |||||
| //IMessageChannel | //IMessageChannel | ||||
| IReadOnlyCollection<IMessage> IMessageChannel.CachedMessages | |||||
| => ImmutableArray.Create<IMessage>(); | |||||
| IMessage IMessageChannel.GetCachedMessage(ulong id) | |||||
| => null; | |||||
| async Task<IMessage> IMessageChannel.GetMessageAsync(ulong id) | |||||
| => await GetMessageAsync(id); | |||||
| IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(int limit) | |||||
| => GetMessagesAsync(limit); | |||||
| IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit) | |||||
| => GetMessagesAsync(fromMessageId, dir, limit); | |||||
| async Task<IMessage> IMessageChannel.GetMessageAsync(ulong id, CacheMode mode) | |||||
| { | |||||
| if (mode == CacheMode.AllowDownload) | |||||
| return await GetMessageAsync(id); | |||||
| else | |||||
| return null; | |||||
| } | |||||
| IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode) | |||||
| { | |||||
| if (mode == CacheMode.AllowDownload) | |||||
| return GetMessagesAsync(limit); | |||||
| else | |||||
| return ImmutableArray.Create<IReadOnlyCollection<IMessage>>().ToAsyncEnumerable(); | |||||
| } | |||||
| IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit, CacheMode mode) | |||||
| { | |||||
| if (mode == CacheMode.AllowDownload) | |||||
| return GetMessagesAsync(fromMessageId, dir, limit); | |||||
| else | |||||
| return ImmutableArray.Create<IReadOnlyCollection<IMessage>>().ToAsyncEnumerable(); | |||||
| } | |||||
| async Task<IReadOnlyCollection<IMessage>> IMessageChannel.GetPinnedMessagesAsync() | async Task<IReadOnlyCollection<IMessage>> IMessageChannel.GetPinnedMessagesAsync() | ||||
| => await GetPinnedMessagesAsync(); | => await GetPinnedMessagesAsync(); | ||||
| @@ -11,7 +11,7 @@ using Model = Discord.API.Channel; | |||||
| namespace Discord.Rest | namespace Discord.Rest | ||||
| { | { | ||||
| [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | ||||
| public class RestVoiceChannel : RestGuildChannel, IVoiceChannel | |||||
| public class RestVoiceChannel : RestGuildChannel, IVoiceChannel, IRestAudioChannel | |||||
| { | { | ||||
| public int Bitrate { get; private set; } | public int Bitrate { get; private set; } | ||||
| public int UserLimit { get; private set; } | public int UserLimit { get; private set; } | ||||
| @@ -41,9 +41,9 @@ namespace Discord.Rest | |||||
| Task<IAudioClient> IVoiceChannel.ConnectAsync() { throw new NotSupportedException(); } | Task<IAudioClient> IVoiceChannel.ConnectAsync() { throw new NotSupportedException(); } | ||||
| //IGuildChannel | //IGuildChannel | ||||
| Task<IGuildUser> IGuildChannel.GetUserAsync(ulong id) | |||||
| Task<IGuildUser> IGuildChannel.GetUserAsync(ulong id, CacheMode mode) | |||||
| => Task.FromResult<IGuildUser>(null); | => Task.FromResult<IGuildUser>(null); | ||||
| IAsyncEnumerable<IReadOnlyCollection<IGuildUser>> IGuildChannel.GetUsersAsync() | |||||
| IAsyncEnumerable<IReadOnlyCollection<IGuildUser>> IGuildChannel.GetUsersAsync(CacheMode mode) | |||||
| => ImmutableArray.Create<IReadOnlyCollection<IGuildUser>>().ToAsyncEnumerable(); | => ImmutableArray.Create<IReadOnlyCollection<IGuildUser>>().ToAsyncEnumerable(); | ||||
| } | } | ||||
| } | } | ||||
| @@ -6,6 +6,7 @@ using System.Collections.Generic; | |||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| using Discord.API.Rest; | using Discord.API.Rest; | ||||
| using Model = Discord.API.Guild; | using Model = Discord.API.Guild; | ||||
| using System.Linq; | |||||
| namespace Discord.Rest | namespace Discord.Rest | ||||
| { | { | ||||
| @@ -149,7 +150,7 @@ namespace Discord.Rest | |||||
| return null; | return null; | ||||
| } | } | ||||
| public async Task<IRole> CreateRoleAsync(string name, GuildPermissions? permissions = default(GuildPermissions?), Color? color = default(Color?), bool isHoisted = false) | |||||
| public async Task<RestRole> CreateRoleAsync(string name, GuildPermissions? permissions = default(GuildPermissions?), Color? color = default(Color?), bool isHoisted = false) | |||||
| { | { | ||||
| var role = await GuildHelper.CreateRoleAsync(this, Discord, name, permissions, color, isHoisted); | var role = await GuildHelper.CreateRoleAsync(this, Discord, name, permissions, color, isHoisted); | ||||
| _roles = _roles.Add(role.Id, role); | _roles = _roles.Add(role.Id, role); | ||||
| @@ -157,8 +158,8 @@ namespace Discord.Rest | |||||
| } | } | ||||
| //Users | //Users | ||||
| public Task<IReadOnlyCollection<RestGuildUser>> GetUsersAsync() | |||||
| => GuildHelper.GetUsersAsync(this, Discord); | |||||
| public IAsyncEnumerable<IReadOnlyCollection<RestGuildUser>> GetUsersAsync() | |||||
| => GuildHelper.GetUsersAsync(this, Discord).ToAsyncEnumerable(); | |||||
| public Task<RestGuildUser> GetUserAsync(ulong id) | public Task<RestGuildUser> GetUserAsync(ulong id) | ||||
| => GuildHelper.GetUserAsync(this, Discord, id); | => GuildHelper.GetUserAsync(this, Discord, id); | ||||
| public Task<RestGuildUser> GetCurrentUserAsync() | public Task<RestGuildUser> GetCurrentUserAsync() | ||||
| @@ -170,19 +171,26 @@ namespace Discord.Rest | |||||
| //IGuild | //IGuild | ||||
| bool IGuild.Available => true; | bool IGuild.Available => true; | ||||
| IAudioClient IGuild.AudioClient => null; | IAudioClient IGuild.AudioClient => null; | ||||
| IReadOnlyCollection<IGuildUser> IGuild.CachedUsers => ImmutableArray.Create<IGuildUser>(); | |||||
| IRole IGuild.EveryoneRole => EveryoneRole; | IRole IGuild.EveryoneRole => EveryoneRole; | ||||
| IReadOnlyCollection<IRole> IGuild.Roles => Roles; | IReadOnlyCollection<IRole> IGuild.Roles => Roles; | ||||
| async Task<IReadOnlyCollection<IBan>> IGuild.GetBansAsync() | async Task<IReadOnlyCollection<IBan>> IGuild.GetBansAsync() | ||||
| => await GetBansAsync(); | => await GetBansAsync(); | ||||
| async Task<IReadOnlyCollection<IGuildChannel>> IGuild.GetChannelsAsync() | |||||
| => await GetChannelsAsync(); | |||||
| async Task<IGuildChannel> IGuild.GetChannelAsync(ulong id) | |||||
| => await GetChannelAsync(id); | |||||
| IGuildChannel IGuild.GetCachedChannel(ulong id) | |||||
| => null; | |||||
| async Task<IReadOnlyCollection<IGuildChannel>> IGuild.GetChannelsAsync(CacheMode mode) | |||||
| { | |||||
| if (mode == CacheMode.AllowDownload) | |||||
| return await GetChannelsAsync(); | |||||
| else | |||||
| return ImmutableArray.Create<IGuildChannel>(); | |||||
| } | |||||
| async Task<IGuildChannel> IGuild.GetChannelAsync(ulong id, CacheMode mode) | |||||
| { | |||||
| if (mode == CacheMode.AllowDownload) | |||||
| return await GetChannelAsync(id); | |||||
| else | |||||
| return null; | |||||
| } | |||||
| async Task<ITextChannel> IGuild.CreateTextChannelAsync(string name) | async Task<ITextChannel> IGuild.CreateTextChannelAsync(string name) | ||||
| => await CreateTextChannelAsync(name); | => await CreateTextChannelAsync(name); | ||||
| async Task<IVoiceChannel> IGuild.CreateVoiceChannelAsync(string name) | async Task<IVoiceChannel> IGuild.CreateVoiceChannelAsync(string name) | ||||
| @@ -198,15 +206,30 @@ namespace Discord.Rest | |||||
| IRole IGuild.GetRole(ulong id) | IRole IGuild.GetRole(ulong id) | ||||
| => GetRole(id); | => GetRole(id); | ||||
| async Task<IRole> IGuild.CreateRoleAsync(string name, GuildPermissions? permissions, Color? color, bool isHoisted) | |||||
| => await CreateRoleAsync(name, permissions, color, isHoisted); | |||||
| async Task<IReadOnlyCollection<IGuildUser>> IGuild.GetUsersAsync() | |||||
| => await GetUsersAsync(); | |||||
| async Task<IGuildUser> IGuild.GetUserAsync(ulong id) | |||||
| => await GetUserAsync(id); | |||||
| IGuildUser IGuild.GetCachedUser(ulong id) | |||||
| => null; | |||||
| async Task<IGuildUser> IGuild.GetCurrentUserAsync() | |||||
| => await GetCurrentUserAsync(); | |||||
| async Task<IGuildUser> IGuild.GetUserAsync(ulong id, CacheMode mode) | |||||
| { | |||||
| if (mode == CacheMode.AllowDownload) | |||||
| return await GetUserAsync(id); | |||||
| else | |||||
| return null; | |||||
| } | |||||
| async Task<IGuildUser> IGuild.GetCurrentUserAsync(CacheMode mode) | |||||
| { | |||||
| if (mode == CacheMode.AllowDownload) | |||||
| return await GetCurrentUserAsync(); | |||||
| else | |||||
| return null; | |||||
| } | |||||
| async Task<IReadOnlyCollection<IGuildUser>> IGuild.GetUsersAsync(CacheMode mode) | |||||
| { | |||||
| if (mode == CacheMode.AllowDownload) | |||||
| return (await GetUsersAsync().Flatten()).ToImmutableArray(); | |||||
| else | |||||
| return ImmutableArray.Create<IGuildUser>(); | |||||
| } | |||||
| Task IGuild.DownloadUsersAsync() { throw new NotSupportedException(); } | Task IGuild.DownloadUsersAsync() { throw new NotSupportedException(); } | ||||
| } | } | ||||
| } | } | ||||
| @@ -1,4 +1,5 @@ | |||||
| using System.Diagnostics; | |||||
| using System; | |||||
| using System.Diagnostics; | |||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| using Model = Discord.API.User; | using Model = Discord.API.User; | ||||
| @@ -42,10 +43,13 @@ namespace Discord.Rest | |||||
| public virtual async Task UpdateAsync() | public virtual async Task UpdateAsync() | ||||
| => Update(await UserHelper.GetAsync(this, Discord)); | => Update(await UserHelper.GetAsync(this, Discord)); | ||||
| public Task<IDMChannel> CreateDMChannelAsync() | |||||
| public Task<RestDMChannel> CreateDMChannelAsync() | |||||
| => UserHelper.CreateDMChannelAsync(this, Discord); | => UserHelper.CreateDMChannelAsync(this, Discord); | ||||
| IDMChannel IUser.GetCachedDMChannel() => null; | |||||
| Task<IDMChannel> IUser.GetDMChannelAsync(CacheMode mode) | |||||
| => Task.FromResult<IDMChannel>(null); | |||||
| async Task<IDMChannel> IUser.CreateDMChannelAsync() | |||||
| => await CreateDMChannelAsync(); | |||||
| } | } | ||||
| } | } | ||||
| @@ -44,7 +44,7 @@ namespace Discord.Rest | |||||
| await client.ApiClient.RemoveGuildMemberAsync(user.GuildId, user.Id); | await client.ApiClient.RemoveGuildMemberAsync(user.GuildId, user.Id); | ||||
| } | } | ||||
| public static async Task<IDMChannel> CreateDMChannelAsync(IUser user, BaseDiscordClient client) | |||||
| public static async Task<RestDMChannel> CreateDMChannelAsync(IUser user, BaseDiscordClient client) | |||||
| { | { | ||||
| var args = new CreateDMChannelParams(user.Id); | var args = new CreateDMChannelParams(user.Id); | ||||
| return RestDMChannel.Create(client, await client.ApiClient.CreateDMChannelAsync(args)); | return RestDMChannel.Create(client, await client.ApiClient.CreateDMChannelAsync(args)); | ||||
| @@ -14,7 +14,7 @@ using System.Threading.Tasks; | |||||
| namespace Discord.Audio | namespace Discord.Audio | ||||
| { | { | ||||
| internal class AudioClient : IAudioClient, IDisposable | |||||
| public class AudioClient : IAudioClient, IDisposable | |||||
| { | { | ||||
| public event Func<Task> Connected | public event Func<Task> Connected | ||||
| { | { | ||||
| @@ -56,7 +56,7 @@ namespace Discord.Audio | |||||
| private DiscordSocketClient Discord => Guild.Discord; | private DiscordSocketClient Discord => Guild.Discord; | ||||
| /// <summary> Creates a new REST/WebSocket discord client. </summary> | /// <summary> Creates a new REST/WebSocket discord client. </summary> | ||||
| public AudioClient(SocketGuild guild, int id) | |||||
| internal AudioClient(SocketGuild guild, int id) | |||||
| { | { | ||||
| Guild = guild; | Guild = guild; | ||||
| @@ -90,7 +90,7 @@ namespace Discord.Audio | |||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public async Task ConnectAsync(string url, ulong userId, string sessionId, string token) | |||||
| internal async Task ConnectAsync(string url, ulong userId, string sessionId, string token) | |||||
| { | { | ||||
| await _connectionLock.WaitAsync().ConfigureAwait(false); | await _connectionLock.WaitAsync().ConfigureAwait(false); | ||||
| try | try | ||||
| @@ -24,9 +24,9 @@ namespace Discord.WebSocket | |||||
| internal IReadOnlyCollection<SocketGuild> Guilds => _guilds.ToReadOnlyCollection(); | internal IReadOnlyCollection<SocketGuild> Guilds => _guilds.ToReadOnlyCollection(); | ||||
| internal IReadOnlyCollection<SocketGlobalUser> Users => _users.ToReadOnlyCollection(); | internal IReadOnlyCollection<SocketGlobalUser> Users => _users.ToReadOnlyCollection(); | ||||
| internal IReadOnlyCollection<IPrivateChannel> PrivateChannels => | |||||
| _dmChannels.Select(x => x.Value as IPrivateChannel).Concat( | |||||
| _groupChannels.Select(x => GetChannel(x) as IPrivateChannel)) | |||||
| internal IReadOnlyCollection<ISocketPrivateChannel> PrivateChannels => | |||||
| _dmChannels.Select(x => x.Value as ISocketPrivateChannel).Concat( | |||||
| _groupChannels.Select(x => GetChannel(x) as ISocketPrivateChannel)) | |||||
| .ToReadOnlyCollection(() => _dmChannels.Count + _groupChannels.Count); | .ToReadOnlyCollection(() => _dmChannels.Count + _groupChannels.Count); | ||||
| public ClientState(int guildCount, int dmChannelCount) | public ClientState(int guildCount, int dmChannelCount) | ||||
| @@ -33,170 +33,170 @@ namespace Discord.WebSocket | |||||
| private readonly AsyncEvent<Func<int, int, Task>> _latencyUpdatedEvent = new AsyncEvent<Func<int, int, Task>>(); | private readonly AsyncEvent<Func<int, int, Task>> _latencyUpdatedEvent = new AsyncEvent<Func<int, int, Task>>(); | ||||
| //Channels | //Channels | ||||
| public event Func<IChannel, Task> ChannelCreated | |||||
| public event Func<SocketChannel, Task> ChannelCreated | |||||
| { | { | ||||
| add { _channelCreatedEvent.Add(value); } | add { _channelCreatedEvent.Add(value); } | ||||
| remove { _channelCreatedEvent.Remove(value); } | remove { _channelCreatedEvent.Remove(value); } | ||||
| } | } | ||||
| private readonly AsyncEvent<Func<IChannel, Task>> _channelCreatedEvent = new AsyncEvent<Func<IChannel, Task>>(); | |||||
| public event Func<IChannel, Task> ChannelDestroyed | |||||
| private readonly AsyncEvent<Func<SocketChannel, Task>> _channelCreatedEvent = new AsyncEvent<Func<SocketChannel, Task>>(); | |||||
| public event Func<SocketChannel, Task> ChannelDestroyed | |||||
| { | { | ||||
| add { _channelDestroyedEvent.Add(value); } | add { _channelDestroyedEvent.Add(value); } | ||||
| remove { _channelDestroyedEvent.Remove(value); } | remove { _channelDestroyedEvent.Remove(value); } | ||||
| } | } | ||||
| private readonly AsyncEvent<Func<IChannel, Task>> _channelDestroyedEvent = new AsyncEvent<Func<IChannel, Task>>(); | |||||
| public event Func<IChannel, IChannel, Task> ChannelUpdated | |||||
| private readonly AsyncEvent<Func<SocketChannel, Task>> _channelDestroyedEvent = new AsyncEvent<Func<SocketChannel, Task>>(); | |||||
| public event Func<SocketChannel, SocketChannel, Task> ChannelUpdated | |||||
| { | { | ||||
| add { _channelUpdatedEvent.Add(value); } | add { _channelUpdatedEvent.Add(value); } | ||||
| remove { _channelUpdatedEvent.Remove(value); } | remove { _channelUpdatedEvent.Remove(value); } | ||||
| } | } | ||||
| private readonly AsyncEvent<Func<IChannel, IChannel, Task>> _channelUpdatedEvent = new AsyncEvent<Func<IChannel, IChannel, Task>>(); | |||||
| private readonly AsyncEvent<Func<SocketChannel, SocketChannel, Task>> _channelUpdatedEvent = new AsyncEvent<Func<SocketChannel, SocketChannel, Task>>(); | |||||
| //Messages | //Messages | ||||
| public event Func<IMessage, Task> MessageReceived | |||||
| public event Func<SocketMessage, Task> MessageReceived | |||||
| { | { | ||||
| add { _messageReceivedEvent.Add(value); } | add { _messageReceivedEvent.Add(value); } | ||||
| remove { _messageReceivedEvent.Remove(value); } | remove { _messageReceivedEvent.Remove(value); } | ||||
| } | } | ||||
| private readonly AsyncEvent<Func<IMessage, Task>> _messageReceivedEvent = new AsyncEvent<Func<IMessage, Task>>(); | |||||
| public event Func<ulong, Optional<IMessage>, Task> MessageDeleted | |||||
| private readonly AsyncEvent<Func<SocketMessage, Task>> _messageReceivedEvent = new AsyncEvent<Func<SocketMessage, Task>>(); | |||||
| public event Func<ulong, Optional<SocketMessage>, Task> MessageDeleted | |||||
| { | { | ||||
| add { _messageDeletedEvent.Add(value); } | add { _messageDeletedEvent.Add(value); } | ||||
| remove { _messageDeletedEvent.Remove(value); } | remove { _messageDeletedEvent.Remove(value); } | ||||
| } | } | ||||
| private readonly AsyncEvent<Func<ulong, Optional<IMessage>, Task>> _messageDeletedEvent = new AsyncEvent<Func<ulong, Optional<IMessage>, Task>>(); | |||||
| public event Func<Optional<IMessage>, IMessage, Task> MessageUpdated | |||||
| private readonly AsyncEvent<Func<ulong, Optional<SocketMessage>, Task>> _messageDeletedEvent = new AsyncEvent<Func<ulong, Optional<SocketMessage>, Task>>(); | |||||
| public event Func<Optional<SocketMessage>, SocketMessage, Task> MessageUpdated | |||||
| { | { | ||||
| add { _messageUpdatedEvent.Add(value); } | add { _messageUpdatedEvent.Add(value); } | ||||
| remove { _messageUpdatedEvent.Remove(value); } | remove { _messageUpdatedEvent.Remove(value); } | ||||
| } | } | ||||
| private readonly AsyncEvent<Func<Optional<IMessage>, IMessage, Task>> _messageUpdatedEvent = new AsyncEvent<Func<Optional<IMessage>, IMessage, Task>>(); | |||||
| private readonly AsyncEvent<Func<Optional<SocketMessage>, SocketMessage, Task>> _messageUpdatedEvent = new AsyncEvent<Func<Optional<SocketMessage>, SocketMessage, Task>>(); | |||||
| //Roles | //Roles | ||||
| public event Func<IRole, Task> RoleCreated | |||||
| public event Func<SocketRole, Task> RoleCreated | |||||
| { | { | ||||
| add { _roleCreatedEvent.Add(value); } | add { _roleCreatedEvent.Add(value); } | ||||
| remove { _roleCreatedEvent.Remove(value); } | remove { _roleCreatedEvent.Remove(value); } | ||||
| } | } | ||||
| private readonly AsyncEvent<Func<IRole, Task>> _roleCreatedEvent = new AsyncEvent<Func<IRole, Task>>(); | |||||
| public event Func<IRole, Task> RoleDeleted | |||||
| private readonly AsyncEvent<Func<SocketRole, Task>> _roleCreatedEvent = new AsyncEvent<Func<SocketRole, Task>>(); | |||||
| public event Func<SocketRole, Task> RoleDeleted | |||||
| { | { | ||||
| add { _roleDeletedEvent.Add(value); } | add { _roleDeletedEvent.Add(value); } | ||||
| remove { _roleDeletedEvent.Remove(value); } | remove { _roleDeletedEvent.Remove(value); } | ||||
| } | } | ||||
| private readonly AsyncEvent<Func<IRole, Task>> _roleDeletedEvent = new AsyncEvent<Func<IRole, Task>>(); | |||||
| public event Func<IRole, IRole, Task> RoleUpdated | |||||
| private readonly AsyncEvent<Func<SocketRole, Task>> _roleDeletedEvent = new AsyncEvent<Func<SocketRole, Task>>(); | |||||
| public event Func<SocketRole, SocketRole, Task> RoleUpdated | |||||
| { | { | ||||
| add { _roleUpdatedEvent.Add(value); } | add { _roleUpdatedEvent.Add(value); } | ||||
| remove { _roleUpdatedEvent.Remove(value); } | remove { _roleUpdatedEvent.Remove(value); } | ||||
| } | } | ||||
| private readonly AsyncEvent<Func<IRole, IRole, Task>> _roleUpdatedEvent = new AsyncEvent<Func<IRole, IRole, Task>>(); | |||||
| private readonly AsyncEvent<Func<SocketRole, SocketRole, Task>> _roleUpdatedEvent = new AsyncEvent<Func<SocketRole, SocketRole, Task>>(); | |||||
| //Guilds | //Guilds | ||||
| public event Func<IGuild, Task> JoinedGuild | |||||
| public event Func<SocketGuild, Task> JoinedGuild | |||||
| { | { | ||||
| add { _joinedGuildEvent.Add(value); } | add { _joinedGuildEvent.Add(value); } | ||||
| remove { _joinedGuildEvent.Remove(value); } | remove { _joinedGuildEvent.Remove(value); } | ||||
| } | } | ||||
| private AsyncEvent<Func<IGuild, Task>> _joinedGuildEvent = new AsyncEvent<Func<IGuild, Task>>(); | |||||
| public event Func<IGuild, Task> LeftGuild | |||||
| private AsyncEvent<Func<SocketGuild, Task>> _joinedGuildEvent = new AsyncEvent<Func<SocketGuild, Task>>(); | |||||
| public event Func<SocketGuild, Task> LeftGuild | |||||
| { | { | ||||
| add { _leftGuildEvent.Add(value); } | add { _leftGuildEvent.Add(value); } | ||||
| remove { _leftGuildEvent.Remove(value); } | remove { _leftGuildEvent.Remove(value); } | ||||
| } | } | ||||
| private AsyncEvent<Func<IGuild, Task>> _leftGuildEvent = new AsyncEvent<Func<IGuild, Task>>(); | |||||
| public event Func<IGuild, Task> GuildAvailable | |||||
| private AsyncEvent<Func<SocketGuild, Task>> _leftGuildEvent = new AsyncEvent<Func<SocketGuild, Task>>(); | |||||
| public event Func<SocketGuild, Task> GuildAvailable | |||||
| { | { | ||||
| add { _guildAvailableEvent.Add(value); } | add { _guildAvailableEvent.Add(value); } | ||||
| remove { _guildAvailableEvent.Remove(value); } | remove { _guildAvailableEvent.Remove(value); } | ||||
| } | } | ||||
| private AsyncEvent<Func<IGuild, Task>> _guildAvailableEvent = new AsyncEvent<Func<IGuild, Task>>(); | |||||
| public event Func<IGuild, Task> GuildUnavailable | |||||
| private AsyncEvent<Func<SocketGuild, Task>> _guildAvailableEvent = new AsyncEvent<Func<SocketGuild, Task>>(); | |||||
| public event Func<SocketGuild, Task> GuildUnavailable | |||||
| { | { | ||||
| add { _guildUnavailableEvent.Add(value); } | add { _guildUnavailableEvent.Add(value); } | ||||
| remove { _guildUnavailableEvent.Remove(value); } | remove { _guildUnavailableEvent.Remove(value); } | ||||
| } | } | ||||
| private AsyncEvent<Func<IGuild, Task>> _guildUnavailableEvent = new AsyncEvent<Func<IGuild, Task>>(); | |||||
| public event Func<IGuild, Task> GuildMembersDownloaded | |||||
| private AsyncEvent<Func<SocketGuild, Task>> _guildUnavailableEvent = new AsyncEvent<Func<SocketGuild, Task>>(); | |||||
| public event Func<SocketGuild, Task> GuildMembersDownloaded | |||||
| { | { | ||||
| add { _guildMembersDownloadedEvent.Add(value); } | add { _guildMembersDownloadedEvent.Add(value); } | ||||
| remove { _guildMembersDownloadedEvent.Remove(value); } | remove { _guildMembersDownloadedEvent.Remove(value); } | ||||
| } | } | ||||
| private AsyncEvent<Func<IGuild, Task>> _guildMembersDownloadedEvent = new AsyncEvent<Func<IGuild, Task>>(); | |||||
| public event Func<IGuild, IGuild, Task> GuildUpdated | |||||
| private AsyncEvent<Func<SocketGuild, Task>> _guildMembersDownloadedEvent = new AsyncEvent<Func<SocketGuild, Task>>(); | |||||
| public event Func<SocketGuild, SocketGuild, Task> GuildUpdated | |||||
| { | { | ||||
| add { _guildUpdatedEvent.Add(value); } | add { _guildUpdatedEvent.Add(value); } | ||||
| remove { _guildUpdatedEvent.Remove(value); } | remove { _guildUpdatedEvent.Remove(value); } | ||||
| } | } | ||||
| private AsyncEvent<Func<IGuild, IGuild, Task>> _guildUpdatedEvent = new AsyncEvent<Func<IGuild, IGuild, Task>>(); | |||||
| private AsyncEvent<Func<SocketGuild, SocketGuild, Task>> _guildUpdatedEvent = new AsyncEvent<Func<SocketGuild, SocketGuild, Task>>(); | |||||
| //Users | //Users | ||||
| public event Func<IGuildUser, Task> UserJoined | |||||
| public event Func<SocketGuildUser, Task> UserJoined | |||||
| { | { | ||||
| add { _userJoinedEvent.Add(value); } | add { _userJoinedEvent.Add(value); } | ||||
| remove { _userJoinedEvent.Remove(value); } | remove { _userJoinedEvent.Remove(value); } | ||||
| } | } | ||||
| private readonly AsyncEvent<Func<IGuildUser, Task>> _userJoinedEvent = new AsyncEvent<Func<IGuildUser, Task>>(); | |||||
| public event Func<IGuildUser, Task> UserLeft | |||||
| private readonly AsyncEvent<Func<SocketGuildUser, Task>> _userJoinedEvent = new AsyncEvent<Func<SocketGuildUser, Task>>(); | |||||
| public event Func<SocketGuildUser, Task> UserLeft | |||||
| { | { | ||||
| add { _userLeftEvent.Add(value); } | add { _userLeftEvent.Add(value); } | ||||
| remove { _userLeftEvent.Remove(value); } | remove { _userLeftEvent.Remove(value); } | ||||
| } | } | ||||
| private readonly AsyncEvent<Func<IGuildUser, Task>> _userLeftEvent = new AsyncEvent<Func<IGuildUser, Task>>(); | |||||
| public event Func<IUser, IGuild, Task> UserBanned | |||||
| private readonly AsyncEvent<Func<SocketGuildUser, Task>> _userLeftEvent = new AsyncEvent<Func<SocketGuildUser, Task>>(); | |||||
| public event Func<SocketUser, SocketGuild, Task> UserBanned | |||||
| { | { | ||||
| add { _userBannedEvent.Add(value); } | add { _userBannedEvent.Add(value); } | ||||
| remove { _userBannedEvent.Remove(value); } | remove { _userBannedEvent.Remove(value); } | ||||
| } | } | ||||
| private readonly AsyncEvent<Func<IUser, IGuild, Task>> _userBannedEvent = new AsyncEvent<Func<IUser, IGuild, Task>>(); | |||||
| public event Func<IUser, IGuild, Task> UserUnbanned | |||||
| private readonly AsyncEvent<Func<SocketUser, SocketGuild, Task>> _userBannedEvent = new AsyncEvent<Func<SocketUser, SocketGuild, Task>>(); | |||||
| public event Func<SocketUser, SocketGuild, Task> UserUnbanned | |||||
| { | { | ||||
| add { _userUnbannedEvent.Add(value); } | add { _userUnbannedEvent.Add(value); } | ||||
| remove { _userUnbannedEvent.Remove(value); } | remove { _userUnbannedEvent.Remove(value); } | ||||
| } | } | ||||
| private readonly AsyncEvent<Func<IUser, IGuild, Task>> _userUnbannedEvent = new AsyncEvent<Func<IUser, IGuild, Task>>(); | |||||
| public event Func<IGuildUser, IGuildUser, Task> UserUpdated | |||||
| private readonly AsyncEvent<Func<SocketUser, SocketGuild, Task>> _userUnbannedEvent = new AsyncEvent<Func<SocketUser, SocketGuild, Task>>(); | |||||
| public event Func<SocketGuildUser, SocketGuildUser, Task> UserUpdated | |||||
| { | { | ||||
| add { _userUpdatedEvent.Add(value); } | add { _userUpdatedEvent.Add(value); } | ||||
| remove { _userUpdatedEvent.Remove(value); } | remove { _userUpdatedEvent.Remove(value); } | ||||
| } | } | ||||
| private readonly AsyncEvent<Func<IGuildUser, IGuildUser, Task>> _userUpdatedEvent = new AsyncEvent<Func<IGuildUser, IGuildUser, Task>>(); | |||||
| public event Func<IGuildUser, IPresence, IPresence, Task> UserPresenceUpdated | |||||
| private readonly AsyncEvent<Func<SocketGuildUser, SocketGuildUser, Task>> _userUpdatedEvent = new AsyncEvent<Func<SocketGuildUser, SocketGuildUser, Task>>(); | |||||
| public event Func<SocketUser, SocketPresence, SocketPresence, Task> UserPresenceUpdated | |||||
| { | { | ||||
| add { _userPresenceUpdatedEvent.Add(value); } | add { _userPresenceUpdatedEvent.Add(value); } | ||||
| remove { _userPresenceUpdatedEvent.Remove(value); } | remove { _userPresenceUpdatedEvent.Remove(value); } | ||||
| } | } | ||||
| private readonly AsyncEvent<Func<IGuildUser, IPresence, IPresence, Task>> _userPresenceUpdatedEvent = new AsyncEvent<Func<IGuildUser, IPresence, IPresence, Task>>(); | |||||
| public event Func<IUser, IVoiceState, IVoiceState, Task> UserVoiceStateUpdated | |||||
| private readonly AsyncEvent<Func<SocketUser, SocketPresence, SocketPresence, Task>> _userPresenceUpdatedEvent = new AsyncEvent<Func<SocketUser, SocketPresence, SocketPresence, Task>>(); | |||||
| public event Func<SocketUser, SocketVoiceState, SocketVoiceState, Task> UserVoiceStateUpdated | |||||
| { | { | ||||
| add { _userVoiceStateUpdatedEvent.Add(value); } | add { _userVoiceStateUpdatedEvent.Add(value); } | ||||
| remove { _userVoiceStateUpdatedEvent.Remove(value); } | remove { _userVoiceStateUpdatedEvent.Remove(value); } | ||||
| } | } | ||||
| private readonly AsyncEvent<Func<IUser, IVoiceState, IVoiceState, Task>> _userVoiceStateUpdatedEvent = new AsyncEvent<Func<IUser, IVoiceState, IVoiceState, Task>>(); | |||||
| public event Func<ISelfUser, ISelfUser, Task> CurrentUserUpdated | |||||
| private readonly AsyncEvent<Func<SocketUser, SocketVoiceState, SocketVoiceState, Task>> _userVoiceStateUpdatedEvent = new AsyncEvent<Func<SocketUser, SocketVoiceState, SocketVoiceState, Task>>(); | |||||
| public event Func<SocketSelfUser, SocketSelfUser, Task> CurrentUserUpdated | |||||
| { | { | ||||
| add { _selfUpdatedEvent.Add(value); } | add { _selfUpdatedEvent.Add(value); } | ||||
| remove { _selfUpdatedEvent.Remove(value); } | remove { _selfUpdatedEvent.Remove(value); } | ||||
| } | } | ||||
| private readonly AsyncEvent<Func<ISelfUser, ISelfUser, Task>> _selfUpdatedEvent = new AsyncEvent<Func<ISelfUser, ISelfUser, Task>>(); | |||||
| public event Func<IUser, IChannel, Task> UserIsTyping | |||||
| private readonly AsyncEvent<Func<SocketSelfUser, SocketSelfUser, Task>> _selfUpdatedEvent = new AsyncEvent<Func<SocketSelfUser, SocketSelfUser, Task>>(); | |||||
| public event Func<SocketUser, ISocketMessageChannel, Task> UserIsTyping | |||||
| { | { | ||||
| add { _userIsTypingEvent.Add(value); } | add { _userIsTypingEvent.Add(value); } | ||||
| remove { _userIsTypingEvent.Remove(value); } | remove { _userIsTypingEvent.Remove(value); } | ||||
| } | } | ||||
| private readonly AsyncEvent<Func<IUser, IChannel, Task>> _userIsTypingEvent = new AsyncEvent<Func<IUser, IChannel, Task>>(); | |||||
| public event Func<IGroupUser, Task> RecipientAdded | |||||
| private readonly AsyncEvent<Func<SocketUser, ISocketMessageChannel, Task>> _userIsTypingEvent = new AsyncEvent<Func<SocketUser, ISocketMessageChannel, Task>>(); | |||||
| public event Func<SocketGroupUser, Task> RecipientAdded | |||||
| { | { | ||||
| add { _recipientAddedEvent.Add(value); } | add { _recipientAddedEvent.Add(value); } | ||||
| remove { _recipientAddedEvent.Remove(value); } | remove { _recipientAddedEvent.Remove(value); } | ||||
| } | } | ||||
| private readonly AsyncEvent<Func<IGroupUser, Task>> _recipientAddedEvent = new AsyncEvent<Func<IGroupUser, Task>>(); | |||||
| public event Func<IGroupUser, Task> RecipientRemoved | |||||
| private readonly AsyncEvent<Func<SocketGroupUser, Task>> _recipientAddedEvent = new AsyncEvent<Func<SocketGroupUser, Task>>(); | |||||
| public event Func<SocketGroupUser, Task> RecipientRemoved | |||||
| { | { | ||||
| add { _recipientRemovedEvent.Add(value); } | add { _recipientRemovedEvent.Add(value); } | ||||
| remove { _recipientRemovedEvent.Remove(value); } | remove { _recipientRemovedEvent.Remove(value); } | ||||
| } | } | ||||
| private readonly AsyncEvent<Func<IGroupUser, Task>> _recipientRemovedEvent = new AsyncEvent<Func<IGroupUser, Task>>(); | |||||
| private readonly AsyncEvent<Func<SocketGroupUser, Task>> _recipientRemovedEvent = new AsyncEvent<Func<SocketGroupUser, Task>>(); | |||||
| //TODO: Add PresenceUpdated? VoiceStateUpdated?, VoiceConnected, VoiceDisconnected; | //TODO: Add PresenceUpdated? VoiceStateUpdated?, VoiceConnected, VoiceDisconnected; | ||||
| } | } | ||||
| @@ -54,8 +54,8 @@ namespace Discord.WebSocket | |||||
| internal WebSocketProvider WebSocketProvider { get; private set; } | internal WebSocketProvider WebSocketProvider { get; private set; } | ||||
| public new DiscordSocketApiClient ApiClient => base.ApiClient as DiscordSocketApiClient; | public new DiscordSocketApiClient ApiClient => base.ApiClient as DiscordSocketApiClient; | ||||
| public new SocketSelfUser CurrentUser => base.CurrentUser as SocketSelfUser; | |||||
| public IReadOnlyCollection<IPrivateChannel> PrivateChannels => State.PrivateChannels; | |||||
| public new SocketSelfUser CurrentUser { get { return base.CurrentUser as SocketSelfUser; } private set { base.CurrentUser = value; } } | |||||
| public IReadOnlyCollection<ISocketPrivateChannel> PrivateChannels => State.PrivateChannels; | |||||
| internal IReadOnlyCollection<SocketGuild> Guilds => State.Guilds; | internal IReadOnlyCollection<SocketGuild> Guilds => State.Guilds; | ||||
| /// <summary> Creates a new REST/WebSocket discord client. </summary> | /// <summary> Creates a new REST/WebSocket discord client. </summary> | ||||
| @@ -167,14 +167,23 @@ namespace Discord.WebSocket | |||||
| connectTask.TrySetException(new TimeoutException()); | connectTask.TrySetException(new TimeoutException()); | ||||
| }); | }); | ||||
| await _gatewayLogger.DebugAsync("Connecting ApiClient").ConfigureAwait(false); | |||||
| await ApiClient.ConnectAsync().ConfigureAwait(false); | await ApiClient.ConnectAsync().ConfigureAwait(false); | ||||
| await _gatewayLogger.DebugAsync("Raising Event").ConfigureAwait(false); | |||||
| await _connectedEvent.InvokeAsync().ConfigureAwait(false); | await _connectedEvent.InvokeAsync().ConfigureAwait(false); | ||||
| if (_sessionId != null) | if (_sessionId != null) | ||||
| { | |||||
| await _gatewayLogger.DebugAsync("Resuming").ConfigureAwait(false); | |||||
| await ApiClient.SendResumeAsync(_sessionId, _lastSeq).ConfigureAwait(false); | await ApiClient.SendResumeAsync(_sessionId, _lastSeq).ConfigureAwait(false); | ||||
| } | |||||
| else | else | ||||
| await ApiClient.SendIdentifyAsync(shardID: ShardId, totalShards:TotalShards).ConfigureAwait(false); | |||||
| { | |||||
| await _gatewayLogger.DebugAsync("Identifying").ConfigureAwait(false); | |||||
| await ApiClient.SendIdentifyAsync(shardID: ShardId, totalShards: TotalShards).ConfigureAwait(false); | |||||
| } | |||||
| await _gatewayLogger.DebugAsync("Raising Event").ConfigureAwait(false); | |||||
| await _connectTask.Task.ConfigureAwait(false); | await _connectTask.Task.ConfigureAwait(false); | ||||
| if (!isReconnecting) | if (!isReconnecting) | ||||
| _canReconnect = true; | _canReconnect = true; | ||||
| @@ -216,32 +225,33 @@ namespace Discord.WebSocket | |||||
| ConnectionState = ConnectionState.Disconnecting; | ConnectionState = ConnectionState.Disconnecting; | ||||
| await _gatewayLogger.InfoAsync("Disconnecting").ConfigureAwait(false); | await _gatewayLogger.InfoAsync("Disconnecting").ConfigureAwait(false); | ||||
| await _gatewayLogger.DebugAsync("Disconnecting - CancelToken").ConfigureAwait(false); | |||||
| await _gatewayLogger.DebugAsync("Cancelling current tasks").ConfigureAwait(false); | |||||
| //Signal tasks to complete | //Signal tasks to complete | ||||
| try { _cancelToken.Cancel(); } catch { } | try { _cancelToken.Cancel(); } catch { } | ||||
| await _gatewayLogger.DebugAsync("Disconnecting - ApiClient").ConfigureAwait(false); | |||||
| await _gatewayLogger.DebugAsync("Disconnecting ApiClient").ConfigureAwait(false); | |||||
| //Disconnect from server | //Disconnect from server | ||||
| await ApiClient.DisconnectAsync().ConfigureAwait(false); | await ApiClient.DisconnectAsync().ConfigureAwait(false); | ||||
| //Wait for tasks to complete | //Wait for tasks to complete | ||||
| await _gatewayLogger.DebugAsync("Disconnecting - Heartbeat").ConfigureAwait(false); | |||||
| await _gatewayLogger.DebugAsync("Waiting for heartbeater").ConfigureAwait(false); | |||||
| var heartbeatTask = _heartbeatTask; | var heartbeatTask = _heartbeatTask; | ||||
| if (heartbeatTask != null) | if (heartbeatTask != null) | ||||
| await heartbeatTask.ConfigureAwait(false); | await heartbeatTask.ConfigureAwait(false); | ||||
| _heartbeatTask = null; | _heartbeatTask = null; | ||||
| await _gatewayLogger.DebugAsync("Disconnecting - Guild Downloader").ConfigureAwait(false); | |||||
| await _gatewayLogger.DebugAsync("Waiting for guild downloader").ConfigureAwait(false); | |||||
| var guildDownloadTask = _guildDownloadTask; | var guildDownloadTask = _guildDownloadTask; | ||||
| if (guildDownloadTask != null) | if (guildDownloadTask != null) | ||||
| await guildDownloadTask.ConfigureAwait(false); | await guildDownloadTask.ConfigureAwait(false); | ||||
| _guildDownloadTask = null; | _guildDownloadTask = null; | ||||
| //Clear large guild queue | //Clear large guild queue | ||||
| await _gatewayLogger.DebugAsync("Disconnecting - Clean Large Guilds").ConfigureAwait(false); | |||||
| await _gatewayLogger.DebugAsync("Clearing large guild queue").ConfigureAwait(false); | |||||
| while (_largeGuilds.TryDequeue(out guildId)) { } | while (_largeGuilds.TryDequeue(out guildId)) { } | ||||
| //Raise virtual GUILD_UNAVAILABLEs | //Raise virtual GUILD_UNAVAILABLEs | ||||
| await _gatewayLogger.DebugAsync("Raising virtual GuildUnavailables").ConfigureAwait(false); | |||||
| foreach (var guild in State.Guilds) | foreach (var guild in State.Guilds) | ||||
| { | { | ||||
| if (guild._available) | if (guild._available) | ||||
| @@ -318,7 +328,6 @@ namespace Discord.WebSocket | |||||
| public Task<RestApplication> GetApplicationInfoAsync() | public Task<RestApplication> GetApplicationInfoAsync() | ||||
| => ClientHelper.GetApplicationInfoAsync(this); | => ClientHelper.GetApplicationInfoAsync(this); | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public SocketGuild GetGuild(ulong id) | public SocketGuild GetGuild(ulong id) | ||||
| { | { | ||||
| @@ -329,7 +338,7 @@ namespace Discord.WebSocket | |||||
| => ClientHelper.CreateGuildAsync(this, name, region, jpegIcon); | => ClientHelper.CreateGuildAsync(this, name, region, jpegIcon); | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public IChannel GetChannel(ulong id) | |||||
| public SocketChannel GetChannel(ulong id) | |||||
| { | { | ||||
| return State.GetChannel(id); | return State.GetChannel(id); | ||||
| } | } | ||||
| @@ -343,15 +352,38 @@ namespace Discord.WebSocket | |||||
| => ClientHelper.GetInviteAsync(this, inviteId); | => ClientHelper.GetInviteAsync(this, inviteId); | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public IUser GetUser(ulong id) | |||||
| public SocketUser GetUser(ulong id) | |||||
| { | { | ||||
| return State.GetUser(id); | return State.GetUser(id); | ||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public IUser GetUser(string username, string discriminator) | |||||
| public SocketUser GetUser(string username, string discriminator) | |||||
| { | { | ||||
| return State.Users.Where(x => x.Discriminator == discriminator && x.Username == username).FirstOrDefault(); | return State.Users.Where(x => x.Discriminator == discriminator && x.Username == username).FirstOrDefault(); | ||||
| } | } | ||||
| internal SocketGlobalUser GetOrCreateUser(ClientState state, Discord.API.User model) | |||||
| { | |||||
| return state.GetOrAddUser(model.Id, x => | |||||
| { | |||||
| var user = SocketGlobalUser.Create(this, state, model); | |||||
| user.GlobalUser.AddRef(); | |||||
| return user; | |||||
| }); | |||||
| } | |||||
| internal SocketGlobalUser GetOrCreateSelfUser(ClientState state, Discord.API.User model) | |||||
| { | |||||
| return state.GetOrAddUser(model.Id, x => | |||||
| { | |||||
| var user = SocketGlobalUser.Create(this, state, model); | |||||
| user.GlobalUser.AddRef(); | |||||
| user.Presence = new SocketPresence(null, UserStatus.Online); | |||||
| return user; | |||||
| }); | |||||
| } | |||||
| internal void RemoveUser(ulong id) | |||||
| { | |||||
| State.RemoveUser(id); | |||||
| } | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public RestVoiceRegion GetVoiceRegion(string id) | public RestVoiceRegion GetVoiceRegion(string id) | ||||
| @@ -363,8 +395,8 @@ namespace Discord.WebSocket | |||||
| } | } | ||||
| /// <summary> Downloads the users list for all large guilds. </summary> | /// <summary> Downloads the users list for all large guilds. </summary> | ||||
| /*public Task DownloadAllUsersAsync() | |||||
| => DownloadUsersAsync(DataStore.Guilds.Where(x => !x.HasAllMembers)); | |||||
| public Task DownloadAllUsersAsync() | |||||
| => DownloadUsersAsync(State.Guilds.Where(x => !x.HasAllMembers)); | |||||
| /// <summary> Downloads the users list for the provided guilds, if they don't have a complete list. </summary> | /// <summary> Downloads the users list for the provided guilds, if they don't have a complete list. </summary> | ||||
| public Task DownloadUsersAsync(IEnumerable<IGuild> guilds) | public Task DownloadUsersAsync(IEnumerable<IGuild> guilds) | ||||
| => DownloadUsersAsync(guilds.Select(x => x as SocketGuild).Where(x => x != null)); | => DownloadUsersAsync(guilds.Select(x => x as SocketGuild).Where(x => x != null)); | ||||
| @@ -414,7 +446,7 @@ namespace Discord.WebSocket | |||||
| else | else | ||||
| await Task.WhenAll(batchTasks).ConfigureAwait(false); | await Task.WhenAll(batchTasks).ConfigureAwait(false); | ||||
| } | } | ||||
| }*/ | |||||
| } | |||||
| private async Task ProcessMessageAsync(GatewayOpCode opCode, int? seq, string type, object payload) | private async Task ProcessMessageAsync(GatewayOpCode opCode, int? seq, string type, object payload) | ||||
| { | { | ||||
| @@ -486,26 +518,26 @@ namespace Discord.WebSocket | |||||
| await _gatewayLogger.DebugAsync("Received Dispatch (READY)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (READY)").ConfigureAwait(false); | ||||
| var data = (payload as JToken).ToObject<ReadyEvent>(_serializer); | var data = (payload as JToken).ToObject<ReadyEvent>(_serializer); | ||||
| var dataStore = new ClientState(data.Guilds.Length, data.PrivateChannels.Length); | |||||
| var state = new ClientState(data.Guilds.Length, data.PrivateChannels.Length); | |||||
| var currentUser = SocketSelfUser.Create(this, data.User); | |||||
| var currentUser = SocketSelfUser.Create(this, state, data.User); | |||||
| int unavailableGuilds = 0; | int unavailableGuilds = 0; | ||||
| /*for (int i = 0; i < data.Guilds.Length; i++) | |||||
| for (int i = 0; i < data.Guilds.Length; i++) | |||||
| { | { | ||||
| var model = data.Guilds[i]; | var model = data.Guilds[i]; | ||||
| var guild = AddGuild(model, dataStore); | |||||
| var guild = AddGuild(model, state); | |||||
| if (!guild._available || ApiClient.AuthTokenType == TokenType.User) | if (!guild._available || ApiClient.AuthTokenType == TokenType.User) | ||||
| unavailableGuilds++; | unavailableGuilds++; | ||||
| else | else | ||||
| await _guildAvailableEvent.InvokeAsync(guild).ConfigureAwait(false); | await _guildAvailableEvent.InvokeAsync(guild).ConfigureAwait(false); | ||||
| } | } | ||||
| for (int i = 0; i < data.PrivateChannels.Length; i++) | for (int i = 0; i < data.PrivateChannels.Length; i++) | ||||
| AddPrivateChannel(data.PrivateChannels[i], dataStore);*/ | |||||
| AddPrivateChannel(data.PrivateChannels[i], state); | |||||
| _sessionId = data.SessionId; | _sessionId = data.SessionId; | ||||
| base.CurrentUser = currentUser; | |||||
| _unavailableGuilds = unavailableGuilds; | _unavailableGuilds = unavailableGuilds; | ||||
| State = dataStore; | |||||
| CurrentUser = currentUser; | |||||
| State = state; | |||||
| } | } | ||||
| catch (Exception ex) | catch (Exception ex) | ||||
| { | { | ||||
| @@ -525,14 +557,14 @@ namespace Discord.WebSocket | |||||
| await _gatewayLogger.InfoAsync("Ready").ConfigureAwait(false); | await _gatewayLogger.InfoAsync("Ready").ConfigureAwait(false); | ||||
| } | } | ||||
| break; | break; | ||||
| /*case "RESUMED": | |||||
| case "RESUMED": | |||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (RESUMED)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (RESUMED)").ConfigureAwait(false); | ||||
| var _ = _connectTask.TrySetResultAsync(true); //Signal the .Connect() call to complete | var _ = _connectTask.TrySetResultAsync(true); //Signal the .Connect() call to complete | ||||
| //Notify the client that these guilds are available again | //Notify the client that these guilds are available again | ||||
| foreach (var guild in DataStore.Guilds) | |||||
| foreach (var guild in State.Guilds) | |||||
| { | { | ||||
| if (guild._available) | if (guild._available) | ||||
| await _guildAvailableEvent.InvokeAsync(guild).ConfigureAwait(false); | await _guildAvailableEvent.InvokeAsync(guild).ConfigureAwait(false); | ||||
| @@ -553,10 +585,10 @@ namespace Discord.WebSocket | |||||
| _lastGuildAvailableTime = Environment.TickCount; | _lastGuildAvailableTime = Environment.TickCount; | ||||
| await _gatewayLogger.DebugAsync($"Received Dispatch (GUILD_AVAILABLE)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync($"Received Dispatch (GUILD_AVAILABLE)").ConfigureAwait(false); | ||||
| var guild = DataStore.GetGuild(data.Id); | |||||
| var guild = State.GetGuild(data.Id); | |||||
| if (guild != null) | if (guild != null) | ||||
| { | { | ||||
| guild.Update(data, UpdateSource.WebSocket, DataStore); | |||||
| guild.Update(State, data); | |||||
| var unavailableGuilds = _unavailableGuilds; | var unavailableGuilds = _unavailableGuilds; | ||||
| if (unavailableGuilds != 0) | if (unavailableGuilds != 0) | ||||
| @@ -573,7 +605,7 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| await _gatewayLogger.DebugAsync($"Received Dispatch (GUILD_CREATE)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync($"Received Dispatch (GUILD_CREATE)").ConfigureAwait(false); | ||||
| var guild = AddGuild(data, DataStore); | |||||
| var guild = AddGuild(data, State); | |||||
| if (guild != null) | if (guild != null) | ||||
| { | { | ||||
| if (ApiClient.AuthTokenType == TokenType.User) | if (ApiClient.AuthTokenType == TokenType.User) | ||||
| @@ -593,11 +625,11 @@ namespace Discord.WebSocket | |||||
| await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_UPDATE)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_UPDATE)").ConfigureAwait(false); | ||||
| var data = (payload as JToken).ToObject<API.Guild>(_serializer); | var data = (payload as JToken).ToObject<API.Guild>(_serializer); | ||||
| var guild = DataStore.GetGuild(data.Id); | |||||
| var guild = State.GetGuild(data.Id); | |||||
| if (guild != null) | if (guild != null) | ||||
| { | { | ||||
| var before = guild.Clone(); | var before = guild.Clone(); | ||||
| guild.Update(data, UpdateSource.WebSocket); | |||||
| guild.Update(State, data); | |||||
| await _guildUpdatedEvent.InvokeAsync(before, guild).ConfigureAwait(false); | await _guildUpdatedEvent.InvokeAsync(before, guild).ConfigureAwait(false); | ||||
| } | } | ||||
| else | else | ||||
| @@ -612,11 +644,11 @@ namespace Discord.WebSocket | |||||
| await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_EMOJIS_UPDATE)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_EMOJIS_UPDATE)").ConfigureAwait(false); | ||||
| var data = (payload as JToken).ToObject<API.Gateway.GuildEmojiUpdateEvent>(_serializer); | var data = (payload as JToken).ToObject<API.Gateway.GuildEmojiUpdateEvent>(_serializer); | ||||
| var guild = DataStore.GetGuild(data.GuildId); | |||||
| var guild = State.GetGuild(data.GuildId); | |||||
| if (guild != null) | if (guild != null) | ||||
| { | { | ||||
| var before = guild.Clone(); | var before = guild.Clone(); | ||||
| guild.Update(data, UpdateSource.WebSocket); | |||||
| guild.Update(State, data); | |||||
| await _guildUpdatedEvent.InvokeAsync(before, guild).ConfigureAwait(false); | await _guildUpdatedEvent.InvokeAsync(before, guild).ConfigureAwait(false); | ||||
| } | } | ||||
| else | else | ||||
| @@ -626,20 +658,15 @@ namespace Discord.WebSocket | |||||
| } | } | ||||
| } | } | ||||
| return; | return; | ||||
| case "GUILD_INTEGRATIONS_UPDATE": | |||||
| { | |||||
| await _gatewayLogger.DebugAsync("Ignored Dispatch (GUILD_INTEGRATIONS_UPDATE)").ConfigureAwait(false); | |||||
| } | |||||
| return; | |||||
| case "GUILD_SYNC": | case "GUILD_SYNC": | ||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_SYNC)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_SYNC)").ConfigureAwait(false); | ||||
| var data = (payload as JToken).ToObject<GuildSyncEvent>(_serializer); | var data = (payload as JToken).ToObject<GuildSyncEvent>(_serializer); | ||||
| var guild = DataStore.GetGuild(data.Id); | |||||
| var guild = State.GetGuild(data.Id); | |||||
| if (guild != null) | if (guild != null) | ||||
| { | { | ||||
| var before = guild.Clone(); | var before = guild.Clone(); | ||||
| guild.Update(data, UpdateSource.WebSocket, DataStore); | |||||
| guild.Update(State, data); | |||||
| //This is treated as an extension of GUILD_AVAILABLE | //This is treated as an extension of GUILD_AVAILABLE | ||||
| _unavailableGuilds--; | _unavailableGuilds--; | ||||
| _lastGuildAvailableTime = Environment.TickCount; | _lastGuildAvailableTime = Environment.TickCount; | ||||
| @@ -661,11 +688,9 @@ namespace Discord.WebSocket | |||||
| type = "GUILD_UNAVAILABLE"; | type = "GUILD_UNAVAILABLE"; | ||||
| await _gatewayLogger.DebugAsync($"Received Dispatch (GUILD_UNAVAILABLE)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync($"Received Dispatch (GUILD_UNAVAILABLE)").ConfigureAwait(false); | ||||
| var guild = DataStore.GetGuild(data.Id); | |||||
| var guild = State.GetGuild(data.Id); | |||||
| if (guild != null) | if (guild != null) | ||||
| { | { | ||||
| foreach (var member in guild.Members) | |||||
| member.User.RemoveRef(this); | |||||
| await _guildUnavailableEvent.InvokeAsync(guild).ConfigureAwait(false); | await _guildUnavailableEvent.InvokeAsync(guild).ConfigureAwait(false); | ||||
| _unavailableGuilds++; | _unavailableGuilds++; | ||||
| } | } | ||||
| @@ -700,14 +725,13 @@ namespace Discord.WebSocket | |||||
| await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_CREATE)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_CREATE)").ConfigureAwait(false); | ||||
| var data = (payload as JToken).ToObject<API.Channel>(_serializer); | var data = (payload as JToken).ToObject<API.Channel>(_serializer); | ||||
| ISocketChannel channel = null; | |||||
| SocketChannel channel = null; | |||||
| if (data.GuildId.IsSpecified) | if (data.GuildId.IsSpecified) | ||||
| { | { | ||||
| var guild = DataStore.GetGuild(data.GuildId.Value); | |||||
| var guild = State.GetGuild(data.GuildId.Value); | |||||
| if (guild != null) | if (guild != null) | ||||
| { | { | ||||
| guild.AddChannel(data, DataStore); | |||||
| channel = guild.AddChannel(data, DataStore); | |||||
| channel = guild.AddChannel(State, data); | |||||
| if (!guild.IsSynced) | if (!guild.IsSynced) | ||||
| { | { | ||||
| @@ -722,7 +746,7 @@ namespace Discord.WebSocket | |||||
| } | } | ||||
| } | } | ||||
| else | else | ||||
| channel = AddPrivateChannel(data, DataStore); | |||||
| channel = AddPrivateChannel(data, State) as SocketChannel; | |||||
| if (channel != null) | if (channel != null) | ||||
| await _channelCreatedEvent.InvokeAsync(channel).ConfigureAwait(false); | await _channelCreatedEvent.InvokeAsync(channel).ConfigureAwait(false); | ||||
| @@ -733,13 +757,13 @@ namespace Discord.WebSocket | |||||
| await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_UPDATE)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_UPDATE)").ConfigureAwait(false); | ||||
| var data = (payload as JToken).ToObject<API.Channel>(_serializer); | var data = (payload as JToken).ToObject<API.Channel>(_serializer); | ||||
| var channel = DataStore.GetChannel(data.Id); | |||||
| var channel = State.GetChannel(data.Id); | |||||
| if (channel != null) | if (channel != null) | ||||
| { | { | ||||
| var before = channel.Clone(); | var before = channel.Clone(); | ||||
| channel.Update(data, UpdateSource.WebSocket); | |||||
| channel.Update(State, data); | |||||
| if (!((channel as ISocketGuildChannel)?.Guild.IsSynced ?? true)) | |||||
| if (!((channel as SocketGuildChannel)?.Guild.IsSynced ?? true)) | |||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Ignored CHANNEL_UPDATE, guild is not synced yet.").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Ignored CHANNEL_UPDATE, guild is not synced yet.").ConfigureAwait(false); | ||||
| return; | return; | ||||
| @@ -758,14 +782,14 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_DELETE)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_DELETE)").ConfigureAwait(false); | ||||
| ISocketChannel channel = null; | |||||
| SocketChannel channel = null; | |||||
| var data = (payload as JToken).ToObject<API.Channel>(_serializer); | var data = (payload as JToken).ToObject<API.Channel>(_serializer); | ||||
| if (data.GuildId.IsSpecified) | if (data.GuildId.IsSpecified) | ||||
| { | { | ||||
| var guild = DataStore.GetGuild(data.GuildId.Value); | |||||
| var guild = State.GetGuild(data.GuildId.Value); | |||||
| if (guild != null) | if (guild != null) | ||||
| { | { | ||||
| channel = guild.RemoveChannel(data.Id); | |||||
| channel = guild.RemoveChannel(State, data.Id); | |||||
| if (!guild.IsSynced) | if (!guild.IsSynced) | ||||
| { | { | ||||
| @@ -780,7 +804,7 @@ namespace Discord.WebSocket | |||||
| } | } | ||||
| } | } | ||||
| else | else | ||||
| channel = RemovePrivateChannel(data.Id); | |||||
| channel = RemovePrivateChannel(data.Id) as SocketChannel; | |||||
| if (channel != null) | if (channel != null) | ||||
| await _channelDestroyedEvent.InvokeAsync(channel).ConfigureAwait(false); | await _channelDestroyedEvent.InvokeAsync(channel).ConfigureAwait(false); | ||||
| @@ -798,10 +822,10 @@ namespace Discord.WebSocket | |||||
| await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_MEMBER_ADD)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_MEMBER_ADD)").ConfigureAwait(false); | ||||
| var data = (payload as JToken).ToObject<GuildMemberAddEvent>(_serializer); | var data = (payload as JToken).ToObject<GuildMemberAddEvent>(_serializer); | ||||
| var guild = DataStore.GetGuild(data.GuildId); | |||||
| var guild = State.GetGuild(data.GuildId); | |||||
| if (guild != null) | if (guild != null) | ||||
| { | { | ||||
| var user = guild.AddOrUpdateUser(data, DataStore); | |||||
| var user = guild.AddOrUpdateUser(data); | |||||
| guild.MemberCount++; | guild.MemberCount++; | ||||
| if (!guild.IsSynced) | if (!guild.IsSynced) | ||||
| @@ -824,7 +848,7 @@ namespace Discord.WebSocket | |||||
| await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_MEMBER_UPDATE)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_MEMBER_UPDATE)").ConfigureAwait(false); | ||||
| var data = (payload as JToken).ToObject<GuildMemberUpdateEvent>(_serializer); | var data = (payload as JToken).ToObject<GuildMemberUpdateEvent>(_serializer); | ||||
| var guild = DataStore.GetGuild(data.GuildId); | |||||
| var guild = State.GetGuild(data.GuildId); | |||||
| if (guild != null) | if (guild != null) | ||||
| { | { | ||||
| var user = guild.GetUser(data.User.Id); | var user = guild.GetUser(data.User.Id); | ||||
| @@ -838,7 +862,7 @@ namespace Discord.WebSocket | |||||
| if (user != null) | if (user != null) | ||||
| { | { | ||||
| var before = user.Clone(); | var before = user.Clone(); | ||||
| user.Update(data, UpdateSource.WebSocket); | |||||
| user.Update(State, data); | |||||
| await _userUpdatedEvent.InvokeAsync(before, user).ConfigureAwait(false); | await _userUpdatedEvent.InvokeAsync(before, user).ConfigureAwait(false); | ||||
| } | } | ||||
| else | else | ||||
| @@ -865,7 +889,7 @@ namespace Discord.WebSocket | |||||
| await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_MEMBER_REMOVE)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_MEMBER_REMOVE)").ConfigureAwait(false); | ||||
| var data = (payload as JToken).ToObject<GuildMemberRemoveEvent>(_serializer); | var data = (payload as JToken).ToObject<GuildMemberRemoveEvent>(_serializer); | ||||
| var guild = DataStore.GetGuild(data.GuildId); | |||||
| var guild = State.GetGuild(data.GuildId); | |||||
| if (guild != null) | if (guild != null) | ||||
| { | { | ||||
| var user = guild.RemoveUser(data.User.Id); | var user = guild.RemoveUser(data.User.Id); | ||||
| @@ -878,10 +902,7 @@ namespace Discord.WebSocket | |||||
| } | } | ||||
| if (user != null) | if (user != null) | ||||
| { | |||||
| user.User.RemoveRef(this); | |||||
| await _userLeftEvent.InvokeAsync(user).ConfigureAwait(false); | await _userLeftEvent.InvokeAsync(user).ConfigureAwait(false); | ||||
| } | |||||
| else | else | ||||
| { | { | ||||
| if (!guild.HasAllMembers) | if (!guild.HasAllMembers) | ||||
| @@ -906,15 +927,15 @@ namespace Discord.WebSocket | |||||
| await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_MEMBERS_CHUNK)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_MEMBERS_CHUNK)").ConfigureAwait(false); | ||||
| var data = (payload as JToken).ToObject<GuildMembersChunkEvent>(_serializer); | var data = (payload as JToken).ToObject<GuildMembersChunkEvent>(_serializer); | ||||
| var guild = DataStore.GetGuild(data.GuildId); | |||||
| var guild = State.GetGuild(data.GuildId); | |||||
| if (guild != null) | if (guild != null) | ||||
| { | { | ||||
| foreach (var memberModel in data.Members) | foreach (var memberModel in data.Members) | ||||
| guild.AddOrUpdateUser(memberModel, DataStore); | |||||
| guild.AddOrUpdateUser(memberModel); | |||||
| if (guild.DownloadedMemberCount >= guild.MemberCount) //Finished downloading for there | if (guild.DownloadedMemberCount >= guild.MemberCount) //Finished downloading for there | ||||
| { | { | ||||
| guild.CompleteDownloadMembers(); | |||||
| guild.CompleteDownloadUsers(); | |||||
| await _guildMembersDownloadedEvent.InvokeAsync(guild).ConfigureAwait(false); | await _guildMembersDownloadedEvent.InvokeAsync(guild).ConfigureAwait(false); | ||||
| } | } | ||||
| } | } | ||||
| @@ -930,10 +951,10 @@ namespace Discord.WebSocket | |||||
| await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_RECIPIENT_ADD)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_RECIPIENT_ADD)").ConfigureAwait(false); | ||||
| var data = (payload as JToken).ToObject<RecipientEvent>(_serializer); | var data = (payload as JToken).ToObject<RecipientEvent>(_serializer); | ||||
| var channel = DataStore.GetChannel(data.ChannelId) as SocketGroupChannel; | |||||
| var channel = State.GetChannel(data.ChannelId) as SocketGroupChannel; | |||||
| if (channel != null) | if (channel != null) | ||||
| { | { | ||||
| var user = channel.AddUser(data.User, DataStore); | |||||
| var user = channel.AddUser(data.User); | |||||
| await _recipientAddedEvent.InvokeAsync(user).ConfigureAwait(false); | await _recipientAddedEvent.InvokeAsync(user).ConfigureAwait(false); | ||||
| } | } | ||||
| else | else | ||||
| @@ -948,15 +969,12 @@ namespace Discord.WebSocket | |||||
| await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_RECIPIENT_REMOVE)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_RECIPIENT_REMOVE)").ConfigureAwait(false); | ||||
| var data = (payload as JToken).ToObject<RecipientEvent>(_serializer); | var data = (payload as JToken).ToObject<RecipientEvent>(_serializer); | ||||
| var channel = DataStore.GetChannel(data.ChannelId) as SocketGroupChannel; | |||||
| var channel = State.GetChannel(data.ChannelId) as SocketGroupChannel; | |||||
| if (channel != null) | if (channel != null) | ||||
| { | { | ||||
| var user = channel.RemoveUser(data.User.Id); | var user = channel.RemoveUser(data.User.Id); | ||||
| if (user != null) | if (user != null) | ||||
| { | |||||
| user.User.RemoveRef(this); | |||||
| await _recipientRemovedEvent.InvokeAsync(user).ConfigureAwait(false); | await _recipientRemovedEvent.InvokeAsync(user).ConfigureAwait(false); | ||||
| } | |||||
| else | else | ||||
| { | { | ||||
| await _gatewayLogger.WarningAsync("CHANNEL_RECIPIENT_REMOVE referenced an unknown user.").ConfigureAwait(false); | await _gatewayLogger.WarningAsync("CHANNEL_RECIPIENT_REMOVE referenced an unknown user.").ConfigureAwait(false); | ||||
| @@ -977,7 +995,7 @@ namespace Discord.WebSocket | |||||
| await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_ROLE_CREATE)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_ROLE_CREATE)").ConfigureAwait(false); | ||||
| var data = (payload as JToken).ToObject<GuildRoleCreateEvent>(_serializer); | var data = (payload as JToken).ToObject<GuildRoleCreateEvent>(_serializer); | ||||
| var guild = DataStore.GetGuild(data.GuildId); | |||||
| var guild = State.GetGuild(data.GuildId); | |||||
| if (guild != null) | if (guild != null) | ||||
| { | { | ||||
| var role = guild.AddRole(data.Role); | var role = guild.AddRole(data.Role); | ||||
| @@ -1001,14 +1019,14 @@ namespace Discord.WebSocket | |||||
| await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_ROLE_UPDATE)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_ROLE_UPDATE)").ConfigureAwait(false); | ||||
| var data = (payload as JToken).ToObject<GuildRoleUpdateEvent>(_serializer); | var data = (payload as JToken).ToObject<GuildRoleUpdateEvent>(_serializer); | ||||
| var guild = DataStore.GetGuild(data.GuildId); | |||||
| var guild = State.GetGuild(data.GuildId); | |||||
| if (guild != null) | if (guild != null) | ||||
| { | { | ||||
| var role = guild.GetRole(data.Role.Id); | var role = guild.GetRole(data.Role.Id); | ||||
| if (role != null) | if (role != null) | ||||
| { | { | ||||
| var before = role.Clone(); | var before = role.Clone(); | ||||
| role.Update(data.Role, UpdateSource.WebSocket); | |||||
| role.Update(State, data.Role); | |||||
| if (!guild.IsSynced) | if (!guild.IsSynced) | ||||
| { | { | ||||
| @@ -1036,7 +1054,7 @@ namespace Discord.WebSocket | |||||
| await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_ROLE_DELETE)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_ROLE_DELETE)").ConfigureAwait(false); | ||||
| var data = (payload as JToken).ToObject<GuildRoleDeleteEvent>(_serializer); | var data = (payload as JToken).ToObject<GuildRoleDeleteEvent>(_serializer); | ||||
| var guild = DataStore.GetGuild(data.GuildId); | |||||
| var guild = State.GetGuild(data.GuildId); | |||||
| if (guild != null) | if (guild != null) | ||||
| { | { | ||||
| var role = guild.RemoveRole(data.RoleId); | var role = guild.RemoveRole(data.RoleId); | ||||
| @@ -1070,7 +1088,7 @@ namespace Discord.WebSocket | |||||
| await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_BAN_ADD)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_BAN_ADD)").ConfigureAwait(false); | ||||
| var data = (payload as JToken).ToObject<GuildBanEvent>(_serializer); | var data = (payload as JToken).ToObject<GuildBanEvent>(_serializer); | ||||
| var guild = DataStore.GetGuild(data.GuildId); | |||||
| var guild = State.GetGuild(data.GuildId); | |||||
| if (guild != null) | if (guild != null) | ||||
| { | { | ||||
| if (!guild.IsSynced) | if (!guild.IsSynced) | ||||
| @@ -1078,8 +1096,8 @@ namespace Discord.WebSocket | |||||
| await _gatewayLogger.DebugAsync("Ignored GUILD_BAN_ADD, guild is not synced yet.").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Ignored GUILD_BAN_ADD, guild is not synced yet.").ConfigureAwait(false); | ||||
| return; | return; | ||||
| } | } | ||||
| await _userBannedEvent.InvokeAsync(new User(data.User), guild).ConfigureAwait(false); | |||||
| await _userBannedEvent.InvokeAsync(SocketSimpleUser.Create(this, State, data.User), guild).ConfigureAwait(false); | |||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| @@ -1093,7 +1111,7 @@ namespace Discord.WebSocket | |||||
| await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_BAN_REMOVE)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_BAN_REMOVE)").ConfigureAwait(false); | ||||
| var data = (payload as JToken).ToObject<GuildBanEvent>(_serializer); | var data = (payload as JToken).ToObject<GuildBanEvent>(_serializer); | ||||
| var guild = DataStore.GetGuild(data.GuildId); | |||||
| var guild = State.GetGuild(data.GuildId); | |||||
| if (guild != null) | if (guild != null) | ||||
| { | { | ||||
| if (!guild.IsSynced) | if (!guild.IsSynced) | ||||
| @@ -1102,7 +1120,10 @@ namespace Discord.WebSocket | |||||
| return; | return; | ||||
| } | } | ||||
| await _userUnbannedEvent.InvokeAsync(new User(data.User), guild).ConfigureAwait(false); | |||||
| SocketUser user = State.GetUser(data.User.Id); | |||||
| if (user == null) | |||||
| user = SocketSimpleUser.Create(this, State, data.User); | |||||
| await _userUnbannedEvent.InvokeAsync(user, guild).ConfigureAwait(false); | |||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| @@ -1118,20 +1139,28 @@ namespace Discord.WebSocket | |||||
| await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_CREATE)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_CREATE)").ConfigureAwait(false); | ||||
| var data = (payload as JToken).ToObject<API.Message>(_serializer); | var data = (payload as JToken).ToObject<API.Message>(_serializer); | ||||
| var channel = DataStore.GetChannel(data.ChannelId) as ISocketMessageChannel; | |||||
| var channel = State.GetChannel(data.ChannelId) as ISocketMessageChannel; | |||||
| if (channel != null) | if (channel != null) | ||||
| { | { | ||||
| if (!((channel as ISocketGuildChannel)?.Guild.IsSynced ?? true)) | |||||
| var guild = (channel as SocketGuildChannel)?.Guild; | |||||
| if (guild != null && !guild.IsSynced) | |||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Ignored MESSAGE_CREATE, guild is not synced yet.").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Ignored MESSAGE_CREATE, guild is not synced yet.").ConfigureAwait(false); | ||||
| return; | return; | ||||
| } | } | ||||
| var author = channel.GetUser(data.Author.Value.Id, true); | |||||
| SocketUser author; | |||||
| if (guild != null) | |||||
| author = guild.GetUser(data.Author.Value.Id); | |||||
| else | |||||
| author = (channel as SocketChannel).GetUser(data.Author.Value.Id); | |||||
| if (author == null) | |||||
| author = SocketSimpleUser.Create(this, State, data.Author.Value); | |||||
| if (author != null) | if (author != null) | ||||
| { | { | ||||
| var msg = channel.AddMessage(author, data); | |||||
| var msg = SocketMessage.Create(this, State, author, data); | |||||
| SocketChannelHelper.AddMessage(channel, this, msg); | |||||
| await _messageReceivedEvent.InvokeAsync(msg).ConfigureAwait(false); | await _messageReceivedEvent.InvokeAsync(msg).ConfigureAwait(false); | ||||
| } | } | ||||
| else | else | ||||
| @@ -1152,38 +1181,41 @@ namespace Discord.WebSocket | |||||
| await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_UPDATE)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_UPDATE)").ConfigureAwait(false); | ||||
| var data = (payload as JToken).ToObject<API.Message>(_serializer); | var data = (payload as JToken).ToObject<API.Message>(_serializer); | ||||
| var channel = DataStore.GetChannel(data.ChannelId) as ISocketMessageChannel; | |||||
| var channel = State.GetChannel(data.ChannelId) as ISocketMessageChannel; | |||||
| if (channel != null) | if (channel != null) | ||||
| { | { | ||||
| if (!((channel as ISocketGuildChannel)?.Guild.IsSynced ?? true)) | |||||
| var guild = (channel as SocketGuildChannel)?.Guild; | |||||
| if (guild != null && !guild.IsSynced) | |||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Ignored MESSAGE_UPDATE, guild is not synced yet.").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Ignored MESSAGE_UPDATE, guild is not synced yet.").ConfigureAwait(false); | ||||
| return; | return; | ||||
| } | } | ||||
| IMessage before = null, after = null; | |||||
| ISocketMessage cachedMsg = channel.GetMessage(data.Id); | |||||
| SocketMessage before = null, after = null; | |||||
| SocketMessage cachedMsg = channel.GetCachedMessage(data.Id); | |||||
| if (cachedMsg != null) | if (cachedMsg != null) | ||||
| { | { | ||||
| before = cachedMsg.Clone(); | before = cachedMsg.Clone(); | ||||
| cachedMsg.Update(data, UpdateSource.WebSocket); | |||||
| cachedMsg.Update(State, data); | |||||
| after = cachedMsg; | after = cachedMsg; | ||||
| } | } | ||||
| else if (data.Author.IsSpecified) | else if (data.Author.IsSpecified) | ||||
| { | { | ||||
| //Edited message isnt in cache, create a detached one | //Edited message isnt in cache, create a detached one | ||||
| var author = channel.GetUser(data.Author.Value.Id, true); | |||||
| if (author != null) | |||||
| after = channel.CreateMessage(author, data); | |||||
| } | |||||
| if (after != null) | |||||
| await _messageUpdatedEvent.InvokeAsync(Optional.Create(before), after).ConfigureAwait(false); | |||||
| { | |||||
| if (before != null) | |||||
| await _messageUpdatedEvent.InvokeAsync(Optional.Create<IMessage>(before), after).ConfigureAwait(false); | |||||
| SocketUser author; | |||||
| if (guild != null) | |||||
| author = guild.GetUser(data.Author.Value.Id); | |||||
| else | else | ||||
| await _messageUpdatedEvent.InvokeAsync(Optional.Create<IMessage>(), after).ConfigureAwait(false); | |||||
| author = (channel as SocketChannel).GetUser(data.Author.Value.Id); | |||||
| if (author == null) | |||||
| author = SocketSimpleUser.Create(this, State, data.Author.Value); | |||||
| after = SocketMessage.Create(this, State, author, data); | |||||
| } | } | ||||
| if (before != null) | |||||
| await _messageUpdatedEvent.InvokeAsync(before, after).ConfigureAwait(false); | |||||
| else | |||||
| await _messageUpdatedEvent.InvokeAsync(Optional.Create<SocketMessage>(), after).ConfigureAwait(false); | |||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| @@ -1197,20 +1229,20 @@ namespace Discord.WebSocket | |||||
| await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_DELETE)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_DELETE)").ConfigureAwait(false); | ||||
| var data = (payload as JToken).ToObject<API.Message>(_serializer); | var data = (payload as JToken).ToObject<API.Message>(_serializer); | ||||
| var channel = DataStore.GetChannel(data.ChannelId) as ISocketMessageChannel; | |||||
| var channel = State.GetChannel(data.ChannelId) as ISocketMessageChannel; | |||||
| if (channel != null) | if (channel != null) | ||||
| { | { | ||||
| if (!((channel as ISocketGuildChannel)?.Guild.IsSynced ?? true)) | |||||
| if (!((channel as SocketGuildChannel)?.Guild.IsSynced ?? true)) | |||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Ignored MESSAGE_DELETE, guild is not synced yet.").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Ignored MESSAGE_DELETE, guild is not synced yet.").ConfigureAwait(false); | ||||
| return; | return; | ||||
| } | } | ||||
| var msg = channel.RemoveMessage(data.Id); | |||||
| var msg = SocketChannelHelper.RemoveMessage(channel, this, data.Id); | |||||
| if (msg != null) | if (msg != null) | ||||
| await _messageDeletedEvent.InvokeAsync(data.Id, Optional.Create<IMessage>(msg)).ConfigureAwait(false); | |||||
| await _messageDeletedEvent.InvokeAsync(data.Id, msg).ConfigureAwait(false); | |||||
| else | else | ||||
| await _messageDeletedEvent.InvokeAsync(data.Id, Optional.Create<IMessage>()).ConfigureAwait(false); | |||||
| await _messageDeletedEvent.InvokeAsync(data.Id, Optional.Create<SocketMessage>()).ConfigureAwait(false); | |||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| @@ -1224,10 +1256,10 @@ namespace Discord.WebSocket | |||||
| await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_DELETE_BULK)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_DELETE_BULK)").ConfigureAwait(false); | ||||
| var data = (payload as JToken).ToObject<MessageDeleteBulkEvent>(_serializer); | var data = (payload as JToken).ToObject<MessageDeleteBulkEvent>(_serializer); | ||||
| var channel = DataStore.GetChannel(data.ChannelId) as ISocketMessageChannel; | |||||
| var channel = State.GetChannel(data.ChannelId) as ISocketMessageChannel; | |||||
| if (channel != null) | if (channel != null) | ||||
| { | { | ||||
| if (!((channel as ISocketGuildChannel)?.Guild.IsSynced ?? true)) | |||||
| if (!((channel as SocketGuildChannel)?.Guild.IsSynced ?? true)) | |||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Ignored MESSAGE_DELETE_BULK, guild is not synced yet.").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Ignored MESSAGE_DELETE_BULK, guild is not synced yet.").ConfigureAwait(false); | ||||
| return; | return; | ||||
| @@ -1235,11 +1267,11 @@ namespace Discord.WebSocket | |||||
| foreach (var id in data.Ids) | foreach (var id in data.Ids) | ||||
| { | { | ||||
| var msg = channel.RemoveMessage(id); | |||||
| var msg = SocketChannelHelper.RemoveMessage(channel, this, id); | |||||
| if (msg != null) | if (msg != null) | ||||
| await _messageDeletedEvent.InvokeAsync(id, Optional.Create<IMessage>(msg)).ConfigureAwait(false); | |||||
| await _messageDeletedEvent.InvokeAsync(id, msg).ConfigureAwait(false); | |||||
| else | else | ||||
| await _messageDeletedEvent.InvokeAsync(id, Optional.Create<IMessage>()).ConfigureAwait(false); | |||||
| await _messageDeletedEvent.InvokeAsync(id, Optional.Create<SocketMessage>()).ConfigureAwait(false); | |||||
| } | } | ||||
| } | } | ||||
| else | else | ||||
| @@ -1258,39 +1290,43 @@ namespace Discord.WebSocket | |||||
| var data = (payload as JToken).ToObject<API.Presence>(_serializer); | var data = (payload as JToken).ToObject<API.Presence>(_serializer); | ||||
| if (data.GuildId.IsSpecified) | if (data.GuildId.IsSpecified) | ||||
| { | { | ||||
| var guild = DataStore.GetGuild(data.GuildId.Value); | |||||
| var guild = State.GetGuild(data.GuildId.Value); | |||||
| if (guild == null) | if (guild == null) | ||||
| { | { | ||||
| await _gatewayLogger.WarningAsync("PRESENCE_UPDATE referenced an unknown guild.").ConfigureAwait(false); | await _gatewayLogger.WarningAsync("PRESENCE_UPDATE referenced an unknown guild.").ConfigureAwait(false); | ||||
| break; | break; | ||||
| } | } | ||||
| if (!guild.IsSynced) | if (!guild.IsSynced) | ||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Ignored PRESENCE_UPDATE, guild is not synced yet.").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Ignored PRESENCE_UPDATE, guild is not synced yet.").ConfigureAwait(false); | ||||
| return; | return; | ||||
| } | } | ||||
| IPresence before; | |||||
| var user = guild.GetUser(data.User.Id); | |||||
| SocketPresence before; | |||||
| SocketUser user = guild.GetUser(data.User.Id); | |||||
| if (user != null) | if (user != null) | ||||
| { | { | ||||
| before = user.Presence.Clone(); | before = user.Presence.Clone(); | ||||
| user.Update(data, UpdateSource.WebSocket); | |||||
| user.Update(State, data); | |||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| before = new SocketPresence(null, UserStatus.Offline); | before = new SocketPresence(null, UserStatus.Offline); | ||||
| user = guild.AddOrUpdateUser(data, DataStore); | |||||
| user = guild.AddOrUpdateUser(data); | |||||
| } | } | ||||
| await _userPresenceUpdatedEvent.InvokeAsync(user, before, user).ConfigureAwait(false); | |||||
| await _userPresenceUpdatedEvent.InvokeAsync(user, before, user.Presence).ConfigureAwait(false); | |||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| var channel = DataStore.GetDMChannel(data.User.Id); | |||||
| var channel = State.GetChannel(data.User.Id); | |||||
| if (channel != null) | if (channel != null) | ||||
| channel.Recipient.Update(data, UpdateSource.WebSocket); | |||||
| { | |||||
| var user = channel.GetUser(data.User.Id); | |||||
| var before = user.Presence.Clone(); | |||||
| user.Update(State, data); | |||||
| await _userPresenceUpdatedEvent.InvokeAsync(user, before, user.Presence).ConfigureAwait(false); | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| break; | break; | ||||
| @@ -1299,16 +1335,16 @@ namespace Discord.WebSocket | |||||
| await _gatewayLogger.DebugAsync("Received Dispatch (TYPING_START)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (TYPING_START)").ConfigureAwait(false); | ||||
| var data = (payload as JToken).ToObject<TypingStartEvent>(_serializer); | var data = (payload as JToken).ToObject<TypingStartEvent>(_serializer); | ||||
| var channel = DataStore.GetChannel(data.ChannelId) as ISocketMessageChannel; | |||||
| var channel = State.GetChannel(data.ChannelId) as ISocketMessageChannel; | |||||
| if (channel != null) | if (channel != null) | ||||
| { | { | ||||
| if (!((channel as ISocketGuildChannel)?.Guild.IsSynced ?? true)) | |||||
| if (!((channel as SocketGuildChannel)?.Guild.IsSynced ?? true)) | |||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Ignored TYPING_START, guild is not synced yet.").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Ignored TYPING_START, guild is not synced yet.").ConfigureAwait(false); | ||||
| return; | return; | ||||
| } | } | ||||
| var user = channel.GetUser(data.UserId, true); | |||||
| var user = (channel as SocketChannel).GetUser(data.UserId); | |||||
| if (user != null) | if (user != null) | ||||
| await _userIsTypingEvent.InvokeAsync(user, channel).ConfigureAwait(false); | await _userIsTypingEvent.InvokeAsync(user, channel).ConfigureAwait(false); | ||||
| } | } | ||||
| @@ -1324,7 +1360,7 @@ namespace Discord.WebSocket | |||||
| if (data.Id == CurrentUser.Id) | if (data.Id == CurrentUser.Id) | ||||
| { | { | ||||
| var before = CurrentUser.Clone(); | var before = CurrentUser.Clone(); | ||||
| CurrentUser.Update(data, UpdateSource.WebSocket); | |||||
| CurrentUser.Update(State, data); | |||||
| await _selfUpdatedEvent.InvokeAsync(before, CurrentUser).ConfigureAwait(false); | await _selfUpdatedEvent.InvokeAsync(before, CurrentUser).ConfigureAwait(false); | ||||
| } | } | ||||
| else | else | ||||
| @@ -1343,56 +1379,53 @@ namespace Discord.WebSocket | |||||
| var data = (payload as JToken).ToObject<API.VoiceState>(_serializer); | var data = (payload as JToken).ToObject<API.VoiceState>(_serializer); | ||||
| if (data.GuildId.HasValue) | if (data.GuildId.HasValue) | ||||
| { | { | ||||
| ISocketUser user; | |||||
| SocketUser user; | |||||
| SocketVoiceState before, after; | SocketVoiceState before, after; | ||||
| if (data.GuildId != null) | if (data.GuildId != null) | ||||
| { | { | ||||
| var guild = DataStore.GetGuild(data.GuildId.Value); | |||||
| if (guild != null) | |||||
| var guild = State.GetGuild(data.GuildId.Value); | |||||
| if (guild == null) | |||||
| { | { | ||||
| if (!guild.IsSynced) | |||||
| { | |||||
| await _gatewayLogger.DebugAsync("Ignored VOICE_STATE_UPDATE, guild is not synced yet.").ConfigureAwait(false); | |||||
| return; | |||||
| } | |||||
| await _gatewayLogger.WarningAsync("VOICE_STATE_UPDATE referenced an unknown guild.").ConfigureAwait(false); | |||||
| return; | |||||
| } | |||||
| else if (!guild.IsSynced) | |||||
| { | |||||
| await _gatewayLogger.DebugAsync("Ignored VOICE_STATE_UPDATE, guild is not synced yet.").ConfigureAwait(false); | |||||
| return; | |||||
| } | |||||
| if (data.ChannelId != null) | |||||
| { | |||||
| before = guild.GetVoiceState(data.UserId)?.Clone() ?? new SocketVoiceState(null, null, false, false, false); | |||||
| after = guild.AddOrUpdateVoiceState(data, DataStore); | |||||
| if (data.UserId == _currentUser.Id) | |||||
| { | |||||
| var _ = guild.FinishJoinAudioChannel().ConfigureAwait(false); | |||||
| } | |||||
| } | |||||
| else | |||||
| if (data.ChannelId != null) | |||||
| { | |||||
| before = guild.GetVoiceState(data.UserId)?.Clone() ?? new SocketVoiceState(null, null, false, false, false); | |||||
| after = guild.AddOrUpdateVoiceState(State, data); | |||||
| if (data.UserId == CurrentUser.Id) | |||||
| { | { | ||||
| before = guild.RemoveVoiceState(data.UserId) ?? new SocketVoiceState(null, null, false, false, false); | |||||
| after = new SocketVoiceState(null, data); | |||||
| var _ = guild.FinishJoinAudioChannel().ConfigureAwait(false); | |||||
| } | } | ||||
| user = guild.GetUser(data.UserId); | |||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| await _gatewayLogger.WarningAsync("VOICE_STATE_UPDATE referenced an unknown guild.").ConfigureAwait(false); | |||||
| return; | |||||
| before = guild.RemoveVoiceState(data.UserId) ?? new SocketVoiceState(null, null, false, false, false); | |||||
| after = SocketVoiceState.Create(null, data); | |||||
| } | } | ||||
| user = guild.GetUser(data.UserId); | |||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| var groupChannel = DataStore.GetChannel(data.ChannelId.Value) as SocketGroupChannel; | |||||
| var groupChannel = State.GetChannel(data.ChannelId.Value) as SocketGroupChannel; | |||||
| if (groupChannel != null) | if (groupChannel != null) | ||||
| { | { | ||||
| if (data.ChannelId != null) | if (data.ChannelId != null) | ||||
| { | { | ||||
| before = groupChannel.GetVoiceState(data.UserId)?.Clone() ?? new SocketVoiceState(null, null, false, false, false); | before = groupChannel.GetVoiceState(data.UserId)?.Clone() ?? new SocketVoiceState(null, null, false, false, false); | ||||
| after = groupChannel.AddOrUpdateVoiceState(data, DataStore); | |||||
| after = groupChannel.AddOrUpdateVoiceState(State, data); | |||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| before = groupChannel.RemoveVoiceState(data.UserId) ?? new SocketVoiceState(null, null, false, false, false); | before = groupChannel.RemoveVoiceState(data.UserId) ?? new SocketVoiceState(null, null, false, false, false); | ||||
| after = new SocketVoiceState(null, data); | |||||
| after = SocketVoiceState.Create(null, data); | |||||
| } | } | ||||
| user = groupChannel.GetUser(data.UserId); | user = groupChannel.GetUser(data.UserId); | ||||
| } | } | ||||
| @@ -1419,7 +1452,7 @@ namespace Discord.WebSocket | |||||
| if (AudioMode != AudioMode.Disabled) | if (AudioMode != AudioMode.Disabled) | ||||
| { | { | ||||
| var data = (payload as JToken).ToObject<VoiceServerUpdateEvent>(_serializer); | var data = (payload as JToken).ToObject<VoiceServerUpdateEvent>(_serializer); | ||||
| var guild = DataStore.GetGuild(data.GuildId); | |||||
| var guild = State.GetGuild(data.GuildId); | |||||
| if (guild != null) | if (guild != null) | ||||
| { | { | ||||
| string endpoint = data.Endpoint.Substring(0, data.Endpoint.LastIndexOf(':')); | string endpoint = data.Endpoint.Substring(0, data.Endpoint.LastIndexOf(':')); | ||||
| @@ -1431,8 +1464,7 @@ namespace Discord.WebSocket | |||||
| return; | return; | ||||
| } | } | ||||
| } | } | ||||
| return;*/ | |||||
| return; | |||||
| //Ignored (User only) | //Ignored (User only) | ||||
| case "CHANNEL_PINS_ACK": | case "CHANNEL_PINS_ACK": | ||||
| @@ -1441,12 +1473,15 @@ namespace Discord.WebSocket | |||||
| case "CHANNEL_PINS_UPDATE": | case "CHANNEL_PINS_UPDATE": | ||||
| await _gatewayLogger.DebugAsync("Ignored Dispatch (CHANNEL_PINS_UPDATE)"); | await _gatewayLogger.DebugAsync("Ignored Dispatch (CHANNEL_PINS_UPDATE)"); | ||||
| break; | break; | ||||
| case "USER_SETTINGS_UPDATE": | |||||
| await _gatewayLogger.DebugAsync("Ignored Dispatch (USER_SETTINGS_UPDATE)").ConfigureAwait(false); | |||||
| return; | |||||
| case "MESSAGE_ACK": | case "MESSAGE_ACK": | ||||
| await _gatewayLogger.DebugAsync("Ignored Dispatch (MESSAGE_ACK)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Ignored Dispatch (MESSAGE_ACK)").ConfigureAwait(false); | ||||
| return; | return; | ||||
| case "GUILD_INTEGRATIONS_UPDATE": | |||||
| await _gatewayLogger.DebugAsync("Ignored Dispatch (GUILD_INTEGRATIONS_UPDATE)").ConfigureAwait(false); | |||||
| return; | |||||
| case "USER_SETTINGS_UPDATE": | |||||
| await _gatewayLogger.DebugAsync("Ignored Dispatch (USER_SETTINGS_UPDATE)").ConfigureAwait(false); | |||||
| return; | |||||
| //Others | //Others | ||||
| default: | default: | ||||
| @@ -1532,6 +1567,42 @@ namespace Discord.WebSocket | |||||
| await ApiClient.SendGuildSyncAsync(guildIds).ConfigureAwait(false); | await ApiClient.SendGuildSyncAsync(guildIds).ConfigureAwait(false); | ||||
| } | } | ||||
| internal SocketGuild AddGuild(ExtendedGuild model, ClientState state) | |||||
| { | |||||
| var guild = SocketGuild.Create(this, state, model); | |||||
| state.AddGuild(guild); | |||||
| if (model.Large) | |||||
| _largeGuilds.Enqueue(model.Id); | |||||
| return guild; | |||||
| } | |||||
| internal SocketGuild RemoveGuild(ulong id) | |||||
| { | |||||
| var guild = State.RemoveGuild(id); | |||||
| if (guild != null) | |||||
| { | |||||
| foreach (var channel in guild.Channels) | |||||
| State.RemoveChannel(id); | |||||
| foreach (var user in guild.Users) | |||||
| user.GlobalUser.RemoveRef(this); | |||||
| } | |||||
| return guild; | |||||
| } | |||||
| internal ISocketPrivateChannel AddPrivateChannel(API.Channel model, ClientState state) | |||||
| { | |||||
| return SocketChannel.CreatePrivate(this, state, model); | |||||
| } | |||||
| internal ISocketPrivateChannel RemovePrivateChannel(ulong id) | |||||
| { | |||||
| var channel = State.RemoveChannel(id) as ISocketPrivateChannel; | |||||
| if (channel != null) | |||||
| { | |||||
| foreach (var recipient in channel.Recipients) | |||||
| recipient.GlobalUser.RemoveRef(this); | |||||
| } | |||||
| return channel; | |||||
| } | |||||
| //IDiscordClient | //IDiscordClient | ||||
| DiscordRestApiClient IDiscordClient.ApiClient => ApiClient; | DiscordRestApiClient IDiscordClient.ApiClient => ApiClient; | ||||
| @@ -0,0 +1,6 @@ | |||||
| namespace Discord.WebSocket | |||||
| { | |||||
| public interface ISocketAudioChannel : IAudioChannel | |||||
| { | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,32 @@ | |||||
| using Discord.Rest; | |||||
| using System.Collections.Generic; | |||||
| using System.IO; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord.WebSocket | |||||
| { | |||||
| public interface ISocketMessageChannel : IMessageChannel | |||||
| { | |||||
| /// <summary> Gets all messages in this channel's cache. </summary> | |||||
| IReadOnlyCollection<SocketMessage> CachedMessages { get; } | |||||
| /// <summary> Sends a message to this message channel. </summary> | |||||
| new Task<RestUserMessage> SendMessageAsync(string text, bool isTTS = false); | |||||
| /// <summary> Sends a file to this text channel, with an optional caption. </summary> | |||||
| new Task<RestUserMessage> SendFileAsync(string filePath, string text = null, bool isTTS = false); | |||||
| /// <summary> Sends a file to this text channel, with an optional caption. </summary> | |||||
| new Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false); | |||||
| SocketMessage GetCachedMessage(ulong id); | |||||
| /// <summary> Gets a message from this message channel with the given id, or null if not found. </summary> | |||||
| Task<IMessage> GetMessageAsync(ulong id); | |||||
| /// <summary> Gets the last N messages from this message channel. </summary> | |||||
| Task<IReadOnlyCollection<IMessage>> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch); | |||||
| /// <summary> Gets a collection of messages in this channel. </summary> | |||||
| Task<IReadOnlyCollection<IMessage>> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch); | |||||
| /// <summary> Gets a collection of messages in this channel. </summary> | |||||
| Task<IReadOnlyCollection<IMessage>> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch); | |||||
| /// <summary> Gets a collection of pinned messages in this channel. </summary> | |||||
| new Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync(); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,9 @@ | |||||
| using System.Collections.Generic; | |||||
| namespace Discord.WebSocket | |||||
| { | |||||
| public interface ISocketPrivateChannel : IPrivateChannel | |||||
| { | |||||
| new IReadOnlyCollection<SocketUser> Recipients { get; } | |||||
| } | |||||
| } | |||||
| @@ -11,35 +11,37 @@ namespace Discord.WebSocket | |||||
| [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | ||||
| public abstract class SocketChannel : SocketEntity<ulong>, IChannel | public abstract class SocketChannel : SocketEntity<ulong>, IChannel | ||||
| { | { | ||||
| public IReadOnlyCollection<SocketUser> Users => GetUsersInternal(); | |||||
| internal SocketChannel(DiscordSocketClient discord, ulong id) | internal SocketChannel(DiscordSocketClient discord, ulong id) | ||||
| : base(discord, id) | : base(discord, id) | ||||
| { | { | ||||
| } | } | ||||
| internal static SocketChannel Create(DiscordSocketClient discord, Model model) | |||||
| internal static ISocketPrivateChannel CreatePrivate(DiscordSocketClient discord, ClientState state, Model model) | |||||
| { | { | ||||
| switch (model.Type) | switch (model.Type) | ||||
| { | { | ||||
| case ChannelType.Text: | |||||
| return SocketTextChannel.Create(discord, model); | |||||
| case ChannelType.Voice: | |||||
| return SocketVoiceChannel.Create(discord, model); | |||||
| case ChannelType.DM: | case ChannelType.DM: | ||||
| return SocketDMChannel.Create(discord, model); | |||||
| return SocketDMChannel.Create(discord, state, model); | |||||
| case ChannelType.Group: | case ChannelType.Group: | ||||
| return SocketGroupChannel.Create(discord, model); | |||||
| return SocketGroupChannel.Create(discord, state, model); | |||||
| default: | default: | ||||
| throw new InvalidOperationException($"Unexpected channel type: {model.Type}"); | throw new InvalidOperationException($"Unexpected channel type: {model.Type}"); | ||||
| } | } | ||||
| } | } | ||||
| internal abstract void Update(ClientState state, Model model); | |||||
| //User | |||||
| public SocketUser GetUser(ulong id) => GetUserInternal(id); | |||||
| internal abstract SocketUser GetUserInternal(ulong id); | |||||
| internal abstract IReadOnlyCollection<SocketUser> GetUsersInternal(); | |||||
| //IChannel | |||||
| IReadOnlyCollection<IUser> IChannel.CachedUsers => ImmutableArray.Create<IUser>(); | |||||
| internal SocketChannel Clone() => MemberwiseClone() as SocketChannel; | |||||
| IUser IChannel.GetCachedUser(ulong id) | |||||
| => null; | |||||
| Task<IUser> IChannel.GetUserAsync(ulong id) | |||||
| => Task.FromResult<IUser>(null); | |||||
| IAsyncEnumerable<IReadOnlyCollection<IUser>> IChannel.GetUsersAsync() | |||||
| => ImmutableArray.Create<IReadOnlyCollection<IUser>>().ToAsyncEnumerable(); | |||||
| //IChannel | |||||
| Task<IUser> IChannel.GetUserAsync(ulong id, CacheMode mode) | |||||
| => Task.FromResult<IUser>(null); //Overridden | |||||
| IAsyncEnumerable<IReadOnlyCollection<IUser>> IChannel.GetUsersAsync(CacheMode mode) | |||||
| => ImmutableArray.Create<IReadOnlyCollection<IUser>>().ToAsyncEnumerable(); //Overridden | |||||
| } | } | ||||
| } | } | ||||
| @@ -0,0 +1,81 @@ | |||||
| using Discord.Rest; | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Collections.Immutable; | |||||
| using System.Linq; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord.WebSocket | |||||
| { | |||||
| internal static class SocketChannelHelper | |||||
| { | |||||
| public static async Task<IReadOnlyCollection<IMessage>> GetMessagesAsync(SocketChannel channel, DiscordSocketClient discord, MessageCache messages, | |||||
| ulong? fromMessageId, Direction dir, int limit) | |||||
| { | |||||
| if (messages == null) //Cache disabled | |||||
| { | |||||
| var msgs = await ChannelHelper.GetMessagesAsync(channel, discord, fromMessageId, dir, limit).Flatten(); | |||||
| return msgs.ToImmutableArray(); | |||||
| } | |||||
| var cachedMessages = messages.GetMany(fromMessageId, dir, limit); | |||||
| limit -= cachedMessages.Count; | |||||
| if (limit == 0) | |||||
| return cachedMessages; | |||||
| if (dir == Direction.Before) | |||||
| fromMessageId = cachedMessages.Min(x => x.Id); | |||||
| else | |||||
| fromMessageId = cachedMessages.Max(x => x.Id); | |||||
| var downloadedMessages = await ChannelHelper.GetMessagesAsync(channel, discord, fromMessageId, dir, limit).Flatten(); | |||||
| return cachedMessages.Concat<IMessage>(downloadedMessages).ToImmutableArray(); | |||||
| } | |||||
| public static IAsyncEnumerable<IReadOnlyCollection<IMessage>> PagedGetMessagesAsync(SocketChannel channel, DiscordSocketClient discord, MessageCache messages, | |||||
| ulong? fromMessageId, Direction dir, int limit) | |||||
| { | |||||
| if (messages == null) //Cache disabled | |||||
| return ChannelHelper.GetMessagesAsync(channel, discord, fromMessageId, dir, limit); | |||||
| var cachedMessages = messages.GetMany(fromMessageId, dir, limit); | |||||
| var result = ImmutableArray.Create(cachedMessages).ToAsyncEnumerable<IReadOnlyCollection<IMessage>>(); | |||||
| limit -= cachedMessages.Count; | |||||
| if (limit == 0) | |||||
| return result; | |||||
| if (dir == Direction.Before) | |||||
| fromMessageId = cachedMessages.Min(x => x.Id); | |||||
| else | |||||
| fromMessageId = cachedMessages.Max(x => x.Id); | |||||
| var downloadedMessages = ChannelHelper.GetMessagesAsync(channel, discord, fromMessageId, dir, limit); | |||||
| return result.Concat(downloadedMessages); | |||||
| } | |||||
| public static void AddMessage(ISocketMessageChannel channel, DiscordSocketClient discord, | |||||
| SocketMessage msg) | |||||
| { | |||||
| //C#7 Candidate for pattern matching | |||||
| if (channel is SocketDMChannel) | |||||
| (channel as SocketDMChannel).AddMessage(msg); | |||||
| else if (channel is SocketGroupChannel) | |||||
| (channel as SocketDMChannel).AddMessage(msg); | |||||
| else if (channel is SocketTextChannel) | |||||
| (channel as SocketDMChannel).AddMessage(msg); | |||||
| else | |||||
| throw new NotSupportedException("Unexpected ISocketMessageChannel type"); | |||||
| } | |||||
| public static SocketMessage RemoveMessage(ISocketMessageChannel channel, DiscordSocketClient discord, | |||||
| ulong id) | |||||
| { | |||||
| //C#7 Candidate for pattern matching | |||||
| if (channel is SocketDMChannel) | |||||
| return (channel as SocketDMChannel).RemoveMessage(id); | |||||
| else if (channel is SocketGroupChannel) | |||||
| return (channel as SocketDMChannel).RemoveMessage(id); | |||||
| else if (channel is SocketTextChannel) | |||||
| return (channel as SocketDMChannel).RemoveMessage(id); | |||||
| else | |||||
| throw new NotSupportedException("Unexpected ISocketMessageChannel type"); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -12,60 +12,52 @@ using Model = Discord.API.Channel; | |||||
| namespace Discord.WebSocket | namespace Discord.WebSocket | ||||
| { | { | ||||
| [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | ||||
| public class SocketDMChannel : SocketChannel, IDMChannel | |||||
| public class SocketDMChannel : SocketChannel, IDMChannel, ISocketPrivateChannel, ISocketMessageChannel | |||||
| { | { | ||||
| private readonly MessageCache _messages; | private readonly MessageCache _messages; | ||||
| public SocketUser Recipient { get; private set; } | public SocketUser Recipient { get; private set; } | ||||
| public IReadOnlyCollection<SocketUser> Users => ImmutableArray.Create(Discord.CurrentUser, Recipient); | |||||
| public IReadOnlyCollection<SocketMessage> CachedMessages => _messages?.Messages ?? ImmutableArray.Create<SocketMessage>(); | |||||
| public new IReadOnlyCollection<SocketUser> Users => ImmutableArray.Create(Discord.CurrentUser, Recipient); | |||||
| internal SocketDMChannel(DiscordSocketClient discord, ulong id, ulong recipientId) | |||||
| internal SocketDMChannel(DiscordSocketClient discord, ulong id, SocketGlobalUser recipient) | |||||
| : base(discord, id) | : base(discord, id) | ||||
| { | { | ||||
| Recipient = new SocketUser(Discord, recipientId); | |||||
| Recipient = recipient; | |||||
| if (Discord.MessageCacheSize > 0) | if (Discord.MessageCacheSize > 0) | ||||
| _messages = new MessageCache(Discord, this); | _messages = new MessageCache(Discord, this); | ||||
| } | } | ||||
| internal new static SocketDMChannel Create(DiscordSocketClient discord, Model model) | |||||
| internal static SocketDMChannel Create(DiscordSocketClient discord, ClientState state, Model model) | |||||
| { | { | ||||
| var entity = new SocketDMChannel(discord, model.Id, model.Recipients.Value[0].Id); | |||||
| entity.Update(model); | |||||
| var entity = new SocketDMChannel(discord, model.Id, discord.GetOrCreateUser(state, model.Recipients.Value[0])); | |||||
| entity.Update(state, model); | |||||
| return entity; | return entity; | ||||
| } | } | ||||
| internal void Update(Model model) | |||||
| internal override void Update(ClientState state, Model model) | |||||
| { | { | ||||
| Recipient.Update(model.Recipients.Value[0]); | |||||
| Recipient.Update(state, model.Recipients.Value[0]); | |||||
| } | } | ||||
| public Task CloseAsync() | public Task CloseAsync() | ||||
| => ChannelHelper.DeleteAsync(this, Discord); | => ChannelHelper.DeleteAsync(this, Discord); | ||||
| public SocketUser GetUser(ulong id) | |||||
| { | |||||
| if (id == Recipient.Id) | |||||
| return Recipient; | |||||
| else if (id == Discord.CurrentUser.Id) | |||||
| return Discord.CurrentUser as SocketSelfUser; | |||||
| else | |||||
| return null; | |||||
| } | |||||
| public SocketMessage GetMessage(ulong id) | |||||
| //Messages | |||||
| public SocketMessage GetCachedMessage(ulong id) | |||||
| => _messages?.Get(id); | => _messages?.Get(id); | ||||
| public async Task<IMessage> GetMessageAsync(ulong id, bool allowDownload = true) | |||||
| public async Task<IMessage> GetMessageAsync(ulong id) | |||||
| { | { | ||||
| IMessage msg = _messages?.Get(id); | IMessage msg = _messages?.Get(id); | ||||
| if (msg == null && allowDownload) | |||||
| if (msg == null) | |||||
| msg = await ChannelHelper.GetMessageAsync(this, Discord, id); | msg = await ChannelHelper.GetMessageAsync(this, Discord, id); | ||||
| return msg; | return msg; | ||||
| } | } | ||||
| public IAsyncEnumerable<IReadOnlyCollection<IMessage>> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch) | |||||
| => ChannelHelper.GetMessagesAsync(this, Discord, limit: limit); | |||||
| public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) | |||||
| => ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit); | |||||
| public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) | |||||
| => ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit); | |||||
| public Task<IReadOnlyCollection<IMessage>> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch) | |||||
| => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit); | |||||
| public Task<IReadOnlyCollection<IMessage>> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) | |||||
| => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit); | |||||
| public Task<IReadOnlyCollection<IMessage>> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) | |||||
| => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit); | |||||
| public Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync() | public Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync() | ||||
| => ChannelHelper.GetPinnedMessagesAsync(this, Discord); | => ChannelHelper.GetPinnedMessagesAsync(this, Discord); | ||||
| @@ -82,38 +74,61 @@ namespace Discord.WebSocket | |||||
| public IDisposable EnterTypingState() | public IDisposable EnterTypingState() | ||||
| => ChannelHelper.EnterTypingState(this, Discord); | => ChannelHelper.EnterTypingState(this, Discord); | ||||
| internal SocketMessage AddMessage(SocketUser author, MessageModel model) | |||||
| { | |||||
| var msg = SocketMessage.Create(Discord, author, model); | |||||
| _messages.Add(msg); | |||||
| return msg; | |||||
| } | |||||
| internal void AddMessage(SocketMessage msg) | |||||
| => _messages.Add(msg); | |||||
| internal SocketMessage RemoveMessage(ulong id) | internal SocketMessage RemoveMessage(ulong id) | ||||
| => _messages.Remove(id); | |||||
| //Users | |||||
| public new SocketUser GetUser(ulong id) | |||||
| { | { | ||||
| return _messages.Remove(id); | |||||
| if (id == Recipient.Id) | |||||
| return Recipient; | |||||
| else if (id == Discord.CurrentUser.Id) | |||||
| return Discord.CurrentUser as SocketSelfUser; | |||||
| else | |||||
| return null; | |||||
| } | } | ||||
| public SocketDMChannel Clone() => MemberwiseClone() as SocketDMChannel; | |||||
| public override string ToString() => $"@{Recipient}"; | public override string ToString() => $"@{Recipient}"; | ||||
| private string DebuggerDisplay => $"@{Recipient} ({Id}, DM)"; | private string DebuggerDisplay => $"@{Recipient} ({Id}, DM)"; | ||||
| internal new SocketDMChannel Clone() => MemberwiseClone() as SocketDMChannel; | |||||
| //SocketChannel | |||||
| internal override IReadOnlyCollection<SocketUser> GetUsersInternal() => Users; | |||||
| internal override SocketUser GetUserInternal(ulong id) => GetUser(id); | |||||
| //IDMChannel | //IDMChannel | ||||
| IUser IDMChannel.Recipient => Recipient; | IUser IDMChannel.Recipient => Recipient; | ||||
| //ISocketPrivateChannel | |||||
| IReadOnlyCollection<SocketUser> ISocketPrivateChannel.Recipients => ImmutableArray.Create(Recipient); | |||||
| //IPrivateChannel | //IPrivateChannel | ||||
| IReadOnlyCollection<IUser> IPrivateChannel.Recipients => ImmutableArray.Create<IUser>(Recipient); | IReadOnlyCollection<IUser> IPrivateChannel.Recipients => ImmutableArray.Create<IUser>(Recipient); | ||||
| //IMessageChannel | //IMessageChannel | ||||
| IReadOnlyCollection<IMessage> IMessageChannel.CachedMessages => ImmutableArray.Create<IMessage>(); | |||||
| IMessage IMessageChannel.GetCachedMessage(ulong id) => null; | |||||
| async Task<IMessage> IMessageChannel.GetMessageAsync(ulong id) | |||||
| => await GetMessageAsync(id); | |||||
| IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(int limit) | |||||
| => GetMessagesAsync(limit); | |||||
| IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit) | |||||
| => GetMessagesAsync(fromMessageId, dir, limit); | |||||
| async Task<IMessage> IMessageChannel.GetMessageAsync(ulong id, CacheMode mode) | |||||
| { | |||||
| if (mode == CacheMode.AllowDownload) | |||||
| return await GetMessageAsync(id); | |||||
| else | |||||
| return GetCachedMessage(id); | |||||
| } | |||||
| IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode) | |||||
| { | |||||
| if (mode == CacheMode.AllowDownload) | |||||
| return SocketChannelHelper.PagedGetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit); | |||||
| else | |||||
| return ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit); | |||||
| } | |||||
| IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit, CacheMode mode) | |||||
| { | |||||
| if (mode == CacheMode.AllowDownload) | |||||
| return SocketChannelHelper.PagedGetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit); | |||||
| else | |||||
| return ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit); | |||||
| } | |||||
| async Task<IReadOnlyCollection<IMessage>> IMessageChannel.GetPinnedMessagesAsync() | async Task<IReadOnlyCollection<IMessage>> IMessageChannel.GetPinnedMessagesAsync() | ||||
| => await GetPinnedMessagesAsync().ConfigureAwait(false); | => await GetPinnedMessagesAsync().ConfigureAwait(false); | ||||
| async Task<IUserMessage> IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS) | async Task<IUserMessage> IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS) | ||||
| @@ -125,14 +140,10 @@ namespace Discord.WebSocket | |||||
| IDisposable IMessageChannel.EnterTypingState() | IDisposable IMessageChannel.EnterTypingState() | ||||
| => EnterTypingState(); | => EnterTypingState(); | ||||
| //IChannel | |||||
| IReadOnlyCollection<IUser> IChannel.CachedUsers => Users; | |||||
| IUser IChannel.GetCachedUser(ulong id) | |||||
| => GetUser(id); | |||||
| Task<IUser> IChannel.GetUserAsync(ulong id) | |||||
| //IChannel | |||||
| Task<IUser> IChannel.GetUserAsync(ulong id, CacheMode mode) | |||||
| => Task.FromResult<IUser>(GetUser(id)); | => Task.FromResult<IUser>(GetUser(id)); | ||||
| IAsyncEnumerable<IReadOnlyCollection<IUser>> IChannel.GetUsersAsync() | |||||
| => ImmutableArray.Create<IReadOnlyCollection<IUser>>().ToAsyncEnumerable(); | |||||
| IAsyncEnumerable<IReadOnlyCollection<IUser>> IChannel.GetUsersAsync(CacheMode mode) | |||||
| => ImmutableArray.Create<IReadOnlyCollection<IUser>>(Users).ToAsyncEnumerable(); | |||||
| } | } | ||||
| } | } | ||||
| @@ -7,7 +7,6 @@ using System.Diagnostics; | |||||
| using System.IO; | using System.IO; | ||||
| using System.Linq; | using System.Linq; | ||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| using MessageModel = Discord.API.Message; | |||||
| using Model = Discord.API.Channel; | using Model = Discord.API.Channel; | ||||
| using UserModel = Discord.API.User; | using UserModel = Discord.API.User; | ||||
| using VoiceStateModel = Discord.API.VoiceState; | using VoiceStateModel = Discord.API.VoiceState; | ||||
| @@ -15,7 +14,7 @@ using VoiceStateModel = Discord.API.VoiceState; | |||||
| namespace Discord.WebSocket | namespace Discord.WebSocket | ||||
| { | { | ||||
| [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | ||||
| public class SocketGroupChannel : SocketChannel, IGroupChannel | |||||
| public class SocketGroupChannel : SocketChannel, IGroupChannel, ISocketPrivateChannel, ISocketMessageChannel, ISocketAudioChannel | |||||
| { | { | ||||
| private readonly MessageCache _messages; | private readonly MessageCache _messages; | ||||
| @@ -25,7 +24,8 @@ namespace Discord.WebSocket | |||||
| public string Name { get; private set; } | public string Name { get; private set; } | ||||
| public IReadOnlyCollection<SocketGroupUser> Users => _users.ToReadOnlyCollection(); | |||||
| public IReadOnlyCollection<SocketMessage> CachedMessages => _messages?.Messages ?? ImmutableArray.Create<SocketMessage>(); | |||||
| public new IReadOnlyCollection<SocketGroupUser> Users => _users.ToReadOnlyCollection(); | |||||
| public IReadOnlyCollection<SocketGroupUser> Recipients | public IReadOnlyCollection<SocketGroupUser> Recipients | ||||
| => _users.Select(x => x.Value).Where(x => x.Id != Discord.CurrentUser.Id).ToReadOnlyCollection(() => _users.Count - 1); | => _users.Select(x => x.Value).Where(x => x.Id != Discord.CurrentUser.Id).ToReadOnlyCollection(() => _users.Count - 1); | ||||
| @@ -37,13 +37,13 @@ namespace Discord.WebSocket | |||||
| _voiceStates = new ConcurrentDictionary<ulong, SocketVoiceState>(1, 5); | _voiceStates = new ConcurrentDictionary<ulong, SocketVoiceState>(1, 5); | ||||
| _users = new ConcurrentDictionary<ulong, SocketGroupUser>(1, 5); | _users = new ConcurrentDictionary<ulong, SocketGroupUser>(1, 5); | ||||
| } | } | ||||
| internal new static SocketGroupChannel Create(DiscordSocketClient discord, Model model) | |||||
| internal static SocketGroupChannel Create(DiscordSocketClient discord, ClientState state, Model model) | |||||
| { | { | ||||
| var entity = new SocketGroupChannel(discord, model.Id); | var entity = new SocketGroupChannel(discord, model.Id); | ||||
| entity.Update(model); | |||||
| entity.Update(state, model); | |||||
| return entity; | return entity; | ||||
| } | } | ||||
| internal void Update(Model model) | |||||
| internal override void Update(ClientState state, Model model) | |||||
| { | { | ||||
| if (model.Name.IsSpecified) | if (model.Name.IsSpecified) | ||||
| Name = model.Name.Value; | Name = model.Name.Value; | ||||
| @@ -51,37 +51,35 @@ namespace Discord.WebSocket | |||||
| _iconId = model.Icon.Value; | _iconId = model.Icon.Value; | ||||
| if (model.Recipients.IsSpecified) | if (model.Recipients.IsSpecified) | ||||
| UpdateUsers(model.Recipients.Value); | |||||
| UpdateUsers(state, model.Recipients.Value); | |||||
| } | } | ||||
| internal virtual void UpdateUsers(API.User[] models) | |||||
| internal virtual void UpdateUsers(ClientState state, UserModel[] models) | |||||
| { | { | ||||
| var users = new ConcurrentDictionary<ulong, SocketGroupUser>(1, (int)(models.Length * 1.05)); | var users = new ConcurrentDictionary<ulong, SocketGroupUser>(1, (int)(models.Length * 1.05)); | ||||
| for (int i = 0; i < models.Length; i++) | for (int i = 0; i < models.Length; i++) | ||||
| users[models[i].Id] = SocketGroupUser.Create(Discord, models[i]); | |||||
| users[models[i].Id] = SocketGroupUser.Create(this, state, models[i]); | |||||
| _users = users; | _users = users; | ||||
| } | } | ||||
| public async Task UpdateAsync() | |||||
| => Update(await ChannelHelper.GetAsync(this, Discord)); | |||||
| public Task LeaveAsync() | public Task LeaveAsync() | ||||
| => ChannelHelper.DeleteAsync(this, Discord); | => ChannelHelper.DeleteAsync(this, Discord); | ||||
| public SocketGroupUser GetUser(ulong id) | |||||
| //Messages | |||||
| public SocketMessage GetCachedMessage(ulong id) | |||||
| => _messages?.Get(id); | |||||
| public async Task<IMessage> GetMessageAsync(ulong id) | |||||
| { | { | ||||
| SocketGroupUser user; | |||||
| if (_users.TryGetValue(id, out user)) | |||||
| return user; | |||||
| return null; | |||||
| IMessage msg = _messages?.Get(id); | |||||
| if (msg == null) | |||||
| msg = await ChannelHelper.GetMessageAsync(this, Discord, id); | |||||
| return msg; | |||||
| } | } | ||||
| public Task<RestMessage> GetMessageAsync(ulong id) | |||||
| => ChannelHelper.GetMessageAsync(this, Discord, id); | |||||
| public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch) | |||||
| => ChannelHelper.GetMessagesAsync(this, Discord, limit: limit); | |||||
| public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) | |||||
| => ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit); | |||||
| public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) | |||||
| => ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit); | |||||
| public Task<IReadOnlyCollection<IMessage>> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch) | |||||
| => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit); | |||||
| public Task<IReadOnlyCollection<IMessage>> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) | |||||
| => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit); | |||||
| public Task<IReadOnlyCollection<IMessage>> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) | |||||
| => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit); | |||||
| public Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync() | public Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync() | ||||
| => ChannelHelper.GetPinnedMessagesAsync(this, Discord); | => ChannelHelper.GetPinnedMessagesAsync(this, Discord); | ||||
| @@ -98,20 +96,101 @@ namespace Discord.WebSocket | |||||
| public IDisposable EnterTypingState() | public IDisposable EnterTypingState() | ||||
| => ChannelHelper.EnterTypingState(this, Discord); | => ChannelHelper.EnterTypingState(this, Discord); | ||||
| internal void AddMessage(SocketMessage msg) | |||||
| => _messages.Add(msg); | |||||
| internal SocketMessage RemoveMessage(ulong id) | |||||
| => _messages.Remove(id); | |||||
| //Users | |||||
| public new SocketGroupUser GetUser(ulong id) | |||||
| { | |||||
| SocketGroupUser user; | |||||
| if (_users.TryGetValue(id, out user)) | |||||
| return user; | |||||
| return null; | |||||
| } | |||||
| internal SocketGroupUser AddUser(UserModel model) | |||||
| { | |||||
| SocketGroupUser user; | |||||
| if (_users.TryGetValue(model.Id, out user)) | |||||
| return user as SocketGroupUser; | |||||
| else | |||||
| { | |||||
| var privateUser = SocketGroupUser.Create(this, Discord.State, model); | |||||
| _users[privateUser.Id] = privateUser; | |||||
| return privateUser; | |||||
| } | |||||
| } | |||||
| internal SocketGroupUser RemoveUser(ulong id) | |||||
| { | |||||
| SocketGroupUser user; | |||||
| if (_users.TryRemove(id, out user)) | |||||
| { | |||||
| user.GlobalUser.RemoveRef(Discord); | |||||
| return user as SocketGroupUser; | |||||
| } | |||||
| return null; | |||||
| } | |||||
| //Voice States | |||||
| internal SocketVoiceState AddOrUpdateVoiceState(ClientState state, VoiceStateModel model) | |||||
| { | |||||
| var voiceChannel = state.GetChannel(model.ChannelId.Value) as SocketVoiceChannel; | |||||
| var voiceState = SocketVoiceState.Create(voiceChannel, model); | |||||
| _voiceStates[model.UserId] = voiceState; | |||||
| return voiceState; | |||||
| } | |||||
| internal SocketVoiceState? GetVoiceState(ulong id) | |||||
| { | |||||
| SocketVoiceState voiceState; | |||||
| if (_voiceStates.TryGetValue(id, out voiceState)) | |||||
| return voiceState; | |||||
| return null; | |||||
| } | |||||
| internal SocketVoiceState? RemoveVoiceState(ulong id) | |||||
| { | |||||
| SocketVoiceState voiceState; | |||||
| if (_voiceStates.TryRemove(id, out voiceState)) | |||||
| return voiceState; | |||||
| return null; | |||||
| } | |||||
| public override string ToString() => Name; | |||||
| private string DebuggerDisplay => $"{Name} ({Id}, Group)"; | |||||
| internal new SocketGroupChannel Clone() => MemberwiseClone() as SocketGroupChannel; | |||||
| //SocketChannel | |||||
| internal override IReadOnlyCollection<SocketUser> GetUsersInternal() => Users; | |||||
| internal override SocketUser GetUserInternal(ulong id) => GetUser(id); | |||||
| //ISocketPrivateChannel | |||||
| IReadOnlyCollection<SocketUser> ISocketPrivateChannel.Recipients => Recipients; | |||||
| //IPrivateChannel | //IPrivateChannel | ||||
| IReadOnlyCollection<IUser> IPrivateChannel.Recipients => Recipients; | IReadOnlyCollection<IUser> IPrivateChannel.Recipients => Recipients; | ||||
| //IMessageChannel | //IMessageChannel | ||||
| IReadOnlyCollection<IMessage> IMessageChannel.CachedMessages => ImmutableArray.Create<IMessage>(); | |||||
| IMessage IMessageChannel.GetCachedMessage(ulong id) | |||||
| => null; | |||||
| async Task<IMessage> IMessageChannel.GetMessageAsync(ulong id) | |||||
| => await GetMessageAsync(id); | |||||
| IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(int limit) | |||||
| => GetMessagesAsync(limit); | |||||
| IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit) | |||||
| => GetMessagesAsync(fromMessageId, dir, limit); | |||||
| async Task<IMessage> IMessageChannel.GetMessageAsync(ulong id, CacheMode mode) | |||||
| { | |||||
| if (mode == CacheMode.AllowDownload) | |||||
| return await GetMessageAsync(id); | |||||
| else | |||||
| return GetCachedMessage(id); | |||||
| } | |||||
| IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode) | |||||
| { | |||||
| if (mode == CacheMode.AllowDownload) | |||||
| return SocketChannelHelper.PagedGetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit); | |||||
| else | |||||
| return ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit); | |||||
| } | |||||
| IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit, CacheMode mode) | |||||
| { | |||||
| if (mode == CacheMode.AllowDownload) | |||||
| return SocketChannelHelper.PagedGetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit); | |||||
| else | |||||
| return ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit); | |||||
| } | |||||
| async Task<IReadOnlyCollection<IMessage>> IMessageChannel.GetPinnedMessagesAsync() | async Task<IReadOnlyCollection<IMessage>> IMessageChannel.GetPinnedMessagesAsync() | ||||
| => await GetPinnedMessagesAsync(); | => await GetPinnedMessagesAsync(); | ||||
| @@ -124,14 +203,10 @@ namespace Discord.WebSocket | |||||
| IDisposable IMessageChannel.EnterTypingState() | IDisposable IMessageChannel.EnterTypingState() | ||||
| => EnterTypingState(); | => EnterTypingState(); | ||||
| //IChannel | |||||
| IReadOnlyCollection<IUser> IChannel.CachedUsers => Users; | |||||
| IUser IChannel.GetCachedUser(ulong id) | |||||
| => GetUser(id); | |||||
| Task<IUser> IChannel.GetUserAsync(ulong id) | |||||
| //IChannel | |||||
| Task<IUser> IChannel.GetUserAsync(ulong id, CacheMode mode) | |||||
| => Task.FromResult<IUser>(GetUser(id)); | => Task.FromResult<IUser>(GetUser(id)); | ||||
| IAsyncEnumerable<IReadOnlyCollection<IUser>> IChannel.GetUsersAsync() | |||||
| IAsyncEnumerable<IReadOnlyCollection<IUser>> IChannel.GetUsersAsync(CacheMode mode) | |||||
| => ImmutableArray.Create<IReadOnlyCollection<IUser>>(Users).ToAsyncEnumerable(); | => ImmutableArray.Create<IReadOnlyCollection<IUser>>(Users).ToAsyncEnumerable(); | ||||
| } | } | ||||
| } | } | ||||
| @@ -15,31 +15,31 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| private ImmutableArray<Overwrite> _overwrites; | private ImmutableArray<Overwrite> _overwrites; | ||||
| public IReadOnlyCollection<Overwrite> PermissionOverwrites => _overwrites; | |||||
| public ulong GuildId { get; } | |||||
| public SocketGuild Guild { get; } | |||||
| public string Name { get; private set; } | public string Name { get; private set; } | ||||
| public int Position { get; private set; } | public int Position { get; private set; } | ||||
| internal SocketGuildChannel(DiscordSocketClient discord, ulong id, ulong guildId) | |||||
| public IReadOnlyCollection<Overwrite> PermissionOverwrites => _overwrites; | |||||
| public new abstract IReadOnlyCollection<SocketGuildUser> Users { get; } | |||||
| internal SocketGuildChannel(DiscordSocketClient discord, ulong id, SocketGuild guild) | |||||
| : base(discord, id) | : base(discord, id) | ||||
| { | { | ||||
| GuildId = guildId; | |||||
| Guild = guild; | |||||
| } | } | ||||
| internal new static SocketGuildChannel Create(DiscordSocketClient discord, Model model) | |||||
| internal static SocketGuildChannel Create(SocketGuild guild, ClientState state, Model model) | |||||
| { | { | ||||
| switch (model.Type) | switch (model.Type) | ||||
| { | { | ||||
| case ChannelType.Text: | case ChannelType.Text: | ||||
| return SocketTextChannel.Create(discord, model); | |||||
| return SocketTextChannel.Create(guild, state, model); | |||||
| case ChannelType.Voice: | case ChannelType.Voice: | ||||
| return SocketVoiceChannel.Create(discord, model); | |||||
| return SocketVoiceChannel.Create(guild, state, model); | |||||
| default: | default: | ||||
| throw new InvalidOperationException("Unknown guild channel type"); | throw new InvalidOperationException("Unknown guild channel type"); | ||||
| } | } | ||||
| } | } | ||||
| internal virtual void Update(Model model) | |||||
| internal override void Update(ClientState state, Model model) | |||||
| { | { | ||||
| Name = model.Name.Value; | Name = model.Name.Value; | ||||
| Position = model.Position.Value; | Position = model.Position.Value; | ||||
| @@ -50,9 +50,7 @@ namespace Discord.WebSocket | |||||
| newOverwrites.Add(new Overwrite(overwrites[i])); | newOverwrites.Add(new Overwrite(overwrites[i])); | ||||
| _overwrites = newOverwrites.ToImmutable(); | _overwrites = newOverwrites.ToImmutable(); | ||||
| } | } | ||||
| public async Task UpdateAsync() | |||||
| => Update(await ChannelHelper.GetAsync(this, Discord)); | |||||
| public Task ModifyAsync(Action<ModifyGuildChannelParams> func) | public Task ModifyAsync(Action<ModifyGuildChannelParams> func) | ||||
| => ChannelHelper.ModifyAsync(this, Discord, func); | => ChannelHelper.ModifyAsync(this, Discord, func); | ||||
| public Task DeleteAsync() | public Task DeleteAsync() | ||||
| @@ -118,7 +116,18 @@ namespace Discord.WebSocket | |||||
| public async Task<RestInviteMetadata> CreateInviteAsync(int? maxAge = 3600, int? maxUses = null, bool isTemporary = true) | public async Task<RestInviteMetadata> CreateInviteAsync(int? maxAge = 3600, int? maxUses = null, bool isTemporary = true) | ||||
| => await ChannelHelper.CreateInviteAsync(this, Discord, maxAge, maxUses, isTemporary); | => await ChannelHelper.CreateInviteAsync(this, Discord, maxAge, maxUses, isTemporary); | ||||
| public new abstract SocketGuildUser GetUser(ulong id); | |||||
| public override string ToString() => Name; | |||||
| internal new SocketGuildChannel Clone() => MemberwiseClone() as SocketGuildChannel; | |||||
| //SocketChannel | |||||
| internal override IReadOnlyCollection<SocketUser> GetUsersInternal() => Users; | |||||
| internal override SocketUser GetUserInternal(ulong id) => GetUser(id); | |||||
| //IGuildChannel | //IGuildChannel | ||||
| ulong IGuildChannel.GuildId => Guild.Id; | |||||
| async Task<IReadOnlyCollection<IInviteMetadata>> IGuildChannel.GetInvitesAsync() | async Task<IReadOnlyCollection<IInviteMetadata>> IGuildChannel.GetInvitesAsync() | ||||
| => await GetInvitesAsync(); | => await GetInvitesAsync(); | ||||
| async Task<IInviteMetadata> IGuildChannel.CreateInviteAsync(int? maxAge, int? maxUses, bool isTemporary) | async Task<IInviteMetadata> IGuildChannel.CreateInviteAsync(int? maxAge, int? maxUses, bool isTemporary) | ||||
| @@ -136,24 +145,16 @@ namespace Discord.WebSocket | |||||
| => await RemovePermissionOverwriteAsync(role); | => await RemovePermissionOverwriteAsync(role); | ||||
| async Task IGuildChannel.RemovePermissionOverwriteAsync(IUser user) | async Task IGuildChannel.RemovePermissionOverwriteAsync(IUser user) | ||||
| => await RemovePermissionOverwriteAsync(user); | => await RemovePermissionOverwriteAsync(user); | ||||
| IReadOnlyCollection<IGuildUser> IGuildChannel.CachedUsers | |||||
| => ImmutableArray.Create<IGuildUser>(); | |||||
| IAsyncEnumerable<IReadOnlyCollection<IGuildUser>> IGuildChannel.GetUsersAsync() | |||||
| => ImmutableArray.Create<IReadOnlyCollection<IGuildUser>>().ToAsyncEnumerable(); //Overriden in Text/Voice //TODO: Does this actually override? | |||||
| Task<IGuildUser> IGuildChannel.GetUserAsync(ulong id) | |||||
| => Task.FromResult<IGuildUser>(null); //Overriden in Text/Voice //TODO: Does this actually override? | |||||
| IGuildUser IGuildChannel.GetCachedUser(ulong id) | |||||
| => null; | |||||
| IAsyncEnumerable<IReadOnlyCollection<IGuildUser>> IGuildChannel.GetUsersAsync(CacheMode mode) | |||||
| => ImmutableArray.Create<IReadOnlyCollection<IGuildUser>>(Users).ToAsyncEnumerable(); | |||||
| Task<IGuildUser> IGuildChannel.GetUserAsync(ulong id, CacheMode mode) | |||||
| => Task.FromResult<IGuildUser>(GetUser(id)); | |||||
| //IChannel | //IChannel | ||||
| IReadOnlyCollection<IUser> IChannel.CachedUsers | |||||
| => ImmutableArray.Create<IUser>(); | |||||
| IUser IChannel.GetCachedUser(ulong id) | |||||
| => null; | |||||
| IAsyncEnumerable<IReadOnlyCollection<IUser>> IChannel.GetUsersAsync() | |||||
| => ImmutableArray.Create<IReadOnlyCollection<IUser>>().ToAsyncEnumerable(); //Overriden in Text/Voice //TODO: Does this actually override? | |||||
| Task<IUser> IChannel.GetUserAsync(ulong id) | |||||
| => Task.FromResult<IUser>(null); //Overriden in Text/Voice //TODO: Does this actually override? | |||||
| IAsyncEnumerable<IReadOnlyCollection<IUser>> IChannel.GetUsersAsync(CacheMode mode) | |||||
| => ImmutableArray.Create<IReadOnlyCollection<IUser>>(Users).ToAsyncEnumerable(); //Overriden in Text/Voice //TODO: Does this actually override? | |||||
| Task<IUser> IChannel.GetUserAsync(ulong id, CacheMode mode) | |||||
| => Task.FromResult<IUser>(GetUser(id)); //Overriden in Text/Voice //TODO: Does this actually override? | |||||
| } | } | ||||
| } | } | ||||
| @@ -13,29 +13,34 @@ using Model = Discord.API.Channel; | |||||
| namespace Discord.WebSocket | namespace Discord.WebSocket | ||||
| { | { | ||||
| [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | ||||
| public class SocketTextChannel : SocketGuildChannel, ITextChannel | |||||
| public class SocketTextChannel : SocketGuildChannel, ITextChannel, ISocketMessageChannel | |||||
| { | { | ||||
| private readonly MessageCache _messages; | private readonly MessageCache _messages; | ||||
| public string Topic { get; private set; } | public string Topic { get; private set; } | ||||
| public string Mention => MentionUtils.MentionChannel(Id); | public string Mention => MentionUtils.MentionChannel(Id); | ||||
| internal SocketTextChannel(DiscordSocketClient discord, ulong id, ulong guildId) | |||||
| : base(discord, id, guildId) | |||||
| public IReadOnlyCollection<SocketMessage> CachedMessages => _messages?.Messages ?? ImmutableArray.Create<SocketMessage>(); | |||||
| public override IReadOnlyCollection<SocketGuildUser> Users | |||||
| => Guild.Users.Where(x => Permissions.GetValue( | |||||
| Permissions.ResolveChannel(Guild, x, this, Permissions.ResolveGuild(Guild, x)), | |||||
| ChannelPermission.ReadMessages)).ToImmutableArray(); | |||||
| internal SocketTextChannel(DiscordSocketClient discord, ulong id, SocketGuild guild) | |||||
| : base(discord, id, guild) | |||||
| { | { | ||||
| if (Discord.MessageCacheSize > 0) | if (Discord.MessageCacheSize > 0) | ||||
| _messages = new MessageCache(Discord, this); | _messages = new MessageCache(Discord, this); | ||||
| } | } | ||||
| internal new static SocketTextChannel Create(DiscordSocketClient discord, Model model) | |||||
| internal new static SocketTextChannel Create(SocketGuild guild, ClientState state, Model model) | |||||
| { | { | ||||
| var entity = new SocketTextChannel(discord, model.Id, model.GuildId.Value); | |||||
| entity.Update(model); | |||||
| var entity = new SocketTextChannel(guild.Discord, model.Id, guild); | |||||
| entity.Update(state, model); | |||||
| return entity; | return entity; | ||||
| } | } | ||||
| internal override void Update(Model model) | |||||
| internal override void Update(ClientState state, Model model) | |||||
| { | { | ||||
| base.Update(model); | |||||
| base.Update(state, model); | |||||
| Topic = model.Topic.Value; | Topic = model.Topic.Value; | ||||
| } | } | ||||
| @@ -43,19 +48,22 @@ namespace Discord.WebSocket | |||||
| public Task ModifyAsync(Action<ModifyTextChannelParams> func) | public Task ModifyAsync(Action<ModifyTextChannelParams> func) | ||||
| => ChannelHelper.ModifyAsync(this, Discord, func); | => ChannelHelper.ModifyAsync(this, Discord, func); | ||||
| public Task<RestGuildUser> GetUserAsync(ulong id) | |||||
| => ChannelHelper.GetUserAsync(this, Discord, id); | |||||
| public IAsyncEnumerable<IReadOnlyCollection<RestGuildUser>> GetUsersAsync() | |||||
| => ChannelHelper.GetUsersAsync(this, Discord); | |||||
| public Task<RestMessage> GetMessageAsync(ulong id) | |||||
| => ChannelHelper.GetMessageAsync(this, Discord, id); | |||||
| public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch) | |||||
| => ChannelHelper.GetMessagesAsync(this, Discord, limit: limit); | |||||
| public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) | |||||
| => ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit); | |||||
| public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) | |||||
| => ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit); | |||||
| //Messages | |||||
| public SocketMessage GetCachedMessage(ulong id) | |||||
| => _messages?.Get(id); | |||||
| public async Task<IMessage> GetMessageAsync(ulong id) | |||||
| { | |||||
| IMessage msg = _messages?.Get(id); | |||||
| if (msg == null) | |||||
| msg = await ChannelHelper.GetMessageAsync(this, Discord, id); | |||||
| return msg; | |||||
| } | |||||
| public Task<IReadOnlyCollection<IMessage>> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch) | |||||
| => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit); | |||||
| public Task<IReadOnlyCollection<IMessage>> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) | |||||
| => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit); | |||||
| public Task<IReadOnlyCollection<IMessage>> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) | |||||
| => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit); | |||||
| public Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync() | public Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync() | ||||
| => ChannelHelper.GetPinnedMessagesAsync(this, Discord); | => ChannelHelper.GetPinnedMessagesAsync(this, Discord); | ||||
| @@ -72,36 +80,56 @@ namespace Discord.WebSocket | |||||
| public IDisposable EnterTypingState() | public IDisposable EnterTypingState() | ||||
| => ChannelHelper.EnterTypingState(this, Discord); | => ChannelHelper.EnterTypingState(this, Discord); | ||||
| internal SocketMessage AddMessage(SocketUser author, MessageModel model) | |||||
| { | |||||
| var msg = SocketMessage.Create(Discord, author, model); | |||||
| _messages.Add(msg); | |||||
| return msg; | |||||
| } | |||||
| internal void AddMessage(SocketMessage msg) | |||||
| => _messages.Add(msg); | |||||
| internal SocketMessage RemoveMessage(ulong id) | internal SocketMessage RemoveMessage(ulong id) | ||||
| => _messages.Remove(id); | |||||
| //Users | |||||
| public override SocketGuildUser GetUser(ulong id) | |||||
| { | { | ||||
| return _messages.Remove(id); | |||||
| var user = Guild.GetUser(id); | |||||
| if (user != null) | |||||
| { | |||||
| var guildPerms = Permissions.ResolveGuild(Guild, user); | |||||
| var channelPerms = Permissions.ResolveChannel(Guild, user, this, guildPerms); | |||||
| if (Permissions.GetValue(channelPerms, ChannelPermission.ReadMessages)) | |||||
| return user; | |||||
| } | |||||
| return null; | |||||
| } | } | ||||
| public override string ToString() => Name; | |||||
| private string DebuggerDisplay => $"@{Name} ({Id}, Text)"; | |||||
| private string DebuggerDisplay => $"{Name} ({Id}, Text)"; | |||||
| internal new SocketTextChannel Clone() => MemberwiseClone() as SocketTextChannel; | |||||
| //IGuildChannel | //IGuildChannel | ||||
| async Task<IGuildUser> IGuildChannel.GetUserAsync(ulong id) | |||||
| => await GetUserAsync(id); | |||||
| IAsyncEnumerable<IReadOnlyCollection<IGuildUser>> IGuildChannel.GetUsersAsync() | |||||
| => GetUsersAsync(); | |||||
| Task<IGuildUser> IGuildChannel.GetUserAsync(ulong id, CacheMode mode) | |||||
| => Task.FromResult<IGuildUser>(GetUser(id)); | |||||
| IAsyncEnumerable<IReadOnlyCollection<IGuildUser>> IGuildChannel.GetUsersAsync(CacheMode mode) | |||||
| => ImmutableArray.Create<IReadOnlyCollection<IGuildUser>>(Users).ToAsyncEnumerable(); | |||||
| //IMessageChannel | //IMessageChannel | ||||
| IReadOnlyCollection<IMessage> IMessageChannel.CachedMessages => ImmutableArray.Create<IMessage>(); | |||||
| IMessage IMessageChannel.GetCachedMessage(ulong id) => null; | |||||
| async Task<IMessage> IMessageChannel.GetMessageAsync(ulong id) | |||||
| => await GetMessageAsync(id); | |||||
| IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(int limit) | |||||
| => GetMessagesAsync(limit); | |||||
| IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit) | |||||
| => GetMessagesAsync(fromMessageId, dir, limit); | |||||
| async Task<IMessage> IMessageChannel.GetMessageAsync(ulong id, CacheMode mode) | |||||
| { | |||||
| if (mode == CacheMode.AllowDownload) | |||||
| return await GetMessageAsync(id); | |||||
| else | |||||
| throw new NotImplementedException(); | |||||
| } | |||||
| IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode) | |||||
| { | |||||
| if (mode == CacheMode.AllowDownload) | |||||
| return SocketChannelHelper.PagedGetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit); | |||||
| else | |||||
| return ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit); | |||||
| } | |||||
| IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit, CacheMode mode) | |||||
| { | |||||
| if (mode == CacheMode.AllowDownload) | |||||
| return SocketChannelHelper.PagedGetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit); | |||||
| else | |||||
| return ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit); | |||||
| } | |||||
| async Task<IReadOnlyCollection<IMessage>> IMessageChannel.GetPinnedMessagesAsync() | async Task<IReadOnlyCollection<IMessage>> IMessageChannel.GetPinnedMessagesAsync() | ||||
| => await GetPinnedMessagesAsync().ConfigureAwait(false); | => await GetPinnedMessagesAsync().ConfigureAwait(false); | ||||
| async Task<IUserMessage> IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS) | async Task<IUserMessage> IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS) | ||||
| @@ -12,24 +12,27 @@ using Model = Discord.API.Channel; | |||||
| namespace Discord.WebSocket | namespace Discord.WebSocket | ||||
| { | { | ||||
| [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | ||||
| public class SocketVoiceChannel : SocketGuildChannel, IVoiceChannel | |||||
| public class SocketVoiceChannel : SocketGuildChannel, IVoiceChannel, ISocketAudioChannel | |||||
| { | { | ||||
| public int Bitrate { get; private set; } | public int Bitrate { get; private set; } | ||||
| public int UserLimit { get; private set; } | public int UserLimit { get; private set; } | ||||
| internal SocketVoiceChannel(DiscordSocketClient discord, ulong id, ulong guildId) | |||||
| : base(discord, id, guildId) | |||||
| public override IReadOnlyCollection<SocketGuildUser> Users | |||||
| => Guild.Users.Where(x => x.VoiceChannel?.Id == Id).ToImmutableArray(); | |||||
| internal SocketVoiceChannel(DiscordSocketClient discord, ulong id, SocketGuild guild) | |||||
| : base(discord, id, guild) | |||||
| { | { | ||||
| } | } | ||||
| internal new static SocketVoiceChannel Create(DiscordSocketClient discord, Model model) | |||||
| internal new static SocketVoiceChannel Create(SocketGuild guild, ClientState state, Model model) | |||||
| { | { | ||||
| var entity = new SocketVoiceChannel(discord, model.Id, model.GuildId.Value); | |||||
| entity.Update(model); | |||||
| var entity = new SocketVoiceChannel(guild.Discord, model.Id, guild); | |||||
| entity.Update(state, model); | |||||
| return entity; | return entity; | ||||
| } | } | ||||
| internal override void Update(Model model) | |||||
| internal override void Update(ClientState state, Model model) | |||||
| { | { | ||||
| base.Update(model); | |||||
| base.Update(state, model); | |||||
| Bitrate = model.Bitrate.Value; | Bitrate = model.Bitrate.Value; | ||||
| UserLimit = model.UserLimit.Value; | UserLimit = model.UserLimit.Value; | ||||
| @@ -38,13 +41,24 @@ namespace Discord.WebSocket | |||||
| public Task ModifyAsync(Action<ModifyVoiceChannelParams> func) | public Task ModifyAsync(Action<ModifyVoiceChannelParams> func) | ||||
| => ChannelHelper.ModifyAsync(this, Discord, func); | => ChannelHelper.ModifyAsync(this, Discord, func); | ||||
| public override SocketGuildUser GetUser(ulong id) | |||||
| { | |||||
| var user = Guild.GetUser(id); | |||||
| if (user?.VoiceChannel?.Id == Id) | |||||
| return user; | |||||
| return null; | |||||
| } | |||||
| private string DebuggerDisplay => $"{Name} ({Id}, Voice)"; | |||||
| internal new SocketVoiceChannel Clone() => MemberwiseClone() as SocketVoiceChannel; | |||||
| //IVoiceChannel | //IVoiceChannel | ||||
| Task<IAudioClient> IVoiceChannel.ConnectAsync() { throw new NotSupportedException(); } | Task<IAudioClient> IVoiceChannel.ConnectAsync() { throw new NotSupportedException(); } | ||||
| //IGuildChannel | //IGuildChannel | ||||
| Task<IGuildUser> IGuildChannel.GetUserAsync(ulong id) | |||||
| => Task.FromResult<IGuildUser>(null); | |||||
| IAsyncEnumerable<IReadOnlyCollection<IGuildUser>> IGuildChannel.GetUsersAsync() | |||||
| => ImmutableArray.Create<IReadOnlyCollection<IGuildUser>>().ToAsyncEnumerable(); | |||||
| Task<IGuildUser> IGuildChannel.GetUserAsync(ulong id, CacheMode mode) | |||||
| => Task.FromResult<IGuildUser>(GetUser(id)); | |||||
| IAsyncEnumerable<IReadOnlyCollection<IGuildUser>> IGuildChannel.GetUsersAsync(CacheMode mode) | |||||
| => ImmutableArray.Create<IReadOnlyCollection<IGuildUser>>(Users).ToAsyncEnumerable(); | |||||
| } | } | ||||
| } | } | ||||
| @@ -22,7 +22,14 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| public class SocketGuild : SocketEntity<ulong>, IGuild | public class SocketGuild : SocketEntity<ulong>, IGuild | ||||
| { | { | ||||
| private ImmutableDictionary<ulong, RestRole> _roles; | |||||
| private readonly SemaphoreSlim _audioLock; | |||||
| private TaskCompletionSource<bool> _syncPromise, _downloaderPromise; | |||||
| private TaskCompletionSource<AudioClient> _audioConnectPromise; | |||||
| private ConcurrentHashSet<ulong> _channels; | |||||
| private ConcurrentDictionary<ulong, SocketGuildUser> _members; | |||||
| private ConcurrentDictionary<ulong, SocketRole> _roles; | |||||
| private ConcurrentDictionary<ulong, SocketVoiceState> _voiceStates; | |||||
| private ConcurrentDictionary<ulong, PresenceModel> _cachedPresences; | |||||
| private ImmutableArray<Emoji> _emojis; | private ImmutableArray<Emoji> _emojis; | ||||
| private ImmutableArray<string> _features; | private ImmutableArray<string> _features; | ||||
| internal bool _available; | internal bool _available; | ||||
| @@ -33,6 +40,9 @@ namespace Discord.WebSocket | |||||
| public VerificationLevel VerificationLevel { get; private set; } | public VerificationLevel VerificationLevel { get; private set; } | ||||
| public MfaLevel MfaLevel { get; private set; } | public MfaLevel MfaLevel { get; private set; } | ||||
| public DefaultMessageNotifications DefaultMessageNotifications { get; private set; } | public DefaultMessageNotifications DefaultMessageNotifications { get; private set; } | ||||
| public int MemberCount { get; set; } | |||||
| public int DownloadedMemberCount { get; private set; } | |||||
| public AudioClient AudioClient { get; private set; } | |||||
| public ulong? AFKChannelId { get; private set; } | public ulong? AFKChannelId { get; private set; } | ||||
| public ulong? EmbedChannelId { get; private set; } | public ulong? EmbedChannelId { get; private set; } | ||||
| @@ -44,24 +54,117 @@ namespace Discord.WebSocket | |||||
| public ulong DefaultChannelId => Id; | public ulong DefaultChannelId => Id; | ||||
| public string IconUrl => API.CDN.GetGuildIconUrl(Id, IconId); | public string IconUrl => API.CDN.GetGuildIconUrl(Id, IconId); | ||||
| public string SplashUrl => API.CDN.GetGuildSplashUrl(Id, SplashId); | public string SplashUrl => API.CDN.GetGuildSplashUrl(Id, SplashId); | ||||
| public bool IsSynced => false; | |||||
| public bool HasAllMembers => _downloaderPromise.Task.IsCompleted; | |||||
| public bool IsSynced => _syncPromise.Task.IsCompleted; | |||||
| public Task SyncPromise => _syncPromise.Task; | |||||
| public Task DownloaderPromise => _downloaderPromise.Task; | |||||
| public RestRole EveryoneRole => GetRole(Id); | |||||
| public IReadOnlyCollection<RestRole> Roles => _roles.ToReadOnlyCollection(); | |||||
| public SocketRole EveryoneRole => GetRole(Id); | |||||
| public IReadOnlyCollection<SocketGuildChannel> Channels | |||||
| { | |||||
| get | |||||
| { | |||||
| var channels = _channels; | |||||
| var state = Discord.State; | |||||
| return channels.Select(x => state.GetChannel(x) as SocketGuildChannel).Where(x => x != null).ToReadOnlyCollection(channels); | |||||
| } | |||||
| } | |||||
| public IReadOnlyCollection<Emoji> Emojis => _emojis; | public IReadOnlyCollection<Emoji> Emojis => _emojis; | ||||
| public IReadOnlyCollection<string> Features => _features; | public IReadOnlyCollection<string> Features => _features; | ||||
| public IReadOnlyCollection<SocketGuildUser> Users => _members.ToReadOnlyCollection(); | |||||
| public IReadOnlyCollection<SocketRole> Roles => _roles.ToReadOnlyCollection(); | |||||
| public IReadOnlyCollection<SocketVoiceState> VoiceStates => _voiceStates.ToReadOnlyCollection(); | |||||
| internal SocketGuild(DiscordSocketClient client, ulong id) | internal SocketGuild(DiscordSocketClient client, ulong id) | ||||
| : base(client, id) | : base(client, id) | ||||
| { | { | ||||
| _emojis = ImmutableArray.Create<Emoji>(); | |||||
| _features = ImmutableArray.Create<string>(); | |||||
| } | } | ||||
| internal static SocketGuild Create(DiscordSocketClient discord, Model model) | |||||
| internal static SocketGuild Create(DiscordSocketClient discord, ClientState state, ExtendedModel model) | |||||
| { | { | ||||
| var entity = new SocketGuild(discord, model.Id); | var entity = new SocketGuild(discord, model.Id); | ||||
| entity.Update(model); | |||||
| entity.Update(state, model); | |||||
| return entity; | return entity; | ||||
| } | } | ||||
| internal void Update(Model model) | |||||
| internal void Update(ClientState state, ExtendedModel model) | |||||
| { | |||||
| _available = !(model.Unavailable ?? false); | |||||
| if (!_available) | |||||
| { | |||||
| if (_channels == null) | |||||
| _channels = new ConcurrentHashSet<ulong>(); | |||||
| if (_members == null) | |||||
| _members = new ConcurrentDictionary<ulong, SocketGuildUser>(); | |||||
| if (_roles == null) | |||||
| _roles = new ConcurrentDictionary<ulong, SocketRole>(); | |||||
| /*if (Emojis == null) | |||||
| _emojis = ImmutableArray.Create<Emoji>(); | |||||
| if (Features == null) | |||||
| _features = ImmutableArray.Create<string>();*/ | |||||
| return; | |||||
| } | |||||
| Update(state, model as Model); | |||||
| var channels = new ConcurrentHashSet<ulong>(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(model.Channels.Length * 1.05)); | |||||
| { | |||||
| for (int i = 0; i < model.Channels.Length; i++) | |||||
| { | |||||
| var channel = SocketGuildChannel.Create(this, state, model.Channels[i]); | |||||
| state.AddChannel(channel); | |||||
| channels.TryAdd(channel.Id); | |||||
| } | |||||
| } | |||||
| _channels = channels; | |||||
| var members = new ConcurrentDictionary<ulong, SocketGuildUser>(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(model.Members.Length * 1.05)); | |||||
| { | |||||
| for (int i = 0; i < model.Members.Length; i++) | |||||
| { | |||||
| var member = SocketGuildUser.Create(this, state, model.Members[i]); | |||||
| members.TryAdd(member.Id, member); | |||||
| } | |||||
| DownloadedMemberCount = members.Count; | |||||
| } | |||||
| var cachedPresences = new ConcurrentDictionary<ulong, PresenceModel>(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(model.Presences.Length * 1.05)); | |||||
| { | |||||
| for (int i = 0; i < model.Presences.Length; i++) | |||||
| { | |||||
| SocketGuildUser member; | |||||
| if (_members.TryGetValue(model.Presences[i].User.Id, out member)) | |||||
| member.Update(state, model.Presences[i]); | |||||
| else | |||||
| cachedPresences.TryAdd(model.Presences[i].User.Id, model.Presences[i]); | |||||
| } | |||||
| } | |||||
| _members = members; | |||||
| _cachedPresences = cachedPresences; | |||||
| MemberCount = model.MemberCount; | |||||
| var voiceStates = new ConcurrentDictionary<ulong, SocketVoiceState>(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(model.VoiceStates.Length * 1.05)); | |||||
| { | |||||
| for (int i = 0; i < model.VoiceStates.Length; i++) | |||||
| { | |||||
| SocketVoiceChannel channel = null; | |||||
| if (model.VoiceStates[i].ChannelId.HasValue) | |||||
| channel = state.GetChannel(model.VoiceStates[i].ChannelId.Value) as SocketVoiceChannel; | |||||
| var voiceState = SocketVoiceState.Create(channel, model.VoiceStates[i]); | |||||
| voiceStates.TryAdd(model.VoiceStates[i].UserId, voiceState); | |||||
| } | |||||
| } | |||||
| _voiceStates = voiceStates; | |||||
| _syncPromise = new TaskCompletionSource<bool>(); | |||||
| _downloaderPromise = new TaskCompletionSource<bool>(); | |||||
| if (Discord.ApiClient.AuthTokenType != TokenType.User) | |||||
| { | |||||
| var _ = _syncPromise.TrySetResultAsync(true); | |||||
| if (!model.Large) | |||||
| _ = _downloaderPromise.TrySetResultAsync(true); | |||||
| } | |||||
| } | |||||
| internal void Update(ClientState state, Model model) | |||||
| { | { | ||||
| AFKChannelId = model.AFKChannelId; | AFKChannelId = model.AFKChannelId; | ||||
| EmbedChannelId = model.EmbedChannelId; | EmbedChannelId = model.EmbedChannelId; | ||||
| @@ -81,7 +184,7 @@ namespace Discord.WebSocket | |||||
| var emojis = ImmutableArray.CreateBuilder<Emoji>(model.Emojis.Length); | var emojis = ImmutableArray.CreateBuilder<Emoji>(model.Emojis.Length); | ||||
| for (int i = 0; i < model.Emojis.Length; i++) | for (int i = 0; i < model.Emojis.Length; i++) | ||||
| emojis.Add(Emoji.Create(model.Emojis[i])); | emojis.Add(Emoji.Create(model.Emojis[i])); | ||||
| _emojis = emojis.ToImmutableArray(); | |||||
| _emojis = emojis.ToImmutable(); | |||||
| } | } | ||||
| else | else | ||||
| _emojis = ImmutableArray.Create<Emoji>(); | _emojis = ImmutableArray.Create<Emoji>(); | ||||
| @@ -91,17 +194,52 @@ namespace Discord.WebSocket | |||||
| else | else | ||||
| _features = ImmutableArray.Create<string>(); | _features = ImmutableArray.Create<string>(); | ||||
| var roles = ImmutableDictionary.CreateBuilder<ulong, RestRole>(); | |||||
| var roles = new ConcurrentDictionary<ulong, SocketRole>(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(model.Roles.Length * 1.05)); | |||||
| if (model.Roles != null) | if (model.Roles != null) | ||||
| { | { | ||||
| throw new NotImplementedException(); | |||||
| for (int i = 0; i < model.Roles.Length; i++) | |||||
| { | |||||
| var role = SocketRole.Create(this, state, model.Roles[i]); | |||||
| roles.TryAdd(role.Id, role); | |||||
| } | |||||
| } | } | ||||
| _roles = roles.ToImmutable(); | |||||
| _roles = roles; | |||||
| } | |||||
| internal void Update(ClientState state, GuildSyncModel model) | |||||
| { | |||||
| var members = new ConcurrentDictionary<ulong, SocketGuildUser>(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(model.Members.Length * 1.05)); | |||||
| { | |||||
| for (int i = 0; i < model.Members.Length; i++) | |||||
| { | |||||
| var member = SocketGuildUser.Create(this, state, model.Members[i]); | |||||
| members.TryAdd(member.Id, member); | |||||
| } | |||||
| DownloadedMemberCount = members.Count; | |||||
| } | |||||
| var cachedPresences = new ConcurrentDictionary<ulong, PresenceModel>(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(model.Presences.Length * 1.05)); | |||||
| { | |||||
| for (int i = 0; i < model.Presences.Length; i++) | |||||
| { | |||||
| SocketGuildUser member; | |||||
| if (_members.TryGetValue(model.Presences[i].User.Id, out member)) | |||||
| member.Update(state, model.Presences[i]); | |||||
| else | |||||
| cachedPresences.TryAdd(model.Presences[i].User.Id, model.Presences[i]); | |||||
| } | |||||
| } | |||||
| _members = members; | |||||
| _cachedPresences = cachedPresences; | |||||
| } | |||||
| internal void Update(ClientState state, EmojiUpdateModel model) | |||||
| { | |||||
| var emojis = ImmutableArray.CreateBuilder<Emoji>(model.Emojis.Length); | |||||
| for (int i = 0; i < model.Emojis.Length; i++) | |||||
| emojis.Add(Emoji.Create(model.Emojis[i])); | |||||
| _emojis = emojis.ToImmutable(); | |||||
| } | } | ||||
| //General | //General | ||||
| public async Task UpdateAsync() | |||||
| => Update(await Discord.ApiClient.GetGuildAsync(Id)); | |||||
| public Task DeleteAsync() | public Task DeleteAsync() | ||||
| => GuildHelper.DeleteAsync(this, Discord); | => GuildHelper.DeleteAsync(this, Discord); | ||||
| @@ -132,14 +270,30 @@ namespace Discord.WebSocket | |||||
| => GuildHelper.RemoveBanAsync(this, Discord, userId); | => GuildHelper.RemoveBanAsync(this, Discord, userId); | ||||
| //Channels | //Channels | ||||
| public Task<IReadOnlyCollection<RestGuildChannel>> GetChannelsAsync() | |||||
| => GuildHelper.GetChannelsAsync(this, Discord); | |||||
| public Task<RestGuildChannel> GetChannelAsync(ulong id) | |||||
| => GuildHelper.GetChannelAsync(this, Discord, id); | |||||
| public SocketGuildChannel GetChannel(ulong id) | |||||
| { | |||||
| var channel = Discord.State.GetChannel(id) as SocketGuildChannel; | |||||
| if (channel?.Guild.Id == Id) | |||||
| return channel; | |||||
| return null; | |||||
| } | |||||
| public Task<RestTextChannel> CreateTextChannelAsync(string name) | public Task<RestTextChannel> CreateTextChannelAsync(string name) | ||||
| => GuildHelper.CreateTextChannelAsync(this, Discord, name); | => GuildHelper.CreateTextChannelAsync(this, Discord, name); | ||||
| public Task<RestVoiceChannel> CreateVoiceChannelAsync(string name) | public Task<RestVoiceChannel> CreateVoiceChannelAsync(string name) | ||||
| => GuildHelper.CreateVoiceChannelAsync(this, Discord, name); | => GuildHelper.CreateVoiceChannelAsync(this, Discord, name); | ||||
| internal SocketGuildChannel AddChannel(ClientState state, ChannelModel model) | |||||
| { | |||||
| var channel = SocketGuildChannel.Create(this, state, model); | |||||
| _channels.TryAdd(model.Id); | |||||
| state.AddChannel(channel); | |||||
| return channel; | |||||
| } | |||||
| internal SocketGuildChannel RemoveChannel(ClientState state, ulong id) | |||||
| { | |||||
| if (_channels.TryRemove(id)) | |||||
| return state.RemoveChannel(id) as SocketGuildChannel; | |||||
| return null; | |||||
| } | |||||
| //Integrations | //Integrations | ||||
| public Task<IReadOnlyCollection<RestGuildIntegration>> GetIntegrationsAsync() | public Task<IReadOnlyCollection<RestGuildIntegration>> GetIntegrationsAsync() | ||||
| @@ -152,48 +306,238 @@ namespace Discord.WebSocket | |||||
| => GuildHelper.GetInvitesAsync(this, Discord); | => GuildHelper.GetInvitesAsync(this, Discord); | ||||
| //Roles | //Roles | ||||
| public RestRole GetRole(ulong id) | |||||
| public SocketRole GetRole(ulong id) | |||||
| { | { | ||||
| RestRole value; | |||||
| SocketRole value; | |||||
| if (_roles.TryGetValue(id, out value)) | if (_roles.TryGetValue(id, out value)) | ||||
| return value; | return value; | ||||
| return null; | return null; | ||||
| } | } | ||||
| public async Task<IRole> CreateRoleAsync(string name, GuildPermissions? permissions = default(GuildPermissions?), Color? color = default(Color?), bool isHoisted = false) | |||||
| public Task<RestRole> CreateRoleAsync(string name, GuildPermissions? permissions = default(GuildPermissions?), Color? color = default(Color?), bool isHoisted = false) | |||||
| => GuildHelper.CreateRoleAsync(this, Discord, name, permissions, color, isHoisted); | |||||
| internal SocketRole AddRole(RoleModel model) | |||||
| { | { | ||||
| var role = await GuildHelper.CreateRoleAsync(this, Discord, name, permissions, color, isHoisted); | |||||
| _roles = _roles.Add(role.Id, role); | |||||
| var role = SocketRole.Create(this, Discord.State, model); | |||||
| _roles[model.Id] = role; | |||||
| return role; | return role; | ||||
| } | } | ||||
| internal SocketRole RemoveRole(ulong id) | |||||
| { | |||||
| SocketRole role; | |||||
| if (_roles.TryRemove(id, out role)) | |||||
| return role; | |||||
| return null; | |||||
| } | |||||
| //Users | //Users | ||||
| public Task<IReadOnlyCollection<RestGuildUser>> GetUsersAsync() | |||||
| => GuildHelper.GetUsersAsync(this, Discord); | |||||
| public Task<RestGuildUser> GetUserAsync(ulong id) | |||||
| => GuildHelper.GetUserAsync(this, Discord, id); | |||||
| public Task<RestGuildUser> GetCurrentUserAsync() | |||||
| => GuildHelper.GetUserAsync(this, Discord, Discord.CurrentUser.Id); | |||||
| public SocketGuildUser GetUser(ulong id) | |||||
| { | |||||
| SocketGuildUser member; | |||||
| if (_members.TryGetValue(id, out member)) | |||||
| return member; | |||||
| return null; | |||||
| } | |||||
| public SocketGuildUser GetCurrentUser() | |||||
| { | |||||
| SocketGuildUser member; | |||||
| if (_members.TryGetValue(Discord.CurrentUser.Id, out member)) | |||||
| return member; | |||||
| return null; | |||||
| } | |||||
| public Task<int> PruneUsersAsync(int days = 30, bool simulate = false) | public Task<int> PruneUsersAsync(int days = 30, bool simulate = false) | ||||
| => GuildHelper.PruneUsersAsync(this, Discord, days, simulate); | => GuildHelper.PruneUsersAsync(this, Discord, days, simulate); | ||||
| internal SocketGuildUser AddOrUpdateUser(MemberModel model) | |||||
| { | |||||
| SocketGuildUser member; | |||||
| if (_members.TryGetValue(model.User.Id, out member)) | |||||
| member.Update(Discord.State, model); | |||||
| else | |||||
| { | |||||
| member = SocketGuildUser.Create(this, Discord.State, model); | |||||
| _members[member.Id] = member; | |||||
| DownloadedMemberCount++; | |||||
| } | |||||
| return member; | |||||
| } | |||||
| internal SocketGuildUser AddOrUpdateUser(PresenceModel model) | |||||
| { | |||||
| SocketGuildUser member; | |||||
| if (_members.TryGetValue(model.User.Id, out member)) | |||||
| member.Update(Discord.State, model); | |||||
| else | |||||
| { | |||||
| member = SocketGuildUser.Create(this, Discord.State, model); | |||||
| _members[member.Id] = member; | |||||
| DownloadedMemberCount++; | |||||
| } | |||||
| return member; | |||||
| } | |||||
| internal SocketGuildUser RemoveUser(ulong id) | |||||
| { | |||||
| SocketGuildUser member; | |||||
| if (_members.TryRemove(id, out member)) | |||||
| { | |||||
| DownloadedMemberCount--; | |||||
| return member; | |||||
| } | |||||
| member.GlobalUser.RemoveRef(Discord); | |||||
| return null; | |||||
| } | |||||
| public async Task DownloadUsersAsync() | |||||
| { | |||||
| await Discord.DownloadUsersAsync(new[] { this }); | |||||
| } | |||||
| internal void CompleteDownloadUsers() | |||||
| { | |||||
| _downloaderPromise.TrySetResultAsync(true); | |||||
| } | |||||
| //Voice States | |||||
| internal SocketVoiceState AddOrUpdateVoiceState(ClientState state, VoiceStateModel model) | |||||
| { | |||||
| var voiceChannel = state.GetChannel(model.ChannelId.Value) as SocketVoiceChannel; | |||||
| var voiceState = SocketVoiceState.Create(voiceChannel, model); | |||||
| _voiceStates[model.UserId] = voiceState; | |||||
| return voiceState; | |||||
| } | |||||
| internal SocketVoiceState? GetVoiceState(ulong id) | |||||
| { | |||||
| SocketVoiceState voiceState; | |||||
| if (_voiceStates.TryGetValue(id, out voiceState)) | |||||
| return voiceState; | |||||
| return null; | |||||
| } | |||||
| internal SocketVoiceState? RemoveVoiceState(ulong id) | |||||
| { | |||||
| SocketVoiceState voiceState; | |||||
| if (_voiceStates.TryRemove(id, out voiceState)) | |||||
| return voiceState; | |||||
| return null; | |||||
| } | |||||
| //Audio | |||||
| public async Task DisconnectAudioAsync(AudioClient client = null) | |||||
| { | |||||
| await _audioLock.WaitAsync().ConfigureAwait(false); | |||||
| try | |||||
| { | |||||
| await DisconnectAudioInternalAsync(client).ConfigureAwait(false); | |||||
| } | |||||
| finally | |||||
| { | |||||
| _audioLock.Release(); | |||||
| } | |||||
| } | |||||
| private async Task DisconnectAudioInternalAsync(AudioClient client = null) | |||||
| { | |||||
| var oldClient = AudioClient; | |||||
| if (oldClient != null) | |||||
| { | |||||
| if (client == null || oldClient == client) | |||||
| { | |||||
| _audioConnectPromise?.TrySetCanceledAsync(); //Cancel any previous audio connection | |||||
| _audioConnectPromise = null; | |||||
| } | |||||
| if (oldClient == client) | |||||
| { | |||||
| AudioClient = null; | |||||
| await oldClient.DisconnectAsync().ConfigureAwait(false); | |||||
| } | |||||
| } | |||||
| } | |||||
| internal async Task FinishConnectAudio(int id, string url, string token) | |||||
| { | |||||
| var voiceState = GetVoiceState(Discord.CurrentUser.Id).Value; | |||||
| await _audioLock.WaitAsync().ConfigureAwait(false); | |||||
| try | |||||
| { | |||||
| if (AudioClient == null) | |||||
| { | |||||
| var audioClient = new AudioClient(this, id); | |||||
| audioClient.Disconnected += async ex => | |||||
| { | |||||
| await _audioLock.WaitAsync().ConfigureAwait(false); | |||||
| try | |||||
| { | |||||
| if (AudioClient == audioClient) //Only reconnect if we're still assigned as this guild's audio client | |||||
| { | |||||
| if (ex != null) | |||||
| { | |||||
| //Reconnect if we still have channel info. | |||||
| //TODO: Is this threadsafe? Could channel data be deleted before we access it? | |||||
| var voiceState2 = GetVoiceState(Discord.CurrentUser.Id); | |||||
| if (voiceState2.HasValue) | |||||
| { | |||||
| var voiceChannelId = voiceState2.Value.VoiceChannel?.Id; | |||||
| if (voiceChannelId != null) | |||||
| await Discord.ApiClient.SendVoiceStateUpdateAsync(Id, voiceChannelId, voiceState2.Value.IsSelfDeafened, voiceState2.Value.IsSelfMuted); | |||||
| } | |||||
| } | |||||
| else | |||||
| { | |||||
| try { AudioClient.Dispose(); } catch { } | |||||
| AudioClient = null; | |||||
| } | |||||
| } | |||||
| } | |||||
| finally | |||||
| { | |||||
| _audioLock.Release(); | |||||
| } | |||||
| }; | |||||
| AudioClient = audioClient; | |||||
| } | |||||
| await AudioClient.ConnectAsync(url, Discord.CurrentUser.Id, voiceState.VoiceSessionId, token).ConfigureAwait(false); | |||||
| await _audioConnectPromise.TrySetResultAsync(AudioClient).ConfigureAwait(false); | |||||
| } | |||||
| catch (OperationCanceledException) | |||||
| { | |||||
| await DisconnectAudioAsync(); | |||||
| } | |||||
| catch (Exception e) | |||||
| { | |||||
| await _audioConnectPromise.SetExceptionAsync(e).ConfigureAwait(false); | |||||
| await DisconnectAudioAsync(); | |||||
| } | |||||
| finally | |||||
| { | |||||
| _audioLock.Release(); | |||||
| } | |||||
| } | |||||
| internal async Task FinishJoinAudioChannel() | |||||
| { | |||||
| await _audioLock.WaitAsync().ConfigureAwait(false); | |||||
| try | |||||
| { | |||||
| if (AudioClient != null) | |||||
| await _audioConnectPromise.TrySetResultAsync(AudioClient).ConfigureAwait(false); | |||||
| } | |||||
| finally | |||||
| { | |||||
| _audioLock.Release(); | |||||
| } | |||||
| } | |||||
| public override string ToString() => Name; | |||||
| private string DebuggerDisplay => $"{Name} ({Id})"; | |||||
| internal SocketGuild Clone() => MemberwiseClone() as SocketGuild; | |||||
| //IGuild | //IGuild | ||||
| bool IGuild.Available => true; | bool IGuild.Available => true; | ||||
| IAudioClient IGuild.AudioClient => null; | IAudioClient IGuild.AudioClient => null; | ||||
| IReadOnlyCollection<IGuildUser> IGuild.CachedUsers => ImmutableArray.Create<IGuildUser>(); | |||||
| IRole IGuild.EveryoneRole => EveryoneRole; | IRole IGuild.EveryoneRole => EveryoneRole; | ||||
| IReadOnlyCollection<IRole> IGuild.Roles => Roles; | IReadOnlyCollection<IRole> IGuild.Roles => Roles; | ||||
| async Task<IReadOnlyCollection<IBan>> IGuild.GetBansAsync() | async Task<IReadOnlyCollection<IBan>> IGuild.GetBansAsync() | ||||
| => await GetBansAsync(); | => await GetBansAsync(); | ||||
| async Task<IReadOnlyCollection<IGuildChannel>> IGuild.GetChannelsAsync() | |||||
| => await GetChannelsAsync(); | |||||
| async Task<IGuildChannel> IGuild.GetChannelAsync(ulong id) | |||||
| => await GetChannelAsync(id); | |||||
| IGuildChannel IGuild.GetCachedChannel(ulong id) | |||||
| => null; | |||||
| Task<IReadOnlyCollection<IGuildChannel>> IGuild.GetChannelsAsync(CacheMode mode) | |||||
| => Task.FromResult<IReadOnlyCollection<IGuildChannel>>(Channels); | |||||
| Task<IGuildChannel> IGuild.GetChannelAsync(ulong id, CacheMode mode) | |||||
| => Task.FromResult<IGuildChannel>(GetChannel(id)); | |||||
| async Task<ITextChannel> IGuild.CreateTextChannelAsync(string name) | async Task<ITextChannel> IGuild.CreateTextChannelAsync(string name) | ||||
| => await CreateTextChannelAsync(name); | => await CreateTextChannelAsync(name); | ||||
| async Task<IVoiceChannel> IGuild.CreateVoiceChannelAsync(string name) | async Task<IVoiceChannel> IGuild.CreateVoiceChannelAsync(string name) | ||||
| @@ -209,15 +553,15 @@ namespace Discord.WebSocket | |||||
| IRole IGuild.GetRole(ulong id) | IRole IGuild.GetRole(ulong id) | ||||
| => GetRole(id); | => GetRole(id); | ||||
| async Task<IRole> IGuild.CreateRoleAsync(string name, GuildPermissions? permissions, Color? color, bool isHoisted) | |||||
| => await CreateRoleAsync(name, permissions, color, isHoisted); | |||||
| async Task<IReadOnlyCollection<IGuildUser>> IGuild.GetUsersAsync() | |||||
| => await GetUsersAsync(); | |||||
| async Task<IGuildUser> IGuild.GetUserAsync(ulong id) | |||||
| => await GetUserAsync(id); | |||||
| IGuildUser IGuild.GetCachedUser(ulong id) | |||||
| => null; | |||||
| async Task<IGuildUser> IGuild.GetCurrentUserAsync() | |||||
| => await GetCurrentUserAsync(); | |||||
| Task<IReadOnlyCollection<IGuildUser>> IGuild.GetUsersAsync(CacheMode mode) | |||||
| => Task.FromResult<IReadOnlyCollection<IGuildUser>>(Users); | |||||
| Task<IGuildUser> IGuild.GetUserAsync(ulong id, CacheMode mode) | |||||
| => Task.FromResult<IGuildUser>(GetUser(id)); | |||||
| Task<IGuildUser> IGuild.GetCurrentUserAsync(CacheMode mode) | |||||
| => Task.FromResult<IGuildUser>(GetCurrentUser()); | |||||
| Task IGuild.DownloadUsersAsync() { throw new NotSupportedException(); } | Task IGuild.DownloadUsersAsync() { throw new NotSupportedException(); } | ||||
| } | } | ||||
| } | } | ||||
| @@ -1,11 +1,8 @@ | |||||
| using Discord.Rest; | |||||
| using Discord.WebSocket; | |||||
| using System; | |||||
| using System; | |||||
| using System.Collections.Concurrent; | using System.Collections.Concurrent; | ||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| using System.Collections.Immutable; | using System.Collections.Immutable; | ||||
| using System.Linq; | using System.Linq; | ||||
| using System.Threading.Tasks; | |||||
| namespace Discord.WebSocket | namespace Discord.WebSocket | ||||
| { | { | ||||
| @@ -51,7 +48,7 @@ namespace Discord.WebSocket | |||||
| return result; | return result; | ||||
| return null; | return null; | ||||
| } | } | ||||
| public IImmutableList<SocketMessage> GetMany(ulong? fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) | |||||
| public IReadOnlyCollection<SocketMessage> GetMany(ulong? fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) | |||||
| { | { | ||||
| if (limit < 0) throw new ArgumentOutOfRangeException(nameof(limit)); | if (limit < 0) throw new ArgumentOutOfRangeException(nameof(limit)); | ||||
| if (limit == 0) return ImmutableArray<SocketMessage>.Empty; | if (limit == 0) return ImmutableArray<SocketMessage>.Empty; | ||||
| @@ -2,13 +2,12 @@ | |||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| using System.Collections.Immutable; | using System.Collections.Immutable; | ||||
| using System.Diagnostics; | using System.Diagnostics; | ||||
| using System.Threading.Tasks; | |||||
| using Model = Discord.API.Message; | using Model = Discord.API.Message; | ||||
| namespace Discord.WebSocket | namespace Discord.WebSocket | ||||
| { | { | ||||
| [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | ||||
| public abstract class SocketMessage : SocketEntity<ulong>, IMessage, IUpdateable | |||||
| public abstract class SocketMessage : SocketEntity<ulong>, IMessage | |||||
| { | { | ||||
| private long _timestampTicks; | private long _timestampTicks; | ||||
| @@ -35,14 +34,14 @@ namespace Discord.WebSocket | |||||
| ChannelId = channelId; | ChannelId = channelId; | ||||
| Author = author; | Author = author; | ||||
| } | } | ||||
| internal static SocketMessage Create(DiscordSocketClient discord, SocketUser author, Model model) | |||||
| internal static SocketMessage Create(DiscordSocketClient discord, ClientState state, SocketUser author, Model model) | |||||
| { | { | ||||
| if (model.Type == MessageType.Default) | if (model.Type == MessageType.Default) | ||||
| return SocketUserMessage.Create(discord, author, model); | |||||
| return SocketUserMessage.Create(discord, state, author, model); | |||||
| else | else | ||||
| return SocketSystemMessage.Create(discord, author, model); | |||||
| return SocketSystemMessage.Create(discord, state, author, model); | |||||
| } | } | ||||
| internal virtual void Update(Model model) | |||||
| internal virtual void Update(ClientState state, Model model) | |||||
| { | { | ||||
| if (model.Timestamp.IsSpecified) | if (model.Timestamp.IsSpecified) | ||||
| _timestampTicks = model.Timestamp.Value.UtcTicks; | _timestampTicks = model.Timestamp.Value.UtcTicks; | ||||
| @@ -50,12 +49,8 @@ namespace Discord.WebSocket | |||||
| if (model.Content.IsSpecified) | if (model.Content.IsSpecified) | ||||
| Content = model.Content.Value; | Content = model.Content.Value; | ||||
| } | } | ||||
| public async Task UpdateAsync() | |||||
| { | |||||
| var model = await Discord.ApiClient.GetChannelMessageAsync(ChannelId, Id).ConfigureAwait(false); | |||||
| Update(model); | |||||
| } | |||||
| internal SocketMessage Clone() => MemberwiseClone() as SocketMessage; | |||||
| //IMessage | //IMessage | ||||
| IUser IMessage.Author => Author; | IUser IMessage.Author => Author; | ||||
| @@ -1,5 +1,4 @@ | |||||
| using Discord.Rest; | |||||
| using Model = Discord.API.Message; | |||||
| using Model = Discord.API.Message; | |||||
| namespace Discord.WebSocket | namespace Discord.WebSocket | ||||
| { | { | ||||
| @@ -11,17 +10,21 @@ namespace Discord.WebSocket | |||||
| : base(discord, id, channelId, author) | : base(discord, id, channelId, author) | ||||
| { | { | ||||
| } | } | ||||
| internal new static SocketSystemMessage Create(DiscordSocketClient discord, SocketUser author, Model model) | |||||
| internal new static SocketSystemMessage Create(DiscordSocketClient discord, ClientState state, SocketUser author, Model model) | |||||
| { | { | ||||
| var entity = new SocketSystemMessage(discord, model.Id, model.ChannelId, author); | var entity = new SocketSystemMessage(discord, model.Id, model.ChannelId, author); | ||||
| entity.Update(model); | |||||
| entity.Update(state, model); | |||||
| return entity; | return entity; | ||||
| } | } | ||||
| internal override void Update(Model model) | |||||
| internal override void Update(ClientState state, Model model) | |||||
| { | { | ||||
| base.Update(model); | |||||
| base.Update(state, model); | |||||
| Type = model.Type; | Type = model.Type; | ||||
| } | } | ||||
| public override string ToString() => Content; | |||||
| private string DebuggerDisplay => $"{Author}: {Content} ({Id}, {Type})"; | |||||
| internal new SocketSystemMessage Clone() => MemberwiseClone() as SocketSystemMessage; | |||||
| } | } | ||||
| } | } | ||||
| @@ -8,7 +8,7 @@ using Model = Discord.API.Message; | |||||
| namespace Discord.WebSocket | namespace Discord.WebSocket | ||||
| { | { | ||||
| internal class SocketUserMessage : SocketMessage, IUserMessage | |||||
| public class SocketUserMessage : SocketMessage, IUserMessage | |||||
| { | { | ||||
| private bool _isMentioningEveryone, _isTTS, _isPinned; | private bool _isMentioningEveryone, _isTTS, _isPinned; | ||||
| private long? _editedTimestampTicks; | private long? _editedTimestampTicks; | ||||
| @@ -32,16 +32,16 @@ namespace Discord.WebSocket | |||||
| : base(discord, id, channelId, author) | : base(discord, id, channelId, author) | ||||
| { | { | ||||
| } | } | ||||
| internal new static SocketUserMessage Create(DiscordSocketClient discord, SocketUser author, Model model) | |||||
| internal new static SocketUserMessage Create(DiscordSocketClient discord, ClientState state, SocketUser author, Model model) | |||||
| { | { | ||||
| var entity = new SocketUserMessage(discord, model.Id, model.ChannelId, author); | var entity = new SocketUserMessage(discord, model.Id, model.ChannelId, author); | ||||
| entity.Update(model); | |||||
| entity.Update(state, model); | |||||
| return entity; | return entity; | ||||
| } | } | ||||
| internal override void Update(Model model) | |||||
| internal override void Update(ClientState state, Model model) | |||||
| { | { | ||||
| base.Update(model); | |||||
| base.Update(state, model); | |||||
| if (model.IsTextToSpeech.IsSpecified) | if (model.IsTextToSpeech.IsSpecified) | ||||
| _isTTS = model.IsTextToSpeech.Value; | _isTTS = model.IsTextToSpeech.Value; | ||||
| @@ -129,5 +129,9 @@ namespace Discord.WebSocket | |||||
| text = MentionsHelper.ResolveEveryoneMentions(text, everyoneHandling); | text = MentionsHelper.ResolveEveryoneMentions(text, everyoneHandling); | ||||
| return text; | return text; | ||||
| } | } | ||||
| public override string ToString() => Content; | |||||
| private string DebuggerDisplay => $"{Author}: {Content} ({Id}{(Attachments.Count > 0 ? $", {Attachments.Count} Attachments" : "")}"; | |||||
| internal new SocketUserMessage Clone() => MemberwiseClone() as SocketUserMessage; | |||||
| } | } | ||||
| } | } | ||||
| @@ -0,0 +1,58 @@ | |||||
| using Discord.API.Rest; | |||||
| using Discord.Rest; | |||||
| using System; | |||||
| using System.Diagnostics; | |||||
| using System.Threading.Tasks; | |||||
| using Model = Discord.API.Role; | |||||
| namespace Discord.WebSocket | |||||
| { | |||||
| [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | |||||
| public class SocketRole : SocketEntity<ulong>, IRole | |||||
| { | |||||
| public SocketGuild Guild { get; } | |||||
| public Color Color { get; private set; } | |||||
| public bool IsHoisted { get; private set; } | |||||
| public bool IsManaged { get; private set; } | |||||
| public string Name { get; private set; } | |||||
| public GuildPermissions Permissions { get; private set; } | |||||
| public int Position { get; private set; } | |||||
| public bool IsEveryone => Id == Guild.Id; | |||||
| public string Mention => MentionUtils.MentionRole(Id); | |||||
| internal SocketRole(SocketGuild guild, ulong id) | |||||
| : base(guild.Discord, id) | |||||
| { | |||||
| Guild = guild; | |||||
| } | |||||
| internal static SocketRole Create(SocketGuild guild, ClientState state, Model model) | |||||
| { | |||||
| var entity = new SocketRole(guild, model.Id); | |||||
| entity.Update(state, model); | |||||
| return entity; | |||||
| } | |||||
| internal void Update(ClientState state, Model model) | |||||
| { | |||||
| Name = model.Name; | |||||
| IsHoisted = model.Hoist; | |||||
| IsManaged = model.Managed; | |||||
| Position = model.Position; | |||||
| Color = new Color(model.Color); | |||||
| Permissions = new GuildPermissions(model.Permissions); | |||||
| } | |||||
| public Task ModifyAsync(Action<ModifyGuildRoleParams> func) | |||||
| => RoleHelper.ModifyAsync(this, Discord, func); | |||||
| public Task DeleteAsync() | |||||
| => RoleHelper.DeleteAsync(this, Discord); | |||||
| public override string ToString() => Name; | |||||
| private string DebuggerDisplay => $"{Name} ({Id})"; | |||||
| internal SocketRole Clone() => MemberwiseClone() as SocketRole; | |||||
| //IRole | |||||
| IGuild IRole.Guild => Guild; | |||||
| } | |||||
| } | |||||
| @@ -1,10 +1,56 @@ | |||||
| namespace Discord.WebSocket | |||||
| using Model = Discord.API.User; | |||||
| using System.Collections.Concurrent; | |||||
| namespace Discord.WebSocket | |||||
| { | { | ||||
| internal class SocketGlobalUser : SocketUser | internal class SocketGlobalUser : SocketUser | ||||
| { | { | ||||
| internal SocketGlobalUser(DiscordSocketClient discord, ulong id) | |||||
| public override bool IsBot { get; internal set; } | |||||
| public override string Username { get; internal set; } | |||||
| public override ushort DiscriminatorValue { get; internal set; } | |||||
| public override string AvatarId { get; internal set; } | |||||
| public SocketDMChannel DMChannel { get; internal set; } | |||||
| internal override SocketGlobalUser GlobalUser => this; | |||||
| internal override SocketPresence Presence { get; set; } | |||||
| private readonly object _lockObj = new object(); | |||||
| private ushort _references; | |||||
| private SocketGlobalUser(DiscordSocketClient discord, ulong id) | |||||
| : base(discord, id) | : base(discord, id) | ||||
| { | { | ||||
| } | } | ||||
| internal static SocketGlobalUser Create(DiscordSocketClient discord, ClientState state, Model model) | |||||
| { | |||||
| var entity = new SocketGlobalUser(discord, model.Id); | |||||
| entity.Update(state, model); | |||||
| return entity; | |||||
| } | |||||
| internal void AddRef() | |||||
| { | |||||
| checked | |||||
| { | |||||
| lock (_lockObj) | |||||
| _references++; | |||||
| } | |||||
| } | |||||
| internal void RemoveRef(DiscordSocketClient discord) | |||||
| { | |||||
| lock (_lockObj) | |||||
| { | |||||
| if (--_references <= 0) | |||||
| discord.RemoveUser(Id); | |||||
| } | |||||
| } | |||||
| internal new SocketGlobalUser Clone() => MemberwiseClone() as SocketGlobalUser; | |||||
| //Updates are only ever called from the gateway thread, thus threadsafe | |||||
| internal override void Update(ClientState state, Model model) | |||||
| { | |||||
| base.Update(state, model); | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,5 +1,4 @@ | |||||
| using Discord.Rest; | |||||
| using System.Diagnostics; | |||||
| using System.Diagnostics; | |||||
| using Model = Discord.API.User; | using Model = Discord.API.User; | ||||
| namespace Discord.WebSocket | namespace Discord.WebSocket | ||||
| @@ -7,17 +6,30 @@ namespace Discord.WebSocket | |||||
| [DebuggerDisplay("{DebuggerDisplay,nq}")] | [DebuggerDisplay("{DebuggerDisplay,nq}")] | ||||
| public class SocketGroupUser : SocketUser, IGroupUser | public class SocketGroupUser : SocketUser, IGroupUser | ||||
| { | { | ||||
| internal SocketGroupUser(DiscordSocketClient discord, ulong id) | |||||
| : base(discord, id) | |||||
| public SocketGroupChannel Channel { get; } | |||||
| internal override SocketGlobalUser GlobalUser { get; } | |||||
| public override bool IsBot { get { return GlobalUser.IsBot; } internal set { GlobalUser.IsBot = value; } } | |||||
| public override string Username { get { return GlobalUser.Username; } internal set { GlobalUser.Username = value; } } | |||||
| public override ushort DiscriminatorValue { get { return GlobalUser.DiscriminatorValue; } internal set { GlobalUser.DiscriminatorValue = value; } } | |||||
| public override string AvatarId { get { return GlobalUser.AvatarId; } internal set { GlobalUser.AvatarId = value; } } | |||||
| internal override SocketPresence Presence { get { return GlobalUser.Presence; } set { GlobalUser.Presence = value; } } | |||||
| internal SocketGroupUser(SocketGroupChannel channel, SocketGlobalUser globalUser) | |||||
| : base(channel.Discord, globalUser.Id) | |||||
| { | { | ||||
| Channel = channel; | |||||
| GlobalUser = globalUser; | |||||
| } | } | ||||
| internal new static SocketGroupUser Create(DiscordSocketClient discord, Model model) | |||||
| internal static SocketGroupUser Create(SocketGroupChannel channel, ClientState state, Model model) | |||||
| { | { | ||||
| var entity = new SocketGroupUser(discord, model.Id); | |||||
| entity.Update(model); | |||||
| var entity = new SocketGroupUser(channel, channel.Discord.GetOrCreateUser(state, model)); | |||||
| entity.Update(state, model); | |||||
| return entity; | return entity; | ||||
| } | } | ||||
| internal new SocketGroupUser Clone() => MemberwiseClone() as SocketGroupUser; | |||||
| //IVoiceState | //IVoiceState | ||||
| bool IVoiceState.IsDeafened => false; | bool IVoiceState.IsDeafened => false; | ||||
| bool IVoiceState.IsMuted => false; | bool IVoiceState.IsMuted => false; | ||||
| @@ -9,46 +9,76 @@ using PresenceModel = Discord.API.Presence; | |||||
| namespace Discord.WebSocket | namespace Discord.WebSocket | ||||
| { | { | ||||
| internal class SocketGuildUser : SocketUser, IGuildUser | |||||
| public class SocketGuildUser : SocketUser, IGuildUser | |||||
| { | { | ||||
| private long? _joinedAtTicks; | private long? _joinedAtTicks; | ||||
| private ImmutableArray<ulong> _roleIds; | private ImmutableArray<ulong> _roleIds; | ||||
| internal override SocketGlobalUser GlobalUser { get; } | |||||
| public SocketGuild Guild { get; } | |||||
| public string Nickname { get; private set; } | public string Nickname { get; private set; } | ||||
| public ulong GuildId { get; private set; } | |||||
| public override bool IsBot { get { return GlobalUser.IsBot; } internal set { GlobalUser.IsBot = value; } } | |||||
| public override string Username { get { return GlobalUser.Username; } internal set { GlobalUser.Username = value; } } | |||||
| public override ushort DiscriminatorValue { get { return GlobalUser.DiscriminatorValue; } internal set { GlobalUser.DiscriminatorValue = value; } } | |||||
| public override string AvatarId { get { return GlobalUser.AvatarId; } internal set { GlobalUser.AvatarId = value; } } | |||||
| internal override SocketPresence Presence { get { return GlobalUser.Presence; } set { GlobalUser.Presence = value; } } | |||||
| public IReadOnlyCollection<ulong> RoleIds => _roleIds; | public IReadOnlyCollection<ulong> RoleIds => _roleIds; | ||||
| public SocketVoiceState? VoiceState => Guild.GetVoiceState(Id); | |||||
| public bool IsSelfDeafened => VoiceState?.IsSelfDeafened ?? false; | |||||
| public bool IsSelfMuted => VoiceState?.IsSelfMuted ?? false; | |||||
| public bool IsSuppressed => VoiceState?.IsSuppressed ?? false; | |||||
| public SocketVoiceChannel VoiceChannel => VoiceState?.VoiceChannel; | |||||
| public bool IsDeafened => VoiceState?.IsDeafened ?? false; | |||||
| public bool IsMuted => VoiceState?.IsMuted ?? false; | |||||
| public string VoiceSessionId => VoiceState?.VoiceSessionId ?? ""; | |||||
| public DateTimeOffset? JoinedAt => DateTimeUtils.FromTicks(_joinedAtTicks); | public DateTimeOffset? JoinedAt => DateTimeUtils.FromTicks(_joinedAtTicks); | ||||
| internal SocketGuildUser(DiscordSocketClient discord, ulong id) | |||||
| : base(discord, id) | |||||
| internal SocketGuildUser(SocketGuild guild, SocketGlobalUser globalUser) | |||||
| : base(guild.Discord, globalUser.Id) | |||||
| { | |||||
| Guild = guild; | |||||
| GlobalUser = globalUser; | |||||
| } | |||||
| internal static SocketGuildUser Create(SocketGuild guild, ClientState state, Model model) | |||||
| { | { | ||||
| var entity = new SocketGuildUser(guild, guild.Discord.GetOrCreateUser(state, model.User)); | |||||
| entity.Update(state, model); | |||||
| return entity; | |||||
| } | } | ||||
| internal static SocketGuildUser Create(DiscordSocketClient discord, Model model) | |||||
| internal static SocketGuildUser Create(SocketGuild guild, ClientState state, PresenceModel model) | |||||
| { | { | ||||
| var entity = new SocketGuildUser(discord, model.User.Id); | |||||
| entity.Update(model); | |||||
| var entity = new SocketGuildUser(guild, guild.Discord.GetOrCreateUser(state, model.User)); | |||||
| entity.Update(state, model); | |||||
| return entity; | return entity; | ||||
| } | } | ||||
| internal void Update(Model model) | |||||
| internal void Update(ClientState state, Model model) | |||||
| { | { | ||||
| base.Update(state, model.User); | |||||
| _joinedAtTicks = model.JoinedAt.UtcTicks; | _joinedAtTicks = model.JoinedAt.UtcTicks; | ||||
| if (model.Nick.IsSpecified) | if (model.Nick.IsSpecified) | ||||
| Nickname = model.Nick.Value; | Nickname = model.Nick.Value; | ||||
| UpdateRoles(model.Roles); | UpdateRoles(model.Roles); | ||||
| } | } | ||||
| internal override void Update(ClientState state, PresenceModel model) | |||||
| { | |||||
| base.Update(state, model); | |||||
| if (model.Roles.IsSpecified) | |||||
| UpdateRoles(model.Roles.Value); | |||||
| if (model.Nick.IsSpecified) | |||||
| Nickname = model.Nick.Value; | |||||
| } | |||||
| private void UpdateRoles(ulong[] roleIds) | private void UpdateRoles(ulong[] roleIds) | ||||
| { | { | ||||
| var roles = ImmutableArray.CreateBuilder<ulong>(roleIds.Length + 1); | var roles = ImmutableArray.CreateBuilder<ulong>(roleIds.Length + 1); | ||||
| roles.Add(GuildId); | |||||
| roles.Add(Guild.Id); | |||||
| for (int i = 0; i < roleIds.Length; i++) | for (int i = 0; i < roleIds.Length; i++) | ||||
| roles.Add(roleIds[i]); | roles.Add(roleIds[i]); | ||||
| _roleIds = roles.ToImmutable(); | _roleIds = roles.ToImmutable(); | ||||
| } | } | ||||
| public override async Task UpdateAsync() | |||||
| => Update(await UserHelper.GetAsync(this, Discord)); | |||||
| public Task ModifyAsync(Action<ModifyGuildMemberParams> func) | public Task ModifyAsync(Action<ModifyGuildMemberParams> func) | ||||
| => UserHelper.ModifyAsync(this, Discord, func); | => UserHelper.ModifyAsync(this, Discord, func); | ||||
| public Task KickAsync() | public Task KickAsync() | ||||
| @@ -59,16 +89,17 @@ namespace Discord.WebSocket | |||||
| throw new NotImplementedException(); //TODO: Impl | throw new NotImplementedException(); //TODO: Impl | ||||
| } | } | ||||
| internal new SocketGuildUser Clone() => MemberwiseClone() as SocketGuildUser; | |||||
| //IGuildUser | //IGuildUser | ||||
| ulong IGuildUser.GuildId => Guild.Id; | |||||
| IReadOnlyCollection<ulong> IGuildUser.RoleIds => RoleIds; | IReadOnlyCollection<ulong> IGuildUser.RoleIds => RoleIds; | ||||
| //IUser | |||||
| Task<IDMChannel> IUser.GetDMChannelAsync(CacheMode mode) | |||||
| => Task.FromResult<IDMChannel>(GlobalUser.DMChannel); | |||||
| //IVoiceState | //IVoiceState | ||||
| bool IVoiceState.IsDeafened => false; | |||||
| bool IVoiceState.IsMuted => false; | |||||
| bool IVoiceState.IsSelfDeafened => false; | |||||
| bool IVoiceState.IsSelfMuted => false; | |||||
| bool IVoiceState.IsSuppressed => false; | |||||
| IVoiceChannel IVoiceState.VoiceChannel => null; | |||||
| string IVoiceState.VoiceSessionId => null; | |||||
| IVoiceChannel IVoiceState.VoiceChannel => VoiceChannel; | |||||
| } | } | ||||
| } | } | ||||
| @@ -3,7 +3,7 @@ | |||||
| namespace Discord.WebSocket | namespace Discord.WebSocket | ||||
| { | { | ||||
| //TODO: C#7 Candidate for record type | //TODO: C#7 Candidate for record type | ||||
| internal struct SocketPresence : IPresence | |||||
| public struct SocketPresence : IPresence | |||||
| { | { | ||||
| public Game? Game { get; } | public Game? Game { get; } | ||||
| public UserStatus Status { get; } | public UserStatus Status { get; } | ||||
| @@ -13,11 +13,11 @@ namespace Discord.WebSocket | |||||
| Game = game; | Game = game; | ||||
| Status = status; | Status = status; | ||||
| } | } | ||||
| internal SocketPresence Create(Model model) | |||||
| internal static SocketPresence Create(Model model) | |||||
| { | { | ||||
| return new SocketPresence(model.Game != null ? Discord.Game.Create(model.Game) : (Game?)null, model.Status); | return new SocketPresence(model.Game != null ? Discord.Game.Create(model.Game) : (Game?)null, model.Status); | ||||
| } | } | ||||
| public SocketPresence Clone() => this; | |||||
| internal SocketPresence Clone() => this; | |||||
| } | } | ||||
| } | } | ||||
| @@ -11,21 +11,28 @@ namespace Discord.WebSocket | |||||
| public string Email { get; private set; } | public string Email { get; private set; } | ||||
| public bool IsVerified { get; private set; } | public bool IsVerified { get; private set; } | ||||
| public bool IsMfaEnabled { get; private set; } | public bool IsMfaEnabled { get; private set; } | ||||
| internal override SocketGlobalUser GlobalUser { get; } | |||||
| internal SocketSelfUser(DiscordSocketClient discord, ulong id) | |||||
| : base(discord, id) | |||||
| public override bool IsBot { get { return GlobalUser.IsBot; } internal set { GlobalUser.IsBot = value; } } | |||||
| public override string Username { get { return GlobalUser.Username; } internal set { GlobalUser.Username = value; } } | |||||
| public override ushort DiscriminatorValue { get { return GlobalUser.DiscriminatorValue; } internal set { GlobalUser.DiscriminatorValue = value; } } | |||||
| public override string AvatarId { get { return GlobalUser.AvatarId; } internal set { GlobalUser.AvatarId = value; } } | |||||
| internal override SocketPresence Presence { get { return GlobalUser.Presence; } set { GlobalUser.Presence = value; } } | |||||
| internal SocketSelfUser(DiscordSocketClient discord, SocketGlobalUser globalUser) | |||||
| : base(discord, globalUser.Id) | |||||
| { | { | ||||
| Status = UserStatus.Online; | |||||
| GlobalUser = globalUser; | |||||
| } | } | ||||
| internal new static SocketSelfUser Create(DiscordSocketClient discord, Model model) | |||||
| internal static SocketSelfUser Create(DiscordSocketClient discord, ClientState state, Model model) | |||||
| { | { | ||||
| var entity = new SocketSelfUser(discord, model.Id); | |||||
| entity.Update(model); | |||||
| var entity = new SocketSelfUser(discord, discord.GetOrCreateSelfUser(state, model)); | |||||
| entity.Update(state, model); | |||||
| return entity; | return entity; | ||||
| } | } | ||||
| internal override void Update(Model model) | |||||
| internal override void Update(ClientState state, Model model) | |||||
| { | { | ||||
| base.Update(model); | |||||
| base.Update(state, model); | |||||
| if (model.Email.IsSpecified) | if (model.Email.IsSpecified) | ||||
| Email = model.Email.Value; | Email = model.Email.Value; | ||||
| @@ -34,12 +41,13 @@ namespace Discord.WebSocket | |||||
| if (model.MfaEnabled.IsSpecified) | if (model.MfaEnabled.IsSpecified) | ||||
| IsMfaEnabled = model.MfaEnabled.Value; | IsMfaEnabled = model.MfaEnabled.Value; | ||||
| } | } | ||||
| public override async Task UpdateAsync() | |||||
| => Update(await UserHelper.GetAsync(this, Discord)); | |||||
| public Task ModifyAsync(Action<ModifyCurrentUserParams> func) | public Task ModifyAsync(Action<ModifyCurrentUserParams> func) | ||||
| => UserHelper.ModifyAsync(this, Discord, func); | => UserHelper.ModifyAsync(this, Discord, func); | ||||
| internal new SocketSelfUser Clone() => MemberwiseClone() as SocketSelfUser; | |||||
| //ISelfUser | |||||
| Task ISelfUser.ModifyStatusAsync(Action<ModifyPresenceParams> func) { throw new NotSupportedException(); } | Task ISelfUser.ModifyStatusAsync(Action<ModifyPresenceParams> func) { throw new NotSupportedException(); } | ||||
| } | } | ||||
| } | } | ||||
| @@ -0,0 +1,34 @@ | |||||
| using System; | |||||
| using Model = Discord.API.User; | |||||
| using PresenceModel = Discord.API.Presence; | |||||
| namespace Discord.WebSocket | |||||
| { | |||||
| public class SocketSimpleUser : SocketUser | |||||
| { | |||||
| public override bool IsBot { get; internal set; } | |||||
| public override string Username { get; internal set; } | |||||
| public override ushort DiscriminatorValue { get; internal set; } | |||||
| public override string AvatarId { get; internal set; } | |||||
| internal override SocketPresence Presence { get { return new SocketPresence(null, UserStatus.Offline); } set { } } | |||||
| internal override SocketGlobalUser GlobalUser { get { throw new NotSupportedException(); } } | |||||
| internal SocketSimpleUser(DiscordSocketClient discord, ulong id) | |||||
| : base(discord, id) | |||||
| { | |||||
| } | |||||
| internal static SocketSimpleUser Create(DiscordSocketClient discord, ClientState state, Model model) | |||||
| { | |||||
| var entity = new SocketSimpleUser(discord, model.Id); | |||||
| entity.Update(state, model); | |||||
| return entity; | |||||
| } | |||||
| internal override void Update(ClientState state, PresenceModel model) | |||||
| { | |||||
| } | |||||
| internal new SocketSimpleUser Clone() => MemberwiseClone() as SocketSimpleUser; | |||||
| } | |||||
| } | |||||
| @@ -1,33 +1,30 @@ | |||||
| using Discord.Rest; | using Discord.Rest; | ||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| using Model = Discord.API.User; | using Model = Discord.API.User; | ||||
| using PresenceModel = Discord.API.Presence; | |||||
| namespace Discord.WebSocket | namespace Discord.WebSocket | ||||
| { | { | ||||
| public class SocketUser : SocketEntity<ulong>, IUser | |||||
| public abstract class SocketUser : SocketEntity<ulong>, IUser | |||||
| { | { | ||||
| public bool IsBot { get; private set; } | |||||
| public string Username { get; private set; } | |||||
| public ushort DiscriminatorValue { get; private set; } | |||||
| public string AvatarId { get; private set; } | |||||
| public abstract bool IsBot { get; internal set; } | |||||
| public abstract string Username { get; internal set; } | |||||
| public abstract ushort DiscriminatorValue { get; internal set; } | |||||
| public abstract string AvatarId { get; internal set; } | |||||
| internal abstract SocketGlobalUser GlobalUser { get; } | |||||
| internal abstract SocketPresence Presence { get; set; } | |||||
| public string AvatarUrl => API.CDN.GetUserAvatarUrl(Id, AvatarId); | public string AvatarUrl => API.CDN.GetUserAvatarUrl(Id, AvatarId); | ||||
| public string Discriminator => DiscriminatorValue.ToString("D4"); | public string Discriminator => DiscriminatorValue.ToString("D4"); | ||||
| public string Mention => MentionUtils.MentionUser(Id); | public string Mention => MentionUtils.MentionUser(Id); | ||||
| public virtual Game? Game => null; | |||||
| public virtual UserStatus Status { get; internal set; } | |||||
| public Game? Game => Presence.Game; | |||||
| public UserStatus Status => Presence.Status; | |||||
| internal SocketUser(DiscordSocketClient discord, ulong id) | internal SocketUser(DiscordSocketClient discord, ulong id) | ||||
| : base(discord, id) | : base(discord, id) | ||||
| { | { | ||||
| } | } | ||||
| internal static SocketUser Create(DiscordSocketClient discord, Model model) | |||||
| { | |||||
| var entity = new SocketUser(discord, model.Id); | |||||
| entity.Update(model); | |||||
| return entity; | |||||
| } | |||||
| internal virtual void Update(Model model) | |||||
| internal virtual void Update(ClientState state, Model model) | |||||
| { | { | ||||
| if (model.Avatar.IsSpecified) | if (model.Avatar.IsSpecified) | ||||
| AvatarId = model.Avatar.Value; | AvatarId = model.Avatar.Value; | ||||
| @@ -38,13 +35,22 @@ namespace Discord.WebSocket | |||||
| if (model.Username.IsSpecified) | if (model.Username.IsSpecified) | ||||
| Username = model.Username.Value; | Username = model.Username.Value; | ||||
| } | } | ||||
| internal virtual void Update(ClientState state, PresenceModel model) | |||||
| { | |||||
| Presence = SocketPresence.Create(model); | |||||
| } | |||||
| public virtual async Task UpdateAsync() | |||||
| => Update(await UserHelper.GetAsync(this, Discord)); | |||||
| public Task<IDMChannel> CreateDMChannelAsync() | |||||
| public Task<RestDMChannel> CreateDMChannelAsync() | |||||
| => UserHelper.CreateDMChannelAsync(this, Discord); | => UserHelper.CreateDMChannelAsync(this, Discord); | ||||
| IDMChannel IUser.GetCachedDMChannel() => null; | |||||
| public override string ToString() => $"{Username}#{Discriminator}"; | |||||
| private string DebuggerDisplay => $"{Username}#{Discriminator} (Id{(IsBot ? ", Bot" : "")})"; | |||||
| internal SocketUser Clone() => MemberwiseClone() as SocketUser; | |||||
| //IUser | |||||
| Task<IDMChannel> IUser.GetDMChannelAsync(CacheMode mode) | |||||
| => Task.FromResult<IDMChannel>(GlobalUser.DMChannel); | |||||
| async Task<IDMChannel> IUser.CreateDMChannelAsync() | |||||
| => await CreateDMChannelAsync(); | |||||
| } | } | ||||
| } | } | ||||
| @@ -47,7 +47,7 @@ namespace Discord.WebSocket | |||||
| return new SocketVoiceState(voiceChannel, model.SessionId, model.SelfMute, model.SelfDeaf, model.Suppress); | return new SocketVoiceState(voiceChannel, model.SessionId, model.SelfMute, model.SelfDeaf, model.Suppress); | ||||
| } | } | ||||
| public SocketVoiceState Clone() => this; | |||||
| internal SocketVoiceState Clone() => this; | |||||
| IVoiceChannel IVoiceState.VoiceChannel => VoiceChannel; | IVoiceChannel IVoiceState.VoiceChannel => VoiceChannel; | ||||
| } | } | ||||