feature: Forum channels (#2316)

* initial implementation

* Update SocketForumChannel.cs

* rest forum channel and remove message builder for 4.x

* Update src/Discord.Net.Core/DiscordConfig.cs

Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com>

* Update src/Discord.Net.Core/Entities/Channels/IForumChannel.cs

Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com>

* Update src/Discord.Net.Core/DiscordConfig.cs

Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com>

* Update src/Discord.Net.Core/Entities/Channels/IForumChannel.cs

Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com>

* Update src/Discord.Net.Core/Entities/Channels/IForumChannel.cs

Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com>

* Update src/Discord.Net.Core/Entities/Channels/IForumChannel.cs

Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com>

Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com>
This commit is contained in:
Quin Lynch
2022-05-24 02:30:25 -03:00
committed by GitHub
parent aae549a976
commit 7a07fd62e4
22 changed files with 887 additions and 16 deletions

View File

@@ -67,6 +67,10 @@ namespace Discord.API
[JsonProperty("member_count")]
public Optional<int> MemberCount { get; set; }
//ForumChannel
[JsonProperty("available_tags")]
public Optional<ForumTags[]> ForumTags { get; set; }
[JsonProperty("default_auto_archive_duration")]
public Optional<ThreadArchiveDuration> AutoArchiveDuration { get; set; }
}

View File

@@ -9,8 +9,5 @@ namespace Discord.API.Rest
[JsonProperty("members")]
public ThreadMember[] Members { get; set; }
[JsonProperty("has_more")]
public bool HasMore { get; set; }
}
}

View File

@@ -0,0 +1,21 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Discord.API
{
internal class ForumTags
{
[JsonProperty("id")]
public 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; }
}
}

View File

@@ -0,0 +1,33 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Discord.API
{
internal class ForumThreadMessage
{
[JsonProperty("content")]
public Optional<string> Content { get; set; }
[JsonProperty("nonce")]
public Optional<string> Nonce { get; set; }
[JsonProperty("embeds")]
public Optional<Embed[]> Embeds { get; set; }
[JsonProperty("allowed_mentions")]
public Optional<AllowedMentions> AllowedMentions { get; set; }
[JsonProperty("components")]
public Optional<API.ActionRowComponent[]> Components { get; set; }
[JsonProperty("sticker_ids")]
public Optional<ulong[]> Stickers { get; set; }
[JsonProperty("flags")]
public Optional<MessageFlags> Flags { get; set; }
}
}

View File

@@ -0,0 +1,96 @@
using Discord.Net.Converters;
using Discord.Net.Rest;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Discord.API.Rest
{
internal class CreateMultipartPostAsync
{
private static JsonSerializer _serializer = new JsonSerializer { ContractResolver = new DiscordContractResolver() };
public FileAttachment[] Files { get; }
public string Title { get; set; }
public ThreadArchiveDuration ArchiveDuration { get; set; }
public Optional<int?> Slowmode { get; set; }
public Optional<string> Content { get; set; }
public Optional<Embed[]> Embeds { get; set; }
public Optional<AllowedMentions> AllowedMentions { get; set; }
public Optional<ActionRowComponent[]> MessageComponent { get; set; }
public Optional<MessageFlags?> Flags { get; set; }
public Optional<ulong[]> Stickers { get; set; }
public CreateMultipartPostAsync(params FileAttachment[] attachments)
{
Files = attachments;
}
public IReadOnlyDictionary<string, object> ToDictionary()
{
var d = new Dictionary<string, object>();
var payload = new Dictionary<string, object>();
var message = new Dictionary<string, object>();
payload["name"] = Title;
payload["auto_archive_duration"] = ArchiveDuration;
if (Slowmode.IsSpecified)
payload["rate_limit_per_user"] = Slowmode.Value;
// message
if (Content.IsSpecified)
message["content"] = Content.Value;
if (Embeds.IsSpecified)
message["embeds"] = Embeds.Value;
if (AllowedMentions.IsSpecified)
message["allowed_mentions"] = AllowedMentions.Value;
if (MessageComponent.IsSpecified)
message["components"] = MessageComponent.Value;
if (Stickers.IsSpecified)
message["sticker_ids"] = Stickers.Value;
if (Flags.IsSpecified)
message["flags"] = Flags.Value;
List<object> attachments = new();
for (int n = 0; n != Files.Length; n++)
{
var attachment = Files[n];
var filename = attachment.FileName ?? "unknown.dat";
if (attachment.IsSpoiler && !filename.StartsWith(AttachmentExtensions.SpoilerPrefix))
filename = filename.Insert(0, AttachmentExtensions.SpoilerPrefix);
d[$"files[{n}]"] = new MultipartFile(attachment.Stream, filename);
attachments.Add(new
{
id = (ulong)n,
filename = filename,
description = attachment.Description ?? Optional<string>.Unspecified
});
}
message["attachments"] = attachments;
payload["message"] = message;
var json = new StringBuilder();
using (var text = new StringWriter(json))
using (var writer = new JsonTextWriter(text))
_serializer.Serialize(writer, payload);
d["payload_json"] = json.ToString();
return d;
}
}
}

