Skip to content

Commit

Permalink
nostr: add nip22::extract_root and nip22:extract_parent
Browse files Browse the repository at this point in the history
Closes #677

Signed-off-by: Yuki Kishimoto <yukikishimoto@protonmail.com>
  • Loading branch information
yukibtc committed Jan 23, 2025
1 parent 8f267c6 commit b0f61dc
Show file tree
Hide file tree
Showing 4 changed files with 163 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@
* nostr: add `HttpData::to_authorization` ([Yuki Kishimoto])
* nostr: add `CoordinateBorrow` struct ([Yuki Kishimoto])
* nostr: add `Filter::custom_tags` ([Yuki Kishimoto])
* nostr: add `nip22::extract_root` and `nip22:extract_parent` ([Yuki Kishimoto])
* database: add `Events::first_owned` and `Events::last_owned` ([Yuki Kishimoto])
* database: impl `FlatBufferDecodeBorrowed` for `EventBorrow` ([Yuki Kishimoto])
* database: add `NostrDatabaseWipe` trait ([Yuki Kishimoto])
Expand Down
2 changes: 1 addition & 1 deletion crates/nostr/src/event/tag/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ impl Tags {
self.find(kind).and_then(|t| t.as_standardized())
}

/// Get first tag that match [`TagKind`].
/// Filter tags that match [`TagKind`].
#[inline]
pub fn filter<'a>(&'a self, kind: TagKind<'a>) -> impl Iterator<Item = &'a Tag> {
self.list.iter().filter(move |t| t.kind() == kind)
Expand Down
1 change: 1 addition & 0 deletions crates/nostr/src/nips/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ pub mod nip15;
pub mod nip17;
pub mod nip19;
pub mod nip21;
pub mod nip22;
pub mod nip26;
pub mod nip34;
pub mod nip35;
Expand Down
160 changes: 160 additions & 0 deletions crates/nostr/src/nips/nip22.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
// Copyright (c) 2022-2023 Yuki Kishimoto
// Copyright (c) 2023-2024 Rust Nostr Developers
// Distributed under the MIT software license

//! NIP22: Comment
//!
//! <https://github.com/nostr-protocol/nips/blob/master/22.md>
use crate::nips::nip01::Coordinate;
use crate::nips::nip73::ExternalContentId;
use crate::{Event, EventId, Kind, PublicKey, RelayUrl, TagKind, TagStandard, Url};

/// Borrowed comment extracted data
pub enum Comment<'a> {
/// Event
Event {
/// Event ID
id: &'a EventId,
/// Relay hint
relay_hint: Option<&'a RelayUrl>,
/// Public key hint
pubkey_hint: Option<&'a PublicKey>,
/// Kind
kind: Option<&'a Kind>,
},
/// Coordinate
Coordinate {
/// Coordinate
address: &'a Coordinate,
/// Relay hint
relay_hint: Option<&'a RelayUrl>,
/// Kind
kind: Option<&'a Kind>,
},
/// External content
External {
/// Content
content: &'a ExternalContentId,
/// Web hint
hint: Option<&'a Url>,
/// Kind
///
/// This isn't properly defined yet but it will surely be needed in the future. It could i.e. be a root domain
kind: Option<&'a Kind>,
},
}

/// Extract NIP22 root data
pub fn extract_root(event: &Event) -> Option<Comment> {
extract_data(event, true)
}

/// Extract NIP22 parent data
pub fn extract_parent(event: &Event) -> Option<Comment> {
extract_data(event, false)
}

fn extract_data(event: &Event, is_root: bool) -> Option<Comment> {
if event.kind != Kind::Comment {
return None;
}

let kind: Option<&Kind> = extract_kind(event, is_root);
let pubkey_hint: Option<&PublicKey> = extract_public_key(event, is_root);

// Try to extract event
if let Some((event_id, relay_hint)) = extract_event(event, is_root) {
return Some(Comment::Event {
id: event_id,
relay_hint,
pubkey_hint,
kind,
});
}

// Try to extract coordinate
if let Some((address, relay_hint)) = extract_coordinate(event, is_root) {
return Some(Comment::Coordinate {
address,
relay_hint,
kind,
});
}

if let Some((content, hint)) = extract_external(event, is_root) {
return Some(Comment::External {
content,
hint,
kind,
});
}

None
}

fn check_return<T>(val: T, is_root: bool, uppercase: bool) -> Option<T> {
if (is_root && uppercase) || (!is_root && !uppercase) {
return Some(val);
}

None
}

fn extract_kind(event: &Event, is_root: bool) -> Option<&Kind> {
let tag = event.tags.find_standardized(TagKind::k())?;
match tag {
TagStandard::Kind { kind, uppercase } => check_return(kind, is_root, *uppercase),
_ => None,
}
}

fn extract_public_key(event: &Event, is_root: bool) -> Option<&PublicKey> {
let tag = event.tags.find_standardized(TagKind::p())?;
match tag {
TagStandard::PublicKey {
public_key,
uppercase,
..
} => check_return(public_key, is_root, *uppercase),
_ => None,
}
}

fn extract_event(event: &Event, is_root: bool) -> Option<(&EventId, Option<&RelayUrl>)> {
let tag = event.tags.find_standardized(TagKind::e())?;
match tag {
TagStandard::Event {
event_id,
relay_url,
uppercase,
..
} => check_return((event_id, relay_url.as_ref()), is_root, *uppercase),
_ => None,
}
}

fn extract_coordinate(event: &Event, is_root: bool) -> Option<(&Coordinate, Option<&RelayUrl>)> {
let tag = event.tags.find_standardized(TagKind::a())?;
match tag {
TagStandard::Coordinate {
coordinate,
relay_url,
uppercase,
..
} => check_return((coordinate, relay_url.as_ref()), is_root, *uppercase),
_ => None,
}
}

fn extract_external(event: &Event, is_root: bool) -> Option<(&ExternalContentId, Option<&Url>)> {
let tag = event.tags.find_standardized(TagKind::i())?;
match tag {
TagStandard::ExternalContent {
content,
hint,
uppercase,
} => check_return((content, hint.as_ref()), is_root, *uppercase),
_ => None,
}
}

0 comments on commit b0f61dc

Please sign in to comment.