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; } }