View File

@@ -0,0 +1,25 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Discord.API.Rest
{
internal class CreatePostParams
{
// thread
[JsonProperty("name")]
public string Title { get; set; }
[JsonProperty("auto_archive_duration")]
public ThreadArchiveDuration ArchiveDuration { get; set; }
[JsonProperty("rate_limit_per_user")]
public Optional<int?> Slowmode { get; set; }
[JsonProperty("message")]
public ForumThreadMessage Message { get; set; }
}
}

View File

@@ -37,7 +37,7 @@ namespace Discord.API.Rest
if (Content.IsSpecified)
payload["content"] = Content.Value;
if (IsTTS.IsSpecified)
payload["tts"] = IsTTS.Value.ToString();
payload["tts"] = IsTTS.Value;
if (Nonce.IsSpecified)
payload["nonce"] = Nonce.Value;
if (Embeds.IsSpecified)

View File

@@ -50,7 +50,7 @@ namespace Discord.API.Rest
if (Content.IsSpecified)
data["content"] = Content.Value;
if (IsTTS.IsSpecified)
data["tts"] = IsTTS.Value.ToString();
data["tts"] = IsTTS.Value;
if (MessageComponents.IsSpecified)
data["components"] = MessageComponents.Value;
if (Embeds.IsSpecified)

View File

@@ -36,7 +36,7 @@ namespace Discord.API.Rest
if (Content.IsSpecified)
payload["content"] = Content.Value;
if (IsTTS.IsSpecified)
payload["tts"] = IsTTS.Value.ToString();
payload["tts"] = IsTTS.Value;
if (Nonce.IsSpecified)
payload["nonce"] = Nonce.Value;
if (Username.IsSpecified)

View File

@@ -466,6 +466,24 @@ namespace Discord.API
#endregion
#region Threads
public async Task<Channel> CreatePostAsync(ulong channelId, CreatePostParams args, RequestOptions options = null)
{
Preconditions.NotEqual(channelId, 0, nameof(channelId));
var bucket = new BucketIds(channelId: channelId);
return await SendJsonAsync<Channel>("POST", () => $"channels/{channelId}/threads", args, bucket, options: options);
}
public async Task<Channel> CreatePostAsync(ulong channelId, CreateMultipartPostAsync args, RequestOptions options = null)
{
Preconditions.NotEqual(channelId, 0, nameof(channelId));
var bucket = new BucketIds(channelId: channelId);
return await SendMultipartAsync<Channel>("POST", () => $"channels/{channelId}/threads", args.ToDictionary(), bucket, options: options);
}
public async Task<Channel> ModifyThreadAsync(ulong channelId, ModifyThreadParams args, RequestOptions options = null)
{
Preconditions.NotEqual(channelId, 0, nameof(channelId));
@@ -566,15 +584,15 @@ namespace Discord.API
return await SendAsync<ThreadMember>("GET", () => $"channels/{channelId}/thread-members/{userId}", bucket, options: options).ConfigureAwait(false);
}
public async Task<ChannelThreads> GetActiveThreadsAsync(ulong channelId, RequestOptions options = null)
public async Task<ChannelThreads> GetActiveThreadsAsync(ulong guildId, RequestOptions options = null)
{
Preconditions.NotEqual(channelId, 0, nameof(channelId));
Preconditions.NotEqual(guildId, 0, nameof(guildId));
options = RequestOptions.CreateOrClone(options);
var bucket = new BucketIds(channelId: channelId);
var bucket = new BucketIds(guildId: guildId);
return await SendAsync<ChannelThreads>("GET", () => $"channels/{channelId}/threads/active", bucket, options: options);
return await SendAsync<ChannelThreads>("GET", () => $"guilds/{guildId}/threads/active", bucket, options: options);
}
public async Task<ChannelThreads> GetPublicArchivedThreadsAsync(ulong channelId, DateTimeOffset? before = null, int? limit = null, RequestOptions options = null)

