From 6de2df11690dc94a7e743db175cf2745986e8693 Mon Sep 17 00:00:00 2001 From: "Vladyslav G." Date: Wed, 2 Sep 2026 04:20:33 +0200 Subject: [PATCH] fix(emoji): render a standard emoji for the reaction endpoints MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit EmojiBuilder rendered a standard emoji held under name as "✅:" — the name, a colon, and an id that was not there — which made every reaction on a standard emoji a malformed request, with an undefined array key warning on the way out. Standard emoji arrive under name, since that is where Discord puts them in a reaction event and therefore what fromPart() copies across. They were only ever rendered correctly when written by hand into setId(), which is the shape the existing test used and the reason this held. Both keys now work, and the pair is what marks a custom emoji. --- src/Rest/Helpers/Emoji/EmojiBuilder.php | 21 ++++++++++-- tests/Rest/Helpers/Emoji/EmojiBuilderTest.php | 32 +++++++++++++++++++ 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/src/Rest/Helpers/Emoji/EmojiBuilder.php b/src/Rest/Helpers/Emoji/EmojiBuilder.php index 10258ae..cd8562a 100644 --- a/src/Rest/Helpers/Emoji/EmojiBuilder.php +++ b/src/Rest/Helpers/Emoji/EmojiBuilder.php @@ -73,10 +73,25 @@ public function get(): array return $this->data; } + /** + * The emoji as a reaction endpoint takes it. + * + * A custom emoji is "name:id". A standard one is the character itself, + * percent encoded, and may be held under either key: fromPart() puts it in + * name, because that is where Discord sends it in a reaction event, while + * setId() has long been the documented way to write one by hand. + * + * @see https://discord.com/developers/docs/resources/channel#create-reaction + */ public function __toString(): string { - return isset($this->data['name']) - ? $this->data['name'] . ':' . $this->data['id'] - : urlencode($this->data['id']); + $id = $this->data['id'] ?? null; + $name = $this->data['name'] ?? null; + + if ($id !== null && $name !== null) { + return $name . ':' . $id; + } + + return rawurlencode((string) ($name ?? $id)); } } diff --git a/tests/Rest/Helpers/Emoji/EmojiBuilderTest.php b/tests/Rest/Helpers/Emoji/EmojiBuilderTest.php index 6447041..e423a93 100644 --- a/tests/Rest/Helpers/Emoji/EmojiBuilderTest.php +++ b/tests/Rest/Helpers/Emoji/EmojiBuilderTest.php @@ -53,6 +53,38 @@ public function testCreateEmojiFromIdAndName(): void $this->assertEquals('name:12345', (string) $emojiBuilder); } + /** + * A reaction event carries a standard emoji as its name with no id, which + * is what fromPart() copies across. Rendering that as "✅:" — with the id + * missing entirely — made every reaction on a standard emoji a malformed + * request, and warned about an undefined key on the way out. + */ + public function testCreateEmojiFromNameAlone(): void + { + $emojiBuilder = new EmojiBuilder(); + $emojiBuilder->setName('✅'); + + $this->assertEquals(rawurlencode('✅'), (string) $emojiBuilder); + } + + public function testAStandardEmojiFromAReactionEventIsRenderedForTheEndpoint(): void + { + $emoji = new Emoji(); + $emoji->name = '❌'; + $emoji->id = null; + + $this->assertEquals(rawurlencode('❌'), (string) EmojiBuilder::fromPart($emoji)); + } + + public function testACustomEmojiFromAReactionEventKeepsBothHalves(): void + { + $emoji = new Emoji(); + $emoji->name = 'apex'; + $emoji->id = '12345'; + + $this->assertEquals('apex:12345', (string) EmojiBuilder::fromPart($emoji)); + } + #[DataProvider('getFromPartProvider')] public function testGetFromPart(Emoji $emoji, array $result): void {