diff --git a/README.md b/README.md index 4015d4bfa..0a8fde924 100644 --- a/README.md +++ b/README.md @@ -129,7 +129,7 @@ Learn more about `rust-nostr` at . | ❌ | [B0 - Web Bookmarks](https://github.com/nostr-protocol/nips/blob/master/B0.md) | | ✅ | [B7 - Blossom](https://github.com/nostr-protocol/nips/blob/master/B7.md) | | ✅ | [C0 - Code Snippets](https://github.com/nostr-protocol/nips/blob/master/C0.md) | -| ❌ | [C7 - Chats](https://github.com/nostr-protocol/nips/blob/master/C7.md) | +| ✅ | [C7 - Chats](https://github.com/nostr-protocol/nips/blob/master/C7.md) | | ✅ | [EE - Messaging using the MLS Protocol](https://github.com/nostr-protocol/nips/blob/master/EE.md) | ## State diff --git a/crates/nostr/CHANGELOG.md b/crates/nostr/CHANGELOG.md index 5a74d4775..ea5da5f59 100644 --- a/crates/nostr/CHANGELOG.md +++ b/crates/nostr/CHANGELOG.md @@ -35,6 +35,7 @@ ### Added +- Add NIP-C7 support (https://github.com/rust-nostr/nostr/pull/1067) - Implement `ToBech32` trait for `Nip21` - Add nip47 holdinvoice methods and notification (https://github.com/rust-nostr/nostr/pull/1019) - Add `TransactionState` to `LookupInvoiceResponse` and `PaymentNotification` (https://github.com/rust-nostr/nostr/pull/1045) diff --git a/crates/nostr/src/event/builder.rs b/crates/nostr/src/event/builder.rs index a6b1f665d..a581e0778 100644 --- a/crates/nostr/src/event/builder.rs +++ b/crates/nostr/src/event/builder.rs @@ -49,6 +49,8 @@ pub enum Error { /// NIP04 error #[cfg(feature = "nip04")] NIP04(nip04::Error), + /// NIP21 error + NIP21(nip21::Error), /// NIP44 error #[cfg(all(feature = "std", feature = "nip44"))] NIP44(nip44::Error), @@ -80,6 +82,7 @@ impl fmt::Display for Error { Self::NIP03(e) => e.fmt(f), #[cfg(feature = "nip04")] Self::NIP04(e) => e.fmt(f), + Self::NIP21(e) => e.fmt(f), #[cfg(all(feature = "std", feature = "nip44"))] Self::NIP44(e) => e.fmt(f), Self::NIP58(e) => e.fmt(f), @@ -125,6 +128,12 @@ impl From for Error { } } +impl From for Error { + fn from(e: nip21::Error) -> Self { + Self::NIP21(e) + } +} + #[cfg(all(feature = "std", feature = "nip44"))] impl From for Error { fn from(e: nip44::Error) -> Self { @@ -1706,6 +1715,67 @@ impl EventBuilder { pub fn poll_response(response: PollResponse) -> Self { response.to_event_builder() } + + /// Chat message + /// + /// + #[inline] + pub fn chat_message(content: S) -> Self + where + S: Into, + { + Self::new(Kind::ChatMessage, content) + } + + /// Chat message reply + /// + /// + pub fn chat_message_reply( + content: S, + reply_to: &Event, + relay_url: Option, + ) -> Result + where + S: Into, + { + let mut content = content.into(); + + if !has_nostr_event_uri(&content, &reply_to.id) { + let nevent = Nip19Event { + event_id: reply_to.id, + author: None, + kind: None, + relays: relay_url.clone().into_iter().collect(), + }; + content = format!("{}\n{content}", nevent.to_nostr_uri()?); + } + + Ok( + Self::new(Kind::ChatMessage, content).tag(Tag::from_standardized_without_cell( + TagStandard::Quote { + event_id: reply_to.id, + relay_url, + public_key: Some(reply_to.pubkey), + }, + )), + ) + } +} + +fn has_nostr_event_uri(content: &str, event_id: &EventId) -> bool { + const OPTS: NostrParserOptions = NostrParserOptions::disable_all().nostr_uris(true); + + let parser = NostrParser::new().parse(content).opts(OPTS); + + for token in parser.into_iter() { + if let Token::Nostr(nip21) = token { + if nip21.event_id().as_ref() == Some(event_id) { + return true; + } + } + } + + false } #[cfg(test)] diff --git a/crates/nostr/src/event/kind.rs b/crates/nostr/src/event/kind.rs index ce307b2a4..93568c252 100644 --- a/crates/nostr/src/event/kind.rs +++ b/crates/nostr/src/event/kind.rs @@ -163,6 +163,7 @@ kind_variants! { CodeSnippet => 1337, "Code Snippets", "", Poll => 1068, "Poll", "", PollResponse => 1018, "Poll response", "", + ChatMessage => 9, "Chat Message", "", } impl PartialEq for Kind {