View File

@@ -0,0 +1,131 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Model = Discord.API.Channel;
namespace Discord.Rest
{
/// <summary>
/// Represents a REST-based forum channel in a guild.
/// </summary>
public class RestForumChannel : RestGuildChannel, IForumChannel
{
/// <inheritdoc/>
public bool IsNsfw { get; private set; }
/// <inheritdoc/>
public string Topic { get; private set; }
/// <inheritdoc/>
public ThreadArchiveDuration DefaultAutoArchiveDuration { get; private set; }
/// <inheritdoc/>
public IReadOnlyCollection<ForumTag> Tags { get; private set; }
/// <inheritdoc/>
public string Mention => MentionUtils.MentionChannel(Id);
internal RestForumChannel(BaseDiscordClient client, IGuild guild, ulong id)
: base(client, guild, id)
{
}
internal new static RestStageChannel Create(BaseDiscordClient discord, IGuild guild, Model model)
{
var entity = new RestStageChannel(discord, guild, model.Id);
entity.Update(model);
return entity;
}
internal override void Update(Model model)
{
base.Update(model);
IsNsfw = model.Nsfw.GetValueOrDefault(false);
Topic = model.Topic.GetValueOrDefault();
DefaultAutoArchiveDuration = model.AutoArchiveDuration.GetValueOrDefault(ThreadArchiveDuration.OneDay);
Tags = model.ForumTags.GetValueOrDefault(Array.Empty<API.ForumTags>()).Select(
x => new ForumTag(x.Id, x.Name, x.EmojiId.GetValueOrDefault(null), x.EmojiName.GetValueOrDefault())
).ToImmutableArray();
}
/// <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 cref="IForumChannel.CreatePostWithFileAsync(string, string, ThreadArchiveDuration, int?, string, Embed, RequestOptions, bool, AllowedMentions, MessageComponent, ISticker[], Embed[], MessageFlags)"/>
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)
{
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);
}
/// <inheritdoc cref="IForumChannel.CreatePostWithFileAsync(string, Stream, string, ThreadArchiveDuration, int?, string, Embed, RequestOptions, bool, AllowedMentions, MessageComponent, ISticker[], Embed[], MessageFlags)"/>
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)
{
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);
}
/// <inheritdoc cref="IForumChannel.CreatePostWithFileAsync(string, FileAttachment, ThreadArchiveDuration, int?, string, Embed, RequestOptions, AllowedMentions, MessageComponent, ISticker[], Embed[], MessageFlags)"/>
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);
/// <inheritdoc cref="IForumChannel.CreatePostWithFilesAsync(string, IEnumerable{FileAttachment}, ThreadArchiveDuration, int?, string, Embed, RequestOptions, AllowedMentions, MessageComponent, ISticker[], Embed[], MessageFlags)"/>
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);
/// <inheritdoc cref="IForumChannel.GetActiveThreadsAsync(RequestOptions)"/>
public Task<IReadOnlyCollection<RestThreadChannel>> GetActiveThreadsAsync(RequestOptions options = null)
=> ThreadHelper.GetActiveThreadsAsync(Guild, Discord, options);
/// <inheritdoc cref="IForumChannel.GetJoinedPrivateArchivedThreadsAsync(int?, DateTimeOffset?, RequestOptions)"/>
public Task<IReadOnlyCollection<RestThreadChannel>> GetJoinedPrivateArchivedThreadsAsync(int? limit = null, DateTimeOffset? before = null, RequestOptions options = null)
=> ThreadHelper.GetJoinedPrivateArchivedThreadsAsync(this, Discord, limit, before, options);
/// <inheritdoc cref="IForumChannel.GetPrivateArchivedThreadsAsync(int?, DateTimeOffset?, RequestOptions)"/>
public Task<IReadOnlyCollection<RestThreadChannel>> GetPrivateArchivedThreadsAsync(int? limit = null, DateTimeOffset? before = null, RequestOptions options = null)
=> ThreadHelper.GetPrivateArchivedThreadsAsync(this, Discord, limit, before, options);
/// <inheritdoc cref="IForumChannel.GetPublicArchivedThreadsAsync(int?, DateTimeOffset?, RequestOptions)"/>
public Task<IReadOnlyCollection<RestThreadChannel>> GetPublicArchivedThreadsAsync(int? limit = null, DateTimeOffset? before = null, RequestOptions options = null)
=> ThreadHelper.GetPublicArchivedThreadsAsync(this, Discord, limit, before, options);
#region IForumChannel
async Task<IReadOnlyCollection<IThreadChannel>> IForumChannel.GetActiveThreadsAsync(RequestOptions options)
=> await GetActiveThreadsAsync(options).ConfigureAwait(false);
async Task<IReadOnlyCollection<IThreadChannel>> IForumChannel.GetPublicArchivedThreadsAsync(int? limit, DateTimeOffset? before, RequestOptions options)
=> await GetPublicArchivedThreadsAsync(limit, before, options).ConfigureAwait(false);
async Task<IReadOnlyCollection<IThreadChannel>> IForumChannel.GetPrivateArchivedThreadsAsync(int? limit, DateTimeOffset? before, RequestOptions options)
=> 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)
=> 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)
=> 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)
=> 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)
=> 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)
=> await CreatePostWithFilesAsync(title, attachments, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags);
#endregion
}
}

