chore: convert NIP-21 references to njump link
This commit is contained in:
+113
-1
@@ -8,6 +8,9 @@ use state::FileAttachment;
|
||||
|
||||
pub(crate) const KIND_FILE_MESSAGE: Kind = Kind::Custom(15);
|
||||
|
||||
const SCHEME_WITH_COLON: &str = "nostr:";
|
||||
const NEVENT_PREFIX: &str = "nevent1";
|
||||
|
||||
/// Rendered message.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Message {
|
||||
@@ -148,7 +151,33 @@ impl Message {
|
||||
return format!("[File] {}", file.display_name()).into();
|
||||
}
|
||||
|
||||
self.content.clone().into()
|
||||
// Media-only messages carry their URLs outside `.content`
|
||||
if self.content.is_empty() && !self.media.is_empty() {
|
||||
return self
|
||||
.media
|
||||
.iter()
|
||||
.map(|url| url.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ")
|
||||
.into();
|
||||
}
|
||||
|
||||
let references = nevent_refs(&self.content);
|
||||
if references.is_empty() {
|
||||
return self.content.clone().into();
|
||||
}
|
||||
|
||||
let mut preview = String::with_capacity(self.content.len());
|
||||
let mut last = 0;
|
||||
|
||||
for reference in references {
|
||||
preview.push_str(&self.content[last..reference.range.start]);
|
||||
preview.push_str(&reference.label);
|
||||
last = reference.range.end;
|
||||
}
|
||||
preview.push_str(&self.content[last..]);
|
||||
|
||||
preview.into()
|
||||
}
|
||||
|
||||
/// Image URL of a `:shortcode:` reaction, if the shortcode is declared
|
||||
@@ -219,6 +248,61 @@ fn extract_mentions(content: &str) -> Vec<Mention> {
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// A `nevent` reference found in a content string.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct NeventRef {
|
||||
/// Range of the reference, including the optional `nostr:` scheme
|
||||
pub range: Range<usize>,
|
||||
/// Compact display label, like `https://njump.me/nevent1...d9sp`
|
||||
pub label: String,
|
||||
}
|
||||
|
||||
/// Extracts valid `nevent1...` and `nostr:nevent1...` references from content.
|
||||
pub fn nevent_refs(content: &str) -> Vec<NeventRef> {
|
||||
let mut references = Vec::new();
|
||||
|
||||
for (match_start, _) in content.match_indices(NEVENT_PREFIX) {
|
||||
// Only lowercase references are matched, matching NIP-19 encodings in practice
|
||||
let start = if content[..match_start].ends_with(SCHEME_WITH_COLON) {
|
||||
match_start - SCHEME_WITH_COLON.len()
|
||||
} else {
|
||||
match_start
|
||||
};
|
||||
|
||||
// Skip matches glued to word characters, like the `nevent1` in `xnevent1...`
|
||||
if content[..start].ends_with(char::is_alphanumeric) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let end = match_start
|
||||
+ content[match_start..]
|
||||
.bytes()
|
||||
.take_while(u8::is_ascii_alphanumeric)
|
||||
.count();
|
||||
|
||||
// The bech32 token excludes the optional `nostr:` scheme
|
||||
let bech32 = &content[match_start..end];
|
||||
if Nip19Event::from_bech32(bech32).is_err() {
|
||||
continue;
|
||||
}
|
||||
|
||||
references.push(NeventRef {
|
||||
range: start..end,
|
||||
label: nevent_label(bech32),
|
||||
});
|
||||
}
|
||||
|
||||
references
|
||||
}
|
||||
|
||||
/// Compact display of a nevent link, like `https://njump.me/nevent1...d9sp`.
|
||||
fn nevent_label(bech32: &str) -> String {
|
||||
match bech32.len() {
|
||||
0..=4 => format!("https://njump.me/{bech32}"),
|
||||
_ => format!("https://njump.me/nevent1...{}", &bech32[bech32.len() - 4..]),
|
||||
}
|
||||
}
|
||||
|
||||
/// Extracts all reply (ids) from the event tags.
|
||||
fn extract_reply_ids(inner: &Tags) -> Vec<EventId> {
|
||||
let mut replies_to = vec![];
|
||||
@@ -283,4 +367,32 @@ mod tests {
|
||||
assert_eq!(Message::reaction_image("😂", &emojis), None);
|
||||
assert_eq!(Message::reaction_image(":unknown:", &emojis), None);
|
||||
}
|
||||
|
||||
fn valid_nevent() -> String {
|
||||
Nip19Event::new(EventId::from_byte_array([7; 32]))
|
||||
.to_bech32()
|
||||
.expect("valid nevent")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn image_only_messages_preview_their_url() {
|
||||
let url = "https://example.com/picture.png";
|
||||
let message = Message::from(&rumor(Kind::PrivateDirectMessage, url, Tags::new()));
|
||||
|
||||
assert!(message.content.is_empty());
|
||||
assert_eq!(message.preview().to_string(), url);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn preview_compacts_nevent_references() {
|
||||
let nevent = valid_nevent();
|
||||
let message = Message::from(&rumor(
|
||||
Kind::PrivateDirectMessage,
|
||||
&format!("see nostr:{nevent}"),
|
||||
Tags::new(),
|
||||
));
|
||||
|
||||
let label = format!("https://njump.me/nevent1...{}", &nevent[nevent.len() - 4..]);
|
||||
assert_eq!(message.preview().to_string(), format!("see {label}"));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user