diff --git a/experiment/Discord.Net.Rpc/Entities/Channels/RpcGuildChannel.cs b/experiment/Discord.Net.Rpc/Entities/Channels/RpcGuildChannel.cs index 401263555..576a0489c 100644 --- a/experiment/Discord.Net.Rpc/Entities/Channels/RpcGuildChannel.cs +++ b/experiment/Discord.Net.Rpc/Entities/Channels/RpcGuildChannel.cs @@ -10,6 +10,7 @@ namespace Discord.Rpc { public ulong GuildId { get; } public int Position { get; private set; } + public ulong? CategoryId { get; private set; } internal RpcGuildChannel(DiscordRpcClient discord, ulong id, ulong guildId) : base(discord, id) @@ -57,6 +58,12 @@ namespace Discord.Rpc public override string ToString() => Name; //IGuildChannel + public Task GetCategoryAsync() + { + //Always fails + throw new InvalidOperationException("Unable to return this entity's parent unless it was fetched through that object."); + } + IGuild IGuildChannel.Guild { get diff --git a/src/Discord.Net.Core/Entities/Channels/GuildChannelProperties.cs b/src/Discord.Net.Core/Entities/Channels/GuildChannelProperties.cs index 0ea196a4a..2ac6c8d52 100644 --- a/src/Discord.Net.Core/Entities/Channels/GuildChannelProperties.cs +++ b/src/Discord.Net.Core/Entities/Channels/GuildChannelProperties.cs @@ -26,5 +26,9 @@ /// Move the channel to the following position. This is 0-based! /// public Optional Position { get; set; } + /// + /// Sets the category for this channel + /// + public Optional CategoryId { get; set; } } } diff --git a/src/Discord.Net.Core/Entities/Channels/ICategoryChannel.cs b/src/Discord.Net.Core/Entities/Channels/ICategoryChannel.cs new file mode 100644 index 000000000..0f7f5aa62 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Channels/ICategoryChannel.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord +{ + public interface ICategoryChannel : IGuildChannel + { + } +} diff --git a/src/Discord.Net.Core/Entities/Channels/IGuildChannel.cs b/src/Discord.Net.Core/Entities/Channels/IGuildChannel.cs index c7cf0b3c2..c9841cb15 100644 --- a/src/Discord.Net.Core/Entities/Channels/IGuildChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/IGuildChannel.cs @@ -9,6 +9,10 @@ namespace Discord /// Gets the position of this channel in the guild's channel list, relative to others of the same type. int Position { get; } + /// Gets the parentid (category) of this channel in the guild's channel list. + ulong? CategoryId { get; } + /// Gets the parent channel (category) of this channel. + Task GetCategoryAsync(); /// Gets the guild this channel is a member of. IGuild Guild { get; } /// Gets the id of the guild this channel is a member of. @@ -23,7 +27,7 @@ namespace Discord Task CreateInviteAsync(int? maxAge = 86400, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null); /// Returns a collection of all invites to this channel. Task> GetInvitesAsync(RequestOptions options = null); - + /// Modifies this guild channel. Task ModifyAsync(Action func, RequestOptions options = null); diff --git a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs index 0fdd92405..2f0599d76 100644 --- a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs +++ b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs @@ -84,6 +84,7 @@ namespace Discord Task> GetTextChannelsAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); Task GetTextChannelAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); Task> GetVoiceChannelsAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); + Task> GetCategoriesAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); Task GetVoiceChannelAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); Task GetAFKChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); Task GetSystemChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); @@ -93,6 +94,8 @@ namespace Discord Task CreateTextChannelAsync(string name, RequestOptions options = null); /// Creates a new voice channel. Task CreateVoiceChannelAsync(string name, RequestOptions options = null); + /// Creates a new channel category. + Task CreateCategoryAsync(string name, RequestOptions options = null); Task> GetIntegrationsAsync(RequestOptions options = null); Task CreateIntegrationAsync(ulong id, string type, RequestOptions options = null); diff --git a/src/Discord.Net.Rest/API/Common/Channel.cs b/src/Discord.Net.Rest/API/Common/Channel.cs index 608ddcf66..97c35a57b 100644 --- a/src/Discord.Net.Rest/API/Common/Channel.cs +++ b/src/Discord.Net.Rest/API/Common/Channel.cs @@ -23,6 +23,8 @@ namespace Discord.API public Optional Position { get; set; } [JsonProperty("permission_overwrites")] public Optional PermissionOverwrites { get; set; } + [JsonProperty("parent_id")] + public ulong? CategoryId { get; set; } //TextChannel [JsonProperty("topic")] diff --git a/src/Discord.Net.Rest/API/Rest/ModifyGuildChannelParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyGuildChannelParams.cs index b4add2ac9..120eeb3a8 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyGuildChannelParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyGuildChannelParams.cs @@ -10,5 +10,7 @@ namespace Discord.API.Rest public Optional Name { get; set; } [JsonProperty("position")] public Optional Position { get; set; } + [JsonProperty("parent_id")] + public Optional CategoryId { get; set; } } } diff --git a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs index fa870be17..ad5029785 100644 --- a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs +++ b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs @@ -14,13 +14,13 @@ namespace Discord.Rest internal static class ChannelHelper { //General - public static async Task DeleteAsync(IChannel channel, BaseDiscordClient client, + public static async Task DeleteAsync(IChannel channel, BaseDiscordClient client, RequestOptions options) - { + { await client.ApiClient.DeleteChannelAsync(channel.Id, options).ConfigureAwait(false); } - public static async Task ModifyAsync(IGuildChannel channel, BaseDiscordClient client, - Action func, + public static async Task ModifyAsync(IGuildChannel channel, BaseDiscordClient client, + Action func, RequestOptions options) { var args = new GuildChannelProperties(); @@ -28,12 +28,13 @@ namespace Discord.Rest var apiArgs = new API.Rest.ModifyGuildChannelParams { Name = args.Name, - Position = args.Position + Position = args.Position, + CategoryId = args.CategoryId }; return await client.ApiClient.ModifyGuildChannelAsync(channel.Id, apiArgs, options).ConfigureAwait(false); } - public static async Task ModifyAsync(ITextChannel channel, BaseDiscordClient client, - Action func, + public static async Task ModifyAsync(ITextChannel channel, BaseDiscordClient client, + Action func, RequestOptions options) { var args = new TextChannelProperties(); @@ -42,13 +43,14 @@ namespace Discord.Rest { Name = args.Name, Position = args.Position, + CategoryId = args.CategoryId, Topic = args.Topic, IsNsfw = args.IsNsfw }; return await client.ApiClient.ModifyGuildChannelAsync(channel.Id, apiArgs, options).ConfigureAwait(false); } - public static async Task ModifyAsync(IVoiceChannel channel, BaseDiscordClient client, - Action func, + public static async Task ModifyAsync(IVoiceChannel channel, BaseDiscordClient client, + Action func, RequestOptions options) { var args = new VoiceChannelProperties(); @@ -58,6 +60,7 @@ namespace Discord.Rest Bitrate = args.Bitrate, Name = args.Name, Position = args.Position, + CategoryId = args.CategoryId, UserLimit = args.UserLimit.IsSpecified ? (args.UserLimit.Value ?? 0) : Optional.Create() }; return await client.ApiClient.ModifyGuildChannelAsync(channel.Id, apiArgs, options).ConfigureAwait(false); @@ -87,7 +90,7 @@ namespace Discord.Rest } //Messages - public static async Task GetMessageAsync(IMessageChannel channel, BaseDiscordClient client, + public static async Task GetMessageAsync(IMessageChannel channel, BaseDiscordClient client, ulong id, RequestOptions options) { var guildId = (channel as IGuildChannel)?.GuildId; @@ -98,7 +101,7 @@ namespace Discord.Rest var author = GetAuthor(client, guild, model.Author.Value, model.WebhookId.ToNullable()); return RestMessage.Create(client, channel, author, model); } - public static IAsyncEnumerable> GetMessagesAsync(IMessageChannel channel, BaseDiscordClient client, + public static IAsyncEnumerable> GetMessagesAsync(IMessageChannel channel, BaseDiscordClient client, ulong? fromMessageId, Direction dir, int limit, RequestOptions options) { if (dir == Direction.Around) @@ -124,7 +127,7 @@ namespace Discord.Rest foreach (var model in models) { var author = GetAuthor(client, guild, model.Author.Value, model.WebhookId.ToNullable()); - builder.Add(RestMessage.Create(client, channel, author, model)); + builder.Add(RestMessage.Create(client, channel, author, model)); } return builder.ToImmutable(); }, @@ -180,7 +183,7 @@ namespace Discord.Rest var args = new UploadFileParams(stream) { Filename = filename, Content = text, IsTTS = isTTS }; var model = await client.ApiClient.UploadFileAsync(channel.Id, args, options).ConfigureAwait(false); return RestUserMessage.Create(client, channel, client.CurrentUser, model); - } + } public static async Task DeleteMessagesAsync(ITextChannel channel, BaseDiscordClient client, IEnumerable messageIds, RequestOptions options) @@ -277,7 +280,7 @@ namespace Discord.Rest { await client.ApiClient.TriggerTypingIndicatorAsync(channel.Id, options).ConfigureAwait(false); } - public static IDisposable EnterTypingState(IMessageChannel channel, BaseDiscordClient client, + public static IDisposable EnterTypingState(IMessageChannel channel, BaseDiscordClient client, RequestOptions options) => new TypingNotifier(client, channel, options); diff --git a/src/Discord.Net.Rest/Entities/Channels/ChannelType.cs b/src/Discord.Net.Rest/Entities/Channels/ChannelType.cs index f05f1598e..e9f069a50 100644 --- a/src/Discord.Net.Rest/Entities/Channels/ChannelType.cs +++ b/src/Discord.Net.Rest/Entities/Channels/ChannelType.cs @@ -5,6 +5,7 @@ Text = 0, DM = 1, Voice = 2, - Group = 3 + Group = 3, + Category = 4 } } diff --git a/src/Discord.Net.Rest/Entities/Channels/RestCategoryChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestCategoryChannel.cs new file mode 100644 index 000000000..397e14e76 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Channels/RestCategoryChannel.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Model = Discord.API.Channel; + +namespace Discord.Rest +{ + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + public class RestCategoryChannel : RestGuildChannel, ICategoryChannel + { + internal RestCategoryChannel(BaseDiscordClient discord, IGuild guild, ulong id) + : base(discord, guild, id) + { + } + internal new static RestCategoryChannel Create(BaseDiscordClient discord, IGuild guild, Model model) + { + var entity = new RestCategoryChannel(discord, guild, model.Id); + entity.Update(model); + return entity; + } + + private string DebuggerDisplay => $"{Name} ({Id}, Category)"; + + // IGuildChannel + IAsyncEnumerable> IGuildChannel.GetUsersAsync(CacheMode mode, RequestOptions options) + => throw new NotSupportedException(); + Task IGuildChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) + => throw new NotSupportedException(); + Task IGuildChannel.CreateInviteAsync(int? maxAge, int? maxUses, bool isTemporary, bool isUnique, RequestOptions options) + => throw new NotSupportedException(); + Task> IGuildChannel.GetInvitesAsync(RequestOptions options) + => throw new NotSupportedException(); + + //IChannel + IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) + => throw new NotSupportedException(); + Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) + => throw new NotSupportedException(); + } +} diff --git a/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs index 1ce1c8368..026d03cc8 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs @@ -16,7 +16,7 @@ namespace Discord.Rest internal IGuild Guild { get; } public string Name { get; private set; } public int Position { get; private set; } - + public ulong? CategoryId { get; private set; } public ulong GuildId => Guild.Id; internal RestGuildChannel(BaseDiscordClient discord, IGuild guild, ulong id) @@ -32,6 +32,8 @@ namespace Discord.Rest return RestTextChannel.Create(discord, guild, model); case ChannelType.Voice: return RestVoiceChannel.Create(discord, guild, model); + case ChannelType.Category: + return RestCategoryChannel.Create(discord, guild, model); default: // TODO: Channel categories return new RestGuildChannel(discord, guild, model.Id); @@ -61,7 +63,14 @@ namespace Discord.Rest } public Task DeleteAsync(RequestOptions options = null) => ChannelHelper.DeleteAsync(this, Discord, options); - + + public async Task GetCategoryAsync() + { + if (CategoryId.HasValue) + return (await Guild.GetChannelAsync(CategoryId.Value).ConfigureAwait(false)) as ICategoryChannel; + return null; + } + public OverwritePermissions? GetPermissionOverwrite(IUser user) { for (int i = 0; i < _overwrites.Length; i++) @@ -139,20 +148,20 @@ namespace Discord.Rest => await GetInvitesAsync(options).ConfigureAwait(false); async Task IGuildChannel.CreateInviteAsync(int? maxAge, int? maxUses, bool isTemporary, bool isUnique, RequestOptions options) => await CreateInviteAsync(maxAge, maxUses, isTemporary, isUnique, options).ConfigureAwait(false); - - OverwritePermissions? IGuildChannel.GetPermissionOverwrite(IRole role) + + OverwritePermissions? IGuildChannel.GetPermissionOverwrite(IRole role) => GetPermissionOverwrite(role); OverwritePermissions? IGuildChannel.GetPermissionOverwrite(IUser user) => GetPermissionOverwrite(user); - async Task IGuildChannel.AddPermissionOverwriteAsync(IRole role, OverwritePermissions permissions, RequestOptions options) + async Task IGuildChannel.AddPermissionOverwriteAsync(IRole role, OverwritePermissions permissions, RequestOptions options) => await AddPermissionOverwriteAsync(role, permissions, options).ConfigureAwait(false); - async Task IGuildChannel.AddPermissionOverwriteAsync(IUser user, OverwritePermissions permissions, RequestOptions options) + async Task IGuildChannel.AddPermissionOverwriteAsync(IUser user, OverwritePermissions permissions, RequestOptions options) => await AddPermissionOverwriteAsync(user, permissions, options).ConfigureAwait(false); - async Task IGuildChannel.RemovePermissionOverwriteAsync(IRole role, RequestOptions options) + async Task IGuildChannel.RemovePermissionOverwriteAsync(IRole role, RequestOptions options) => await RemovePermissionOverwriteAsync(role, options).ConfigureAwait(false); - async Task IGuildChannel.RemovePermissionOverwriteAsync(IUser user, RequestOptions options) + async Task IGuildChannel.RemovePermissionOverwriteAsync(IUser user, RequestOptions options) => await RemovePermissionOverwriteAsync(user, options).ConfigureAwait(false); - + IAsyncEnumerable> IGuildChannel.GetUsersAsync(CacheMode mode, RequestOptions options) => AsyncEnumerable.Empty>(); //Overridden //Overridden in Text/Voice Task IGuildChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) diff --git a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs index 7e25b53f3..12fdb075d 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs @@ -157,6 +157,15 @@ namespace Discord.Rest var model = await client.ApiClient.CreateGuildChannelAsync(guild.Id, args, options).ConfigureAwait(false); return RestVoiceChannel.Create(client, guild, model); } + public static async Task CreateCategoryChannelAsync(IGuild guild, BaseDiscordClient client, + string name, RequestOptions options) + { + if (name == null) throw new ArgumentNullException(nameof(name)); + + var args = new CreateGuildChannelParams(name, ChannelType.Category); + var model = await client.ApiClient.CreateGuildChannelAsync(guild.Id, args, options).ConfigureAwait(false); + return RestCategoryChannel.Create(client, guild, model); + } //Integrations public static async Task> GetIntegrationsAsync(IGuild guild, BaseDiscordClient client, diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs index 3b6a2bfa8..401a121e0 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs @@ -23,7 +23,7 @@ namespace Discord.Rest public VerificationLevel VerificationLevel { get; private set; } public MfaLevel MfaLevel { get; private set; } public DefaultMessageNotifications DefaultMessageNotifications { get; private set; } - + public ulong? AFKChannelId { get; private set; } public ulong? EmbedChannelId { get; private set; } public ulong? SystemChannelId { get; private set; } @@ -114,7 +114,7 @@ namespace Discord.Rest Update(model); } public async Task ModifyEmbedAsync(Action func, RequestOptions options = null) - { + { var model = await GuildHelper.ModifyEmbedAsync(this, Discord, func, options).ConfigureAwait(false); Update(model); } @@ -155,7 +155,7 @@ namespace Discord.Rest public Task> GetChannelsAsync(RequestOptions options = null) => GuildHelper.GetChannelsAsync(this, Discord, options); public Task GetChannelAsync(ulong id, RequestOptions options = null) - => GuildHelper.GetChannelAsync(this, Discord, id, options); + => GuildHelper.GetChannelAsync(this, Discord, id, options); public async Task GetTextChannelAsync(ulong id, RequestOptions options = null) { var channel = await GuildHelper.GetChannelAsync(this, Discord, id, options).ConfigureAwait(false); @@ -176,6 +176,11 @@ namespace Discord.Rest var channels = await GuildHelper.GetChannelsAsync(this, Discord, options).ConfigureAwait(false); return channels.Select(x => x as RestVoiceChannel).Where(x => x != null).ToImmutableArray(); } + public async Task> GetCategoryChannelsAsync(RequestOptions options = null) + { + var channels = await GuildHelper.GetChannelsAsync(this, Discord, options).ConfigureAwait(false); + return channels.Select(x => x as RestCategoryChannel).Where(x => x != null).ToImmutableArray(); + } public async Task GetAFKChannelAsync(RequestOptions options = null) { @@ -199,7 +204,7 @@ namespace Discord.Rest public async Task GetEmbedChannelAsync(RequestOptions options = null) { var embedId = EmbedChannelId; - if (embedId.HasValue) + if (embedId.HasValue) return await GuildHelper.GetChannelAsync(this, Discord, embedId.Value, options).ConfigureAwait(false); return null; } @@ -217,6 +222,8 @@ namespace Discord.Rest => GuildHelper.CreateTextChannelAsync(this, Discord, name, options); public Task CreateVoiceChannelAsync(string name, RequestOptions options = null) => GuildHelper.CreateVoiceChannelAsync(this, Discord, name, options); + public Task CreateCategoryChannelAsync(string name, RequestOptions options = null) + => GuildHelper.CreateCategoryChannelAsync(this, Discord, name, options); //Integrations public Task> GetIntegrationsAsync(RequestOptions options = null) @@ -236,7 +243,7 @@ namespace Discord.Rest return null; } - public async Task CreateRoleAsync(string name, GuildPermissions? permissions = default(GuildPermissions?), Color? color = default(Color?), + public async Task CreateRoleAsync(string name, GuildPermissions? permissions = default(GuildPermissions?), Color? color = default(Color?), bool isHoisted = false, RequestOptions options = null) { var role = await GuildHelper.CreateRoleAsync(this, Discord, name, permissions, color, isHoisted, options).ConfigureAwait(false); @@ -320,6 +327,13 @@ namespace Discord.Rest else return ImmutableArray.Create(); } + async Task> IGuild.GetCategoriesAsync(CacheMode mode, RequestOptions options) + { + if (mode == CacheMode.AllowDownload) + return await GetCategoryChannelsAsync(options).ConfigureAwait(false); + else + return null; + } async Task IGuild.GetVoiceChannelAsync(ulong id, CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) @@ -359,6 +373,8 @@ namespace Discord.Rest => await CreateTextChannelAsync(name, options).ConfigureAwait(false); async Task IGuild.CreateVoiceChannelAsync(string name, RequestOptions options) => await CreateVoiceChannelAsync(name, options).ConfigureAwait(false); + async Task IGuild.CreateCategoryAsync(string name, RequestOptions options) + => await CreateCategoryChannelAsync(name, options).ConfigureAwait(false); async Task> IGuild.GetIntegrationsAsync(RequestOptions options) => await GetIntegrationsAsync(options).ConfigureAwait(false); @@ -368,7 +384,7 @@ namespace Discord.Rest async Task> IGuild.GetInvitesAsync(RequestOptions options) => await GetInvitesAsync(options).ConfigureAwait(false); - IRole IGuild.GetRole(ulong id) + IRole IGuild.GetRole(ulong id) => GetRole(id); async Task IGuild.CreateRoleAsync(string name, GuildPermissions? permissions, Color? color, bool isHoisted, RequestOptions options) => await CreateRoleAsync(name, permissions, color, isHoisted, options).ConfigureAwait(false); diff --git a/src/Discord.Net.Rpc/Entities/Channels/RpcCategoryChannel.cs b/src/Discord.Net.Rpc/Entities/Channels/RpcCategoryChannel.cs new file mode 100644 index 000000000..cac766f92 --- /dev/null +++ b/src/Discord.Net.Rpc/Entities/Channels/RpcCategoryChannel.cs @@ -0,0 +1,36 @@ +using Discord.Rest; +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Model = Discord.API.Rpc.Channel; + +namespace Discord.Rpc +{ + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + public class RpcCategoryChannel : RpcGuildChannel, ICategoryChannel + { + public IReadOnlyCollection CachedMessages { get; private set; } + + public string Mention => MentionUtils.MentionChannel(Id); + + internal RpcCategoryChannel(DiscordRpcClient discord, ulong id, ulong guildId) + : base(discord, id, guildId) + { + } + internal new static RpcCategoryChannel Create(DiscordRpcClient discord, Model model) + { + var entity = new RpcCategoryChannel(discord, model.Id, model.GuildId.Value); + entity.Update(model); + return entity; + } + internal override void Update(Model model) + { + base.Update(model); + CachedMessages = model.Messages.Select(x => RpcMessage.Create(Discord, Id, x)).ToImmutableArray(); + } + } +} diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketCategoryChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketCategoryChannel.cs new file mode 100644 index 000000000..d5a183b1e --- /dev/null +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketCategoryChannel.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Discord.Audio; +using Discord.Rest; +using Model = Discord.API.Channel; + +namespace Discord.WebSocket +{ + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + public class SocketCategoryChannel : SocketGuildChannel, ICategoryChannel + { + public override IReadOnlyCollection Users + => Guild.Users.Where(x => x.VoiceChannel?.Id == Id).ToImmutableArray(); + + public IReadOnlyCollection Channels + => Guild.Channels.Where(x => x.CategoryId == CategoryId).ToImmutableArray(); + + internal SocketCategoryChannel(DiscordSocketClient discord, ulong id, SocketGuild guild) + : base(discord, id, guild) + { + } + internal new static SocketCategoryChannel Create(SocketGuild guild, ClientState state, Model model) + { + var entity = new SocketCategoryChannel(guild.Discord, model.Id, guild); + entity.Update(state, model); + return entity; + } + + private string DebuggerDisplay => $"{Name} ({Id}, Category)"; + internal new SocketCategoryChannel Clone() => MemberwiseClone() as SocketCategoryChannel; + + // IGuildChannel + IAsyncEnumerable> IGuildChannel.GetUsersAsync(CacheMode mode, RequestOptions options) + => throw new NotSupportedException(); + Task IGuildChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) + => throw new NotSupportedException(); + Task IGuildChannel.CreateInviteAsync(int? maxAge, int? maxUses, bool isTemporary, bool isUnique, RequestOptions options) + => throw new NotSupportedException(); + Task> IGuildChannel.GetInvitesAsync(RequestOptions options) + => throw new NotSupportedException(); + + //IChannel + IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) + => throw new NotSupportedException(); + Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) + => throw new NotSupportedException(); + } +} diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs index 8e24a5196..2163daf55 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs @@ -17,6 +17,9 @@ namespace Discord.WebSocket public SocketGuild Guild { get; } public string Name { get; private set; } public int Position { get; private set; } + public ulong? CategoryId { get; private set; } + public ICategoryChannel Category + => CategoryId.HasValue ? Guild.GetChannel(CategoryId.Value) as ICategoryChannel : null; public IReadOnlyCollection PermissionOverwrites => _overwrites; public new virtual IReadOnlyCollection Users => ImmutableArray.Create(); @@ -34,6 +37,8 @@ namespace Discord.WebSocket return SocketTextChannel.Create(guild, state, model); case ChannelType.Voice: return SocketVoiceChannel.Create(guild, state, model); + case ChannelType.Category: + return SocketCategoryChannel.Create(guild, state, model); default: // TODO: Proper implementation for channel categories return new SocketGuildChannel(guild.Discord, model.Id, guild); @@ -43,6 +48,7 @@ namespace Discord.WebSocket { Name = model.Name.Value; Position = model.Position.Value; + CategoryId = model.CategoryId; var overwrites = model.PermissionOverwrites.Value; var newOverwrites = ImmutableArray.CreateBuilder(overwrites.Length); @@ -129,6 +135,9 @@ namespace Discord.WebSocket IGuild IGuildChannel.Guild => Guild; ulong IGuildChannel.GuildId => Guild.Id; + Task IGuildChannel.GetCategoryAsync() + => Task.FromResult(Category); + async Task> IGuildChannel.GetInvitesAsync(RequestOptions options) => await GetInvitesAsync(options).ConfigureAwait(false); async Task IGuildChannel.CreateInviteAsync(int? maxAge, int? maxUses, bool isTemporary, bool isUnique, RequestOptions options) diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs index c4158c136..ea68a8f54 100644 --- a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs +++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs @@ -75,7 +75,7 @@ namespace Discord.WebSocket return id.HasValue ? GetVoiceChannel(id.Value) : null; } } - public SocketGuildChannel EmbedChannel + public SocketGuildChannel EmbedChannel { get { @@ -95,6 +95,8 @@ namespace Discord.WebSocket => Channels.Select(x => x as SocketTextChannel).Where(x => x != null).ToImmutableArray(); public IReadOnlyCollection VoiceChannels => Channels.Select(x => x as SocketVoiceChannel).Where(x => x != null).ToImmutableArray(); + public IReadOnlyCollection CategoryChannels + => Channels.Select(x => x as SocketCategoryChannel).Where(x => x != null).ToImmutableArray(); public SocketGuildUser CurrentUser => _members.TryGetValue(Discord.CurrentUser.Id, out SocketGuildUser member) ? member : null; public SocketRole EveryoneRole => GetRole(Id); public IReadOnlyCollection Channels @@ -317,6 +319,9 @@ namespace Discord.WebSocket => GuildHelper.CreateTextChannelAsync(this, Discord, name, options); public Task CreateVoiceChannelAsync(string name, RequestOptions options = null) => GuildHelper.CreateVoiceChannelAsync(this, Discord, name, options); + public Task CreateCategoryChannelAsync(string name, RequestOptions options = null) + => GuildHelper.CreateCategoryChannelAsync(this, Discord, name, options); + internal SocketGuildChannel AddChannel(ClientState state, ChannelModel model) { var channel = SocketGuildChannel.Create(this, state, model); @@ -348,7 +353,7 @@ namespace Discord.WebSocket return value; return null; } - public Task CreateRoleAsync(string name, GuildPermissions? permissions = default(GuildPermissions?), Color? color = default(Color?), + public Task CreateRoleAsync(string name, GuildPermissions? permissions = default(GuildPermissions?), Color? color = default(Color?), bool isHoisted = false, RequestOptions options = null) => GuildHelper.CreateRoleAsync(this, Discord, name, permissions, color, isHoisted, options); internal SocketRole AddRole(RoleModel model) @@ -594,7 +599,7 @@ namespace Discord.WebSocket try { await RepopulateAudioStreamsAsync().ConfigureAwait(false); - await _audioClient.StartAsync(url, Discord.CurrentUser.Id, voiceState.VoiceSessionId, token).ConfigureAwait(false); + await _audioClient.StartAsync(url, Discord.CurrentUser.Id, voiceState.VoiceSessionId, token).ConfigureAwait(false); } catch (OperationCanceledException) { @@ -651,6 +656,8 @@ namespace Discord.WebSocket => Task.FromResult(GetTextChannel(id)); Task> IGuild.GetVoiceChannelsAsync(CacheMode mode, RequestOptions options) => Task.FromResult>(VoiceChannels); + Task> IGuild.GetCategoriesAsync(CacheMode mode , RequestOptions options) + => Task.FromResult>(CategoryChannels); Task IGuild.GetVoiceChannelAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(GetVoiceChannel(id)); Task IGuild.GetAFKChannelAsync(CacheMode mode, RequestOptions options) @@ -665,6 +672,8 @@ namespace Discord.WebSocket => await CreateTextChannelAsync(name, options).ConfigureAwait(false); async Task IGuild.CreateVoiceChannelAsync(string name, RequestOptions options) => await CreateVoiceChannelAsync(name, options).ConfigureAwait(false); + async Task IGuild.CreateCategoryAsync(string name, RequestOptions options) + => await CreateCategoryChannelAsync(name, options).ConfigureAwait(false); async Task> IGuild.GetIntegrationsAsync(RequestOptions options) => await GetIntegrationsAsync(options).ConfigureAwait(false);