diff --git a/DisCatSharp/Entities/Message/DiscordMessageBuilder.cs b/DisCatSharp/Entities/Message/DiscordMessageBuilder.cs
index 8d470b611..92c396e51 100644
--- a/DisCatSharp/Entities/Message/DiscordMessageBuilder.cs
+++ b/DisCatSharp/Entities/Message/DiscordMessageBuilder.cs
@@ -1,459 +1,459 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2022 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace DisCatSharp.Entities;
///
/// Constructs a Message to be sent.
///
public sealed class DiscordMessageBuilder
{
///
/// Gets or Sets the Message to be sent.
///
public string Content
{
get => this._content;
set
{
if (value != null && value.Length > 2000)
throw new ArgumentException("Content cannot exceed 2000 characters.", nameof(value));
this._content = value;
}
}
private string _content;
///
/// Gets or sets the embed for the builder. This will always set the builder to have one embed.
///
public DiscordEmbed Embed
{
get => this._embeds.Count > 0 ? this._embeds[0] : null;
set
{
this._embeds.Clear();
this._embeds.Add(value);
}
}
///
/// Gets the Sticker to be send.
///
public DiscordSticker Sticker { get; set; }
///
/// Gets the Embeds to be sent.
///
public IReadOnlyList Embeds => this._embeds;
private readonly List _embeds = new();
///
/// Gets or Sets if the message should be TTS.
///
public bool IsTts { get; set; }
///
/// Whether to keep previous attachments.
///
internal bool? KeepAttachmentsInternal;
///
/// Gets the Allowed Mentions for the message to be sent.
///
public List Mentions { get; private set; }
///
/// Gets the Files to be sent in the Message.
///
public IReadOnlyCollection Files => this.FilesInternal;
internal readonly List FilesInternal = new();
///
/// Gets the components that will be attached to the message.
///
public IReadOnlyList Components => this.ComponentsInternal;
internal readonly List ComponentsInternal = new(5);
///
/// Gets the Attachments to be sent in the Message.
///
public IReadOnlyList Attachments => this.AttachmentsInternal;
internal readonly List AttachmentsInternal = new();
///
/// Gets the Reply Message ID.
///
public ulong? ReplyId { get; private set; }
///
/// Gets if the Reply should mention the user.
///
public bool MentionOnReply { get; private set; }
///
/// Gets if the embeds should be suppressed.
///
public bool Suppressed { get; private set; }
///
/// Gets if the Reply will error if the Reply Message Id does not reference a valid message.
/// If set to false, invalid replies are send as a regular message.
/// Defaults to false.
///
public bool FailOnInvalidReply { get; set; }
///
/// Sets the Content of the Message.
///
/// The content to be set.
/// The current builder to be chained.
public DiscordMessageBuilder WithContent(string content)
{
this.Content = content;
return this;
}
///
/// Adds a sticker to the message. Sticker must be from current guild.
///
/// The sticker to add.
/// The current builder to be chained.
public DiscordMessageBuilder WithSticker(DiscordSticker sticker)
{
this.Sticker = sticker;
return this;
}
///
/// Adds a row of components to a message, up to 5 components per row, and up to 5 rows per message.
///
/// The components to add to the message.
/// The current builder to be chained.
/// No components were passed.
public DiscordMessageBuilder AddComponents(params DiscordComponent[] components)
=> this.AddComponents((IEnumerable)components);
///
/// Appends several rows of components to the message
///
/// The rows of components to add, holding up to five each.
///
public DiscordMessageBuilder AddComponents(IEnumerable components)
{
var ara = components.ToArray();
if (ara.Length + this.ComponentsInternal.Count > 5)
throw new ArgumentException("ActionRow count exceeds maximum of five.");
foreach (var ar in ara)
this.ComponentsInternal.Add(ar);
return this;
}
///
/// Adds a row of components to a message, up to 5 components per row, and up to 5 rows per message.
///
/// The components to add to the message.
/// The current builder to be chained.
/// No components were passed.
public DiscordMessageBuilder AddComponents(IEnumerable components)
{
var cmpArr = components.ToArray();
var count = cmpArr.Length;
if (!cmpArr.Any())
throw new ArgumentOutOfRangeException(nameof(components), "You must provide at least one component");
if (count > 5)
throw new ArgumentException("Cannot add more than 5 components per action row!");
var comp = new DiscordActionRowComponent(cmpArr);
this.ComponentsInternal.Add(comp);
return this;
}
///
/// Sets if the message should be TTS.
///
/// If TTS should be set.
/// The current builder to be chained.
public DiscordMessageBuilder HasTts(bool isTts)
{
this.IsTts = isTts;
return this;
}
///
/// Sets the embed for the current builder.
///
/// The embed that should be set.
/// The current builder to be chained.
public DiscordMessageBuilder WithEmbed(DiscordEmbed embed)
{
if (embed == null)
return this;
this.Embed = embed;
return this;
}
///
/// Appends an embed to the current builder.
///
/// The embed that should be appended.
/// The current builder to be chained.
public DiscordMessageBuilder AddEmbed(DiscordEmbed embed)
{
if (embed == null)
return this; //Providing null embeds will produce a 400 response from Discord.//
this._embeds.Add(embed);
return this;
}
///
/// Appends several embeds to the current builder.
///
/// The embeds that should be appended.
/// The current builder to be chained.
public DiscordMessageBuilder AddEmbeds(IEnumerable embeds)
{
this._embeds.AddRange(embeds);
return this;
}
///
/// Sets if the message has allowed mentions.
///
/// The allowed Mention that should be sent.
/// The current builder to be chained.
public DiscordMessageBuilder WithAllowedMention(IMention allowedMention)
{
if (this.Mentions != null)
this.Mentions.Add(allowedMention);
else
this.Mentions = new List { allowedMention };
return this;
}
///
/// Sets if the message has allowed mentions.
///
/// The allowed Mentions that should be sent.
/// The current builder to be chained.
public DiscordMessageBuilder WithAllowedMentions(IEnumerable allowedMentions)
{
if (this.Mentions != null)
this.Mentions.AddRange(allowedMentions);
else
this.Mentions = allowedMentions.ToList();
return this;
}
///
/// Sets if the message has files to be sent.
///
/// The fileName that the file should be sent as.
/// The Stream to the file.
/// Tells the API Client to reset the stream position to what it was after the file is sent.
/// Description of the file.
/// The current builder to be chained.
public DiscordMessageBuilder WithFile(string fileName, Stream stream, bool resetStreamPosition = false, string description = null)
{
if (this.Files.Count > 10)
throw new ArgumentException("Cannot send more than 10 files with a single message.");
if (this.FilesInternal.Any(x => x.FileName == fileName))
throw new ArgumentException("A File with that filename already exists");
if (resetStreamPosition)
this.FilesInternal.Add(new DiscordMessageFile(fileName, stream, stream.Position, description: description));
else
this.FilesInternal.Add(new DiscordMessageFile(fileName, stream, null, description: description));
return this;
}
///
/// Sets if the message has files to be sent.
///
/// The Stream to the file.
/// Tells the API Client to reset the stream position to what it was after the file is sent.
/// Description of the file.
/// The current builder to be chained.
public DiscordMessageBuilder WithFile(FileStream stream, bool resetStreamPosition = false, string description = null)
{
if (this.Files.Count > 10)
throw new ArgumentException("Cannot send more than 10 files with a single message.");
if (this.FilesInternal.Any(x => x.FileName == stream.Name))
throw new ArgumentException("A File with that filename already exists");
if (resetStreamPosition)
this.FilesInternal.Add(new DiscordMessageFile(stream.Name, stream, stream.Position, description: description));
else
this.FilesInternal.Add(new DiscordMessageFile(stream.Name, stream, null, description: description));
return this;
}
///
/// Sets if the message has files to be sent.
///
/// The Files that should be sent.
/// Tells the API Client to reset the stream position to what it was after the file is sent.
/// The current builder to be chained.
public DiscordMessageBuilder WithFiles(Dictionary files, bool resetStreamPosition = false)
{
if (this.Files.Count + files.Count > 10)
throw new ArgumentException("Cannot send more than 10 files with a single message.");
foreach (var file in files)
{
if (this.FilesInternal.Any(x => x.FileName == file.Key))
throw new ArgumentException("A File with that filename already exists");
if (resetStreamPosition)
this.FilesInternal.Add(new DiscordMessageFile(file.Key, file.Value, file.Value.Position));
else
this.FilesInternal.Add(new DiscordMessageFile(file.Key, file.Value, null));
}
return this;
}
///
/// Modifies the given attachments on edit.
///
/// Attachments to edit.
///
public DiscordMessageBuilder ModifyAttachments(IEnumerable attachments)
{
this.AttachmentsInternal.AddRange(attachments);
return this;
}
///
/// Whether to keep the message attachments, if new ones are added.
///
///
public DiscordMessageBuilder KeepAttachments(bool keep)
{
this.KeepAttachmentsInternal = keep;
return this;
}
///
/// Sets if the message is a reply
///
/// The ID of the message to reply to.
/// If we should mention the user in the reply.
/// Whether sending a reply that references an invalid message should be
/// The current builder to be chained.
public DiscordMessageBuilder WithReply(ulong messageId, bool mention = false, bool failOnInvalidReply = false)
{
this.ReplyId = messageId;
this.MentionOnReply = mention;
this.FailOnInvalidReply = failOnInvalidReply;
if (mention)
{
this.Mentions ??= new List();
this.Mentions.Add(new RepliedUserMention());
}
return this;
}
///
/// Sends the Message to a specific channel
///
/// The channel the message should be sent to.
/// The current builder to be chained.
public Task SendAsync(DiscordChannel channel) => channel.SendMessageAsync(this);
///
/// Sends the modified message.
/// Note: Message replies cannot be modified. To clear the reply, simply pass to .
///
/// The original Message to modify.
/// The current builder to be chained.
public Task ModifyAsync(DiscordMessage msg) => msg.ModifyAsync(this);
///
/// Clears all message components on this builder.
///
public void ClearComponents()
=> this.ComponentsInternal.Clear();
///
/// Allows for clearing the Message Builder so that it can be used again to send a new message.
///
public void Clear()
{
this.Content = "";
this._embeds.Clear();
this.IsTts = false;
this.Mentions = null;
this.FilesInternal.Clear();
this.ReplyId = null;
this.MentionOnReply = false;
this.ComponentsInternal.Clear();
this.Suppressed = false;
this.Sticker = null;
this.AttachmentsInternal.Clear();
this.KeepAttachmentsInternal = false;
}
///
/// Does the validation before we send a the Create/Modify request.
///
/// Tells the method to perform the Modify Validation or Create Validation.
internal void Validate(bool isModify = false)
{
if (this._embeds.Count > 10)
throw new ArgumentException("A message can only have up to 10 embeds.");
if (!isModify)
{
- if (this.Files?.Count == 0 && string.IsNullOrEmpty(this.Content) && (!this.Embeds?.Any() ?? true) && this.Sticker is null)
- throw new ArgumentException("You must specify content, an embed, a sticker or at least one file.");
+ if (this.Files?.Count == 0 && string.IsNullOrEmpty(this.Content) && (!this.Embeds?.Any() ?? true) && this.Sticker is null && (!this.Embeds?.Any() ?? true))
+ throw new ArgumentException("You must specify content, an embed, a sticker, a component or at least one file.");
if (this.Components.Count > 5)
throw new InvalidOperationException("You can only have 5 action rows per message.");
if (this.Components.Any(c => c.Components.Count > 5))
throw new InvalidOperationException("Action rows can only have 5 components");
}
}
}
diff --git a/DisCatSharp/Entities/Webhook/DiscordWebhookBuilder.cs b/DisCatSharp/Entities/Webhook/DiscordWebhookBuilder.cs
index 1f0ae1792..c1d110702 100644
--- a/DisCatSharp/Entities/Webhook/DiscordWebhookBuilder.cs
+++ b/DisCatSharp/Entities/Webhook/DiscordWebhookBuilder.cs
@@ -1,462 +1,462 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2022 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using DisCatSharp.Enums;
namespace DisCatSharp.Entities;
///
/// Constructs ready-to-send webhook requests.
///
public sealed class DiscordWebhookBuilder
{
///
/// Username to use for this webhook request.
///
public Optional Username { get; set; }
///
/// Avatar url to use for this webhook request.
///
public Optional AvatarUrl { get; set; }
///
/// Whether this webhook request is text-to-speech.
///
public bool IsTts { get; set; }
///
/// Message to send on this webhook request.
///
public string Content
{
get => this._content;
set
{
if (value != null && value.Length > 2000)
throw new ArgumentException("Content length cannot exceed 2000 characters.", nameof(value));
this._content = value;
}
}
private string _content;
///
/// Name of the new thread.
/// Only works if the webhook is send in a .
///
public string ThreadName { get; set; }
///
/// Whether to keep previous attachments.
///
internal bool? KeepAttachmentsInternal;
///
/// Embeds to send on this webhook request.
///
public IReadOnlyList Embeds => this._embeds;
private readonly List _embeds = new();
///
/// Files to send on this webhook request.
///
public IReadOnlyList Files => this._files;
private readonly List _files = new();
///
/// Mentions to send on this webhook request.
///
public IReadOnlyList Mentions => this._mentions;
private readonly List _mentions = new();
///
/// Gets the components.
///
public IReadOnlyList Components => this._components;
private readonly List _components = new();
///
/// Attachments to keep on this webhook request.
///
public IEnumerable Attachments => this.AttachmentsInternal;
internal readonly List AttachmentsInternal = new();
///
/// Constructs a new empty webhook request builder.
///
public DiscordWebhookBuilder() { } // I still see no point in initializing collections with empty collections. //
///
/// Adds a row of components to the builder, up to 5 components per row, and up to 5 rows per message.
///
/// The components to add to the builder.
/// The current builder to be chained.
/// No components were passed.
public DiscordWebhookBuilder AddComponents(params DiscordComponent[] components)
=> this.AddComponents((IEnumerable)components);
///
/// Appends several rows of components to the builder
///
/// The rows of components to add, holding up to five each.
///
public DiscordWebhookBuilder AddComponents(IEnumerable components)
{
var ara = components.ToArray();
if (ara.Length + this._components.Count > 5)
throw new ArgumentException("ActionRow count exceeds maximum of five.");
foreach (var ar in ara)
this._components.Add(ar);
return this;
}
///
/// Adds a row of components to the builder, up to 5 components per row, and up to 5 rows per message.
///
/// The components to add to the builder.
/// The current builder to be chained.
/// No components were passed.
public DiscordWebhookBuilder AddComponents(IEnumerable components)
{
var cmpArr = components.ToArray();
var count = cmpArr.Length;
if (!cmpArr.Any())
throw new ArgumentOutOfRangeException(nameof(components), "You must provide at least one component");
if (count > 5)
throw new ArgumentException("Cannot add more than 5 components per action row!");
var comp = new DiscordActionRowComponent(cmpArr);
this._components.Add(comp);
return this;
}
///
/// Sets the username for this webhook builder.
///
/// Username of the webhook
public DiscordWebhookBuilder WithUsername(string username)
{
this.Username = username;
return this;
}
///
/// Sets the avatar of this webhook builder from its url.
///
/// Avatar url of the webhook
public DiscordWebhookBuilder WithAvatarUrl(string avatarUrl)
{
this.AvatarUrl = avatarUrl;
return this;
}
///
/// Indicates if the webhook must use text-to-speech.
///
/// Text-to-speech
public DiscordWebhookBuilder WithTts(bool tts)
{
this.IsTts = tts;
return this;
}
///
/// Sets the message to send at the execution of the webhook.
///
/// Message to send.
public DiscordWebhookBuilder WithContent(string content)
{
this.Content = content;
return this;
}
///
/// Sets the thread name to create at the execution of the webhook.
/// Only works for .
///
/// The thread name.
public DiscordWebhookBuilder WithThreadName(string name)
{
this.ThreadName = name;
return this;
}
///
/// Adds an embed to send at the execution of the webhook.
///
/// Embed to add.
public DiscordWebhookBuilder AddEmbed(DiscordEmbed embed)
{
if (embed != null)
this._embeds.Add(embed);
return this;
}
///
/// Adds the given embeds to send at the execution of the webhook.
///
/// Embeds to add.
public DiscordWebhookBuilder AddEmbeds(IEnumerable embeds)
{
this._embeds.AddRange(embeds);
return this;
}
///
/// Adds a file to send at the execution of the webhook.
///
/// Name of the file.
/// File data.
/// Tells the API Client to reset the stream position to what it was after the file is sent.
/// Description of the file.
public DiscordWebhookBuilder AddFile(string filename, Stream data, bool resetStreamPosition = false, string description = null)
{
if (this.Files.Count > 10)
throw new ArgumentException("Cannot send more than 10 files with a single message.");
if (this._files.Any(x => x.FileName == filename))
throw new ArgumentException("A File with that filename already exists");
if (resetStreamPosition)
this._files.Add(new DiscordMessageFile(filename, data, data.Position, description: description));
else
this._files.Add(new DiscordMessageFile(filename, data, null, description: description));
return this;
}
///
/// Sets if the message has files to be sent.
///
/// The Stream to the file.
/// Tells the API Client to reset the stream position to what it was after the file is sent.
/// Description of the file.
///
public DiscordWebhookBuilder AddFile(FileStream stream, bool resetStreamPosition = false, string description = null)
{
if (this.Files.Count > 10)
throw new ArgumentException("Cannot send more than 10 files with a single message.");
if (this._files.Any(x => x.FileName == stream.Name))
throw new ArgumentException("A File with that filename already exists");
if (resetStreamPosition)
this._files.Add(new DiscordMessageFile(stream.Name, stream, stream.Position, description: description));
else
this._files.Add(new DiscordMessageFile(stream.Name, stream, null, description: description));
return this;
}
///
/// Adds the given files to send at the execution of the webhook.
///
/// Dictionary of file name and file data.
/// Tells the API Client to reset the stream position to what it was after the file is sent.
public DiscordWebhookBuilder AddFiles(Dictionary files, bool resetStreamPosition = false)
{
if (this.Files.Count + files.Count > 10)
throw new ArgumentException("Cannot send more than 10 files with a single message.");
foreach (var file in files)
{
if (this._files.Any(x => x.FileName == file.Key))
throw new ArgumentException("A File with that filename already exists");
if (resetStreamPosition)
this._files.Add(new DiscordMessageFile(file.Key, file.Value, file.Value.Position));
else
this._files.Add(new DiscordMessageFile(file.Key, file.Value, null));
}
return this;
}
///
/// Modifies the given attachments on edit.
///
/// Attachments to edit.
///
public DiscordWebhookBuilder ModifyAttachments(IEnumerable attachments)
{
this.AttachmentsInternal.AddRange(attachments);
return this;
}
///
/// Whether to keep the message attachments, if new ones are added.
///
///
public DiscordWebhookBuilder KeepAttachments(bool keep)
{
this.KeepAttachmentsInternal = keep;
return this;
}
///
/// Adds the mention to the mentions to parse, etc. at the execution of the webhook.
///
/// Mention to add.
public DiscordWebhookBuilder AddMention(IMention mention)
{
this._mentions.Add(mention);
return this;
}
///
/// Adds the mentions to the mentions to parse, etc. at the execution of the webhook.
///
/// Mentions to add.
public DiscordWebhookBuilder AddMentions(IEnumerable mentions)
{
this._mentions.AddRange(mentions);
return this;
}
///
/// Executes a webhook.
///
/// The webhook that should be executed.
/// The message sent
public async Task SendAsync(DiscordWebhook webhook) => await webhook.ExecuteAsync(this).ConfigureAwait(false);
///
/// Executes a webhook.
///
/// The webhook that should be executed.
/// Target thread id.
/// The message sent
public async Task SendAsync(DiscordWebhook webhook, ulong threadId) => await webhook.ExecuteAsync(this, threadId.ToString()).ConfigureAwait(false);
///
/// Sends the modified webhook message.
///
/// The webhook that should be executed.
/// The message to modify.
/// The modified message
public async Task ModifyAsync(DiscordWebhook webhook, DiscordMessage message) => await this.ModifyAsync(webhook, message.Id).ConfigureAwait(false);
///
/// Sends the modified webhook message.
///
/// The webhook that should be executed.
/// The id of the message to modify.
/// The modified message
public async Task ModifyAsync(DiscordWebhook webhook, ulong messageId) => await webhook.EditMessageAsync(messageId, this).ConfigureAwait(false);
///
/// Sends the modified webhook message.
///
/// The webhook that should be executed.
/// The message to modify.
/// Target thread.
/// The modified message
public async Task ModifyAsync(DiscordWebhook webhook, DiscordMessage message, DiscordThreadChannel thread) => await this.ModifyAsync(webhook, message.Id, thread.Id).ConfigureAwait(false);
///
/// Sends the modified webhook message.
///
/// The webhook that should be executed.
/// The id of the message to modify.
/// Target thread id.
/// The modified message
public async Task ModifyAsync(DiscordWebhook webhook, ulong messageId, ulong threadId) => await webhook.EditMessageAsync(messageId, this, threadId.ToString()).ConfigureAwait(false);
///
/// Clears all message components on this builder.
///
public void ClearComponents()
=> this._components.Clear();
///
/// Allows for clearing the Webhook Builder so that it can be used again to send a new message.
///
public void Clear()
{
this.Content = "";
this._embeds.Clear();
this.IsTts = false;
this._mentions.Clear();
this._files.Clear();
this.AttachmentsInternal.Clear();
this._components.Clear();
this.KeepAttachmentsInternal = false;
this.ThreadName = null;
}
///
/// Does the validation before we send a the Create/Modify request.
///
/// Tells the method to perform the Modify Validation or Create Validation.
/// Tells the method to perform the follow up message validation.
/// Tells the method to perform the interaction response validation.
internal void Validate(bool isModify = false, bool isFollowup = false, bool isInteractionResponse = false)
{
if (isModify)
{
if (this.Username.HasValue)
throw new ArgumentException("You cannot change the username of a message.");
if (this.AvatarUrl.HasValue)
throw new ArgumentException("You cannot change the avatar of a message.");
}
else if (isFollowup)
{
if (this.Username.HasValue)
throw new ArgumentException("You cannot change the username of a follow up message.");
if (this.AvatarUrl.HasValue)
throw new ArgumentException("You cannot change the avatar of a follow up message.");
}
else if (isInteractionResponse)
{
if (this.Username.HasValue)
throw new ArgumentException("You cannot change the username of an interaction response.");
if (this.AvatarUrl.HasValue)
throw new ArgumentException("You cannot change the avatar of an interaction response.");
}
else
{
- if (this.Files?.Count == 0 && string.IsNullOrEmpty(this.Content) && !this.Embeds.Any())
- throw new ArgumentException("You must specify content, an embed, or at least one file.");
+ if (this.Files?.Count == 0 && string.IsNullOrEmpty(this.Content) && !this.Embeds.Any() && !this.Components.Any())
+ throw new ArgumentException("You must specify content, an embed, a component, or at least one file.");
}
}
}