diff --git a/DisCatSharp/Entities/Embed/DiscordEmbedBuilder.cs b/DisCatSharp/Entities/Embed/DiscordEmbedBuilder.cs
index 09e47fd8c..cd4081c8d 100644
--- a/DisCatSharp/Entities/Embed/DiscordEmbedBuilder.cs
+++ b/DisCatSharp/Entities/Embed/DiscordEmbedBuilder.cs
@@ -1,652 +1,642 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2023 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.Collections.ObjectModel;
using System.Linq;
+using DisCatSharp.Attributes;
using DisCatSharp.Net;
namespace DisCatSharp.Entities;
///
/// Constructs embeds.
///
public sealed class DiscordEmbedBuilder
{
///
/// Gets or sets the embed's title.
///
public string Title
{
get => this._title;
set
{
if (value != null && value.Length > 256)
throw new ArgumentException("Title length cannot exceed 256 characters.", nameof(value));
this._title = value;
}
}
private string _title;
///
/// Gets or sets the embed's description.
///
public string Description
{
get => this._description;
set
{
if (value != null && value.Length > 4096)
throw new ArgumentException("Description length cannot exceed 4096 characters.", nameof(value));
this._description = value;
}
}
private string _description;
///
/// Gets or sets the url for the embed's title.
///
public string Url
{
get => this._url?.ToString();
set => this._url = string.IsNullOrEmpty(value) ? null : new Uri(value);
}
private Uri _url;
///
/// Gets or sets the embed's color.
///
public Optional Color { get; set; }
///
/// Gets or sets the embed's timestamp.
///
public DateTimeOffset? Timestamp { get; set; }
///
/// Gets or sets the embed's image url.
///
public string ImageUrl
{
get => this._imageUri?.ToString();
set => this._imageUri = string.IsNullOrEmpty(value) ? null : new DiscordUri(value);
}
private DiscordUri _imageUri;
///
/// Gets or sets the embed's author.
///
public EmbedAuthor Author { get; set; }
///
/// Gets or sets the embed's footer.
///
public EmbedFooter Footer { get; set; }
///
/// Gets or sets the embed's thumbnail.
///
public EmbedThumbnail Thumbnail { get; set; }
///
/// Gets the embed's fields.
///
public IReadOnlyList Fields { get; }
private readonly List _fields = new();
///
/// Constructs a new empty embed builder.
///
public DiscordEmbedBuilder()
{
this.Fields = new ReadOnlyCollection(this._fields);
}
///
/// Constructs a new embed builder using another embed as prototype.
///
/// Embed to use as prototype.
public DiscordEmbedBuilder(DiscordEmbed original)
: this()
{
this.Title = original.Title;
this.Description = original.Description;
this.Url = original.Url?.ToString();
this.ImageUrl = original.Image?.Url?.ToString();
this.Color = original.Color;
this.Timestamp = original.Timestamp;
if (original.Thumbnail != null)
this.Thumbnail = new EmbedThumbnail
{
Url = original.Thumbnail.Url?.ToString(),
Height = original.Thumbnail.Height,
Width = original.Thumbnail.Width
};
if (original.Author != null)
this.Author = new EmbedAuthor
{
IconUrl = original.Author.IconUrl?.ToString(),
Name = original.Author.Name,
Url = original.Author.Url?.ToString()
};
if (original.Footer != null)
this.Footer = new EmbedFooter
{
IconUrl = original.Footer.IconUrl?.ToString(),
Text = original.Footer.Text
};
if (original.Fields?.Any() == true)
this._fields.AddRange(original.Fields);
while (this._fields.Count > 25)
this._fields.RemoveAt(this._fields.Count - 1);
}
///
/// Sets the embed's title.
///
/// Title to set.
/// This embed builder.
public DiscordEmbedBuilder WithTitle(string title)
{
this.Title = title;
return this;
}
///
/// Sets the embed's description.
///
/// Description to set.
/// This embed builder.
public DiscordEmbedBuilder WithDescription(string description)
{
this.Description = description;
return this;
}
///
/// Sets the embed's title url.
///
/// Title url to set.
/// This embed builder.
public DiscordEmbedBuilder WithUrl(string url)
{
this.Url = url;
return this;
}
///
/// Sets the embed's title url.
///
/// Title url to set.
/// This embed builder.
public DiscordEmbedBuilder WithUrl(Uri url)
{
this._url = url;
return this;
}
///
/// Sets the embed's color.
///
/// Embed color to set.
/// This embed builder.
public DiscordEmbedBuilder WithColor(DiscordColor color)
{
this.Color = color;
return this;
}
///
/// Sets the embed's timestamp.
///
/// Timestamp to set.
/// This embed builder.
public DiscordEmbedBuilder WithTimestamp(DateTimeOffset? timestamp)
{
this.Timestamp = timestamp;
return this;
}
///
/// Sets the embed's timestamp.
///
/// Timestamp to set.
/// This embed builder.
public DiscordEmbedBuilder WithTimestamp(DateTime? timestamp)
{
this.Timestamp = timestamp == null ? null : new DateTimeOffset(timestamp.Value);
return this;
}
///
/// Sets the embed's timestamp based on a snowflake.
///
/// Snowflake to calculate timestamp from.
/// This embed builder.
public DiscordEmbedBuilder WithTimestamp(ulong snowflake)
{
this.Timestamp = new DateTimeOffset(2015, 1, 1, 0, 0, 0, TimeSpan.Zero).AddMilliseconds(snowflake >> 22);
return this;
}
///
/// Sets the embed's image url.
///
/// Image url to set.
/// This embed builder.
public DiscordEmbedBuilder WithImageUrl(string url)
{
this.ImageUrl = url;
return this;
}
///
/// Sets the embed's image url.
///
/// Image url to set.
/// This embed builder.
public DiscordEmbedBuilder WithImageUrl(Uri url)
{
this._imageUri = new DiscordUri(url);
return this;
}
///
/// Sets the embed's thumbnail.
///
/// Thumbnail url to set.
/// The height of the thumbnail to set.
/// The width of the thumbnail to set.
/// This embed builder.
public DiscordEmbedBuilder WithThumbnail(string url, int height = 0, int width = 0)
{
this.Thumbnail = new EmbedThumbnail
{
Url = url,
Height = height,
Width = width
};
return this;
}
///
/// Sets the embed's thumbnail.
///
/// Thumbnail url to set.
/// The height of the thumbnail to set.
/// The width of the thumbnail to set.
/// This embed builder.
public DiscordEmbedBuilder WithThumbnail(Uri url, int height = 0, int width = 0)
{
this.Thumbnail = new EmbedThumbnail
{
Uri = new DiscordUri(url),
Height = height,
Width = width
};
return this;
}
///
/// Sets the embed's author.
///
/// Author's name.
/// Author's url.
/// Author icon's url.
/// This embed builder.
public DiscordEmbedBuilder WithAuthor(string name = null, string url = null, string iconUrl = null)
{
if (!string.IsNullOrEmpty(name) && name.Length > 256)
throw new NotSupportedException("Embed author name can not exceed 256 chars. See https://discord.com/developers/docs/resources/channel#embed-limits.");
this.Author = string.IsNullOrEmpty(name) && string.IsNullOrEmpty(url) && string.IsNullOrEmpty(iconUrl)
? null
: new EmbedAuthor
{
Name = name,
Url = url,
IconUrl = iconUrl
};
return this;
}
///
/// Sets the embed's footer.
///
/// Footer's text.
/// Footer icon's url.
/// This embed builder.
public DiscordEmbedBuilder WithFooter(string text = null, string iconUrl = null)
{
if (text != null && text.Length > 2048)
throw new ArgumentException("Footer text length cannot exceed 2048 characters.", nameof(text));
this.Footer = string.IsNullOrEmpty(text) && string.IsNullOrEmpty(iconUrl)
? null
: new EmbedFooter
{
Text = text,
IconUrl = iconUrl
};
return this;
}
- ///
- /// Adds a field to this embed.
- ///
- /// Name of the field to add.
- /// Value of the field to add.
- /// Whether the field is to be inline or not.
- /// This embed builder.
- [Obsolete("DiscordEmbedFields should be constructed manually.")]
- public DiscordEmbedBuilder AddField(string name, string value, bool inline = false)
- => this.AddField(new DiscordEmbedField(name, value, inline));
-
///
/// Adds a field to this embed.
///
/// The field to add.
/// This embed builder.
public DiscordEmbedBuilder AddField(DiscordEmbedField field)
{
if (this._fields.Count >= 25)
throw new InvalidOperationException("Cannot add more than 25 fields.");
this._fields.Add(field);
return this;
}
///
/// Adds multiple fields to this embed.
///
/// The fields to add.
/// This embed builder.
public DiscordEmbedBuilder AddFields(params DiscordEmbedField[] fields)
=> this.AddFields((IEnumerable)fields);
///
/// Adds multiple fields to this embed.
///
/// The fields to add.
/// This embed builder.
public DiscordEmbedBuilder AddFields(IEnumerable fields)
=> fields.Aggregate(this, (x, y) => x.AddField(y));
///
/// Removes a field from this embed, if it is part of it.
///
/// The field to remove.
/// This embed builder.
public DiscordEmbedBuilder RemoveField(DiscordEmbedField field)
{
this._fields.Remove(field);
return this;
}
///
/// Removes multiple fields from this embed, if they are part of it.
///
/// The fields to remove.
/// This embed builder.
public DiscordEmbedBuilder RemoveFields(params DiscordEmbedField[] fields)
{
this.RemoveFields((IEnumerable)fields);
return this;
}
///
/// Removes multiple fields from this embed, if they are part of it.
///
/// The fields to remove.
/// This embed builder.
public DiscordEmbed RemoveFields(IEnumerable fields)
{
this._fields.RemoveAll(x => fields.Contains(x));
return this;
}
///
/// Removes a field of the specified index from this embed.
///
/// Index of the field to remove.
/// This embed builder.
public DiscordEmbedBuilder RemoveFieldAt(int index)
{
this._fields.RemoveAt(index);
return this;
}
///
/// Removes fields of the specified range from this embed.
///
/// Index of the first field to remove.
/// Number of fields to remove.
/// This embed builder.
public DiscordEmbedBuilder RemoveFieldRange(int index, int count)
{
this._fields.RemoveRange(index, count);
return this;
}
///
/// Removes all fields from this embed.
///
/// This embed builder.
public DiscordEmbedBuilder ClearFields()
{
this._fields.Clear();
return this;
}
///
/// Constructs a new embed from data supplied to this builder.
///
/// New discord embed.
public DiscordEmbed Build()
{
var embed = new DiscordEmbed
{
Title = this._title,
Description = this._description,
Url = this._url,
ColorInternal = this.Color.Map(e => e.Value),
Timestamp = this.Timestamp
};
if (this.Footer != null)
embed.Footer = new DiscordEmbedFooter
{
Text = this.Footer.Text,
IconUrl = this.Footer.IconUri
};
if (this.Author != null)
embed.Author = new DiscordEmbedAuthor
{
Name = this.Author.Name,
Url = this.Author.Uri,
IconUrl = this.Author.IconUri
};
if (this._imageUri != null)
embed.Image = new DiscordEmbedImage { Url = this._imageUri };
if (this.Thumbnail != null)
embed.Thumbnail = new DiscordEmbedThumbnail
{
Url = this.Thumbnail.Uri,
Height = this.Thumbnail.Height,
Width = this.Thumbnail.Width
};
embed.Fields = new ReadOnlyCollection(new List(this._fields)); // copy the list, don't wrap it, prevents mutation
var charCount = 0;
if (embed.Fields.Any())
{
foreach (var field in embed.Fields)
{
charCount += field.Name.Length;
charCount += field.Value.Length;
}
}
if (embed.Author != null && !string.IsNullOrEmpty(embed.Author.Name))
charCount += embed.Author.Name.Length;
if (embed.Footer != null && !string.IsNullOrEmpty(embed.Footer.Text))
charCount += embed.Footer.Text.Length;
if (!string.IsNullOrEmpty(embed.Title))
charCount += embed.Title.Length;
if (!string.IsNullOrEmpty(embed.Description))
charCount += embed.Description.Length;
return charCount >= 6000
? throw new NotSupportedException("Total char count can not exceed 6000 chars. See https://discord.com/developers/docs/resources/channel#embed-limits.")
: embed;
}
///
/// Implicitly converts this builder to an embed.
///
/// Builder to convert.
public static implicit operator DiscordEmbed(DiscordEmbedBuilder builder)
=> builder?.Build();
///
/// Represents an embed author.
///
public class EmbedAuthor
{
///
/// Gets or sets the name of the author.
///
public string Name
{
get => this._name;
set
{
if (value != null && value.Length > 256)
throw new ArgumentException("Author name length cannot exceed 256 characters.", nameof(value));
this._name = value;
}
}
private string _name;
///
/// Gets or sets the Url to which the author's link leads.
///
public string Url
{
get => this.Uri?.ToString();
set => this.Uri = string.IsNullOrEmpty(value) ? null : new Uri(value);
}
internal Uri Uri;
///
/// Gets or sets the Author's icon url.
///
public string IconUrl
{
get => this.IconUri?.ToString();
set => this.IconUri = string.IsNullOrEmpty(value) ? null : new DiscordUri(value);
}
internal DiscordUri IconUri;
}
///
/// Represents an embed footer.
///
public class EmbedFooter
{
///
/// Gets or sets the text of the footer.
///
public string Text
{
get => this._text;
set
{
if (value != null && value.Length > 2048)
throw new ArgumentException("Footer text length cannot exceed 2048 characters.", nameof(value));
this._text = value;
}
}
private string _text;
///
/// Gets or sets the Url
///
public string IconUrl
{
get => this.IconUri?.ToString();
set => this.IconUri = string.IsNullOrEmpty(value) ? null : new DiscordUri(value);
}
internal DiscordUri IconUri;
}
///
/// Represents an embed thumbnail.
///
public class EmbedThumbnail
{
///
/// Gets or sets the thumbnail's image url.
///
public string Url
{
get => this.Uri?.ToString();
set => this.Uri = string.IsNullOrEmpty(value) ? null : new DiscordUri(value);
}
internal DiscordUri Uri;
///
/// Gets or sets the thumbnail's height.
///
public int Height
{
get => this._height;
set => this._height = value >= 0 ? value : 0;
}
private int _height;
///
/// Gets or sets the thumbnail's width.
///
public int Width
{
get => this._width;
set => this._width = value >= 0 ? value : 0;
}
private int _width;
}
}