| @@ -1,5 +1,6 @@ | |||||
| using System; | using System; | ||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| using System.IO; | |||||
| using System.Linq; | using System.Linq; | ||||
| using System.Text; | using System.Text; | ||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| @@ -39,15 +40,129 @@ namespace Discord | |||||
| /// </summary> | /// </summary> | ||||
| /// <param name="title">The title of the post.</param> | /// <param name="title">The title of the post.</param> | ||||
| /// <param name="archiveDuration">The archive duration of the post.</param> | /// <param name="archiveDuration">The archive duration of the post.</param> | ||||
| /// <param name="message"> | |||||
| /// The starting message of the post. The content of the message supports full markdown. | |||||
| /// <param name="slowmode">The slowmode for the posts thread.</param> | |||||
| /// <param name="text">The message to be sent.</param> | |||||
| /// <param name="embed">The <see cref="Discord.EmbedType.Rich"/> <see cref="Embed"/> to be sent.</param> | |||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| /// <param name="allowedMentions"> | |||||
| /// Specifies if notifications are sent for mentioned users and roles in the message <paramref name="text"/>. | |||||
| /// If <c>null</c>, all mentioned roles and users will be notified. | |||||
| /// </param> | |||||
| /// <param name="components">The message components to be included with this message. Used for interactions.</param> | |||||
| /// <param name="stickers">A collection of stickers to send with the message.</param> | |||||
| /// <param name="embeds">A array of <see cref="Embed"/>s to send with this response. Max 10.</param> | |||||
| /// <param name="flags">A message flag to be applied to the sent message, only <see cref="MessageFlags.SuppressEmbeds"/> is permitted.</param> | |||||
| /// <returns> | |||||
| /// A task that represents the asynchronous creation operation. | |||||
| /// </returns> | |||||
| Task<IThreadChannel> 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); | |||||
| /// <summary> | |||||
| /// Creates a new post (thread) within the forum. | |||||
| /// </summary> | |||||
| /// <param name="title">The title of the post.</param> | |||||
| /// <param name="archiveDuration">The archive duration of the post.</param> | |||||
| /// <param name="slowmode">The slowmode for the posts thread.</param> | |||||
| /// <param name="filePath">The file path of the file.</param> | |||||
| /// <param name="text">The message to be sent.</param> | |||||
| /// <param name="embed">The <see cref="Discord.EmbedType.Rich" /> <see cref="Embed" /> to be sent.</param> | |||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| /// <param name="isSpoiler">Whether the message attachment should be hidden as a spoiler.</param> | |||||
| /// <param name="allowedMentions"> | |||||
| /// Specifies if notifications are sent for mentioned users and roles in the message <paramref name="text"/>. | |||||
| /// If <c>null</c>, all mentioned roles and users will be notified. | |||||
| /// </param> | |||||
| /// <param name="components">The message components to be included with this message. Used for interactions.</param> | |||||
| /// <param name="stickers">A collection of stickers to send with the file.</param> | |||||
| /// <param name="embeds">A array of <see cref="Embed"/>s to send with this response. Max 10.</param> | |||||
| /// <param name="flags">A message flag to be applied to the sent message, only <see cref="MessageFlags.SuppressEmbeds"/> is permitted.</param> | |||||
| /// <returns> | |||||
| /// A task that represents the asynchronous creation operation. | |||||
| /// </returns> | |||||
| Task<IThreadChannel> 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); | |||||
| /// <summary> | |||||
| /// Creates a new post (thread) within the forum. | |||||
| /// </summary> | |||||
| /// <param name="title">The title of the post.</param> | |||||
| /// <param name="stream">The <see cref="Stream" /> of the file to be sent.</param> | |||||
| /// <param name="filename">The name of the attachment.</param> | |||||
| /// <param name="archiveDuration">The archive duration of the post.</param> | |||||
| /// <param name="slowmode">The slowmode for the posts thread.</param> | |||||
| /// <param name="text">The message to be sent.</param> | |||||
| /// <param name="embed">The <see cref="Discord.EmbedType.Rich"/> <see cref="Embed"/> to be sent.</param> | |||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| /// <param name="isSpoiler">Whether the message attachment should be hidden as a spoiler.</param> | |||||
| /// <param name="allowedMentions"> | |||||
| /// Specifies if notifications are sent for mentioned users and roles in the message <paramref name="text"/>. | |||||
| /// If <c>null</c>, all mentioned roles and users will be notified. | |||||
| /// </param> | |||||
| /// <param name="components">The message components to be included with this message. Used for interactions.</param> | |||||
| /// <param name="stickers">A collection of stickers to send with the file.</param> | |||||
| /// <param name="embeds">A array of <see cref="Embed"/>s to send with this response. Max 10.</param> | |||||
| /// <param name="flags">A message flag to be applied to the sent message, only <see cref="MessageFlags.SuppressEmbeds"/> is permitted.</param> | |||||
| /// <returns> | |||||
| /// A task that represents the asynchronous creation operation. | |||||
| /// </returns> | |||||
| public Task<IThreadChannel> 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); | |||||
| /// <summary> | |||||
| /// Creates a new post (thread) within the forum. | |||||
| /// </summary> | |||||
| /// <param name="title">The title of the post.</param> | |||||
| /// <param name="attachment">The attachment containing the file and description.</param> | |||||
| /// <param name="archiveDuration">The archive duration of the post.</param> | |||||
| /// <param name="slowmode">The slowmode for the posts thread.</param> | |||||
| /// <param name="text">The message to be sent.</param> | |||||
| /// <param name="embed">The <see cref="Discord.EmbedType.Rich"/> <see cref="Embed"/> to be sent.</param> | |||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| /// <param name="allowedMentions"> | |||||
| /// Specifies if notifications are sent for mentioned users and roles in the message <paramref name="text"/>. | |||||
| /// If <c>null</c>, all mentioned roles and users will be notified. | |||||
| /// </param> | /// </param> | ||||
| /// <param name="components">The message components to be included with this message. Used for interactions.</param> | |||||
| /// <param name="stickers">A collection of stickers to send with the file.</param> | |||||
| /// <param name="embeds">A array of <see cref="Embed"/>s to send with this response. Max 10.</param> | |||||
| /// <param name="flags">A message flag to be applied to the sent message, only <see cref="MessageFlags.SuppressEmbeds"/> is permitted.</param> | |||||
| /// <returns> | |||||
| /// A task that represents the asynchronous creation operation. | |||||
| /// </returns> | |||||
| public Task<IThreadChannel> 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); | |||||
| /// <summary> | |||||
| /// Creates a new post (thread) within the forum. | |||||
| /// </summary> | |||||
| /// <param name="title">The title of the post.</param> | |||||
| /// <param name="attachments">A collection of attachments to upload.</param> | |||||
| /// <param name="archiveDuration">The archive duration of the post.</param> | |||||
| /// <param name="slowmode">The slowmode for the posts thread.</param> | /// <param name="slowmode">The slowmode for the posts thread.</param> | ||||
| /// <param name="text">The message to be sent.</param> | |||||
| /// <param name="embed">The <see cref="Discord.EmbedType.Rich"/> <see cref="Embed"/> to be sent.</param> | |||||
| /// <param name="options">The options to be used when sending the request.</param> | /// <param name="options">The options to be used when sending the request.</param> | ||||
| /// <param name="allowedMentions"> | |||||
| /// Specifies if notifications are sent for mentioned users and roles in the message <paramref name="text"/>. | |||||
| /// If <c>null</c>, all mentioned roles and users will be notified. | |||||
| /// </param> | |||||
| /// <param name="components">The message components to be included with this message. Used for interactions.</param> | |||||
| /// <param name="stickers">A collection of stickers to send with the file.</param> | |||||
| /// <param name="embeds">A array of <see cref="Embed"/>s to send with this response. Max 10.</param> | |||||
| /// <param name="flags">A message flag to be applied to the sent message, only <see cref="MessageFlags.SuppressEmbeds"/> is permitted.</param> | |||||
| /// <returns> | /// <returns> | ||||
| /// A task that represents the asynchronous creation operation. | /// A task that represents the asynchronous creation operation. | ||||
| /// </returns> | /// </returns> | ||||
| Task<IThreadChannel> CreatePostAsync(string title, ThreadArchiveDuration archiveDuration, Message message, int? slowmode = null, RequestOptions options = null); | |||||
| public Task<IThreadChannel> 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); | |||||
| /// <summary> | /// <summary> | ||||
| /// Gets a collection of active threads within this forum channel. | /// Gets a collection of active threads within this forum channel. | ||||
| @@ -1,74 +0,0 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Linq; | |||||
| using System.Text; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord | |||||
| { | |||||
| /// <summary> | |||||
| /// Represents a message created by a <see cref="MessageBuilder"/> that can be sent to a channel. | |||||
| /// </summary> | |||||
| public sealed class Message | |||||
| { | |||||
| /// <summary> | |||||
| /// Gets the content of the message. | |||||
| /// </summary> | |||||
| public string Content { get; } | |||||
| /// <summary> | |||||
| /// Gets whether or not this message should be read by a text-to-speech engine. | |||||
| /// </summary> | |||||
| public bool IsTTS { get; } | |||||
| /// <summary> | |||||
| /// Gets a collection of embeds sent along with this message. | |||||
| /// </summary> | |||||
| public IReadOnlyCollection<Embed> Embeds { get; } | |||||
| /// <summary> | |||||
| /// Gets the allowed mentions for this message. | |||||
| /// </summary> | |||||
| public AllowedMentions AllowedMentions { get; } | |||||
| /// <summary> | |||||
| /// Gets the message reference (reply to) for this message. | |||||
| /// </summary> | |||||
| public MessageReference MessageReference { get; } | |||||
| /// <summary> | |||||
| /// Gets the components of this message. | |||||
| /// </summary> | |||||
| public MessageComponent Components { get; } | |||||
| /// <summary> | |||||
| /// Gets a collection of sticker ids that will be sent with this message. | |||||
| /// </summary> | |||||
| public IReadOnlyCollection<ulong> StickerIds { get; } | |||||
| /// <summary> | |||||
| /// Gets a collection of files sent with this message. | |||||
| /// </summary> | |||||
| public IReadOnlyCollection<FileAttachment> Attachments { get; } | |||||
| /// <summary> | |||||
| /// Gets the message flags for this message. | |||||
| /// </summary> | |||||
| public MessageFlags Flags { get; } | |||||
| internal Message(string content, bool istts, IReadOnlyCollection<Embed> embeds, AllowedMentions allowedMentions, | |||||
| MessageReference messagereference, MessageComponent components, IReadOnlyCollection<ulong> stickerIds, | |||||
| IReadOnlyCollection<FileAttachment> attachments, MessageFlags flags) | |||||
| { | |||||
| Content = content; | |||||
| IsTTS = istts; | |||||
| Embeds = embeds; | |||||
| AllowedMentions = allowedMentions; | |||||
| MessageReference = messagereference; | |||||
| Components = components; | |||||
| StickerIds = stickerIds; | |||||
| Attachments = attachments; | |||||
| Flags = flags; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -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 | |||||
| { | |||||
| /// <summary> | |||||
| /// Represents a generic message builder that can build <see cref="Message"/>s. | |||||
| /// </summary> | |||||
| public class MessageBuilder | |||||
| { | |||||
| private string _content; | |||||
| private List<ISticker> _stickers; | |||||
| private List<EmbedBuilder> _embeds; | |||||
| /// <summary> | |||||
| /// Gets or sets the content of this message | |||||
| /// </summary> | |||||
| /// <exception cref="ArgumentOutOfRangeException">The content is bigger than the <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | |||||
| 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; | |||||
| } | |||||
| } | |||||
| /// <summary> | |||||
| /// Gets or sets whether or not this message is TTS. | |||||
| /// </summary> | |||||
| public bool IsTTS { get; set; } | |||||
| /// <summary> | |||||
| /// Gets or sets the embeds of this message. | |||||
| /// </summary> | |||||
| public List<EmbedBuilder> 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; | |||||
| } | |||||
| } | |||||
| /// <summary> | |||||
| /// Gets or sets the allowed mentions of this message. | |||||
| /// </summary> | |||||
| public AllowedMentions AllowedMentions { get; set; } | |||||
| /// <summary> | |||||
| /// Gets or sets the message reference (reply to) of this message. | |||||
| /// </summary> | |||||
| public MessageReference MessageReference { get; set; } | |||||
| /// <summary> | |||||
| /// Gets or sets the components of this message. | |||||
| /// </summary> | |||||
| public ComponentBuilder Components { get; set; } = new(); | |||||
| /// <summary> | |||||
| /// Gets or sets the stickers sent with this message. | |||||
| /// </summary> | |||||
| public List<ISticker> 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; | |||||
| } | |||||
| } | |||||
| /// <summary> | |||||
| /// Gets or sets the files sent with this message. | |||||
| /// </summary> | |||||
| public List<FileAttachment> Files { get; set; } = new(); | |||||
| /// <summary> | |||||
| /// Gets or sets the message flags. | |||||
| /// </summary> | |||||
| public MessageFlags Flags { get; set; } | |||||
| /// <summary> | |||||
| /// Sets the <see cref="Content"/> of this message. | |||||
| /// </summary> | |||||
| /// <param name="content">The content of the message.</param> | |||||
| /// <returns>The current builder.</returns> | |||||
| public MessageBuilder WithContent(string content) | |||||
| { | |||||
| Content = content; | |||||
| return this; | |||||
| } | |||||
| /// <summary> | |||||
| /// Sets the <see cref="IsTTS"/> of this message. | |||||
| /// </summary> | |||||
| /// <param name="isTTS">whether or not this message is tts.</param> | |||||
| /// <returns>The current builder.</returns> | |||||
| 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<SelectMenuOptionBuilder> 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<Embed>.Empty; | |||||
| return new Message( | |||||
| _content, | |||||
| IsTTS, | |||||
| embeds, | |||||
| AllowedMentions, | |||||
| MessageReference, | |||||
| Components?.Build(), | |||||
| _stickers != null && _stickers.Any() ? _stickers.Select(x => x.Id).ToImmutableArray() : ImmutableArray<ulong>.Empty, | |||||
| Files?.ToImmutableArray() ?? ImmutableArray<FileAttachment>.Empty, | |||||
| Flags | |||||
| ); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -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; } | |||||
| } | |||||
| } | |||||
| @@ -22,7 +22,6 @@ namespace Discord.API.Rest | |||||
| public Optional<string> Content { get; set; } | public Optional<string> Content { get; set; } | ||||
| public Optional<bool> IsTTS { get; set; } | |||||
| public Optional<Embed[]> Embeds { get; set; } | public Optional<Embed[]> Embeds { get; set; } | ||||
| public Optional<AllowedMentions> AllowedMentions { get; set; } | public Optional<AllowedMentions> AllowedMentions { get; set; } | ||||
| public Optional<ActionRowComponent[]> MessageComponent { get; set; } | public Optional<ActionRowComponent[]> MessageComponent { get; set; } | ||||
| @@ -39,26 +38,27 @@ namespace Discord.API.Rest | |||||
| var d = new Dictionary<string, object>(); | var d = new Dictionary<string, object>(); | ||||
| var payload = new Dictionary<string, object>(); | var payload = new Dictionary<string, object>(); | ||||
| var message = new Dictionary<string, object>(); | |||||
| payload["title"] = Title; | |||||
| payload["name"] = Title; | |||||
| payload["auto_archive_duration"] = ArchiveDuration; | payload["auto_archive_duration"] = ArchiveDuration; | ||||
| if (Slowmode.IsSpecified) | if (Slowmode.IsSpecified) | ||||
| payload["rate_limit_per_user"] = Slowmode.Value; | payload["rate_limit_per_user"] = Slowmode.Value; | ||||
| // message | |||||
| if (Content.IsSpecified) | if (Content.IsSpecified) | ||||
| payload["content"] = Content.Value; | |||||
| if (IsTTS.IsSpecified) | |||||
| payload["tts"] = IsTTS.Value; | |||||
| message["content"] = Content.Value; | |||||
| if (Embeds.IsSpecified) | if (Embeds.IsSpecified) | ||||
| payload["embeds"] = Embeds.Value; | |||||
| message["embeds"] = Embeds.Value; | |||||
| if (AllowedMentions.IsSpecified) | if (AllowedMentions.IsSpecified) | ||||
| payload["allowed_mentions"] = AllowedMentions.Value; | |||||
| message["allowed_mentions"] = AllowedMentions.Value; | |||||
| if (MessageComponent.IsSpecified) | if (MessageComponent.IsSpecified) | ||||
| payload["components"] = MessageComponent.Value; | |||||
| message["components"] = MessageComponent.Value; | |||||
| if (Stickers.IsSpecified) | if (Stickers.IsSpecified) | ||||
| payload["sticker_ids"] = Stickers.Value; | |||||
| message["sticker_ids"] = Stickers.Value; | |||||
| if (Flags.IsSpecified) | if (Flags.IsSpecified) | ||||
| payload["flags"] = Flags.Value; | |||||
| message["flags"] = Flags.Value; | |||||
| List<object> attachments = new(); | List<object> attachments = new(); | ||||
| @@ -79,7 +79,9 @@ namespace Discord.API.Rest | |||||
| }); | }); | ||||
| } | } | ||||
| payload["attachments"] = attachments; | |||||
| message["attachments"] = attachments; | |||||
| payload["message"] = message; | |||||
| var json = new StringBuilder(); | var json = new StringBuilder(); | ||||
| using (var text = new StringWriter(json)) | using (var text = new StringWriter(json)) | ||||
| @@ -19,26 +19,7 @@ namespace Discord.API.Rest | |||||
| [JsonProperty("rate_limit_per_user")] | [JsonProperty("rate_limit_per_user")] | ||||
| public Optional<int?> Slowmode { get; set; } | public Optional<int?> Slowmode { get; set; } | ||||
| // message | |||||
| [JsonProperty("content")] | |||||
| public string Content { get; set; } | |||||
| [JsonProperty("tts")] | |||||
| public Optional<bool> IsTTS { 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; } | |||||
| [JsonProperty("message")] | |||||
| public ForumThreadMessage Message { get; set; } | |||||
| } | } | ||||
| } | } | ||||
| @@ -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 | |||||
| } | |||||
| } | |||||
| @@ -39,6 +39,7 @@ namespace Discord.Rest | |||||
| ChannelType.Text => RestTextChannel.Create(discord, guild, model), | ChannelType.Text => RestTextChannel.Create(discord, guild, model), | ||||
| ChannelType.Voice => RestVoiceChannel.Create(discord, guild, model), | ChannelType.Voice => RestVoiceChannel.Create(discord, guild, model), | ||||
| ChannelType.Stage => RestStageChannel.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.Category => RestCategoryChannel.Create(discord, guild, model), | ||||
| ChannelType.PublicThread or ChannelType.PrivateThread or ChannelType.NewsThread => RestThreadChannel.Create(discord, guild, model), | ChannelType.PublicThread or ChannelType.PrivateThread or ChannelType.NewsThread => RestThreadChannel.Create(discord, guild, model), | ||||
| _ => new RestGuildChannel(discord, guild, model.Id), | _ => new RestGuildChannel(discord, guild, model.Id), | ||||
| @@ -103,47 +103,112 @@ namespace Discord.Rest | |||||
| return RestThreadUser.Create(client, channel.Guild, model, channel); | return RestThreadUser.Create(client, channel.Guild, model, channel); | ||||
| } | } | ||||
| public static async Task<RestThreadChannel> CreatePostAsync(IForumChannel channel, BaseDiscordClient client, string title, ThreadArchiveDuration archiveDuration, Message message, int? slowmode = null, RequestOptions options = null) | |||||
| 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) | |||||
| { | { | ||||
| Model model; | |||||
| 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."); | |||||
| 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<API.Embed[]>.Unspecified, | |||||
| Flags = message.Flags, | |||||
| IsTTS = message.IsTTS, | |||||
| MessageComponent = message.Components?.Components?.Any() ?? false ? message.Components.Components.Select(x => new API.ActionRowComponent(x)).ToArray() : Optional<API.ActionRowComponent[]>.Unspecified, | |||||
| Slowmode = slowmode, | |||||
| Stickers = message.StickerIds?.Any() ?? false ? message.StickerIds.ToArray() : Optional<ulong[]>.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<API.Embed[]>.Unspecified, | |||||
| Flags = message.Flags, | |||||
| IsTTS = message.IsTTS, | |||||
| Components = message.Components?.Components?.Any() ?? false ? message.Components.Components.Select(x => new API.ActionRowComponent(x)).ToArray() : Optional<API.ActionRowComponent[]>.Unspecified, | |||||
| Slowmode = slowmode, | |||||
| Stickers = message.StickerIds?.Any() ?? false ? message.StickerIds.ToArray() : Optional<ulong[]>.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<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); | return RestThreadChannel.Create(client, channel.Guild, model); | ||||
| } | } | ||||
| } | } | ||||
| @@ -2,6 +2,7 @@ using Discord.Rest; | |||||
| using System; | using System; | ||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| using System.Collections.Immutable; | using System.Collections.Immutable; | ||||
| using System.IO; | |||||
| using System.Linq; | using System.Linq; | ||||
| using System.Text; | using System.Text; | ||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| @@ -45,14 +46,46 @@ namespace Discord.WebSocket | |||||
| Topic = model.Topic.GetValueOrDefault(); | Topic = model.Topic.GetValueOrDefault(); | ||||
| DefaultAutoArchiveDuration = model.AutoArchiveDuration.GetValueOrDefault(ThreadArchiveDuration.OneDay); | DefaultAutoArchiveDuration = model.AutoArchiveDuration.GetValueOrDefault(ThreadArchiveDuration.OneDay); | ||||
| Tags = model.ForumTags.GetValueOrDefault(new API.ForumTags[0]).Select( | |||||
| 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()) | ||||
| ).ToImmutableArray(); | ).ToImmutableArray(); | ||||
| } | } | ||||
| /// <inheritdoc cref="IForumChannel.CreatePostAsync(string, ThreadArchiveDuration, Message, int?, RequestOptions)"/> | |||||
| public Task<RestThreadChannel> CreatePostAsync(string title, ThreadArchiveDuration archiveDuration, Message message, int? slowmode = null, RequestOptions options = null) | |||||
| => ThreadHelper.CreatePostAsync(this, Discord, title, archiveDuration, message, slowmode, options); | |||||
| /// <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)"/> | /// <inheritdoc cref="IForumChannel.GetActiveThreadsAsync(RequestOptions)"/> | ||||
| public Task<IReadOnlyCollection<RestThreadChannel>> GetActiveThreadsAsync(RequestOptions options = null) | public Task<IReadOnlyCollection<RestThreadChannel>> GetActiveThreadsAsync(RequestOptions options = null) | ||||
| @@ -71,8 +104,6 @@ namespace Discord.WebSocket | |||||
| => ThreadHelper.GetPublicArchivedThreadsAsync(this, Discord, limit, before, options); | => ThreadHelper.GetPublicArchivedThreadsAsync(this, Discord, limit, before, options); | ||||
| #region IForumChannel | #region IForumChannel | ||||
| async Task<IThreadChannel> IForumChannel.CreatePostAsync(string title, ThreadArchiveDuration archiveDuration, Message message, int? slowmode, RequestOptions options) | |||||
| => await CreatePostAsync(title, archiveDuration, message, slowmode, options).ConfigureAwait(false); | |||||
| async Task<IReadOnlyCollection<IThreadChannel>> IForumChannel.GetActiveThreadsAsync(RequestOptions options) | async Task<IReadOnlyCollection<IThreadChannel>> IForumChannel.GetActiveThreadsAsync(RequestOptions options) | ||||
| => await GetActiveThreadsAsync(options).ConfigureAwait(false); | => await GetActiveThreadsAsync(options).ConfigureAwait(false); | ||||
| async Task<IReadOnlyCollection<IThreadChannel>> IForumChannel.GetPublicArchivedThreadsAsync(int? limit, DateTimeOffset? before, RequestOptions options) | async Task<IReadOnlyCollection<IThreadChannel>> IForumChannel.GetPublicArchivedThreadsAsync(int? limit, DateTimeOffset? before, RequestOptions options) | ||||
| @@ -81,6 +112,17 @@ namespace Discord.WebSocket | |||||
| => await GetPrivateArchivedThreadsAsync(limit, before, options).ConfigureAwait(false); | => await GetPrivateArchivedThreadsAsync(limit, before, options).ConfigureAwait(false); | ||||
| async Task<IReadOnlyCollection<IThreadChannel>> IForumChannel.GetJoinedPrivateArchivedThreadsAsync(int? limit, DateTimeOffset? before, RequestOptions options) | async Task<IReadOnlyCollection<IThreadChannel>> IForumChannel.GetJoinedPrivateArchivedThreadsAsync(int? limit, DateTimeOffset? before, RequestOptions options) | ||||
| => await GetJoinedPrivateArchivedThreadsAsync(limit, before, options).ConfigureAwait(false); | => 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 | #endregion | ||||
| } | } | ||||
| } | } | ||||
| @@ -705,7 +705,15 @@ namespace Discord.WebSocket | |||||
| /// </returns> | /// </returns> | ||||
| public SocketThreadChannel GetThreadChannel(ulong id) | public SocketThreadChannel GetThreadChannel(ulong id) | ||||
| => GetChannel(id) as SocketThreadChannel; | => GetChannel(id) as SocketThreadChannel; | ||||
| /// <summary> | |||||
| /// Gets a forum channel in this guild. | |||||
| /// </summary> | |||||
| /// <param name="id">The snowflake identifier for the forum channel.</param> | |||||
| /// <returns> | |||||
| /// A forum channel associated with the specified <paramref name="id" />; <see langword="null"/> if none is found. | |||||
| /// </returns> | |||||
| public SocketForumChannel GetForumChannel(ulong id) | |||||
| => GetChannel(id) as SocketForumChannel; | |||||
| /// <summary> | /// <summary> | ||||
| /// Gets a voice channel in this guild. | /// Gets a voice channel in this guild. | ||||
| /// </summary> | /// </summary> | ||||