diff --git a/src/Discord/Helpers/ValidatesDiscordLimits.php b/src/Discord/Helpers/ValidatesDiscordLimits.php new file mode 100644 index 000000000..29fcf7ed0 --- /dev/null +++ b/src/Discord/Helpers/ValidatesDiscordLimits.php @@ -0,0 +1,64 @@ + + * Copyright (c) 2020-present Valithor Obsidion + * + * This file is subject to the MIT license that is bundled + * with this source code in the LICENSE.md file. + */ + +namespace Discord\Helpers; + +/** + * Centralises Discord API payload size limits as shared constants. + * + * Classes that produce or validate Discord-bound payloads implement this + * interface to expose consistent limits in a single location. Grouping + * them prevents the "magic number" drift that happens when the same + * limit is repeated across builders and parts. + * + * @link https://docs.discord.com/developers/resources/message#embed-object-embed-limits + * @link https://docs.discord.com/developers/resources/message#create-message + * + * @since 10.48.0 + */ +interface ValidatesDiscordLimits +{ + /** Maximum characters in an embed title. */ + public const EMBED_TITLE_MAX = 256; + + /** Maximum characters in an embed description. */ + public const EMBED_DESCRIPTION_MAX = 4096; + + /** Maximum characters in an embed author name. */ + public const EMBED_AUTHOR_NAME_MAX = 256; + + /** Maximum characters in an embed footer text. */ + public const EMBED_FOOTER_TEXT_MAX = 2048; + + /** Maximum characters in an embed field name. */ + public const EMBED_FIELD_NAME_MAX = 256; + + /** Maximum characters in an embed field value. */ + public const EMBED_FIELD_VALUE_MAX = 1024; + + /** Maximum number of fields in an embed. */ + public const EMBED_FIELDS_MAX = 25; + + /** Maximum combined characters across all embed text fields. */ + public const EMBED_TOTAL_CHARS_MAX = 6000; + + /** Maximum characters in a message content field. */ + public const MESSAGE_CONTENT_MAX = 2000; + + /** Maximum embeds attached to a single message. */ + public const MESSAGE_EMBEDS_MAX = 10; + + /** Maximum file attachments on a single message. */ + public const MESSAGE_FILES_MAX = 10; +} diff --git a/src/Discord/Parts/Embed/Embed.php b/src/Discord/Parts/Embed/Embed.php index 759e16460..40275f039 100644 --- a/src/Discord/Parts/Embed/Embed.php +++ b/src/Discord/Parts/Embed/Embed.php @@ -16,6 +16,7 @@ use Carbon\Carbon; use Discord\Helpers\ExCollectionInterface; +use Discord\Helpers\ValidatesDiscordLimits; use Discord\Parts\Channel\Attachment; use Discord\Parts\Part; @@ -44,7 +45,7 @@ * @property ?ExCollectionInterface|Field[] $fields A collection of embed fields (max of 25). * @property ?int|null $flags Embedded flags combined as a bitfield. */ -class Embed extends Part +class Embed extends Part implements ValidatesDiscordLimits { public const TYPES = [ 0 => Embed::class, // Fallback for unknown types @@ -211,11 +212,11 @@ protected function setDescriptionAttribute($description): void { if (poly_strlen($description) === 0) { $this->attributes['description'] = null; - } elseif (poly_strlen($description) > 4096) { + } elseif (poly_strlen($description) > self::EMBED_DESCRIPTION_MAX) { throw new \LengthException('Embed description can not be longer than 4096 characters'); } else { if ($this->exceedsOverallLimit(poly_strlen($description))) { - throw new \LengthException('Embed text values collectively can not exceed than 6000 characters'); + throw new \LengthException('Embed text values collectively can not exceed 6000 characters'); } $this->attributes['description'] = $description; @@ -253,10 +254,10 @@ protected function setTitleAttribute(string $title): self { if (poly_strlen($title) === 0) { $this->attributes['title'] = null; - } elseif (poly_strlen($title) > 256) { + } elseif (poly_strlen($title) > self::EMBED_TITLE_MAX) { throw new \LengthException('Embed title can not be longer than 256 characters'); } elseif ($this->exceedsOverallLimit(poly_strlen($title))) { - throw new \LengthException('Embed text values collectively can not exceed than 6000 characters'); + throw new \LengthException('Embed text values collectively can not exceed 6000 characters'); } else { $this->attributes['title'] = $title; } @@ -334,7 +335,7 @@ public function setColor($color): self public function addField(...$fields): self { foreach ($fields as $field) { - if (count($this->fields) > 25) { + if (count($this->fields) >= self::EMBED_FIELDS_MAX) { throw new \OverflowException('Embeds can not have more than 25 fields.'); } @@ -342,6 +343,21 @@ public function addField(...$fields): self $field = $field->getRawAttributes(); } + $name = is_string($field['name'] ?? null) ? $field['name'] : ''; + $value = is_string($field['value'] ?? null) ? $field['value'] : ''; + + if (poly_strlen($name) > self::EMBED_FIELD_NAME_MAX) { + throw new \LengthException('Embed field name can not be longer than 256 characters'); + } + + if (poly_strlen($value) > self::EMBED_FIELD_VALUE_MAX) { + throw new \LengthException('Embed field value can not be longer than 1024 characters'); + } + + if ($this->exceedsOverallLimit(poly_strlen($name) + poly_strlen($value))) { + throw new \LengthException('Embed text values collectively can not exceed 6000 characters'); + } + $this->attributes['fields'][] = $field; } @@ -385,10 +401,10 @@ public function setAuthor(string $name, $iconurl = null, ?string $url = null): s $length = poly_strlen($name); if ($length === 0) { $this->author = null; - } elseif ($length > 256) { + } elseif ($length > self::EMBED_AUTHOR_NAME_MAX) { throw new \LengthException('Author name can not be longer than 256 characters.'); } elseif ($this->exceedsOverallLimit($length)) { - throw new \LengthException('Embed text values collectively can not exceed than 6000 characters'); + throw new \LengthException('Embed text values collectively can not exceed 6000 characters'); } if ($iconurl instanceof Attachment) { @@ -424,10 +440,10 @@ public function setFooter(string $text, $iconurl = null): self $length = poly_strlen($text); if ($length === 0) { $this->footer = null; - } elseif ($length > 2048) { + } elseif ($length > self::EMBED_FOOTER_TEXT_MAX) { throw new \LengthException('Footer text can not be longer than 2048 characters.'); } elseif ($this->exceedsOverallLimit($length)) { - throw new \LengthException('Embed text values collectively can not exceed than 6000 characters'); + throw new \LengthException('Embed text values collectively can not exceed 6000 characters'); } if ($iconurl instanceof Attachment) { @@ -556,7 +572,7 @@ protected function exceedsOverallLimit(int $addition): bool $total += poly_strlen($field['value']); } - return ($total > 6000); + return ($total > self::EMBED_TOTAL_CHARS_MAX); } /**