View File

@@ -39,6 +39,7 @@ namespace Discord.Rest
ChannelType.Text => RestTextChannel.Create(discord, guild, model),
ChannelType.Voice => RestVoiceChannel.Create(discord, guild, model),
ChannelType.Stage => RestStageChannel.Create(discord, guild, model),
ChannelType.Forum => RestForumChannel.Create(discord, guild, model),
ChannelType.Category => RestCategoryChannel.Create(discord, guild, model),
ChannelType.PublicThread or ChannelType.PrivateThread or ChannelType.NewsThread => RestThreadChannel.Create(discord, guild, model),
_ => new RestGuildChannel(discord, guild, model.Id),

View File

@@ -1,5 +1,7 @@
using Discord.API.Rest;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading.Tasks;
using Model = Discord.API.Channel;
@@ -60,6 +62,33 @@ namespace Discord.Rest
return await client.ApiClient.ModifyThreadAsync(channel.Id, apiArgs, options).ConfigureAwait(false);
}
public static async Task<IReadOnlyCollection<RestThreadChannel>> GetActiveThreadsAsync(IGuild guild, BaseDiscordClient client, RequestOptions options)
{
var result = await client.ApiClient.GetActiveThreadsAsync(guild.Id, options).ConfigureAwait(false);
return result.Threads.Select(x => RestThreadChannel.Create(client, guild, x)).ToImmutableArray();
}
public static async Task<IReadOnlyCollection<RestThreadChannel>> GetPublicArchivedThreadsAsync(IGuildChannel channel, BaseDiscordClient client, int? limit = null,
DateTimeOffset? before = null, RequestOptions options = null)
{
var result = await client.ApiClient.GetPublicArchivedThreadsAsync(channel.Id, before, limit, options);
return result.Threads.Select(x => RestThreadChannel.Create(client, channel.Guild, x)).ToImmutableArray();
}
public static async Task<IReadOnlyCollection<RestThreadChannel>> GetPrivateArchivedThreadsAsync(IGuildChannel channel, BaseDiscordClient client, int? limit = null,
DateTimeOffset? before = null, RequestOptions options = null)
{
var result = await client.ApiClient.GetPrivateArchivedThreadsAsync(channel.Id, before, limit, options);
return result.Threads.Select(x => RestThreadChannel.Create(client, channel.Guild, x)).ToImmutableArray();
}
public static async Task<IReadOnlyCollection<RestThreadChannel>> GetJoinedPrivateArchivedThreadsAsync(IGuildChannel channel, BaseDiscordClient client, int? limit = null,
DateTimeOffset? before = null, RequestOptions options = null)
{
var result = await client.ApiClient.GetJoinedPrivateArchivedThreadsAsync(channel.Id, before, limit, options);
return result.Threads.Select(x => RestThreadChannel.Create(client, channel.Guild, x)).ToImmutableArray();
}
public static async Task<RestThreadUser[]> GetUsersAsync(IThreadChannel channel, BaseDiscordClient client, RequestOptions options = null)
{
var users = await client.ApiClient.ListThreadMembersAsync(channel.Id, options);
@@ -73,5 +102,114 @@ 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)
{
embeds ??= Array.Empty<Embed>();
if (embed != null)
embeds = new[] { embed }.Concat(embeds).ToArray();
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.");
// check that user flag and user Id list are exclusive, same with role flag and role Id list
if (allowedMentions != null && allowedMentions.AllowedTypes.HasValue)
{
if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Users) &&
allowedMentions.UserIds != null && allowedMentions.UserIds.Count > 0)
{
throw new ArgumentException("The Users flag is mutually exclusive with the list of User Ids.", nameof(allowedMentions));
}
if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Roles) &&
allowedMentions.RoleIds != null && allowedMentions.RoleIds.Count > 0)
{
throw new ArgumentException("The Roles flag is mutually exclusive with the list of Role Ids.", nameof(allowedMentions));
}
}
if (stickers != null)
{
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));
var args = new CreatePostParams()
{
Title = title,
ArchiveDuration = archiveDuration,
Slowmode = slowmode,
Message = new()
{
AllowedMentions = allowedMentions.ToModel(),
Content = text,
Embeds = embeds.Any() ? embeds.Select(x => x.ToModel()).ToArray() : Optional<API.Embed[]>.Unspecified,
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,
}
};
var model = await client.ApiClient.CreatePostAsync(channel.Id, args, options).ConfigureAwait(false);
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)
{
embeds ??= Array.Empty<Embed>();
if (embed != null)
embeds = new[] { embed }.Concat(embeds).ToArray();
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.");
// check that user flag and user Id list are exclusive, same with role flag and role Id list
if (allowedMentions != null && allowedMentions.AllowedTypes.HasValue)
{
if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Users) &&
allowedMentions.UserIds != null && allowedMentions.UserIds.Count > 0)
{
throw new ArgumentException("The Users flag is mutually exclusive with the list of User Ids.", nameof(allowedMentions));
}
if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Roles) &&
allowedMentions.RoleIds != null && allowedMentions.RoleIds.Count > 0)
{
throw new ArgumentException("The Roles flag is mutually exclusive with the list of Role Ids.", nameof(allowedMentions));
}
}
if (stickers != null)
{
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));
var args = new CreateMultipartPostAsync(attachments.ToArray())
{
AllowedMentions = allowedMentions.ToModel(),
ArchiveDuration = archiveDuration,
Content = text,
Embeds = embeds.Any() ? embeds.Select(x => x.ToModel()).ToArray() : Optional<API.Embed[]>.Unspecified,
Flags = flags,
MessageComponent = components?.Components?.Any() ?? false ? components.Components.Select(x => new API.ActionRowComponent(x)).ToArray() : Optional<API.ActionRowComponent[]>.Unspecified,
Slowmode = slowmode,
Stickers = stickers?.Any() ?? false ? stickers.Select(x => x.Id).ToArray() : Optional<ulong[]>.Unspecified,
Title = title
};
var model = await client.ApiClient.CreatePostAsync(channel.Id, args, options);
return RestThreadChannel.Create(client, channel.Guild, model);
}
}
}

