diff --git a/src/Discord.Net.Core/Entities/Messages/Embed.cs b/src/Discord.Net.Core/Entities/Messages/Embed.cs index ebde05d4c..3210c22f5 100644 --- a/src/Discord.Net.Core/Entities/Messages/Embed.cs +++ b/src/Discord.Net.Core/Entities/Messages/Embed.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Immutable; using System.Diagnostics; +using System.Linq; namespace Discord { @@ -10,7 +11,7 @@ namespace Discord public string Type { get; } public string Description { get; internal set; } - public string Url { get; internal set; } + public Uri Url { get; internal set; } public string Title { get; internal set; } public DateTimeOffset? Timestamp { get; internal set; } public Color? Color { get; internal set; } @@ -30,7 +31,7 @@ namespace Discord internal Embed(string type, string title, string description, - string url, + Uri url, DateTimeOffset? timestamp, Color? color, EmbedImage? image, @@ -56,6 +57,8 @@ namespace Discord Fields = fields; } + public int Length => Title?.Length + Author?.Name?.Length + Description?.Length + Footer?.Text?.Length + Fields.Sum(f => f.Name.Length + f.Value.ToString().Length) ?? 0; + public override string ToString() => Title; private string DebuggerDisplay => $"{Title} ({Type})"; } diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedAuthor.cs b/src/Discord.Net.Core/Entities/Messages/EmbedAuthor.cs index 142e36832..d1f2b9618 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedAuthor.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedAuthor.cs @@ -1,4 +1,5 @@ -using System.Diagnostics; +using System; +using System.Diagnostics; namespace Discord { @@ -6,11 +7,11 @@ namespace Discord public struct EmbedAuthor { public string Name { get; internal set; } - public string Url { get; internal set; } - public string IconUrl { get; internal set; } - public string ProxyIconUrl { get; internal set; } + public Uri Url { get; internal set; } + public Uri IconUrl { get; internal set; } + public Uri ProxyIconUrl { get; internal set; } - internal EmbedAuthor(string name, string url, string iconUrl, string proxyIconUrl) + internal EmbedAuthor(string name, Uri url, Uri iconUrl, Uri proxyIconUrl) { Name = name; Url = url; diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedFooter.cs b/src/Discord.Net.Core/Entities/Messages/EmbedFooter.cs index 33582070a..3c9bf35a9 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedFooter.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedFooter.cs @@ -1,4 +1,5 @@ -using System.Diagnostics; +using System; +using System.Diagnostics; namespace Discord { @@ -6,10 +7,10 @@ namespace Discord public struct EmbedFooter { public string Text { get; internal set; } - public string IconUrl { get; internal set; } - public string ProxyUrl { get; internal set; } + public Uri IconUrl { get; internal set; } + public Uri ProxyUrl { get; internal set; } - internal EmbedFooter(string text, string iconUrl, string proxyUrl) + internal EmbedFooter(string text, Uri iconUrl, Uri proxyUrl) { Text = text; IconUrl = iconUrl; diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedImage.cs b/src/Discord.Net.Core/Entities/Messages/EmbedImage.cs index fa4847721..fd87e3db3 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedImage.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedImage.cs @@ -1,16 +1,17 @@ -using System.Diagnostics; +using System; +using System.Diagnostics; namespace Discord { [DebuggerDisplay("{DebuggerDisplay,nq}")] public struct EmbedImage { - public string Url { get; } - public string ProxyUrl { get; } + public Uri Url { get; } + public Uri ProxyUrl { get; } public int? Height { get; } public int? Width { get; } - internal EmbedImage(string url, string proxyUrl, int? height, int? width) + internal EmbedImage(Uri url, Uri proxyUrl, int? height, int? width) { Url = url; ProxyUrl = proxyUrl; @@ -19,6 +20,6 @@ namespace Discord } private string DebuggerDisplay => $"{Url} ({(Width != null && Height != null ? $"{Width}x{Height}" : "0x0")})"; - public override string ToString() => Url; + public override string ToString() => Url.ToString(); } } diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedProvider.cs b/src/Discord.Net.Core/Entities/Messages/EmbedProvider.cs index 943ac5b52..0b816b32b 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedProvider.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedProvider.cs @@ -1,4 +1,5 @@ -using System.Diagnostics; +using System; +using System.Diagnostics; namespace Discord { @@ -6,9 +7,9 @@ namespace Discord public struct EmbedProvider { public string Name { get; } - public string Url { get; } + public Uri Url { get; } - internal EmbedProvider(string name, string url) + internal EmbedProvider(string name, Uri url) { Name = name; Url = url; diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedThumbnail.cs b/src/Discord.Net.Core/Entities/Messages/EmbedThumbnail.cs index 4e125bf2a..b83401e07 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedThumbnail.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedThumbnail.cs @@ -1,16 +1,17 @@ -using System.Diagnostics; +using System; +using System.Diagnostics; namespace Discord { [DebuggerDisplay("{DebuggerDisplay,nq}")] public struct EmbedThumbnail { - public string Url { get; } - public string ProxyUrl { get; } + public Uri Url { get; } + public Uri ProxyUrl { get; } public int? Height { get; } public int? Width { get; } - internal EmbedThumbnail(string url, string proxyUrl, int? height, int? width) + internal EmbedThumbnail(Uri url, Uri proxyUrl, int? height, int? width) { Url = url; ProxyUrl = proxyUrl; @@ -19,6 +20,6 @@ namespace Discord } private string DebuggerDisplay => $"{Url} ({(Width != null && Height != null ? $"{Width}x{Height}" : "0x0")})"; - public override string ToString() => Url; + public override string ToString() => Url.ToString(); } } diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedVideo.cs b/src/Discord.Net.Core/Entities/Messages/EmbedVideo.cs index eaf6f4a4c..9ea4b11d6 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedVideo.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedVideo.cs @@ -1,15 +1,16 @@ -using System.Diagnostics; +using System; +using System.Diagnostics; namespace Discord { [DebuggerDisplay("{DebuggerDisplay,nq}")] public struct EmbedVideo { - public string Url { get; } + public Uri Url { get; } public int? Height { get; } public int? Width { get; } - internal EmbedVideo(string url, int? height, int? width) + internal EmbedVideo(Uri url, int? height, int? width) { Url = url; Height = height; @@ -17,6 +18,6 @@ namespace Discord } private string DebuggerDisplay => $"{Url} ({(Width != null && Height != null ? $"{Width}x{Height}" : "0x0")})"; - public override string ToString() => Url; + public override string ToString() => Url.ToString(); } } diff --git a/src/Discord.Net.Core/Entities/Messages/IEmbed.cs b/src/Discord.Net.Core/Entities/Messages/IEmbed.cs index 5eef5ec9b..145b1fa3c 100644 --- a/src/Discord.Net.Core/Entities/Messages/IEmbed.cs +++ b/src/Discord.Net.Core/Entities/Messages/IEmbed.cs @@ -5,7 +5,7 @@ namespace Discord { public interface IEmbed { - string Url { get; } + Uri Url { get; } string Type { get; } string Title { get; } string Description { get; } diff --git a/src/Discord.Net.Rest/API/Common/Embed.cs b/src/Discord.Net.Rest/API/Common/Embed.cs index f6325efbb..110c5ec8d 100644 --- a/src/Discord.Net.Rest/API/Common/Embed.cs +++ b/src/Discord.Net.Rest/API/Common/Embed.cs @@ -13,7 +13,7 @@ namespace Discord.API [JsonProperty("description")] public string Description { get; set; } [JsonProperty("url")] - public string Url { get; set; } + public Uri Url { get; set; } [JsonProperty("color")] public uint? Color { get; set; } [JsonProperty("timestamp")] diff --git a/src/Discord.Net.Rest/API/Common/EmbedAuthor.cs b/src/Discord.Net.Rest/API/Common/EmbedAuthor.cs index e69fee6eb..9ade58edf 100644 --- a/src/Discord.Net.Rest/API/Common/EmbedAuthor.cs +++ b/src/Discord.Net.Rest/API/Common/EmbedAuthor.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json; +using System; +using Newtonsoft.Json; namespace Discord.API { @@ -7,10 +8,10 @@ namespace Discord.API [JsonProperty("name")] public string Name { get; set; } [JsonProperty("url")] - public string Url { get; set; } + public Uri Url { get; set; } [JsonProperty("icon_url")] - public string IconUrl { get; set; } + public Uri IconUrl { get; set; } [JsonProperty("proxy_icon_url")] - public string ProxyIconUrl { get; set; } + public Uri ProxyIconUrl { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/EmbedFooter.cs b/src/Discord.Net.Rest/API/Common/EmbedFooter.cs index 27048972e..1e079d03e 100644 --- a/src/Discord.Net.Rest/API/Common/EmbedFooter.cs +++ b/src/Discord.Net.Rest/API/Common/EmbedFooter.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json; +using System; +using Newtonsoft.Json; namespace Discord.API { @@ -7,8 +8,8 @@ namespace Discord.API [JsonProperty("text")] public string Text { get; set; } [JsonProperty("icon_url")] - public string IconUrl { get; set; } + public Uri IconUrl { get; set; } [JsonProperty("proxy_icon_url")] - public string ProxyIconUrl { get; set; } + public Uri ProxyIconUrl { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/EmbedImage.cs b/src/Discord.Net.Rest/API/Common/EmbedImage.cs index a5ef748f8..a12299783 100644 --- a/src/Discord.Net.Rest/API/Common/EmbedImage.cs +++ b/src/Discord.Net.Rest/API/Common/EmbedImage.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +using System; using Newtonsoft.Json; namespace Discord.API @@ -6,9 +7,9 @@ namespace Discord.API internal class EmbedImage { [JsonProperty("url")] - public string Url { get; set; } + public Uri Url { get; set; } [JsonProperty("proxy_url")] - public string ProxyUrl { get; set; } + public Uri ProxyUrl { get; set; } [JsonProperty("height")] public Optional Height { get; set; } [JsonProperty("width")] diff --git a/src/Discord.Net.Rest/API/Common/EmbedProvider.cs b/src/Discord.Net.Rest/API/Common/EmbedProvider.cs index 8c46b10dc..7ca87185c 100644 --- a/src/Discord.Net.Rest/API/Common/EmbedProvider.cs +++ b/src/Discord.Net.Rest/API/Common/EmbedProvider.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +using System; using Newtonsoft.Json; namespace Discord.API @@ -8,6 +9,6 @@ namespace Discord.API [JsonProperty("name")] public string Name { get; set; } [JsonProperty("url")] - public string Url { get; set; } + public Uri Url { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/EmbedThumbnail.cs b/src/Discord.Net.Rest/API/Common/EmbedThumbnail.cs index f22953a25..b4ccd4b21 100644 --- a/src/Discord.Net.Rest/API/Common/EmbedThumbnail.cs +++ b/src/Discord.Net.Rest/API/Common/EmbedThumbnail.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +using System; using Newtonsoft.Json; namespace Discord.API @@ -6,9 +7,9 @@ namespace Discord.API internal class EmbedThumbnail { [JsonProperty("url")] - public string Url { get; set; } + public Uri Url { get; set; } [JsonProperty("proxy_url")] - public string ProxyUrl { get; set; } + public Uri ProxyUrl { get; set; } [JsonProperty("height")] public Optional Height { get; set; } [JsonProperty("width")] diff --git a/src/Discord.Net.Rest/API/Common/EmbedVideo.cs b/src/Discord.Net.Rest/API/Common/EmbedVideo.cs index 09e933784..2512151ed 100644 --- a/src/Discord.Net.Rest/API/Common/EmbedVideo.cs +++ b/src/Discord.Net.Rest/API/Common/EmbedVideo.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +using System; using Newtonsoft.Json; namespace Discord.API @@ -6,7 +7,7 @@ namespace Discord.API internal class EmbedVideo { [JsonProperty("url")] - public string Url { get; set; } + public Uri Url { get; set; } [JsonProperty("height")] public Optional Height { get; set; } [JsonProperty("width")] diff --git a/src/Discord.Net.Rest/Entities/Messages/EmbedBuilder.cs b/src/Discord.Net.Rest/Entities/Messages/EmbedBuilder.cs index 98a191379..a7c2436b0 100644 --- a/src/Discord.Net.Rest/Entities/Messages/EmbedBuilder.cs +++ b/src/Discord.Net.Rest/Entities/Messages/EmbedBuilder.cs @@ -8,19 +8,42 @@ namespace Discord { 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("rich"); Fields = new List(); } - public string Title { get { return _embed.Title; } set { _embed.Title = value; } } - public string Description { get { return _embed.Description; } set { _embed.Description = value; } } - public string Url { get { return _embed.Url; } set { _embed.Url = value; } } - public string ThumbnailUrl { get { return _embed.Thumbnail?.Url; } set { _embed.Thumbnail = new EmbedThumbnail(value, null, null, null); } } - public string ImageUrl { get { return _embed.Image?.Url; } set { _embed.Image = new EmbedImage(value, null, null, null); } } - public DateTimeOffset? Timestamp { get { return _embed.Timestamp; } set { _embed.Timestamp = value; } } - public Color? Color { get { return _embed.Color; } set { _embed.Color = value; } } + 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 Uri Url { get => _embed.Url; set { _embed.Url = value; } } + public Uri ThumbnailUrl { get => _embed.Thumbnail?.Url; set { _embed.Thumbnail = new EmbedThumbnail(value, null, null, null); } } + public Uri ImageUrl { get => _embed.Image?.Url; set { _embed.Image = new EmbedImage(value, null, 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; } @@ -30,8 +53,10 @@ namespace Discord get => _fields; set { - if (value != null) _fields = value; - else throw new ArgumentNullException("Cannot set an embed builder's fields collection to null", nameof(value)); + + 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; } } @@ -45,17 +70,17 @@ namespace Discord Description = description; return this; } - public EmbedBuilder WithUrl(string url) + public EmbedBuilder WithUrl(Uri url) { Url = url; return this; } - public EmbedBuilder WithThumbnailUrl(string thumbnailUrl) + public EmbedBuilder WithThumbnailUrl(Uri thumbnailUrl) { ThumbnailUrl = thumbnailUrl; return this; } - public EmbedBuilder WithImageUrl(string imageUrl) + public EmbedBuilder WithImageUrl(Uri imageUrl) { ImageUrl = imageUrl; return this; @@ -107,7 +132,7 @@ namespace Discord .WithIsInline(false) .WithName(name) .WithValue(value); - Fields.Add(field); + AddField(field); return this; } public EmbedBuilder AddInlineField(string name, object value) @@ -116,11 +141,16 @@ namespace Discord .WithIsInline(true) .WithName(name) .WithValue(value); - Fields.Add(field); + 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; } @@ -128,7 +158,7 @@ namespace Discord { var field = new EmbedFieldBuilder(); action(field); - Fields.Add(field); + this.AddField(field); return this; } @@ -140,6 +170,12 @@ namespace Discord 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 static implicit operator Embed(EmbedBuilder builder) => builder?.Build(); @@ -149,9 +185,32 @@ namespace Discord { private EmbedField _field; - public string Name { get { return _field.Name; } set { _field.Name = value; } } - public object Value { get { return _field.Value; } set { _field.Value = value.ToString(); } } - public bool IsInline { get { return _field.Inline; } set { _field.Inline = value; } } + public const int MaxFieldNameLength = 256; + public const int MaxFieldValueLength = 1024; + + public string Name + { + get => _field.Name; + set + { + if (string.IsNullOrEmpty(value)) throw new ArgumentException($"Field name must not be null or empty.", 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() { @@ -182,9 +241,19 @@ namespace Discord { private EmbedAuthor _author; - public string Name { get { return _author.Name; } set { _author.Name = value; } } - public string Url { get { return _author.Url; } set { _author.Url = value; } } - public string IconUrl { get { return _author.IconUrl; } set { _author.IconUrl = value; } } + 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 Uri Url { get => _author.Url; set { _author.Url = value; } } + public Uri IconUrl { get => _author.IconUrl; set { _author.IconUrl = value; } } public EmbedAuthorBuilder() { @@ -196,12 +265,12 @@ namespace Discord Name = name; return this; } - public EmbedAuthorBuilder WithUrl(string url) + public EmbedAuthorBuilder WithUrl(Uri url) { Url = url; return this; } - public EmbedAuthorBuilder WithIconUrl(string iconUrl) + public EmbedAuthorBuilder WithIconUrl(Uri iconUrl) { IconUrl = iconUrl; return this; @@ -215,8 +284,18 @@ namespace Discord { private EmbedFooter _footer; - public string Text { get { return _footer.Text; } set { _footer.Text = value; } } - public string IconUrl { get { return _footer.IconUrl; } set { _footer.IconUrl = value; } } + 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 Uri IconUrl { get => _footer.IconUrl; set { _footer.IconUrl = value; } } public EmbedFooterBuilder() { @@ -228,7 +307,7 @@ namespace Discord Text = text; return this; } - public EmbedFooterBuilder WithIconUrl(string iconUrl) + public EmbedFooterBuilder WithIconUrl(Uri iconUrl) { IconUrl = iconUrl; return this;