Skip to content

Commit

Permalink
Add support for channel categories (#907)
Browse files Browse the repository at this point in the history
commit a85c5814a74e473e95fe172f0379cbc7f9f951d8
Author: Christopher F <computerizedtaco@gmail.com>
Date:   Sat Jan 6 22:25:48 2018 -0500

    Code cleanup

commit 4b243fd3dd99152b4ebc7ee01d704bd8e57eeee1
Author: Christopher F <computerizedtaco@gmail.com>
Date:   Sat Jan 6 22:08:28 2018 -0500

    Add support for channel categories (#907)

    commit 41ed910
    Author: mrspits4ever <spits.lucas@gmail.com>
    Date:   Thu Dec 14 20:02:57 2017 +0100

        removed mentioning support for RestCategoryChannel, added channels property to SocketCategoryChannel

    commit 71142c3
    Merge: 4589d73 678a723
    Author: mrspits4ever <spits.lucas@gmail.com>
    Date:   Wed Dec 13 21:17:53 2017 +0100

        Merge branch 'dev' of https://github.com/RogueException/Discord.Net into feature/channel-categories

    commit 4589d73
    Author: mrspits4ever <spits.lucas@gmail.com>
    Date:   Wed Dec 13 21:17:46 2017 +0100

        adressed requested changes

    commit d59b038
    Author: pegasy <pegasy@users.noreply.github.com>
    Date:   Mon Sep 25 18:53:23 2017 +0200

        Renamed classes / properties / methods to use CategoryChannel instead of ChannelCategory to be consistant with how text / voice channels are named.

    commit 5c4777d
    Author: pegasy <pegasy@users.noreply.github.com>
    Date:   Sun Sep 24 19:08:25 2017 +0200

        removed Guild from class name for ChannelCategory
        Renamed all properties to use Category instead of Parent
        Throw exception on GetUsers / GetInvites etc for categories

    commit e18bd8c
    Author: pegasy <pegasy@users.noreply.github.com>
    Date:   Sun Sep 24 15:49:51 2017 +0200

        Add support for channel categories (as its own channel type)
  • Loading branch information
foxbot committed Jan 7, 2018
1 parent 39bddca commit 030422f
Show file tree
Hide file tree
Showing 17 changed files with 256 additions and 34 deletions.
Expand Up @@ -10,6 +10,7 @@ public class RpcGuildChannel : RpcChannel, IGuildChannel
{
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)
Expand Down Expand Up @@ -57,6 +58,12 @@ public async Task<RestInviteMetadata> CreateInviteAsync(int? maxAge = 86400, int
public override string ToString() => Name;

//IGuildChannel
public Task<ICategoryChannel> GetCategoryAsync()
{
//Always fails
throw new InvalidOperationException("Unable to return this entity's parent unless it was fetched through that object.");
}

IGuild IGuildChannel.Guild
{
get
Expand Down
Expand Up @@ -26,5 +26,9 @@ public class GuildChannelProperties
/// Move the channel to the following position. This is 0-based!
/// </summary>
public Optional<int> Position { get; set; }
/// <summary>
/// Sets the category for this channel
/// </summary>
public Optional<ulong?> CategoryId { get; set; }
}
}
12 changes: 12 additions & 0 deletions 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
{
}
}
6 changes: 5 additions & 1 deletion src/Discord.Net.Core/Entities/Channels/IGuildChannel.cs
Expand Up @@ -9,6 +9,10 @@ public interface IGuildChannel : IChannel, IDeletable
/// <summary> Gets the position of this channel in the guild's channel list, relative to others of the same type. </summary>
int Position { get; }

/// <summary> Gets the parentid (category) of this channel in the guild's channel list. </summary>
ulong? CategoryId { get; }
/// <summary> Gets the parent channel (category) of this channel. </summary>
Task<ICategoryChannel> GetCategoryAsync();
/// <summary> Gets the guild this channel is a member of. </summary>
IGuild Guild { get; }
/// <summary> Gets the id of the guild this channel is a member of. </summary>
Expand All @@ -23,7 +27,7 @@ public interface IGuildChannel : IChannel, IDeletable
Task<IInviteMetadata> CreateInviteAsync(int? maxAge = 86400, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null);
/// <summary> Returns a collection of all invites to this channel. </summary>
Task<IReadOnlyCollection<IInviteMetadata>> GetInvitesAsync(RequestOptions options = null);

/// <summary> Modifies this guild channel. </summary>
Task ModifyAsync(Action<GuildChannelProperties> func, RequestOptions options = null);

Expand Down
3 changes: 3 additions & 0 deletions src/Discord.Net.Core/Entities/Guilds/IGuild.cs
Expand Up @@ -84,6 +84,7 @@ public interface IGuild : IDeletable, ISnowflakeEntity
Task<IReadOnlyCollection<ITextChannel>> GetTextChannelsAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null);
Task<ITextChannel> GetTextChannelAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null);
Task<IReadOnlyCollection<IVoiceChannel>> GetVoiceChannelsAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null);
Task<IReadOnlyCollection<ICategoryChannel>> GetCategoriesAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null);
Task<IVoiceChannel> GetVoiceChannelAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null);
Task<IVoiceChannel> GetAFKChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null);
Task<ITextChannel> GetSystemChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null);
Expand All @@ -93,6 +94,8 @@ public interface IGuild : IDeletable, ISnowflakeEntity
Task<ITextChannel> CreateTextChannelAsync(string name, RequestOptions options = null);
/// <summary> Creates a new voice channel. </summary>
Task<IVoiceChannel> CreateVoiceChannelAsync(string name, RequestOptions options = null);
/// <summary> Creates a new channel category. </summary>
Task<ICategoryChannel> CreateCategoryAsync(string name, RequestOptions options = null);

