diff --git a/DisCatSharp/Entities/Message/DiscordMessageBuilder.cs b/DisCatSharp/Entities/Message/DiscordMessageBuilder.cs
index 99dd486e0..46e6076b9 100644
--- a/DisCatSharp/Entities/Message/DiscordMessageBuilder.cs
+++ b/DisCatSharp/Entities/Message/DiscordMessageBuilder.cs
@@ -1,445 +1,445 @@
// This file is part of the DisCatSharp project.
//
// Copyright (c) 2021 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; } = false;
///
/// Gets the Allowed Mentions for the message to be sent.
///
public List Mentions { get; private set; } = null;
///
/// Gets the Files to be sent in the Message.
///
public IReadOnlyCollection Files => this._files;
internal readonly List _files = new();
///
/// Gets the components that will be attached to the message.
///
public IReadOnlyList Components => this._components;
internal readonly List _components = new(5);
///
/// Gets the Attachments to be sent in the Message.
///
public IReadOnlyList Attachments => this._attachments;
internal readonly List _attachments = new();
///
/// Gets the Reply Message ID.
///
public ulong? ReplyId { get; private set; } = null;
///
/// Gets if the Reply should mention the user.
///
public bool MentionOnReply { get; private set; } = false;
///
/// Gets if the embeds should be suppressed.
///
public bool Suppressed { get; private set; } = false;
///
/// 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._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 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._components.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._files.Any(x => x.FileName == fileName))
throw new ArgumentException("A File with that filename already exists");
if (resetStreamPosition)
this._files.Add(new DiscordMessageFile(fileName, stream, stream.Position, description: description));
else
this._files.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._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;
}
///
/// 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._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;
}
///
/// Keeps the given attachments on edit.
/// You can modify the attachments descriptions / names before inserting it here.
///
- /// Attachments to keep (on edit).
+ /// Attachments to keep and optional edit (on edit).
///
- public DiscordMessageBuilder KeepAttachments(IEnumerable attachments)
+ public DiscordMessageBuilder KeepOrModifyAttachments(IEnumerable attachments)
{
this._attachments.AddRange(attachments);
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._components.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._files.Clear();
this.ReplyId = null;
this.MentionOnReply = false;
this._components.Clear();
this.Suppressed = false;
this.Sticker = null;
this._attachments.Clear();
}
///
/// 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.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");
}
}
}
}