From f70ec59359f7ee408bcb0ef3546a038a0bafc305 Mon Sep 17 00:00:00 2001 From: thepra Date: Mon, 5 Oct 2026 00:58:48 +0200 Subject: [PATCH] One emoji, one reaction: variation selectors made canonical MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A heart reaction arrived as ❤ from some servers and as ❤️ (with the emoji variation selector) from others, and a persona's own reaction kept whatever it was given: the same emoji was two reactions, counted apart. The selector is now dropped and put back only on a symbol that shows as text without it (U+2000 to U+2BFF), so every heart is ❤️ and every 🔥 is 🔥. Found by the town's Hollo pair. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01LsXgEaXee4GCU1hwYgPJXw --- PrivaPub.Tests/Federation/ReactionTests.cs | 3 +++ PrivaPub/Domain/Social/Reactions.cs | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/PrivaPub.Tests/Federation/ReactionTests.cs b/PrivaPub.Tests/Federation/ReactionTests.cs index db10383..835f589 100644 --- a/PrivaPub.Tests/Federation/ReactionTests.cs +++ b/PrivaPub.Tests/Federation/ReactionTests.cs @@ -17,6 +17,9 @@ namespace PrivaPub.Tests.Federation [InlineData(":blobcat@k.example:", "blobcat")] [InlineData("👍🏽", "👍🏽")] [InlineData("❤️", "❤️")] + [InlineData("❤", "❤️")] + [InlineData("✌", "✌️")] + [InlineData("🔥\uFE0F", "🔥")] [InlineData("nope", null)] [InlineData("👍👍", null)] [InlineData(":unknown:", null)] diff --git a/PrivaPub/Domain/Social/Reactions.cs b/PrivaPub/Domain/Social/Reactions.cs index 05b750b..d1253b8 100644 --- a/PrivaPub/Domain/Social/Reactions.cs +++ b/PrivaPub/Domain/Social/Reactions.cs @@ -178,6 +178,11 @@ namespace PrivaPub.Domain.Social return requireKnownCustom ? default : new Picked(bare, default); return new Picked(bare, emoji.URL); } + // one emoji, one name: ❤ and ❤️ (with the emoji variation selector) were two reactions, counted apart. The selector + // is dropped, then put back on a symbol that shows as text without it (U+2000 to U+2BFF: ❤, ☺, ✌...) + text = text.Replace("\uFE0F", ""); + if (text.Length == 1 && text[0] is >= '\u2000' and <= '\u2BFF') + text += "\uFE0F"; if (text.Any(c => c < 128 || char.IsWhiteSpace(c)) || new StringInfo(text).LengthInTextElements != 1) return default; return new Picked(text, default);