@@ -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; | ||||
} | } | ||||