[Feature] Add missing properties in forum & thread channels (#2469)

* add `AppliedTags` property

* convert collections into immutable arrays

* remove "not supported" remark

* implement `ThreadChannelProperties`

* Add `DefaultSlowModeInterval` and `DefaultSlowModeInterval` properties to forum channels

* add `Moderated` property to `ForumTag``

* `ForumTag` inherits `ISnowflakeEntity`

* Fix `DiscordRestClient.GetChannelAsync` not getting forum channel

* a lot of changes

added:
- channel flags
- `ForumTagBuilder`
- imroved channel modification

* fixed a bug in forum tag emoji parsing

* inherit forum channel from `INesteeChannel`

* implement `INestedChannel` in forum channels

* Add `Flags` property to channels

* add iteraface for forum tags & add equality operators

* Add default reaction emoji property

* add support for modifing default reaction & some renaming

* add createForumChannelAsync to guild

* *fix resharper being a d... and moving code to next line*

* add a `ForumChannels` property

* Some fixes & add support for `default_sort_order`

* fix misleading comment

* fix #2502

* support creating post with applied tags

* fix xmldoc

* set category id on model update

* add limit checks for tag count
This commit is contained in:
Misha133
2022-11-07 19:25:49 +03:00
committed by GitHub
parent 6712ef4573
commit 01ae904fe1
45 changed files with 1132 additions and 119 deletions

View File

@@ -70,8 +70,24 @@ namespace Discord.API
//ForumChannel
[JsonProperty("available_tags")]
public Optional<ForumTags[]> ForumTags { get; set; }
[JsonProperty("applied_tags")]
public Optional<ulong[]> AppliedTags { get; set; }
[JsonProperty("default_auto_archive_duration")]
public Optional<ThreadArchiveDuration> AutoArchiveDuration { get; set; }
[JsonProperty("default_thread_rate_limit_per_user")]
public Optional<int> ThreadRateLimitPerUser { get; set; }
[JsonProperty("flags")]
public Optional<ChannelFlags> Flags { get; set; }
[JsonProperty("default_sort_order")]
public Optional<ForumSortOrder?> DefaultSortOrder { get; set; }
[JsonProperty("default_reaction_emoji")]
public Optional<ForumReactionEmoji> DefaultReactionEmoji { get; set; }
}
}

View File

@@ -0,0 +1,12 @@
using Newtonsoft.Json;
namespace Discord.API;
public class ForumReactionEmoji
{
[JsonProperty("emoji_id")]
public ulong? EmojiId { get; set; }
[JsonProperty("emoji_name")]
public Optional<string> EmojiName { get; set; }
}

View File

@@ -17,5 +17,8 @@ namespace Discord.API
public Optional<ulong?> EmojiId { get; set; }
[JsonProperty("emoji_name")]
public Optional<string> EmojiName { get; set; }
[JsonProperty("moderated")]
public bool Moderated { get; set; }
}
}

View File

@@ -23,6 +23,8 @@ namespace Discord.API.Rest
public Optional<bool> IsNsfw { get; set; }
[JsonProperty("rate_limit_per_user")]
public Optional<int> SlowModeInterval { get; set; }
[JsonProperty("default_auto_archive_duration")]
public Optional<ThreadArchiveDuration> DefaultAutoArchiveDuration { get; set; }
//Voice channels
[JsonProperty("bitrate")]
@@ -30,6 +32,16 @@ namespace Discord.API.Rest
[JsonProperty("user_limit")]
public Optional<int?> UserLimit { get; set; }
//Forum channels
[JsonProperty("default_reaction_emoji")]
public Optional<ModifyForumReactionEmojiParams> DefaultReactionEmoji { get; set; }
[JsonProperty("default_thread_rate_limit_per_user")]
public Optional<int> ThreadRateLimitPerUser { get; set; }
[JsonProperty("available_tags")]
public Optional<ModifyForumTagParams[]> AvailableTags { get; set; }
[JsonProperty("default_sort_order")]
public Optional<ForumSortOrder?> DefaultSortOrder { get; set; }
public CreateGuildChannelParams(string name, ChannelType type)
{
Name = name;

View File

@@ -27,6 +27,7 @@ namespace Discord.API.Rest
public Optional<ActionRowComponent[]> MessageComponent { get; set; }
public Optional<MessageFlags?> Flags { get; set; }
public Optional<ulong[]> Stickers { get; set; }
public Optional<ulong[]> TagIds { get; set; }
public CreateMultipartPostAsync(params FileAttachment[] attachments)
{
@@ -59,6 +60,8 @@ namespace Discord.API.Rest
message["sticker_ids"] = Stickers.Value;
if (Flags.IsSpecified)
message["flags"] = Flags.Value;
if (TagIds.IsSpecified)
message["applied_tags"] = TagIds.Value;
List<object> attachments = new();

View File

@@ -21,5 +21,8 @@ namespace Discord.API.Rest
[JsonProperty("message")]
public ForumThreadMessage Message { get; set; }
[JsonProperty("applied_tags")]
public Optional<ulong[]> Tags { get; set; }
}
}

View File

@@ -0,0 +1,23 @@
using Newtonsoft.Json;
namespace Discord.API.Rest;
[JsonObject(MemberSerialization = MemberSerialization.OptIn)]
internal class ModifyForumChannelParams : ModifyTextChannelParams
{
[JsonProperty("available_tags")]
public Optional<ModifyForumTagParams[]> Tags { get; set; }
[JsonProperty("default_thread_rate_limit_per_user")]
public Optional<int> DefaultSlowModeInterval { get; set; }
[JsonProperty("rate_limit_per_user")]
public Optional<int> ThreadCreationInterval { get; set; }
[JsonProperty("default_reaction_emoji")]
public Optional<ModifyForumReactionEmojiParams> DefaultReactionEmoji { get; set; }
[JsonProperty("default_sort_order")]
public Optional<ForumSortOrder> DefaultSortOrder { get; set; }
}

View File

@@ -0,0 +1,15 @@
using Newtonsoft.Json;
namespace Discord.API;
[JsonObject(MemberSerialization = MemberSerialization.OptIn)]
public class ModifyForumReactionEmojiParams
{
[JsonProperty("emoji_id")]
public Optional<ulong?> EmojiId { get; set; }
[JsonProperty("emoji_name")]
public Optional<string> EmojiName { get; set; }
}

View File

@@ -0,0 +1,23 @@
using Newtonsoft.Json;
namespace Discord.API
{
[JsonObject(MemberSerialization = MemberSerialization.OptIn)]
internal class ModifyForumTagParams
{
[JsonProperty("id")]
public Optional<ulong> Id { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("emoji_id")]
public Optional<ulong?> EmojiId { get; set; }
[JsonProperty("emoji_name")]
public Optional<string> EmojiName { get; set; }
[JsonProperty("moderated")]
public bool Moderated { get; set; }
}
}

View File

@@ -13,5 +13,7 @@ namespace Discord.API.Rest
public Optional<ulong?> CategoryId { get; set; }
[JsonProperty("permission_overwrites")]
public Optional<Overwrite[]> Overwrites { get; set; }
[JsonProperty("flags")]
public Optional<ChannelFlags?> Flags { get; set; }
}
}

View File

@@ -1,4 +1,5 @@
using Newtonsoft.Json;
using System.Collections.Generic;
namespace Discord.API.Rest
{
@@ -18,5 +19,11 @@ namespace Discord.API.Rest
[JsonProperty("rate_limit_per_user")]
public Optional<int> Slowmode { get; set; }
[JsonProperty("applied_tags")]
public Optional<IEnumerable<ulong>> AppliedTags { get; set; }
[JsonProperty("flags")]
public Optional<ChannelFlags> Flags { get; set; }
}
}

View File

@@ -38,6 +38,7 @@ namespace Discord.Rest
Deny = overwrite.Permissions.DenyValue.ToString()
}).ToArray()
: Optional.Create<API.Overwrite[]>(),
Flags = args.Flags.GetValueOrDefault(),
};
return await client.ApiClient.ModifyGuildChannelAsync(channel.Id, apiArgs, options).ConfigureAwait(false);
}

View File

@@ -0,0 +1,63 @@
using Discord.API;
using System;
using System.Linq;
using System.Threading.Tasks;
using Model = Discord.API.Channel;
namespace Discord.Rest;
internal static class ForumHelper
{
public static async Task<Model> ModifyAsync(IForumChannel channel, BaseDiscordClient client,
Action<ForumChannelProperties> func,
RequestOptions options)
{
var args = new ForumChannelProperties();
func(args);
Preconditions.AtMost(args.Tags.IsSpecified ? args.Tags.Value.Count() : 0, 5, nameof(args.Tags), "Forum channel can have max 20 tags.");
var apiArgs = new API.Rest.ModifyForumChannelParams()
{
Name = args.Name,
Position = args.Position,
CategoryId = args.CategoryId,
Overwrites = args.PermissionOverwrites.IsSpecified
? args.PermissionOverwrites.Value.Select(overwrite => new API.Overwrite
{
TargetId = overwrite.TargetId,
TargetType = overwrite.TargetType,
Allow = overwrite.Permissions.AllowValue.ToString(),
Deny = overwrite.Permissions.DenyValue.ToString()
}).ToArray()
: Optional.Create<API.Overwrite[]>(),
DefaultSlowModeInterval = args.DefaultSlowModeInterval,
ThreadCreationInterval = args.ThreadCreationInterval,
Tags = args.Tags.IsSpecified
? args.Tags.Value.Select(tag => new API.ModifyForumTagParams
{
Name = tag.Name,
EmojiId = tag.Emoji is Emote emote
? emote.Id
: Optional<ulong?>.Unspecified,
EmojiName = tag.Emoji is Emoji emoji
? emoji.Name
: Optional<string>.Unspecified
}).ToArray()
: Optional.Create<API.ModifyForumTagParams[]>(),
Flags = args.Flags.GetValueOrDefault(),
Topic = args.Topic,
DefaultReactionEmoji = args.DefaultReactionEmoji.IsSpecified
? new API.ModifyForumReactionEmojiParams
{
EmojiId = args.DefaultReactionEmoji.Value is Emote emote ?
emote.Id : Optional<ulong?>.Unspecified,
EmojiName = args.DefaultReactionEmoji.Value is Emoji emoji ?
emoji.Name : Optional<string>.Unspecified
}
: Optional<ModifyForumReactionEmojiParams>.Unspecified,
DefaultSortOrder = args.DefaultSortOrder
};
return await client.ApiClient.ModifyGuildChannelAsync(channel.Id, apiArgs, options).ConfigureAwait(false);
}
}

View File

@@ -30,13 +30,15 @@ namespace Discord.Rest
ChannelType.Stage or
ChannelType.NewsThread or
ChannelType.PrivateThread or
ChannelType.PublicThread
ChannelType.PublicThread or
ChannelType.Forum
=> RestGuildChannel.Create(discord, new RestGuild(discord, model.GuildId.Value), model),
ChannelType.DM or ChannelType.Group => CreatePrivate(discord, model) as RestChannel,
ChannelType.Category => RestCategoryChannel.Create(discord, new RestGuild(discord, model.GuildId.Value), model),
_ => new RestChannel(discord, model.Id),
};
}
internal static RestChannel Create(BaseDiscordClient discord, Model model, IGuild guild)
{
return model.Type switch
@@ -47,7 +49,8 @@ namespace Discord.Rest
ChannelType.Stage or
ChannelType.NewsThread or
ChannelType.PrivateThread or
ChannelType.PublicThread
ChannelType.PublicThread or
ChannelType.Forum
=> RestGuildChannel.Create(discord, guild, model),
ChannelType.DM or ChannelType.Group => CreatePrivate(discord, model) as RestChannel,
ChannelType.Category => RestCategoryChannel.Create(discord, guild, model),

View File

@@ -26,6 +26,21 @@ namespace Discord.Rest
/// <inheritdoc/>
public IReadOnlyCollection<ForumTag> Tags { get; private set; }
/// <inheritdoc/>
public int ThreadCreationInterval { get; private set; }
/// <inheritdoc/>
public int DefaultSlowModeInterval { get; private set; }
/// <inheritdoc/>
public ulong? CategoryId { get; private set; }
/// <inheritdoc/>
public IEmote DefaultReactionEmoji { get; private set; }
/// <inheritdoc/>
public ForumSortOrder? DefaultSortOrder { get; private set; }
/// <inheritdoc/>
public string Mention => MentionUtils.MentionChannel(Id);
@@ -35,9 +50,9 @@ namespace Discord.Rest
}
internal new static RestStageChannel Create(BaseDiscordClient discord, IGuild guild, Model model)
internal new static RestForumChannel Create(BaseDiscordClient discord, IGuild guild, Model model)
{
var entity = new RestStageChannel(discord, guild, model.Id);
var entity = new RestForumChannel(discord, guild, model.Id);
entity.Update(model);
return entity;
}
@@ -49,46 +64,75 @@ namespace Discord.Rest
Topic = model.Topic.GetValueOrDefault();
DefaultAutoArchiveDuration = model.AutoArchiveDuration.GetValueOrDefault(ThreadArchiveDuration.OneDay);
if (model.ThreadRateLimitPerUser.IsSpecified)
DefaultSlowModeInterval = model.ThreadRateLimitPerUser.Value;
if(model.SlowMode.IsSpecified)
ThreadCreationInterval = model.SlowMode.Value;
DefaultSortOrder = model.DefaultSortOrder.GetValueOrDefault();
Tags = model.ForumTags.GetValueOrDefault(Array.Empty<API.ForumTags>()).Select(
x => new ForumTag(x.Id, x.Name, x.EmojiId.GetValueOrDefault(null), x.EmojiName.GetValueOrDefault())
x => new ForumTag(x.Id, x.Name, x.EmojiId.GetValueOrDefault(null), x.EmojiName.GetValueOrDefault(), x.Moderated)
).ToImmutableArray();
if (model.DefaultReactionEmoji.IsSpecified && model.DefaultReactionEmoji.Value is not null)
{
if (model.DefaultReactionEmoji.Value.EmojiId.HasValue && model.DefaultReactionEmoji.Value.EmojiId.Value != 0)
DefaultReactionEmoji = new Emote(model.DefaultReactionEmoji.Value.EmojiId.GetValueOrDefault(), null, false);
else if (model.DefaultReactionEmoji.Value.EmojiName.IsSpecified)
DefaultReactionEmoji = new Emoji(model.DefaultReactionEmoji.Value.EmojiName.Value);
else
DefaultReactionEmoji = null;
}
CategoryId = model.CategoryId.GetValueOrDefault();
}
/// <inheritdoc cref="IForumChannel.CreatePostAsync(string, ThreadArchiveDuration, int?, string, Embed, RequestOptions, AllowedMentions, MessageComponent, ISticker[], Embed[], MessageFlags)"/>
public Task<RestThreadChannel> CreatePostAsync(string title, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay, int? slowmode = null, string text = null, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
=> ThreadHelper.CreatePostAsync(this, Discord, title, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags);
/// <inheritdoc/>
public async Task ModifyAsync(Action<ForumChannelProperties> func, RequestOptions options = null)
{
var model = await ForumHelper.ModifyAsync(this, Discord, func, options);
Update(model);
}
/// <inheritdoc cref="IForumChannel.CreatePostWithFileAsync(string, string, ThreadArchiveDuration, int?, string, Embed, RequestOptions, bool, AllowedMentions, MessageComponent, ISticker[], Embed[], MessageFlags)"/>
/// <inheritdoc cref="IForumChannel.CreatePostAsync(string, ThreadArchiveDuration, int?, string, Embed, RequestOptions, AllowedMentions, MessageComponent, ISticker[], Embed[], MessageFlags, ForumTag[])"/>
public Task<RestThreadChannel> CreatePostAsync(string title, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay, int? slowmode = null,
string text = null, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageComponent components = null, ISticker[] stickers = null,
Embed[] embeds = null, MessageFlags flags = MessageFlags.None, ForumTag[] tags = null)
=> ThreadHelper.CreatePostAsync(this, Discord, title, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags, tags?.Select(tag => tag.Id).ToArray());
/// <inheritdoc cref="IForumChannel.CreatePostWithFileAsync(string, string, ThreadArchiveDuration, int?, string, Embed, RequestOptions, bool, AllowedMentions, MessageComponent, ISticker[], Embed[], MessageFlags, ForumTag[])"/>
public async Task<RestThreadChannel> CreatePostWithFileAsync(string title, string filePath, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay,
int? slowmode = null, string text = null, Embed embed = null, RequestOptions options = null, bool isSpoiler = false,
AllowedMentions allowedMentions = null, MessageComponent components = null,
ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None, ForumTag[] tags = null)
{
using var file = new FileAttachment(filePath, isSpoiler: isSpoiler);
return await ThreadHelper.CreatePostAsync(this, Discord, title, new FileAttachment[] { file }, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags).ConfigureAwait(false);
return await ThreadHelper.CreatePostAsync(this, Discord, title, new FileAttachment[] { file }, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags, tags?.Select(tag => tag.Id).ToArray()).ConfigureAwait(false);
}
/// <inheritdoc cref="IForumChannel.CreatePostWithFileAsync(string, Stream, string, ThreadArchiveDuration, int?, string, Embed, RequestOptions, bool, AllowedMentions, MessageComponent, ISticker[], Embed[], MessageFlags)"/>
/// <inheritdoc cref="IForumChannel.CreatePostWithFileAsync(string, Stream, string, ThreadArchiveDuration, int?, string, Embed, RequestOptions, bool, AllowedMentions, MessageComponent, ISticker[], Embed[], MessageFlags, ForumTag[])"/>
public async Task<RestThreadChannel> CreatePostWithFileAsync(string title, Stream stream, string filename, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay,
int? slowmode = null, string text = null, Embed embed = null, RequestOptions options = null, bool isSpoiler = false,
AllowedMentions allowedMentions = null, MessageComponent components = null,
ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None, ForumTag[] tags = null)
{
using var file = new FileAttachment(stream, filename, isSpoiler: isSpoiler);
return await ThreadHelper.CreatePostAsync(this, Discord, title, new FileAttachment[] { file }, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags).ConfigureAwait(false);
return await ThreadHelper.CreatePostAsync(this, Discord, title, new FileAttachment[] { file }, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags, tags?.Select(tag => tag.Id).ToArray()).ConfigureAwait(false);
}
/// <inheritdoc cref="IForumChannel.CreatePostWithFileAsync(string, FileAttachment, ThreadArchiveDuration, int?, string, Embed, RequestOptions, AllowedMentions, MessageComponent, ISticker[], Embed[], MessageFlags)"/>
/// <inheritdoc cref="IForumChannel.CreatePostWithFileAsync(string, FileAttachment, ThreadArchiveDuration, int?, string, Embed, RequestOptions, AllowedMentions, MessageComponent, ISticker[], Embed[], MessageFlags, ForumTag[])"/>
public Task<RestThreadChannel> CreatePostWithFileAsync(string title, FileAttachment attachment, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay,
int? slowmode = null, string text = null, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null,
MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
=> ThreadHelper.CreatePostAsync(this, Discord, title, new FileAttachment[] { attachment }, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags);
MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None, ForumTag[] tags = null)
=> ThreadHelper.CreatePostAsync(this, Discord, title, new FileAttachment[] { attachment }, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags, tags?.Select(tag => tag.Id).ToArray());
/// <inheritdoc cref="IForumChannel.CreatePostWithFilesAsync(string, IEnumerable{FileAttachment}, ThreadArchiveDuration, int?, string, Embed, RequestOptions, AllowedMentions, MessageComponent, ISticker[], Embed[], MessageFlags)"/>
/// <inheritdoc cref="IForumChannel.CreatePostWithFilesAsync(string, IEnumerable{FileAttachment}, ThreadArchiveDuration, int?, string, Embed, RequestOptions, AllowedMentions, MessageComponent, ISticker[], Embed[], MessageFlags, ForumTag[])"/>
public Task<RestThreadChannel> CreatePostWithFilesAsync(string title, IEnumerable<FileAttachment> attachments, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay,
int? slowmode = null, string text = null, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null,
MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
=> ThreadHelper.CreatePostAsync(this, Discord, title, attachments, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags);
MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None, ForumTag[] tags = null)
=> ThreadHelper.CreatePostAsync(this, Discord, title, attachments, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags, tags?.Select(tag => tag.Id).ToArray());
/// <inheritdoc cref="IForumChannel.GetActiveThreadsAsync(RequestOptions)"/>
public Task<IReadOnlyCollection<RestThreadChannel>> GetActiveThreadsAsync(RequestOptions options = null)
@@ -115,17 +159,45 @@ namespace Discord.Rest
=> await GetPrivateArchivedThreadsAsync(limit, before, options).ConfigureAwait(false);
async Task<IReadOnlyCollection<IThreadChannel>> IForumChannel.GetJoinedPrivateArchivedThreadsAsync(int? limit, DateTimeOffset? before, RequestOptions options)
=> await GetJoinedPrivateArchivedThreadsAsync(limit, before, options).ConfigureAwait(false);
async Task<IThreadChannel> IForumChannel.CreatePostAsync(string title, ThreadArchiveDuration archiveDuration, int? slowmode, string text, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageComponent components, ISticker[] stickers, Embed[] embeds, MessageFlags flags)
async Task<IThreadChannel> IForumChannel.CreatePostAsync(string title, ThreadArchiveDuration archiveDuration, int? slowmode, string text, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageComponent components, ISticker[] stickers, Embed[] embeds, MessageFlags flags, ForumTag[] tags)
=> await CreatePostAsync(title, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags).ConfigureAwait(false);
async Task<IThreadChannel> IForumChannel.CreatePostWithFileAsync(string title, string filePath, ThreadArchiveDuration archiveDuration, int? slowmode, string text, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions, MessageComponent components, ISticker[] stickers, Embed[] embeds, MessageFlags flags)
async Task<IThreadChannel> IForumChannel.CreatePostWithFileAsync(string title, string filePath, ThreadArchiveDuration archiveDuration, int? slowmode, string text, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions, MessageComponent components, ISticker[] stickers, Embed[] embeds, MessageFlags flags, ForumTag[] tags)
=> await CreatePostWithFileAsync(title, filePath, archiveDuration, slowmode, text, embed, options, isSpoiler, allowedMentions, components, stickers, embeds, flags).ConfigureAwait(false);
async Task<IThreadChannel> IForumChannel.CreatePostWithFileAsync(string title, Stream stream, string filename, ThreadArchiveDuration archiveDuration, int? slowmode, string text, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions, MessageComponent components, ISticker[] stickers, Embed[] embeds, MessageFlags flags)
async Task<IThreadChannel> IForumChannel.CreatePostWithFileAsync(string title, Stream stream, string filename, ThreadArchiveDuration archiveDuration, int? slowmode, string text, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions, MessageComponent components, ISticker[] stickers, Embed[] embeds, MessageFlags flags, ForumTag[] tags)
=> await CreatePostWithFileAsync(title, stream, filename, archiveDuration, slowmode, text, embed, options, isSpoiler, allowedMentions, components, stickers, embeds, flags).ConfigureAwait(false);
async Task<IThreadChannel> IForumChannel.CreatePostWithFileAsync(string title, FileAttachment attachment, ThreadArchiveDuration archiveDuration, int? slowmode, string text, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageComponent components, ISticker[] stickers, Embed[] embeds, MessageFlags flags)
async Task<IThreadChannel> IForumChannel.CreatePostWithFileAsync(string title, FileAttachment attachment, ThreadArchiveDuration archiveDuration, int? slowmode, string text, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageComponent components, ISticker[] stickers, Embed[] embeds, MessageFlags flags, ForumTag[] tags)
=> await CreatePostWithFileAsync(title, attachment, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags).ConfigureAwait(false);
async Task<IThreadChannel> IForumChannel.CreatePostWithFilesAsync(string title, IEnumerable<FileAttachment> attachments, ThreadArchiveDuration archiveDuration, int? slowmode, string text, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageComponent components, ISticker[] stickers, Embed[] embeds, MessageFlags flags)
async Task<IThreadChannel> IForumChannel.CreatePostWithFilesAsync(string title, IEnumerable<FileAttachment> attachments, ThreadArchiveDuration archiveDuration, int? slowmode, string text, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageComponent components, ISticker[] stickers, Embed[] embeds, MessageFlags flags, ForumTag[] tags)
=> await CreatePostWithFilesAsync(title, attachments, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags);
#endregion
#region INestedChannel
/// <inheritdoc />
public virtual async Task<IInviteMetadata> CreateInviteAsync(int? maxAge = 86400, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
=> await ChannelHelper.CreateInviteAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, options).ConfigureAwait(false);
public virtual async Task<IInviteMetadata> CreateInviteToApplicationAsync(ulong applicationId, int? maxAge = 86400, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
=> await ChannelHelper.CreateInviteToApplicationAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, applicationId, options);
/// <inheritdoc />
public virtual async Task<IInviteMetadata> CreateInviteToApplicationAsync(DefaultApplications application, int? maxAge = 86400, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
=> await ChannelHelper.CreateInviteToApplicationAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, (ulong)application, options);
public virtual Task<IInviteMetadata> CreateInviteToStreamAsync(IUser user, int? maxAge, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
=> throw new NotImplementedException();
/// <inheritdoc />
public virtual async Task<IReadOnlyCollection<IInviteMetadata>> GetInvitesAsync(RequestOptions options = null)
=> await ChannelHelper.GetInvitesAsync(this, Discord, options).ConfigureAwait(false);
/// <inheritdoc />
async Task<ICategoryChannel> INestedChannel.GetCategoryAsync(CacheMode mode, RequestOptions options)
{
if (CategoryId.HasValue && mode == CacheMode.AllowDownload)
return (await Guild.GetChannelAsync(CategoryId.Value, mode, options).ConfigureAwait(false)) as ICategoryChannel;
return null;
}
/// <inheritdoc />
public Task SyncPermissionsAsync(RequestOptions options = null)
=> ChannelHelper.SyncPermissionsAsync(this, Discord, options);
#endregion
}
}

View File

@@ -26,6 +26,9 @@ namespace Discord.Rest
/// <inheritdoc />
public ulong GuildId => Guild.Id;
/// <inheritdoc />
public ChannelFlags Flags { get; private set; }
internal RestGuildChannel(BaseDiscordClient discord, IGuild guild, ulong id)
: base(discord, id)
{
@@ -62,6 +65,8 @@ namespace Discord.Rest
newOverwrites.Add(overwrites[i].ToEntity());
_overwrites = newOverwrites.ToImmutable();
}
Flags = model.Flags.GetValueOrDefault(ChannelFlags.None);
}
/// <inheritdoc />

View File

@@ -37,6 +37,9 @@ namespace Discord.Rest
/// <inheritdoc/>
public bool? IsInvitable { get; private set; }
/// <inheritdoc/>
public IReadOnlyCollection<ulong> AppliedTags { get; private set; }
/// <inheritdoc cref="IThreadChannel.CreatedAt"/>
public override DateTimeOffset CreatedAt { get; }
@@ -77,6 +80,8 @@ namespace Discord.Rest
MessageCount = model.MessageCount.GetValueOrDefault(0);
Type = (ThreadType)model.Type;
ParentChannelId = model.CategoryId.Value;
AppliedTags = model.AppliedTags.GetValueOrDefault(Array.Empty<ulong>()).ToImmutableArray();
}
/// <summary>
@@ -109,6 +114,13 @@ namespace Discord.Rest
Update(model);
}
/// <inheritdoc/>
public async Task ModifyAsync(Action<ThreadChannelProperties> func, RequestOptions options = null)
{
var model = await ThreadHelper.ModifyAsync(this, Discord, func, options);
Update(model);
}
/// <inheritdoc/>
/// <remarks>
/// <b>This method is not supported in threads.</b>

View File

@@ -1,3 +1,4 @@
using Discord.API;
using Discord.API.Rest;
using System;
using System.Collections.Generic;
@@ -46,18 +47,23 @@ namespace Discord.Rest
}
public static async Task<Model> ModifyAsync(IThreadChannel channel, BaseDiscordClient client,
Action<TextChannelProperties> func,
Action<ThreadChannelProperties> func,
RequestOptions options)
{
var args = new TextChannelProperties();
var args = new ThreadChannelProperties();
func(args);
Preconditions.AtMost(args.AppliedTags.IsSpecified ? args.AppliedTags.Value.Count() : 0, 5, nameof(args.AppliedTags), "Forum post can have max 5 applied tags.");
var apiArgs = new ModifyThreadParams
{
Name = args.Name,
Archived = args.Archived,
AutoArchiveDuration = args.AutoArchiveDuration,
Locked = args.Locked,
Slowmode = args.SlowModeInterval
Slowmode = args.SlowModeInterval,
AppliedTags = args.AppliedTags,
Flags = args.Flags,
};
return await client.ApiClient.ModifyThreadAsync(channel.Id, apiArgs, options).ConfigureAwait(false);
}
@@ -103,7 +109,10 @@ namespace Discord.Rest
return RestThreadUser.Create(client, channel.Guild, model, channel);
}
public static async Task<RestThreadChannel> CreatePostAsync(IForumChannel channel, BaseDiscordClient client, string title, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay, int? slowmode = null, string text = null, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
public static async Task<RestThreadChannel> CreatePostAsync(IForumChannel channel, BaseDiscordClient client, string title,
ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay, int? slowmode = null, string text = null, Embed embed = null,
RequestOptions options = null, AllowedMentions allowedMentions = null, MessageComponent components = null, ISticker[] stickers = null,
Embed[] embeds = null, MessageFlags flags = MessageFlags.None, ulong[] tagIds = null)
{
embeds ??= Array.Empty<Embed>();
if (embed != null)
@@ -112,6 +121,7 @@ namespace Discord.Rest
Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed.");
Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed.");
Preconditions.AtMost(embeds.Length, 10, nameof(embeds), "A max of 10 embeds are allowed.");
Preconditions.AtMost(tagIds?.Length ?? 0, 5, nameof(tagIds), "Forum post can have max 5 applied tags.");
// check that user flag and user Id list are exclusive, same with role flag and role Id list
if (allowedMentions != null && allowedMentions.AllowedTypes.HasValue)
@@ -134,10 +144,12 @@ namespace Discord.Rest
Preconditions.AtMost(stickers.Length, 3, nameof(stickers), "A max of 3 stickers are allowed.");
}
if (flags is not MessageFlags.None and not MessageFlags.SuppressEmbeds)
throw new ArgumentException("The only valid MessageFlags are SuppressEmbeds and none.", nameof(flags));
if (channel.Flags.HasFlag(ChannelFlags.RequireTag))
throw new ArgumentException($"The channel {channel.Name} requires posts to have at least one tag.");
var args = new CreatePostParams()
{
Title = title,
@@ -151,7 +163,8 @@ namespace Discord.Rest
Flags = flags,
Components = components?.Components?.Any() ?? false ? components.Components.Select(x => new API.ActionRowComponent(x)).ToArray() : Optional<API.ActionRowComponent[]>.Unspecified,
Stickers = stickers?.Any() ?? false ? stickers.Select(x => x.Id).ToArray() : Optional<ulong[]>.Unspecified,
}
},
Tags = tagIds
};
var model = await client.ApiClient.CreatePostAsync(channel.Id, args, options).ConfigureAwait(false);
@@ -159,7 +172,9 @@ namespace Discord.Rest
return RestThreadChannel.Create(client, channel.Guild, model);
}
public static async Task<RestThreadChannel> CreatePostAsync(IForumChannel channel, BaseDiscordClient client, string title, IEnumerable<FileAttachment> attachments, ThreadArchiveDuration archiveDuration, int? slowmode, string text, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageComponent components, ISticker[] stickers, Embed[] embeds, MessageFlags flags)
public static async Task<RestThreadChannel> CreatePostAsync(IForumChannel channel, BaseDiscordClient client, string title, IEnumerable<FileAttachment> attachments,
ThreadArchiveDuration archiveDuration, int? slowmode, string text, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageComponent components,
ISticker[] stickers, Embed[] embeds, MessageFlags flags, ulong[] tagIds = null)
{
embeds ??= Array.Empty<Embed>();
if (embed != null)
@@ -168,6 +183,8 @@ namespace Discord.Rest
Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed.");
Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed.");
Preconditions.AtMost(embeds.Length, 10, nameof(embeds), "A max of 10 embeds are allowed.");
Preconditions.AtMost(tagIds?.Length ?? 0, 5, nameof(tagIds), "Forum post can have max 5 applied tags.");
// check that user flag and user Id list are exclusive, same with role flag and role Id list
if (allowedMentions != null && allowedMentions.AllowedTypes.HasValue)
@@ -190,9 +207,11 @@ namespace Discord.Rest
Preconditions.AtMost(stickers.Length, 3, nameof(stickers), "A max of 3 stickers are allowed.");
}
if (flags is not MessageFlags.None and not MessageFlags.SuppressEmbeds)
throw new ArgumentException("The only valid MessageFlags are SuppressEmbeds and none.", nameof(flags));
if (channel.Flags.HasFlag(ChannelFlags.RequireTag))
throw new ArgumentException($"The channel {channel.Name} requires posts to have at least one tag.");
var args = new CreateMultipartPostAsync(attachments.ToArray())
{

View File

@@ -1,3 +1,4 @@
using Discord.API;
using Discord.API.Rest;
using System;
using System.Collections.Generic;
@@ -252,6 +253,7 @@ namespace Discord.Rest
Deny = overwrite.Permissions.DenyValue.ToString()
}).ToArray()
: Optional.Create<API.Overwrite[]>(),
DefaultAutoArchiveDuration = props.AutoArchiveDuration
};
var model = await client.ApiClient.CreateGuildChannelAsync(guild.Id, args, options).ConfigureAwait(false);
return RestTextChannel.Create(client, guild, model);
@@ -338,6 +340,65 @@ namespace Discord.Rest
var model = await client.ApiClient.CreateGuildChannelAsync(guild.Id, args, options).ConfigureAwait(false);
return RestCategoryChannel.Create(client, guild, model);
}
/// <exception cref="ArgumentNullException"><paramref name="name"/> is <c>null</c>.</exception>
public static async Task<RestForumChannel> CreateForumChannelAsync(IGuild guild, BaseDiscordClient client,
string name, RequestOptions options, Action<ForumChannelProperties> func = null)
{
if (name == null)
throw new ArgumentNullException(paramName: nameof(name));
var props = new ForumChannelProperties();
func?.Invoke(props);
Preconditions.AtMost(props.Tags.IsSpecified ? props.Tags.Value.Count() : 0, 5, nameof(props.Tags), "Forum channel can have max 20 tags.");
var args = new CreateGuildChannelParams(name, ChannelType.Forum)
{
Position = props.Position,
Overwrites = props.PermissionOverwrites.IsSpecified
? props.PermissionOverwrites.Value.Select(overwrite => new API.Overwrite
{
TargetId = overwrite.TargetId,
TargetType = overwrite.TargetType,
Allow = overwrite.Permissions.AllowValue.ToString(),
Deny = overwrite.Permissions.DenyValue.ToString()
}).ToArray()
: Optional.Create<API.Overwrite[]>(),
SlowModeInterval = props.ThreadCreationInterval,
AvailableTags = props.Tags.GetValueOrDefault(Array.Empty<ForumTagProperties>()).Select(
x => new ModifyForumTagParams
{
Id = x.Id,
Name = x.Name,
EmojiId = x.Emoji is Emote emote
? emote.Id
: Optional<ulong?>.Unspecified,
EmojiName = x.Emoji is Emoji emoji
? emoji.Name
: Optional<string>.Unspecified,
Moderated = x.IsModerated
}).ToArray(),
DefaultReactionEmoji = props.DefaultReactionEmoji.IsSpecified
? new API.ModifyForumReactionEmojiParams
{
EmojiId = props.DefaultReactionEmoji.Value is Emote emote ?
emote.Id : Optional<ulong?>.Unspecified,
EmojiName = props.DefaultReactionEmoji.Value is Emoji emoji ?
emoji.Name : Optional<string>.Unspecified
}
: Optional<ModifyForumReactionEmojiParams>.Unspecified,
ThreadRateLimitPerUser = props.DefaultSlowModeInterval,
CategoryId = props.CategoryId,
IsNsfw = props.IsNsfw,
Topic = props.Topic,
DefaultAutoArchiveDuration = props.AutoArchiveDuration,
DefaultSortOrder = props.DefaultSortOrder.GetValueOrDefault(ForumSortOrder.LatestActivity)
};
var model = await client.ApiClient.CreateGuildChannelAsync(guild.Id, args, options).ConfigureAwait(false);
return RestForumChannel.Create(client, guild, model);
}
#endregion
#region Voice Regions

View File

@@ -710,6 +710,19 @@ namespace Discord.Rest
public Task<RestCategoryChannel> CreateCategoryChannelAsync(string name, Action<GuildChannelProperties> func = null, RequestOptions options = null)
=> GuildHelper.CreateCategoryChannelAsync(this, Discord, name, options, func);
/// <summary>
/// Creates a category channel with the provided name.
/// </summary>
/// <param name="name">The name of the new channel.</param>
/// <param name="func">The delegate containing the properties to be applied to the channel upon its creation.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <exception cref="ArgumentNullException"><paramref name="name" /> is <see langword="null"/>.</exception>
/// <returns>
/// The created category channel.
/// </returns>
public Task<RestForumChannel> CreateForumChannelAsync(string name, Action<ForumChannelProperties> func = null, RequestOptions options = null)
=> GuildHelper.CreateForumChannelAsync(this, Discord, name, options, func);
/// <summary>
/// Gets a collection of all the voice regions this guild can access.
/// </summary>
@@ -1370,6 +1383,9 @@ namespace Discord.Rest
/// <inheritdoc />
async Task<ICategoryChannel> IGuild.CreateCategoryAsync(string name, Action<GuildChannelProperties> func, RequestOptions options)
=> await CreateCategoryChannelAsync(name, func, options).ConfigureAwait(false);
/// <inheritdoc />
async Task<IForumChannel> IGuild.CreateForumChannelAsync(string name, Action<ForumChannelProperties> func, RequestOptions options)
=> await CreateForumChannelAsync(name, func, options).ConfigureAwait(false);
/// <inheritdoc />
async Task<IReadOnlyCollection<IVoiceRegion>> IGuild.GetVoiceRegionsAsync(RequestOptions options)