diff --git a/src/Discord.Net.Core/Entities/Channels/IForumChannel.cs b/src/Discord.Net.Core/Entities/Channels/IForumChannel.cs index 06ff0e6a4..4a0e2bf9b 100644 --- a/src/Discord.Net.Core/Entities/Channels/IForumChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/IForumChannel.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -39,15 +40,129 @@ namespace Discord /// /// The title of the post. /// The archive duration of the post. - /// - /// The starting message of the post. The content of the message supports full markdown. + /// The slowmode for the posts thread. + /// The message to be sent. + /// The to be sent. + /// The options to be used when sending the request. + /// + /// Specifies if notifications are sent for mentioned users and roles in the message . + /// If null, all mentioned roles and users will be notified. + /// + /// The message components to be included with this message. Used for interactions. + /// A collection of stickers to send with the message. + /// A array of s to send with this response. Max 10. + /// A message flag to be applied to the sent message, only is permitted. + /// + /// A task that represents the asynchronous creation operation. + /// + Task 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); + + /// + /// Creates a new post (thread) within the forum. + /// + /// The title of the post. + /// The archive duration of the post. + /// The slowmode for the posts thread. + /// The file path of the file. + /// The message to be sent. + /// The to be sent. + /// The options to be used when sending the request. + /// Whether the message attachment should be hidden as a spoiler. + /// + /// Specifies if notifications are sent for mentioned users and roles in the message . + /// If null, all mentioned roles and users will be notified. + /// + /// The message components to be included with this message. Used for interactions. + /// A collection of stickers to send with the file. + /// A array of s to send with this response. Max 10. + /// A message flag to be applied to the sent message, only is permitted. + /// + /// A task that represents the asynchronous creation operation. + /// + Task 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); + + /// + /// Creates a new post (thread) within the forum. + /// + /// The title of the post. + /// The of the file to be sent. + /// The name of the attachment. + /// The archive duration of the post. + /// The slowmode for the posts thread. + /// The message to be sent. + /// The to be sent. + /// The options to be used when sending the request. + /// Whether the message attachment should be hidden as a spoiler. + /// + /// Specifies if notifications are sent for mentioned users and roles in the message . + /// If null, all mentioned roles and users will be notified. + /// + /// The message components to be included with this message. Used for interactions. + /// A collection of stickers to send with the file. + /// A array of s to send with this response. Max 10. + /// A message flag to be applied to the sent message, only is permitted. + /// + /// A task that represents the asynchronous creation operation. + /// + public Task 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); + + /// + /// Creates a new post (thread) within the forum. + /// + /// The title of the post. + /// The attachment containing the file and description. + /// The archive duration of the post. + /// The slowmode for the posts thread. + /// The message to be sent. + /// The to be sent. + /// The options to be used when sending the request. + /// + /// Specifies if notifications are sent for mentioned users and roles in the message . + /// If null, all mentioned roles and users will be notified. /// + /// The message components to be included with this message. Used for interactions. + /// A collection of stickers to send with the file. + /// A array of s to send with this response. Max 10. + /// A message flag to be applied to the sent message, only is permitted. + /// + /// A task that represents the asynchronous creation operation. + /// + public Task 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); + + /// + /// Creates a new post (thread) within the forum. + /// + /// The title of the post. + /// A collection of attachments to upload. + /// The archive duration of the post. /// The slowmode for the posts thread. + /// The message to be sent. + /// The to be sent. /// The options to be used when sending the request. + /// + /// Specifies if notifications are sent for mentioned users and roles in the message . + /// If null, all mentioned roles and users will be notified. + /// + /// The message components to be included with this message. Used for interactions. + /// A collection of stickers to send with the file. + /// A array of s to send with this response. Max 10. + /// A message flag to be applied to the sent message, only is permitted. /// /// A task that represents the asynchronous creation operation. /// - Task CreatePostAsync(string title, ThreadArchiveDuration archiveDuration, Message message, int? slowmode = null, RequestOptions options = null); + public Task CreatePostWithFilesAsync(string title, IEnumerable 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); /// /// Gets a collection of active threads within this forum channel. diff --git a/src/Discord.Net.Core/Entities/Messages/Message.cs b/src/Discord.Net.Core/Entities/Messages/Message.cs deleted file mode 100644 index 581228ba9..000000000 --- a/src/Discord.Net.Core/Entities/Messages/Message.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Discord -{ - /// - /// Represents a message created by a that can be sent to a channel. - /// - public sealed class Message - { - /// - /// Gets the content of the message. - /// - public string Content { get; } - - /// - /// Gets whether or not this message should be read by a text-to-speech engine. - /// - public bool IsTTS { get; } - - /// - /// Gets a collection of embeds sent along with this message. - /// - public IReadOnlyCollection Embeds { get; } - - /// - /// Gets the allowed mentions for this message. - /// - public AllowedMentions AllowedMentions { get; } - - /// - /// Gets the message reference (reply to) for this message. - /// - public MessageReference MessageReference { get; } - - /// - /// Gets the components of this message. - /// - public MessageComponent Components { get; } - - /// - /// Gets a collection of sticker ids that will be sent with this message. - /// - public IReadOnlyCollection StickerIds { get; } - - /// - /// Gets a collection of files sent with this message. - /// - public IReadOnlyCollection Attachments { get; } - - /// - /// Gets the message flags for this message. - /// - public MessageFlags Flags { get; } - - internal Message(string content, bool istts, IReadOnlyCollection embeds, AllowedMentions allowedMentions, - MessageReference messagereference, MessageComponent components, IReadOnlyCollection stickerIds, - IReadOnlyCollection attachments, MessageFlags flags) - { - Content = content; - IsTTS = istts; - Embeds = embeds; - AllowedMentions = allowedMentions; - MessageReference = messagereference; - Components = components; - StickerIds = stickerIds; - Attachments = attachments; - Flags = flags; - } - } -} diff --git a/src/Discord.Net.Core/Entities/Messages/MessageBuilder.cs b/src/Discord.Net.Core/Entities/Messages/MessageBuilder.cs deleted file mode 100644 index 738c4102b..000000000 --- a/src/Discord.Net.Core/Entities/Messages/MessageBuilder.cs +++ /dev/null @@ -1,220 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Discord -{ - /// - /// Represents a generic message builder that can build s. - /// - public class MessageBuilder - { - private string _content; - private List _stickers; - private List _embeds; - - /// - /// Gets or sets the content of this message - /// - /// The content is bigger than the . - public string Content - { - get => _content; - set - { - if (_content?.Length > DiscordConfig.MaxMessageSize) - throw new ArgumentOutOfRangeException(nameof(value), $"Message size must be less than or equal to {DiscordConfig.MaxMessageSize} characters"); - - _content = value; - } - } - - /// - /// Gets or sets whether or not this message is TTS. - /// - public bool IsTTS { get; set; } - - /// - /// Gets or sets the embeds of this message. - /// - public List Embeds - { - get - { - if (_embeds == null) - _embeds = new(); - return _embeds; - } - set - { - if (value?.Count > DiscordConfig.MaxEmbedsPerMessage) - throw new ArgumentOutOfRangeException(nameof(value), $"Embed count must be less than or equal to {DiscordConfig.MaxEmbedsPerMessage}"); - - _embeds = value; - } - } - - /// - /// Gets or sets the allowed mentions of this message. - /// - public AllowedMentions AllowedMentions { get; set; } - - /// - /// Gets or sets the message reference (reply to) of this message. - /// - public MessageReference MessageReference { get; set; } - - /// - /// Gets or sets the components of this message. - /// - public ComponentBuilder Components { get; set; } = new(); - - /// - /// Gets or sets the stickers sent with this message. - /// - public List Stickers - { - get => _stickers; - set - { - if (value?.Count > DiscordConfig.MaxStickersPerMessage) - throw new ArgumentOutOfRangeException(nameof(value), $"Sticker count must be less than or equal to {DiscordConfig.MaxStickersPerMessage}"); - - _stickers = value; - } - } - - /// - /// Gets or sets the files sent with this message. - /// - public List Files { get; set; } = new(); - - /// - /// Gets or sets the message flags. - /// - public MessageFlags Flags { get; set; } - - /// - /// Sets the of this message. - /// - /// The content of the message. - /// The current builder. - public MessageBuilder WithContent(string content) - { - Content = content; - return this; - } - - /// - /// Sets the of this message. - /// - /// whether or not this message is tts. - /// The current builder. - public MessageBuilder WithTTS(bool isTTS) - { - IsTTS = isTTS; - return this; - } - - public MessageBuilder WithEmbeds(params EmbedBuilder[] embeds) - { - Embeds = new(embeds); - return this; - } - - public MessageBuilder AddEmbed(EmbedBuilder embed) - { - if (_embeds?.Count >= DiscordConfig.MaxEmbedsPerMessage) - throw new ArgumentOutOfRangeException(nameof(embed.Length), $"A message can only contain a maximum of {DiscordConfig.MaxEmbedsPerMessage} embeds"); - - _embeds ??= new(); - - _embeds.Add(embed); - - return this; - } - - public MessageBuilder WithAllowedMentions(AllowedMentions allowedMentions) - { - AllowedMentions = allowedMentions; - return this; - } - - public MessageBuilder WithMessageReference(MessageReference reference) - { - MessageReference = reference; - return this; - } - - public MessageBuilder WithMessageReference(IMessage message) - { - if (message != null) - MessageReference = new MessageReference(message.Id, message.Channel?.Id, ((IGuildChannel)message.Channel)?.GuildId); - return this; - } - - public MessageBuilder WithComponentBuilder(ComponentBuilder builder) - { - Components = builder; - return this; - } - - public MessageBuilder WithButton(ButtonBuilder button, int row = 0) - { - Components ??= new(); - Components.WithButton(button, row); - return this; - } - - public MessageBuilder WithButton( - string label = null, - string customId = null, - ButtonStyle style = ButtonStyle.Primary, - IEmote emote = null, - string url = null, - bool disabled = false, - int row = 0) - { - Components ??= new(); - Components.WithButton(label, customId, style, emote, url, disabled, row); - return this; - } - - public MessageBuilder WithSelectMenu(SelectMenuBuilder menu, int row = 0) - { - Components ??= new(); - Components.WithSelectMenu(menu, row); - return this; - } - - public MessageBuilder WithSelectMenu(string customId, List options, - string placeholder = null, int minValues = 1, int maxValues = 1, bool disabled = false, int row = 0) - { - Components ??= new(); - Components.WithSelectMenu(customId, options, placeholder, minValues, maxValues, disabled, row); - return this; - } - - public Message Build() - { - var embeds = _embeds != null && _embeds.Count > 0 - ? _embeds.Select(x => x.Build()).ToImmutableArray() - : ImmutableArray.Empty; - - return new Message( - _content, - IsTTS, - embeds, - AllowedMentions, - MessageReference, - Components?.Build(), - _stickers != null && _stickers.Any() ? _stickers.Select(x => x.Id).ToImmutableArray() : ImmutableArray.Empty, - Files?.ToImmutableArray() ?? ImmutableArray.Empty, - Flags - ); - } - } -} diff --git a/src/Discord.Net.Rest/API/Common/ForumThreadMessage.cs b/src/Discord.Net.Rest/API/Common/ForumThreadMessage.cs new file mode 100644 index 000000000..132e38e5f --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/ForumThreadMessage.cs @@ -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 Content { get; set; } + + [JsonProperty("nonce")] + public Optional Nonce { get; set; } + + [JsonProperty("embeds")] + public Optional Embeds { get; set; } + + [JsonProperty("allowed_mentions")] + public Optional AllowedMentions { get; set; } + + [JsonProperty("components")] + public Optional Components { get; set; } + + [JsonProperty("sticker_ids")] + public Optional Stickers { get; set; } + + [JsonProperty("flags")] + public Optional Flags { get; set; } + } +} diff --git a/src/Discord.Net.Rest/API/Rest/CreateMultipartPostAsync.cs b/src/Discord.Net.Rest/API/Rest/CreateMultipartPostAsync.cs index 0a5c258ad..0c8bc5494 100644 --- a/src/Discord.Net.Rest/API/Rest/CreateMultipartPostAsync.cs +++ b/src/Discord.Net.Rest/API/Rest/CreateMultipartPostAsync.cs @@ -22,7 +22,6 @@ namespace Discord.API.Rest public Optional Content { get; set; } - public Optional IsTTS { get; set; } public Optional Embeds { get; set; } public Optional AllowedMentions { get; set; } public Optional MessageComponent { get; set; } @@ -39,26 +38,27 @@ namespace Discord.API.Rest var d = new Dictionary(); var payload = new Dictionary(); + var message = new Dictionary(); - payload["title"] = Title; + payload["name"] = Title; payload["auto_archive_duration"] = ArchiveDuration; if (Slowmode.IsSpecified) payload["rate_limit_per_user"] = Slowmode.Value; + + // message if (Content.IsSpecified) - payload["content"] = Content.Value; - if (IsTTS.IsSpecified) - payload["tts"] = IsTTS.Value; + message["content"] = Content.Value; if (Embeds.IsSpecified) - payload["embeds"] = Embeds.Value; + message["embeds"] = Embeds.Value; if (AllowedMentions.IsSpecified) - payload["allowed_mentions"] = AllowedMentions.Value; + message["allowed_mentions"] = AllowedMentions.Value; if (MessageComponent.IsSpecified) - payload["components"] = MessageComponent.Value; + message["components"] = MessageComponent.Value; if (Stickers.IsSpecified) - payload["sticker_ids"] = Stickers.Value; + message["sticker_ids"] = Stickers.Value; if (Flags.IsSpecified) - payload["flags"] = Flags.Value; + message["flags"] = Flags.Value; List attachments = new(); @@ -79,7 +79,9 @@ namespace Discord.API.Rest }); } - payload["attachments"] = attachments; + message["attachments"] = attachments; + + payload["message"] = message; var json = new StringBuilder(); using (var text = new StringWriter(json)) diff --git a/src/Discord.Net.Rest/API/Rest/CreatePostParams.cs b/src/Discord.Net.Rest/API/Rest/CreatePostParams.cs index 468610062..974e07c0a 100644 --- a/src/Discord.Net.Rest/API/Rest/CreatePostParams.cs +++ b/src/Discord.Net.Rest/API/Rest/CreatePostParams.cs @@ -19,26 +19,7 @@ namespace Discord.API.Rest [JsonProperty("rate_limit_per_user")] public Optional Slowmode { get; set; } - // message - [JsonProperty("content")] - public string Content { get; set; } - - [JsonProperty("tts")] - public Optional IsTTS { get; set; } - - [JsonProperty("embeds")] - public Optional Embeds { get; set; } - - [JsonProperty("allowed_mentions")] - public Optional AllowedMentions { get; set; } - - [JsonProperty("components")] - public Optional Components { get; set; } - - [JsonProperty("sticker_ids")] - public Optional Stickers { get; set; } - - [JsonProperty("flags")] - public Optional Flags { get; set; } + [JsonProperty("message")] + public ForumThreadMessage Message { get; set; } } } diff --git a/src/Discord.Net.Rest/Entities/Channels/RestForumChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestForumChannel.cs new file mode 100644 index 000000000..aff8400aa --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Channels/RestForumChannel.cs @@ -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 +{ + /// + /// Represents a REST-based forum channel in a guild. + /// + public class RestForumChannel : RestGuildChannel, IForumChannel + { + /// + public bool IsNsfw { get; private set; } + + /// + public string Topic { get; private set; } + + /// + public ThreadArchiveDuration DefaultAutoArchiveDuration { get; private set; } + + /// + public IReadOnlyCollection Tags { get; private set; } + + /// + 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()).Select( + x => new ForumTag(x.Id, x.Name, x.EmojiId.GetValueOrDefault(null), x.EmojiName.GetValueOrDefault()) + ).ToImmutableArray(); + } + + /// + public Task 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); + + /// + public async Task 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); + } + + /// + public async Task 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); + } + + /// + public Task 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); + + /// + public Task CreatePostWithFilesAsync(string title, IEnumerable 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); + + /// + public Task> GetActiveThreadsAsync(RequestOptions options = null) + => ThreadHelper.GetActiveThreadsAsync(Guild, Discord, options); + + /// + public Task> GetJoinedPrivateArchivedThreadsAsync(int? limit = null, DateTimeOffset? before = null, RequestOptions options = null) + => ThreadHelper.GetJoinedPrivateArchivedThreadsAsync(this, Discord, limit, before, options); + + /// + public Task> GetPrivateArchivedThreadsAsync(int? limit = null, DateTimeOffset? before = null, RequestOptions options = null) + => ThreadHelper.GetPrivateArchivedThreadsAsync(this, Discord, limit, before, options); + + /// + public Task> GetPublicArchivedThreadsAsync(int? limit = null, DateTimeOffset? before = null, RequestOptions options = null) + => ThreadHelper.GetPublicArchivedThreadsAsync(this, Discord, limit, before, options); + + #region IForumChannel + async Task> IForumChannel.GetActiveThreadsAsync(RequestOptions options) + => await GetActiveThreadsAsync(options).ConfigureAwait(false); + async Task> IForumChannel.GetPublicArchivedThreadsAsync(int? limit, DateTimeOffset? before, RequestOptions options) + => await GetPublicArchivedThreadsAsync(limit, before, options).ConfigureAwait(false); + async Task> IForumChannel.GetPrivateArchivedThreadsAsync(int? limit, DateTimeOffset? before, RequestOptions options) + => await GetPrivateArchivedThreadsAsync(limit, before, options).ConfigureAwait(false); + async Task> IForumChannel.GetJoinedPrivateArchivedThreadsAsync(int? limit, DateTimeOffset? before, RequestOptions options) + => await GetJoinedPrivateArchivedThreadsAsync(limit, before, options).ConfigureAwait(false); + async Task 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 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 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 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 IForumChannel.CreatePostWithFilesAsync(string title, IEnumerable 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 + } +} diff --git a/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs index fa2362854..4f9af0335 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs @@ -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), diff --git a/src/Discord.Net.Rest/Entities/Channels/ThreadHelper.cs b/src/Discord.Net.Rest/Entities/Channels/ThreadHelper.cs index 95c0496bf..f5fce5a50 100644 --- a/src/Discord.Net.Rest/Entities/Channels/ThreadHelper.cs +++ b/src/Discord.Net.Rest/Entities/Channels/ThreadHelper.cs @@ -103,47 +103,112 @@ namespace Discord.Rest return RestThreadUser.Create(client, channel.Guild, model, channel); } - public static async Task CreatePostAsync(IForumChannel channel, BaseDiscordClient client, string title, ThreadArchiveDuration archiveDuration, Message message, int? slowmode = null, RequestOptions options = null) + public static async Task 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) { - Model model; + embeds ??= Array.Empty(); + 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."); - if (message.Attachments?.Any() ?? false) + // check that user flag and user Id list are exclusive, same with role flag and role Id list + if (allowedMentions != null && allowedMentions.AllowedTypes.HasValue) { - var args = new CreateMultipartPostAsync(message.Attachments.ToArray()) + if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Users) && + allowedMentions.UserIds != null && allowedMentions.UserIds.Count > 0) { - AllowedMentions = message.AllowedMentions.ToModel(), - ArchiveDuration = archiveDuration, - Content = message.Content, - Embeds = message.Embeds.Any() ? message.Embeds.Select(x => x.ToModel()).ToArray() : Optional.Unspecified, - Flags = message.Flags, - IsTTS = message.IsTTS, - MessageComponent = message.Components?.Components?.Any() ?? false ? message.Components.Components.Select(x => new API.ActionRowComponent(x)).ToArray() : Optional.Unspecified, - Slowmode = slowmode, - Stickers = message.StickerIds?.Any() ?? false ? message.StickerIds.ToArray() : Optional.Unspecified, - Title = title - }; - - model = await client.ApiClient.CreatePostAsync(channel.Id, args, options).ConfigureAwait(false); + 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)); + } } - else + + if (stickers != null) { - var args = new CreatePostParams() + 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 = message.AllowedMentions.ToModel(), - ArchiveDuration = archiveDuration, - Content = message.Content, - Embeds = message.Embeds.Any() ? message.Embeds.Select(x => x.ToModel()).ToArray() : Optional.Unspecified, - Flags = message.Flags, - IsTTS = message.IsTTS, - Components = message.Components?.Components?.Any() ?? false ? message.Components.Components.Select(x => new API.ActionRowComponent(x)).ToArray() : Optional.Unspecified, - Slowmode = slowmode, - Stickers = message.StickerIds?.Any() ?? false ? message.StickerIds.ToArray() : Optional.Unspecified, - Title = title - }; - - model = await client.ApiClient.CreatePostAsync(channel.Id, args, options); + AllowedMentions = allowedMentions.ToModel(), + Content = text, + Embeds = embeds.Any() ? embeds.Select(x => x.ToModel()).ToArray() : Optional.Unspecified, + Flags = flags, + Components = components?.Components?.Any() ?? false ? components.Components.Select(x => new API.ActionRowComponent(x)).ToArray() : Optional.Unspecified, + Stickers = stickers?.Any() ?? false ? stickers.Select(x => x.Id).ToArray() : Optional.Unspecified, + } + }; + + var model = await client.ApiClient.CreatePostAsync(channel.Id, args, options).ConfigureAwait(false); + + return RestThreadChannel.Create(client, channel.Guild, model); + } + + public static async Task CreatePostAsync(IForumChannel channel, BaseDiscordClient client, string title, IEnumerable attachments, ThreadArchiveDuration archiveDuration, int? slowmode, string text, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageComponent components, ISticker[] stickers, Embed[] embeds, MessageFlags flags) + { + embeds ??= Array.Empty(); + 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.Unspecified, + Flags = flags, + MessageComponent = components?.Components?.Any() ?? false ? components.Components.Select(x => new API.ActionRowComponent(x)).ToArray() : Optional.Unspecified, + Slowmode = slowmode, + Stickers = stickers?.Any() ?? false ? stickers.Select(x => x.Id).ToArray() : Optional.Unspecified, + Title = title + }; + + var model = await client.ApiClient.CreatePostAsync(channel.Id, args, options); + return RestThreadChannel.Create(client, channel.Guild, model); } } diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketForumChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketForumChannel.cs index 5cb1ff5e4..bc6e28442 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketForumChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketForumChannel.cs @@ -2,6 +2,7 @@ using Discord.Rest; using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -45,14 +46,46 @@ namespace Discord.WebSocket Topic = model.Topic.GetValueOrDefault(); DefaultAutoArchiveDuration = model.AutoArchiveDuration.GetValueOrDefault(ThreadArchiveDuration.OneDay); - Tags = model.ForumTags.GetValueOrDefault(new API.ForumTags[0]).Select( + Tags = model.ForumTags.GetValueOrDefault(Array.Empty()).Select( x => new ForumTag(x.Id, x.Name, x.EmojiId.GetValueOrDefault(null), x.EmojiName.GetValueOrDefault()) ).ToImmutableArray(); } - /// - public Task CreatePostAsync(string title, ThreadArchiveDuration archiveDuration, Message message, int? slowmode = null, RequestOptions options = null) - => ThreadHelper.CreatePostAsync(this, Discord, title, archiveDuration, message, slowmode, options); + /// + public Task 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); + + /// + public async Task 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); + } + + /// + public async Task 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); + } + + /// + public Task 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); + + /// + public Task CreatePostWithFilesAsync(string title, IEnumerable 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); /// public Task> GetActiveThreadsAsync(RequestOptions options = null) @@ -71,8 +104,6 @@ namespace Discord.WebSocket => ThreadHelper.GetPublicArchivedThreadsAsync(this, Discord, limit, before, options); #region IForumChannel - async Task IForumChannel.CreatePostAsync(string title, ThreadArchiveDuration archiveDuration, Message message, int? slowmode, RequestOptions options) - => await CreatePostAsync(title, archiveDuration, message, slowmode, options).ConfigureAwait(false); async Task> IForumChannel.GetActiveThreadsAsync(RequestOptions options) => await GetActiveThreadsAsync(options).ConfigureAwait(false); async Task> IForumChannel.GetPublicArchivedThreadsAsync(int? limit, DateTimeOffset? before, RequestOptions options) @@ -81,6 +112,17 @@ namespace Discord.WebSocket => await GetPrivateArchivedThreadsAsync(limit, before, options).ConfigureAwait(false); async Task> IForumChannel.GetJoinedPrivateArchivedThreadsAsync(int? limit, DateTimeOffset? before, RequestOptions options) => await GetJoinedPrivateArchivedThreadsAsync(limit, before, options).ConfigureAwait(false); + async Task 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 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 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 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 IForumChannel.CreatePostWithFilesAsync(string title, IEnumerable 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 } } diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs index e12f3d1ef..9ce2f507a 100644 --- a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs +++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs @@ -705,7 +705,15 @@ namespace Discord.WebSocket /// public SocketThreadChannel GetThreadChannel(ulong id) => GetChannel(id) as SocketThreadChannel; - + /// + /// Gets a forum channel in this guild. + /// + /// The snowflake identifier for the forum channel. + /// + /// A forum channel associated with the specified ; if none is found. + /// + public SocketForumChannel GetForumChannel(ulong id) + => GetChannel(id) as SocketForumChannel; /// /// Gets a voice channel in this guild. ///