Support sending custom emoji in messages and reactions

This commit is contained in:
2026-10-11 18:11:23 +07:00
parent 6dad27469d
commit c87c789439
13 changed files with 288 additions and 183 deletions
+2 -42
View File
@@ -1,7 +1,7 @@
use std::hash::Hash;
use std::ops::Range;
use common::{EventExt, NostrParser, extract_and_remove_media_urls};
use common::{EventExt, NostrParser, emojis_from_tags, extract_and_remove_media_urls};
use gpui::{SharedString, SharedUri};
use nostr_sdk::prelude::*;
use state::FileAttachment;
@@ -87,7 +87,7 @@ fn from_parts(
let has_file = file.is_some();
let replies_to = extract_reply_ids(tags);
let emojis = Message::emojis_from_tags(tags);
let emojis = emojis_from_tags(tags);
// For file messages `.content` is the encrypted blob URL, not text or media
let mentions = if has_file {
@@ -151,28 +151,6 @@ impl Message {
self.content.clone().into()
}
/// Custom emoji (NIP-30) declared by `emoji` tags, first per shortcode wins
pub fn emojis_from_tags(tags: &Tags) -> Vec<(String, Url)> {
let mut emojis: Vec<(String, Url)> = Vec::new();
for tag in tags.iter() {
let Ok(Nip30Tag::Emoji {
shortcode,
image_url,
..
}) = Nip30Tag::try_from(tag)
else {
continue;
};
if !emojis.iter().any(|(existing, _)| *existing == shortcode) {
emojis.push((shortcode, image_url));
}
}
emojis
}
/// Image URL of a `:shortcode:` reaction, if the shortcode is declared
pub fn reaction_image(content: &str, emojis: &[(String, Url)]) -> Option<Url> {
let shortcode = content.strip_prefix(':')?.strip_suffix(':')?;
@@ -278,24 +256,6 @@ mod tests {
Tag::parse(vec!["emoji", shortcode, url]).expect("valid emoji tag")
}
#[test]
fn emojis_from_tags_parses_and_deduplicates() {
let tags = Tags::from_list(vec![
emoji_tag("soapbox", "https://example.com/soapbox.png"),
emoji_tag("soapbox", "https://example.com/other.png"),
emoji_tag("bad shortcode", "https://example.com/bad.png"),
]);
let emojis = Message::emojis_from_tags(&tags);
assert_eq!(emojis.len(), 1);
assert_eq!(emojis[0].0, "soapbox");
assert_eq!(
emojis[0].1,
Url::parse("https://example.com/soapbox.png").unwrap()
);
}
#[test]
fn message_parses_emoji_tags() {
let tags = Tags::from_list(vec![emoji_tag(
+4 -1
View File
@@ -405,8 +405,11 @@ impl Room {
// Construct a direct message rumor event
// WARNING: never sign and send this event to relays
let mut tags = self.conversation_tags(&replies, sender, cx);
tags.extend(PersonRegistry::emoji_tags(&content, cx));
let mut event = EventBuilder::new(kind, content)
.tags(self.conversation_tags(&replies, sender, cx))
.tags(tags)
.finalize_unsigned(sender);
// Ensure that the ID is set