Task<IReadOnlyCollection<IGuildIntegration>> GetIntegrationsAsync(RequestOptions options = null);
Task<IGuildIntegration> CreateIntegrationAsync(ulong id, string type, RequestOptions options = null);
Expand Down
2 changes: 2 additions & 0 deletions src/Discord.Net.Rest/API/Common/Channel.cs
Expand Up @@ -23,6 +23,8 @@ internal class Channel
public Optional<int> Position { get; set; }
[JsonProperty("permission_overwrites")]
public Optional<Overwrite[]> PermissionOverwrites { get; set; }
[JsonProperty("parent_id")]
public ulong? CategoryId { get; set; }

//TextChannel
[JsonProperty("topic")]
Expand Down
2 changes: 2 additions & 0 deletions src/Discord.Net.Rest/API/Rest/ModifyGuildChannelParams.cs
Expand Up @@ -10,5 +10,7 @@ internal class ModifyGuildChannelParams
public Optional<string> Name { get; set; }
[JsonProperty("position")]
public Optional<int> Position { get; set; }
[JsonProperty("parent_id")]
public Optional<ulong?> CategoryId { get; set; }
}
}
31 changes: 17 additions & 14 deletions src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs
Expand Up @@ -14,26 +14,27 @@ 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<Model> ModifyAsync(IGuildChannel channel, BaseDiscordClient client,
Action<GuildChannelProperties> func,
public static async Task<Model> ModifyAsync(IGuildChannel channel, BaseDiscordClient client,
Action<GuildChannelProperties> func,
RequestOptions options)
{
var args = new GuildChannelProperties();
func(args);
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<Model> ModifyAsync(ITextChannel channel, BaseDiscordClient client,
Action<TextChannelProperties> func,
public static async Task<Model> ModifyAsync(ITextChannel channel, BaseDiscordClient client,
Action<TextChannelProperties> func,
RequestOptions options)
{
var args = new TextChannelProperties();
Expand All @@ -42,13 +43,14 @@ internal static class ChannelHelper
{
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<Model> ModifyAsync(IVoiceChannel channel, BaseDiscordClient client,
Action<VoiceChannelProperties> func,
public static async Task<Model> ModifyAsync(IVoiceChannel channel, BaseDiscordClient client,
Action<VoiceChannelProperties> func,
RequestOptions options)
{
var args = new VoiceChannelProperties();
Expand All @@ -58,6 +60,7 @@ internal static class ChannelHelper
Bitrate = args.Bitrate,
Name = args.Name,
Position = args.Position,
CategoryId = args.CategoryId,
UserLimit = args.UserLimit.IsSpecified ? (args.UserLimit.Value ?? 0) : Optional.Create<int>()
};
return await client.ApiClient.ModifyGuildChannelAsync(channel.Id, apiArgs, options).ConfigureAwait(false);
Expand Down Expand Up @@ -87,7 +90,7 @@ internal static class ChannelHelper
}

//Messages
public static async Task<RestMessage> GetMessageAsync(IMessageChannel channel, BaseDiscordClient client,
public static async Task<RestMessage> GetMessageAsync(IMessageChannel channel, BaseDiscordClient client,
ulong id, RequestOptions options)
{
var guildId = (channel as IGuildChannel)?.GuildId;
Expand All @@ -98,7 +101,7 @@ internal static class ChannelHelper
var author = GetAuthor(client, guild, model.Author.Value, model.WebhookId.ToNullable());
return RestMessage.Create(client, channel, author, model);
}
public static IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(IMessageChannel channel, BaseDiscordClient client,
public static IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(IMessageChannel channel, BaseDiscordClient client,
ulong? fromMessageId, Direction dir, int limit, RequestOptions options)
{
if (dir == Direction.Around)
Expand All @@ -124,7 +127,7 @@ internal static class ChannelHelper
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();
},
Expand Down Expand Up @@ -180,7 +183,7 @@ internal static class ChannelHelper
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<ulong> messageIds, RequestOptions options)
Expand Down Expand Up @@ -277,7 +280,7 @@ internal static class ChannelHelper
{
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);

Expand Down
3 changes: 2 additions & 1 deletion src/Discord.Net.Rest/Entities/Channels/ChannelType.cs
Expand Up @@ -5,6 +5,7 @@ public enum ChannelType
Text = 0,
DM = 1,
Voice = 2,
Group = 3
Group = 3,
Category = 4
}
}
43 changes: 43 additions & 0 deletions 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<IReadOnlyCollection<IGuildUser>> IGuildChannel.GetUsersAsync(CacheMode mode, RequestOptions options)
=> throw new NotSupportedException();
Task<IGuildUser> IGuildChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options)
=> throw new NotSupportedException();
Task<IInviteMetadata> IGuildChannel.CreateInviteAsync(int? maxAge, int? maxUses, bool isTemporary, bool isUnique, RequestOptions options)
=> throw new NotSupportedException();
Task<IReadOnlyCollection<IInviteMetadata>> IGuildChannel.GetInvitesAsync(RequestOptions options)
=> throw new NotSupportedException();

//IChannel
IAsyncEnumerable<IReadOnlyCollection<IUser>> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options)
=> throw new NotSupportedException();
Task<IUser> IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options)
=> throw new NotSupportedException();
}
}
27 changes: 18 additions & 9 deletions src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs
Expand Up @@ -16,7 +16,7 @@ public class RestGuildChannel : RestChannel, IGuildChannel, IUpdateable
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)
Expand All @@ -32,6 +32,8 @@ internal static RestGuildChannel Create(BaseDiscordClient discord, IGuild guild,
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);
Expand Down Expand Up @@ -61,7 +63,14 @@ public async Task ModifyAsync(Action<GuildChannelProperties> func, RequestOption
}
public Task DeleteAsync(RequestOptions options = null)
=> ChannelHelper.DeleteAsync(this, Discord, options);


public async Task<ICategoryChannel> 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++)
Expand Down Expand Up @@ -139,20 +148,20 @@ async Task<IReadOnlyCollection<IInviteMetadata>> IGuildChannel.GetInvitesAsync(R
=> await GetInvitesAsync(options).ConfigureAwait(false);
async Task<IInviteMetadata> 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<IReadOnlyCollection<IGuildUser>> IGuildChannel.GetUsersAsync(CacheMode mode, RequestOptions options)
=> AsyncEnumerable.Empty<IReadOnlyCollection<IGuildUser>>(); //Overridden //Overridden in Text/Voice
Task<IGuildUser> IGuildChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options)
Expand Down
9 changes: 9 additions & 0 deletions src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs
Expand Up @@ -157,6 +157,15 @@ internal static class GuildHelper
var model = await client.ApiClient.CreateGuildChannelAsync(guild.Id, args, options).ConfigureAwait(false);
return RestVoiceChannel.Create(client, guild, model);
}
public static async Task<RestCategoryChannel> 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<IReadOnlyCollection<RestGuildIntegration>> GetIntegrationsAsync(IGuild guild, BaseDiscordClient client,
Expand Down

0 comments on commit 030422f

Please sign in to comment.