using System; using System.Collections.Generic; using System.Collections.Immutable; namespace Discord { public class EmbedBuilder { private readonly Embed _embed; public const int MaxFieldCount = 25; public const int MaxTitleLength = 256; public const int MaxDescriptionLength = 2048; public const int MaxEmbedLength = 6000; // user bot limit is 2000, but we don't validate that here. public EmbedBuilder() { _embed = new Embed(EmbedType.Rich); Fields = new List(); } public string Title { get => _embed.Title; set { if (value?.Length > MaxTitleLength) throw new ArgumentException($"Title length must be less than or equal to {MaxTitleLength}.", nameof(Title)); _embed.Title = value; } } public string Description { get => _embed.Description; set { if (value?.Length > MaxDescriptionLength) throw new ArgumentException($"Description length must be less than or equal to {MaxDescriptionLength}.", nameof(Description)); _embed.Description = value; } } public string Url { get => _embed.Url; set { if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI", nameof(Url)); _embed.Url = value; } } public string ThumbnailUrl { get => _embed.Thumbnail?.Url; set { if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI", nameof(ThumbnailUrl)); _embed.Thumbnail = new EmbedThumbnail(value, null, null, null); } } public string ImageUrl { get => _embed.Image?.Url; set { if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI", nameof(ImageUrl)); _embed.Image = new EmbedImage(value, null, null, null); } } public string VideoUrl { get => _embed.Video?.Url; set { if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI", nameof(VideoUrl)); _embed.Video = new EmbedVideo(value, null, null); } } public DateTimeOffset? Timestamp { get => _embed.Timestamp; set { _embed.Timestamp = value; } } public Color? Color { get => _embed.Color; set { _embed.Color = value; } } public EmbedAuthorBuilder Author { get; set; } public EmbedFooterBuilder Footer { get; set; } public EmbedProviderBuilder Provider { get; set; } private List _fields; public List Fields { get => _fields; set { if (value == null) throw new ArgumentNullException("Cannot set an embed builder's fields collection to null", nameof(Fields)); if (value.Count > MaxFieldCount) throw new ArgumentException($"Field count must be less than or equal to {MaxFieldCount}.", nameof(Fields)); _fields = value; } } public EmbedBuilder WithTitle(string title) { Title = title; return this; } public EmbedBuilder WithDescription(string description) { Description = description; return this; } public EmbedBuilder WithUrl(string url) { Url = url; return this; } public EmbedBuilder WithThumbnailUrl(string thumbnailUrl) { ThumbnailUrl = thumbnailUrl; return this; } public EmbedBuilder WithImageUrl(string imageUrl) { ImageUrl = imageUrl; return this; } public EmbedBuilder WithVideoUrl(string videoUrl) { VideoUrl = videoUrl; return this; } public EmbedBuilder WithCurrentTimestamp() { Timestamp = DateTimeOffset.UtcNow; return this; } public EmbedBuilder WithTimestamp(DateTimeOffset dateTimeOffset) { Timestamp = dateTimeOffset; return this; } public EmbedBuilder WithColor(Color color) { Color = color; return this; } public EmbedBuilder WithAuthor(EmbedAuthorBuilder author) { Author = author; return this; } public EmbedBuilder WithAuthor(Action action) { var author = new EmbedAuthorBuilder(); action(author); Author = author; return this; } public EmbedBuilder WithAuthor(string name, string iconUrl = null, string url = null) { var author = new EmbedAuthorBuilder { Name = name, IconUrl = iconUrl, Url = url }; Author = author; return this; } public EmbedBuilder WithFooter(EmbedFooterBuilder footer) { Footer = footer; return this; } public EmbedBuilder WithFooter(Action action) { var footer = new EmbedFooterBuilder(); action(footer); Footer = footer; return this; } public EmbedBuilder WithFooter(string text, string iconUrl = null) { var footer = new EmbedFooterBuilder { Text = text, IconUrl = iconUrl }; Footer = footer; return this; } public EmbedBuilder WithProvider(EmbedProviderBuilder provider) { Provider = provider; return this; } public EmbedBuilder WithProvider(Action action) { var provider = new EmbedProviderBuilder(); action(provider); Provider = provider; return this; } public EmbedBuilder WithProvider(string name, string url = null) { var provider = new EmbedProviderBuilder { Name = name, Url = url }; Provider = provider; return this; } public EmbedBuilder AddField(string name, object value, bool inline = false) { var field = new EmbedFieldBuilder() .WithIsInline(inline) .WithName(name) .WithValue(value); AddField(field); return this; } public EmbedBuilder AddField(EmbedFieldBuilder field) { if (Fields.Count >= MaxFieldCount) { throw new ArgumentException($"Field count must be less than or equal to {MaxFieldCount}.", nameof(field)); } Fields.Add(field); return this; } public EmbedBuilder AddField(Action action) { var field = new EmbedFieldBuilder(); action(field); this.AddField(field); return this; } public Embed Build() { _embed.Footer = Footer?.Build(); _embed.Author = Author?.Build(); _embed.Provider = Provider?.Build(); var fields = ImmutableArray.CreateBuilder(Fields.Count); for (int i = 0; i < Fields.Count; i++) fields.Add(Fields[i].Build()); _embed.Fields = fields.ToImmutable(); if (_embed.Length > MaxEmbedLength) { throw new InvalidOperationException($"Total embed length must be less than or equal to {MaxEmbedLength}"); } return _embed; } } public class EmbedFieldBuilder { private EmbedField _field; public const int MaxFieldNameLength = 256; public const int MaxFieldValueLength = 1024; public string Name { get => _field.Name; set { if (string.IsNullOrWhiteSpace(value)) throw new ArgumentException($"Field name must not be null, empty or entirely whitespace.", nameof(Name)); if (value.Length > MaxFieldNameLength) throw new ArgumentException($"Field name length must be less than or equal to {MaxFieldNameLength}.", nameof(Name)); _field.Name = value; } } public object Value { get => _field.Value; set { var stringValue = value?.ToString(); if (string.IsNullOrEmpty(stringValue)) throw new ArgumentException($"Field value must not be null or empty.", nameof(Value)); if (stringValue.Length > MaxFieldValueLength) throw new ArgumentException($"Field value length must be less than or equal to {MaxFieldValueLength}.", nameof(Value)); _field.Value = stringValue; } } public bool IsInline { get => _field.Inline; set { _field.Inline = value; } } public EmbedFieldBuilder() { _field = new EmbedField(); } public EmbedFieldBuilder WithName(string name) { Name = name; return this; } public EmbedFieldBuilder WithValue(object value) { Value = value; return this; } public EmbedFieldBuilder WithIsInline(bool isInline) { IsInline = isInline; return this; } public EmbedField Build() => _field; } public class EmbedAuthorBuilder { private EmbedAuthor _author; public const int MaxAuthorNameLength = 256; public string Name { get => _author.Name; set { if (value?.Length > MaxAuthorNameLength) throw new ArgumentException($"Author name length must be less than or equal to {MaxAuthorNameLength}.", nameof(Name)); _author.Name = value; } } public string Url { get => _author.Url; set { if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI", nameof(Url)); _author.Url = value; } } public string IconUrl { get => _author.IconUrl; set { if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI", nameof(IconUrl)); _author.IconUrl = value; } } public EmbedAuthorBuilder() { _author = new EmbedAuthor(); } public EmbedAuthorBuilder WithName(string name) { Name = name; return this; } public EmbedAuthorBuilder WithUrl(string url) { Url = url; return this; } public EmbedAuthorBuilder WithIconUrl(string iconUrl) { IconUrl = iconUrl; return this; } public EmbedAuthor Build() => _author; } public class EmbedFooterBuilder { private EmbedFooter _footer; public const int MaxFooterTextLength = 2048; public string Text { get => _footer.Text; set { if (value?.Length > MaxFooterTextLength) throw new ArgumentException($"Footer text length must be less than or equal to {MaxFooterTextLength}.", nameof(Text)); _footer.Text = value; } } public string IconUrl { get => _footer.IconUrl; set { if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI", nameof(IconUrl)); _footer.IconUrl = value; } } public EmbedFooterBuilder() { _footer = new EmbedFooter(); } public EmbedFooterBuilder WithText(string text) { Text = text; return this; } public EmbedFooterBuilder WithIconUrl(string iconUrl) { IconUrl = iconUrl; return this; } public EmbedFooter Build() => _footer; } public class EmbedProviderBuilder { private EmbedProvider _provider; public string Name { get => _provider.Name; set => _provider.Name = value; } public string Url { get => _provider.Url; set { if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI", nameof(Url)); } } public EmbedProviderBuilder WithName(string name) { Name = name; return this; } public EmbedProviderBuilder WithUrl(string url) { Url = url; return this; } public EmbedProvider Build() => _provider; } }