Browse Source

Add various property validation in EmbedBuilder (#711)

* Add various property validation in EmbedBuilder

* Embed URI changes

Changes property types for any URLs in Embeds to System.URI.
Adding field name/value null/empty checks.

* including property names in argumentexceptions

* Adds overall embed length check
tags/1.0
Pat Murphy RogueException 8 years ago
parent
commit
5601d00285
16 changed files with 163 additions and 69 deletions
  1. +5
    -2
      src/Discord.Net.Core/Entities/Messages/Embed.cs
  2. +6
    -5
      src/Discord.Net.Core/Entities/Messages/EmbedAuthor.cs
  3. +5
    -4
      src/Discord.Net.Core/Entities/Messages/EmbedFooter.cs
  4. +6
    -5
      src/Discord.Net.Core/Entities/Messages/EmbedImage.cs
  5. +4
    -3
      src/Discord.Net.Core/Entities/Messages/EmbedProvider.cs
  6. +6
    -5
      src/Discord.Net.Core/Entities/Messages/EmbedThumbnail.cs
  7. +5
    -4
      src/Discord.Net.Core/Entities/Messages/EmbedVideo.cs
  8. +1
    -1
      src/Discord.Net.Core/Entities/Messages/IEmbed.cs
  9. +1
    -1
      src/Discord.Net.Rest/API/Common/Embed.cs
  10. +5
    -4
      src/Discord.Net.Rest/API/Common/EmbedAuthor.cs
  11. +4
    -3
      src/Discord.Net.Rest/API/Common/EmbedFooter.cs
  12. +3
    -2
      src/Discord.Net.Rest/API/Common/EmbedImage.cs
  13. +2
    -1
      src/Discord.Net.Rest/API/Common/EmbedProvider.cs
  14. +3
    -2
      src/Discord.Net.Rest/API/Common/EmbedThumbnail.cs
  15. +2
    -1
      src/Discord.Net.Rest/API/Common/EmbedVideo.cs
  16. +105
    -26
      src/Discord.Net.Rest/Entities/Messages/EmbedBuilder.cs

+ 5
- 2
src/Discord.Net.Core/Entities/Messages/Embed.cs View File

@@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Diagnostics; using System.Diagnostics;
using System.Linq;


namespace Discord namespace Discord
{ {
@@ -10,7 +11,7 @@ namespace Discord
public string Type { get; } public string Type { get; }


public string Description { get; internal set; } 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 string Title { get; internal set; }
public DateTimeOffset? Timestamp { get; internal set; } public DateTimeOffset? Timestamp { get; internal set; }
public Color? Color { get; internal set; } public Color? Color { get; internal set; }
@@ -30,7 +31,7 @@ namespace Discord
internal Embed(string type, internal Embed(string type,
string title, string title,
string description, string description,
string url,
Uri url,
DateTimeOffset? timestamp, DateTimeOffset? timestamp,
Color? color, Color? color,
EmbedImage? image, EmbedImage? image,
@@ -56,6 +57,8 @@ namespace Discord
Fields = fields; 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; public override string ToString() => Title;
private string DebuggerDisplay => $"{Title} ({Type})"; private string DebuggerDisplay => $"{Title} ({Type})";
} }


+ 6
- 5
src/Discord.Net.Core/Entities/Messages/EmbedAuthor.cs View File

@@ -1,4 +1,5 @@
using System.Diagnostics;
using System;
using System.Diagnostics;


namespace Discord namespace Discord
{ {
@@ -6,11 +7,11 @@ namespace Discord
public struct EmbedAuthor public struct EmbedAuthor
{ {
public string Name { get; internal set; } 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; Name = name;
Url = url; Url = url;


+ 5
- 4
src/Discord.Net.Core/Entities/Messages/EmbedFooter.cs View File

@@ -1,4 +1,5 @@
using System.Diagnostics;
using System;
using System.Diagnostics;


namespace Discord namespace Discord
{ {
@@ -6,10 +7,10 @@ namespace Discord
public struct EmbedFooter public struct EmbedFooter
{ {
public string Text { get; internal set; } 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; Text = text;
IconUrl = iconUrl; IconUrl = iconUrl;


+ 6
- 5
src/Discord.Net.Core/Entities/Messages/EmbedImage.cs View File

@@ -1,16 +1,17 @@
using System.Diagnostics;
using System;
using System.Diagnostics;


namespace Discord namespace Discord
{ {
[DebuggerDisplay("{DebuggerDisplay,nq}")] [DebuggerDisplay("{DebuggerDisplay,nq}")]
public struct EmbedImage 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? Height { get; }
public int? Width { 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; Url = url;
ProxyUrl = proxyUrl; ProxyUrl = proxyUrl;
@@ -19,6 +20,6 @@ namespace Discord
} }


private string DebuggerDisplay => $"{Url} ({(Width != null && Height != null ? $"{Width}x{Height}" : "0x0")})"; private string DebuggerDisplay => $"{Url} ({(Width != null && Height != null ? $"{Width}x{Height}" : "0x0")})";
public override string ToString() => Url;
public override string ToString() => Url.ToString();
} }
} }

+ 4
- 3
src/Discord.Net.Core/Entities/Messages/EmbedProvider.cs View File

@@ -1,4 +1,5 @@
using System.Diagnostics;
using System;
using System.Diagnostics;


namespace Discord namespace Discord
{ {
@@ -6,9 +7,9 @@ namespace Discord
public struct EmbedProvider public struct EmbedProvider
{ {
public string Name { get; } 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; Name = name;
Url = url; Url = url;


+ 6
- 5
src/Discord.Net.Core/Entities/Messages/EmbedThumbnail.cs View File

@@ -1,16 +1,17 @@
using System.Diagnostics;
using System;
using System.Diagnostics;


namespace Discord namespace Discord
{ {
[DebuggerDisplay("{DebuggerDisplay,nq}")] [DebuggerDisplay("{DebuggerDisplay,nq}")]
public struct EmbedThumbnail 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? Height { get; }
public int? Width { 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; Url = url;
ProxyUrl = proxyUrl; ProxyUrl = proxyUrl;
@@ -19,6 +20,6 @@ namespace Discord
} }


private string DebuggerDisplay => $"{Url} ({(Width != null && Height != null ? $"{Width}x{Height}" : "0x0")})"; private string DebuggerDisplay => $"{Url} ({(Width != null && Height != null ? $"{Width}x{Height}" : "0x0")})";
public override string ToString() => Url;
public override string ToString() => Url.ToString();
} }
} }

+ 5
- 4
src/Discord.Net.Core/Entities/Messages/EmbedVideo.cs View File

@@ -1,15 +1,16 @@
using System.Diagnostics;
using System;
using System.Diagnostics;


namespace Discord namespace Discord
{ {
[DebuggerDisplay("{DebuggerDisplay,nq}")] [DebuggerDisplay("{DebuggerDisplay,nq}")]
public struct EmbedVideo public struct EmbedVideo
{ {
public string Url { get; }
public Uri Url { get; }
public int? Height { get; } public int? Height { get; }
public int? Width { get; } public int? Width { get; }


internal EmbedVideo(string url, int? height, int? width)
internal EmbedVideo(Uri url, int? height, int? width)
{ {
Url = url; Url = url;
Height = height; Height = height;
@@ -17,6 +18,6 @@ namespace Discord
} }


private string DebuggerDisplay => $"{Url} ({(Width != null && Height != null ? $"{Width}x{Height}" : "0x0")})"; private string DebuggerDisplay => $"{Url} ({(Width != null && Height != null ? $"{Width}x{Height}" : "0x0")})";
public override string ToString() => Url;
public override string ToString() => Url.ToString();
} }
} }

+ 1
- 1
src/Discord.Net.Core/Entities/Messages/IEmbed.cs View File

@@ -5,7 +5,7 @@ namespace Discord
{ {
public interface IEmbed public interface IEmbed
{ {
string Url { get; }
Uri Url { get; }
string Type { get; } string Type { get; }
string Title { get; } string Title { get; }
string Description { get; } string Description { get; }


+ 1
- 1
src/Discord.Net.Rest/API/Common/Embed.cs View File

@@ -13,7 +13,7 @@ namespace Discord.API
[JsonProperty("description")] [JsonProperty("description")]
public string Description { get; set; } public string Description { get; set; }
[JsonProperty("url")] [JsonProperty("url")]
public string Url { get; set; }
public Uri Url { get; set; }
[JsonProperty("color")] [JsonProperty("color")]
public uint? Color { get; set; } public uint? Color { get; set; }
[JsonProperty("timestamp")] [JsonProperty("timestamp")]


+ 5
- 4
src/Discord.Net.Rest/API/Common/EmbedAuthor.cs View File

@@ -1,4 +1,5 @@
using Newtonsoft.Json;
using System;
using Newtonsoft.Json;


namespace Discord.API namespace Discord.API
{ {
@@ -7,10 +8,10 @@ namespace Discord.API
[JsonProperty("name")] [JsonProperty("name")]
public string Name { get; set; } public string Name { get; set; }
[JsonProperty("url")] [JsonProperty("url")]
public string Url { get; set; }
public Uri Url { get; set; }
[JsonProperty("icon_url")] [JsonProperty("icon_url")]
public string IconUrl { get; set; }
public Uri IconUrl { get; set; }
[JsonProperty("proxy_icon_url")] [JsonProperty("proxy_icon_url")]
public string ProxyIconUrl { get; set; }
public Uri ProxyIconUrl { get; set; }
} }
} }

+ 4
- 3
src/Discord.Net.Rest/API/Common/EmbedFooter.cs View File

@@ -1,4 +1,5 @@
using Newtonsoft.Json;
using System;
using Newtonsoft.Json;


namespace Discord.API namespace Discord.API
{ {
@@ -7,8 +8,8 @@ namespace Discord.API
[JsonProperty("text")] [JsonProperty("text")]
public string Text { get; set; } public string Text { get; set; }
[JsonProperty("icon_url")] [JsonProperty("icon_url")]
public string IconUrl { get; set; }
public Uri IconUrl { get; set; }
[JsonProperty("proxy_icon_url")] [JsonProperty("proxy_icon_url")]
public string ProxyIconUrl { get; set; }
public Uri ProxyIconUrl { get; set; }
} }
} }

+ 3
- 2
src/Discord.Net.Rest/API/Common/EmbedImage.cs View File

@@ -1,4 +1,5 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
using System;
using Newtonsoft.Json; using Newtonsoft.Json;


namespace Discord.API namespace Discord.API
@@ -6,9 +7,9 @@ namespace Discord.API
internal class EmbedImage internal class EmbedImage
{ {
[JsonProperty("url")] [JsonProperty("url")]
public string Url { get; set; }
public Uri Url { get; set; }
[JsonProperty("proxy_url")] [JsonProperty("proxy_url")]
public string ProxyUrl { get; set; }
public Uri ProxyUrl { get; set; }
[JsonProperty("height")] [JsonProperty("height")]
public Optional<int> Height { get; set; } public Optional<int> Height { get; set; }
[JsonProperty("width")] [JsonProperty("width")]


+ 2
- 1
src/Discord.Net.Rest/API/Common/EmbedProvider.cs View File

@@ -1,4 +1,5 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
using System;
using Newtonsoft.Json; using Newtonsoft.Json;


namespace Discord.API namespace Discord.API
@@ -8,6 +9,6 @@ namespace Discord.API
[JsonProperty("name")] [JsonProperty("name")]
public string Name { get; set; } public string Name { get; set; }
[JsonProperty("url")] [JsonProperty("url")]
public string Url { get; set; }
public Uri Url { get; set; }
} }
} }

+ 3
- 2
src/Discord.Net.Rest/API/Common/EmbedThumbnail.cs View File

@@ -1,4 +1,5 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
using System;
using Newtonsoft.Json; using Newtonsoft.Json;


namespace Discord.API namespace Discord.API
@@ -6,9 +7,9 @@ namespace Discord.API
internal class EmbedThumbnail internal class EmbedThumbnail
{ {
[JsonProperty("url")] [JsonProperty("url")]
public string Url { get; set; }
public Uri Url { get; set; }
[JsonProperty("proxy_url")] [JsonProperty("proxy_url")]
public string ProxyUrl { get; set; }
public Uri ProxyUrl { get; set; }
[JsonProperty("height")] [JsonProperty("height")]
public Optional<int> Height { get; set; } public Optional<int> Height { get; set; }
[JsonProperty("width")] [JsonProperty("width")]


+ 2
- 1
src/Discord.Net.Rest/API/Common/EmbedVideo.cs View File

@@ -1,4 +1,5 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
using System;
using Newtonsoft.Json; using Newtonsoft.Json;


namespace Discord.API namespace Discord.API
@@ -6,7 +7,7 @@ namespace Discord.API
internal class EmbedVideo internal class EmbedVideo
{ {
[JsonProperty("url")] [JsonProperty("url")]
public string Url { get; set; }
public Uri Url { get; set; }
[JsonProperty("height")] [JsonProperty("height")]
public Optional<int> Height { get; set; } public Optional<int> Height { get; set; }
[JsonProperty("width")] [JsonProperty("width")]


+ 105
- 26
src/Discord.Net.Rest/Entities/Messages/EmbedBuilder.cs View File

@@ -8,19 +8,42 @@ namespace Discord
{ {
private readonly Embed _embed; 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() public EmbedBuilder()
{ {
_embed = new Embed("rich"); _embed = new Embed("rich");
Fields = new List<EmbedFieldBuilder>(); Fields = new List<EmbedFieldBuilder>();
} }


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 EmbedAuthorBuilder Author { get; set; }
public EmbedFooterBuilder Footer { get; set; } public EmbedFooterBuilder Footer { get; set; }
@@ -30,8 +53,10 @@ namespace Discord
get => _fields; get => _fields;
set 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; Description = description;
return this; return this;
} }
public EmbedBuilder WithUrl(string url)
public EmbedBuilder WithUrl(Uri url)
{ {
Url = url; Url = url;
return this; return this;
} }
public EmbedBuilder WithThumbnailUrl(string thumbnailUrl)
public EmbedBuilder WithThumbnailUrl(Uri thumbnailUrl)
{ {
ThumbnailUrl = thumbnailUrl; ThumbnailUrl = thumbnailUrl;
return this; return this;
} }
public EmbedBuilder WithImageUrl(string imageUrl)
public EmbedBuilder WithImageUrl(Uri imageUrl)
{ {
ImageUrl = imageUrl; ImageUrl = imageUrl;
return this; return this;
@@ -107,7 +132,7 @@ namespace Discord
.WithIsInline(false) .WithIsInline(false)
.WithName(name) .WithName(name)
.WithValue(value); .WithValue(value);
Fields.Add(field);
AddField(field);
return this; return this;
} }
public EmbedBuilder AddInlineField(string name, object value) public EmbedBuilder AddInlineField(string name, object value)
@@ -116,11 +141,16 @@ namespace Discord
.WithIsInline(true) .WithIsInline(true)
.WithName(name) .WithName(name)
.WithValue(value); .WithValue(value);
Fields.Add(field);
AddField(field);
return this; return this;
} }
public EmbedBuilder AddField(EmbedFieldBuilder field) 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); Fields.Add(field);
return this; return this;
} }
@@ -128,7 +158,7 @@ namespace Discord
{ {
var field = new EmbedFieldBuilder(); var field = new EmbedFieldBuilder();
action(field); action(field);
Fields.Add(field);
this.AddField(field);
return this; return this;
} }


@@ -140,6 +170,12 @@ namespace Discord
for (int i = 0; i < Fields.Count; i++) for (int i = 0; i < Fields.Count; i++)
fields.Add(Fields[i].Build()); fields.Add(Fields[i].Build());
_embed.Fields = fields.ToImmutable(); _embed.Fields = fields.ToImmutable();

if (_embed.Length > MaxEmbedLength)
{
throw new InvalidOperationException($"Total embed length must be less than or equal to {MaxEmbedLength}");
}

return _embed; return _embed;
} }
public static implicit operator Embed(EmbedBuilder builder) => builder?.Build(); public static implicit operator Embed(EmbedBuilder builder) => builder?.Build();
@@ -149,9 +185,32 @@ namespace Discord
{ {
private EmbedField _field; 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() public EmbedFieldBuilder()
{ {
@@ -182,9 +241,19 @@ namespace Discord
{ {
private EmbedAuthor _author; 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() public EmbedAuthorBuilder()
{ {
@@ -196,12 +265,12 @@ namespace Discord
Name = name; Name = name;
return this; return this;
} }
public EmbedAuthorBuilder WithUrl(string url)
public EmbedAuthorBuilder WithUrl(Uri url)
{ {
Url = url; Url = url;
return this; return this;
} }
public EmbedAuthorBuilder WithIconUrl(string iconUrl)
public EmbedAuthorBuilder WithIconUrl(Uri iconUrl)
{ {
IconUrl = iconUrl; IconUrl = iconUrl;
return this; return this;
@@ -215,8 +284,18 @@ namespace Discord
{ {
private EmbedFooter _footer; 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() public EmbedFooterBuilder()
{ {
@@ -228,7 +307,7 @@ namespace Discord
Text = text; Text = text;
return this; return this;
} }
public EmbedFooterBuilder WithIconUrl(string iconUrl)
public EmbedFooterBuilder WithIconUrl(Uri iconUrl)
{ {
IconUrl = iconUrl; IconUrl = iconUrl;
return this; return this;


Loading…
Cancel
Save