View File

@@ -369,7 +369,7 @@ namespace Discord.Rest
#endregion
#region Responses
public static async Task<Message> ModifyFollowupMessageAsync(BaseDiscordClient client, RestFollowupMessage message, Action<MessageProperties> func,
public static async Task<Discord.API.Message> ModifyFollowupMessageAsync(BaseDiscordClient client, RestFollowupMessage message, Action<MessageProperties> func,
RequestOptions options = null)
{
var args = new MessageProperties();
@@ -411,7 +411,7 @@ namespace Discord.Rest
}
public static async Task DeleteFollowupMessageAsync(BaseDiscordClient client, RestFollowupMessage message, RequestOptions options = null)
=> await client.ApiClient.DeleteInteractionFollowupMessageAsync(message.Id, message.Token, options);
public static async Task<Message> ModifyInteractionResponseAsync(BaseDiscordClient client, string token, Action<MessageProperties> func,
public static async Task<API.Message> ModifyInteractionResponseAsync(BaseDiscordClient client, string token, Action<MessageProperties> func,
RequestOptions options = null)
{
var args = new MessageProperties();

View File

@@ -1,4 +1,4 @@
using Newtonsoft.Json;
using Newtonsoft.Json;
using System;
using System.Globalization;
@@ -14,7 +14,7 @@ namespace Discord.Net.Converters
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return ulong.Parse((string)reader.Value, NumberStyles.None, CultureInfo.InvariantCulture);
return ulong.Parse(reader.Value?.ToString(), NumberStyles.None, CultureInfo.InvariantCulture);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)