diff --git a/bindings/nostr-ffi/src/error.rs b/bindings/nostr-ffi/src/error.rs index 4fe6a9a0e..7615db851 100644 --- a/bindings/nostr-ffi/src/error.rs +++ b/bindings/nostr-ffi/src/error.rs @@ -123,6 +123,12 @@ impl From for NostrError { } } +impl From for NostrError { + fn from(e: nostr::nips::nip53::Error) -> NostrError { + Self::Generic { err: e.to_string() } + } +} + impl From for NostrError { fn from(e: nostr::nips::nip90::Error) -> NostrError { Self::Generic { err: e.to_string() } diff --git a/bindings/nostr-ffi/src/event/builder.rs b/bindings/nostr-ffi/src/event/builder.rs index 8e025f435..8f5bcb30f 100644 --- a/bindings/nostr-ffi/src/event/builder.rs +++ b/bindings/nostr-ffi/src/event/builder.rs @@ -13,6 +13,7 @@ use nostr::{ use super::{Event, EventId}; use crate::error::Result; use crate::key::Keys; +use crate::nips::nip53::LiveEvent; use crate::nips::nip57::ZapRequestData; use crate::types::{Contact, Metadata}; use crate::{FileMetadata, NostrConnectMessage, PublicKey, Tag, UnsignedEvent}; @@ -254,6 +255,12 @@ impl EventBuilder { }) } + pub fn live_event(live_event: LiveEvent) -> Self { + Self { + builder: EventBuilderSdk::live_event(live_event.into()), + } + } + pub fn report(tags: Vec>, content: String) -> Self { let tags = tags .into_iter() diff --git a/bindings/nostr-ffi/src/event/tag.rs b/bindings/nostr-ffi/src/event/tag.rs index bbcc9bfea..e9f1d51cc 100644 --- a/bindings/nostr-ffi/src/event/tag.rs +++ b/bindings/nostr-ffi/src/event/tag.rs @@ -4,12 +4,11 @@ use std::ops::Deref; use std::str::FromStr; -use nostr::event::tag::{ - self, HttpMethod, Identity, ImageDimensions, LiveEventMarker, LiveEventStatus, Marker, Report, -}; +use nostr::event::tag::{self, HttpMethod, Identity, ImageDimensions, Marker, Report}; use nostr::hashes::sha256::Hash as Sha256Hash; use nostr::nips::nip26::Conditions; use nostr::nips::nip48::Protocol; +use nostr::nips::nip53::{LiveEventMarker, LiveEventStatus}; use nostr::nips::nip90::DataVendingMachineStatus; use nostr::secp256k1::schnorr::Signature; use nostr::secp256k1::XOnlyPublicKey; diff --git a/bindings/nostr-ffi/src/lib.rs b/bindings/nostr-ffi/src/lib.rs index dfd2926fd..b56197d74 100644 --- a/bindings/nostr-ffi/src/lib.rs +++ b/bindings/nostr-ffi/src/lib.rs @@ -31,6 +31,7 @@ mod ffi { pub use crate::nips::nip11::RelayInformationDocument; //pub use crate::nips::nip44::{nip44_decrypt, nip44_encrypt}; pub use crate::nips::nip46::{NostrConnectMessage, NostrConnectURI}; + pub use crate::nips::nip53::{Image, LiveEvent, LiveEventHost, LiveEventStatus, Person}; pub use crate::nips::nip57::ZapRequestData; pub use crate::nips::nip94::FileMetadata; pub use crate::types::{Contact, ImageDimensions, Metadata, Profile, Timestamp}; diff --git a/bindings/nostr-ffi/src/nips/mod.rs b/bindings/nostr-ffi/src/nips/mod.rs index b4773307c..b3ba9bec1 100644 --- a/bindings/nostr-ffi/src/nips/mod.rs +++ b/bindings/nostr-ffi/src/nips/mod.rs @@ -6,5 +6,6 @@ pub mod nip05; pub mod nip11; //pub mod nip44; pub mod nip46; +pub mod nip53; pub mod nip57; pub mod nip94; diff --git a/bindings/nostr-ffi/src/nips/nip53.rs b/bindings/nostr-ffi/src/nips/nip53.rs new file mode 100644 index 000000000..a96a90c6d --- /dev/null +++ b/bindings/nostr-ffi/src/nips/nip53.rs @@ -0,0 +1,109 @@ +use std::str::FromStr; + +use nostr::nips::nip53; +use nostr::secp256k1::schnorr::Signature; +use nostr::types::url::UncheckedUrl; + +use crate::{ImageDimensions, PublicKey, Timestamp}; + +pub struct LiveEventHost { + pub public_key: PublicKey, + pub relay_url: Option, + pub proof: Option, +} + +impl From for nip53::LiveEventHost { + fn from(value: LiveEventHost) -> Self { + Self { + public_key: *value.public_key, + relay_url: value.relay_url.map(UncheckedUrl::from), + proof: match value.proof { + Some(sig) => Signature::from_str(&sig).ok(), + None => None, + }, + } + } +} + +pub struct Image { + pub url: String, + pub dimensions: Option, +} + +pub enum LiveEventStatus { + Planned(), + Live(), + Ended(), + Custom { custom: String }, +} + +impl From for nip53::LiveEventStatus { + fn from(value: LiveEventStatus) -> Self { + match value { + LiveEventStatus::Planned() => Self::Planned, + LiveEventStatus::Live() => Self::Live, + LiveEventStatus::Ended() => Self::Ended, + LiveEventStatus::Custom { custom } => Self::Custom(custom), + } + } +} + +pub struct Person { + pub public_key: PublicKey, + pub url: Option, +} + +pub struct LiveEvent { + pub id: String, + pub title: Option, + pub summary: Option, + pub image: Option, + pub hashtags: Vec, + pub streaming: Option, + pub recording: Option, + pub start: Option, + pub ends: Option, + pub status: Option, + pub current_participants: Option, + pub total_participants: Option, + pub relays: Vec, + pub host: Option, + pub speakers: Vec, + pub participants: Vec, +} + +impl From for nip53::LiveEvent { + fn from(value: LiveEvent) -> Self { + Self { + id: value.id, + title: value.title, + summary: value.summary, + image: value.image.map(|i: Image| { + ( + UncheckedUrl::from(i.url), + i.dimensions.map(|d: ImageDimensions| d.into()), + ) + }), + hashtags: value.hashtags, + streaming: value.streaming.map(UncheckedUrl::from), + recording: value.recording.map(UncheckedUrl::from), + starts: value.start.map(|t| *t), + ends: value.ends.map(|t| *t), + status: value.status.map(|s| s.into()), + current_participants: value.current_participants, + total_participants: value.total_participants, + relays: value.relays.into_iter().map(UncheckedUrl::from).collect(), + host: value.host.map(|h| h.into()), + speakers: value + .speakers + .into_iter() + .map(|s| (*s.public_key, s.url.map(UncheckedUrl::from))) + .collect(), + participants: value + .participants + .into_iter() + .map(|s| (*s.public_key, s.url.map(UncheckedUrl::from))) + .collect(), + } + } +} diff --git a/bindings/nostr-ffi/src/types/image.rs b/bindings/nostr-ffi/src/types/image.rs index eebc886c2..ee91cb540 100644 --- a/bindings/nostr-ffi/src/types/image.rs +++ b/bindings/nostr-ffi/src/types/image.rs @@ -11,6 +11,12 @@ impl From for ImageDimensions { } } +impl From for nostr::ImageDimensions { + fn from(dim: ImageDimensions) -> Self { + dim.inner + } +} + impl From<&ImageDimensions> for nostr::ImageDimensions { fn from(dim: &ImageDimensions) -> Self { dim.inner diff --git a/crates/nostr/src/event/tag/mod.rs b/crates/nostr/src/event/tag/mod.rs index 74fbe4d1c..7c3716d6c 100644 --- a/crates/nostr/src/event/tag/mod.rs +++ b/crates/nostr/src/event/tag/mod.rs @@ -24,6 +24,7 @@ pub use self::indexes::{TagIndexValues, TagIndexes}; use super::id::{self, EventId}; use crate::nips::nip26::{Conditions, Error as Nip26Error}; use crate::nips::nip48::Protocol; +use crate::nips::nip53::{self, LiveEventMarker, LiveEventStatus}; use crate::nips::nip90::DataVendingMachineStatus; use crate::{Event, JsonUtil, Kind, Timestamp, UncheckedUrl}; @@ -34,8 +35,6 @@ pub enum Error { MarkerParseError, /// Unknown [`Report`] UnknownReportType, - /// Unknown [`LiveEventMarker`] - UnknownLiveEventMarker(String), /// Impossible to find tag kind KindNotFound, /// Invalid length @@ -54,6 +53,8 @@ pub enum Error { EventId(id::Error), /// NIP26 error NIP26(Nip26Error), + ///NIP53 error + NIP53(nip53::Error), /// Event Error Event(crate::event::Error), /// NIP-39 Error @@ -74,7 +75,6 @@ impl fmt::Display for Error { match self { Self::MarkerParseError => write!(f, "Impossible to parse marker"), Self::UnknownReportType => write!(f, "Unknown report type"), - Self::UnknownLiveEventMarker(u) => write!(f, "Unknown live event marker: {u}"), Self::KindNotFound => write!(f, "Impossible to find tag kind"), Self::InvalidLength => write!(f, "Invalid length"), Self::InvalidZapRequest => write!(f, "Invalid Zap request"), @@ -84,6 +84,7 @@ impl fmt::Display for Error { Self::Url(e) => write!(f, "Url: {e}"), Self::EventId(e) => write!(f, "Event ID: {e}"), Self::NIP26(e) => write!(f, "NIP26: {e}"), + Self::NIP53(e) => write!(f, "NIP53: {e}"), Self::Event(e) => write!(f, "Event: {e}"), Self::InvalidIdentity => write!(f, "Invalid identity tag"), Self::InvalidImageDimensions => write!(f, "Invalid image dimensions"), @@ -129,6 +130,12 @@ impl From for Error { } } +impl From for Error { + fn from(e: nip53::Error) -> Self { + Self::NIP53(e) + } +} + impl From for Error { fn from(e: crate::event::Error) -> Self { Self::Event(e) @@ -170,78 +177,6 @@ where } } -/// Live Event Marker -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum LiveEventMarker { - /// Host - Host, - /// Speaker - Speaker, - /// Participant - Participant, -} - -impl fmt::Display for LiveEventMarker { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Self::Host => write!(f, "Host"), - Self::Speaker => write!(f, "Speaker"), - Self::Participant => write!(f, "Participant"), - } - } -} - -impl FromStr for LiveEventMarker { - type Err = Error; - fn from_str(s: &str) -> Result { - match s { - "Host" => Ok(Self::Host), - "Speaker" => Ok(Self::Speaker), - "Participant" => Ok(Self::Participant), - s => Err(Error::UnknownLiveEventMarker(s.to_string())), - } - } -} - -/// Live Event Status -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum LiveEventStatus { - /// Planned - Planned, - /// Live - Live, - /// Ended - Ended, - /// Custom - Custom(String), -} - -impl fmt::Display for LiveEventStatus { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Self::Planned => write!(f, "planned"), - Self::Live => write!(f, "live"), - Self::Ended => write!(f, "ended"), - Self::Custom(s) => write!(f, "{s}"), - } - } -} - -impl From for LiveEventStatus -where - S: Into, -{ - fn from(s: S) -> Self { - let s: String = s.into(); - match s.as_str() { - "planned" => Self::Planned, - "live" => Self::Live, - "ended" => Self::Ended, - _ => Self::Custom(s), - } - } -} - /// Report #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum Report { diff --git a/crates/nostr/src/nips/nip53.rs b/crates/nostr/src/nips/nip53.rs index 47d690fe6..664b2797e 100644 --- a/crates/nostr/src/nips/nip53.rs +++ b/crates/nostr/src/nips/nip53.rs @@ -5,15 +5,103 @@ //! //! -use alloc::string::String; +use alloc::string::{String, ToString}; use alloc::vec::Vec; +use core::fmt; +use core::str::FromStr; use bitcoin::secp256k1::schnorr::Signature; use bitcoin::secp256k1::XOnlyPublicKey; -use crate::event::tag::{LiveEventMarker, LiveEventStatus}; use crate::{ImageDimensions, Tag, Timestamp, UncheckedUrl}; +/// NIP53 Error +#[derive(Debug)] +pub enum Error { + /// Unknown [`LiveEventMarker`] + UnknownLiveEventMarker(String), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::UnknownLiveEventMarker(u) => write!(f, "Unknown live event marker: {u}"), + } + } +} + +/// Live Event Marker +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum LiveEventMarker { + /// Host + Host, + /// Speaker + Speaker, + /// Participant + Participant, +} + +impl fmt::Display for LiveEventMarker { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::Host => write!(f, "Host"), + Self::Speaker => write!(f, "Speaker"), + Self::Participant => write!(f, "Participant"), + } + } +} + +impl FromStr for LiveEventMarker { + type Err = Error; + fn from_str(s: &str) -> Result { + match s { + "Host" => Ok(Self::Host), + "Speaker" => Ok(Self::Speaker), + "Participant" => Ok(Self::Participant), + s => Err(Error::UnknownLiveEventMarker(s.to_string())), + } + } +} + +/// Live Event Status +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum LiveEventStatus { + /// Planned + Planned, + /// Live + Live, + /// Ended + Ended, + /// Custom + Custom(String), +} + +impl fmt::Display for LiveEventStatus { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::Planned => write!(f, "planned"), + Self::Live => write!(f, "live"), + Self::Ended => write!(f, "ended"), + Self::Custom(s) => write!(f, "{s}"), + } + } +} + +impl From for LiveEventStatus +where + S: Into, +{ + fn from(s: S) -> Self { + let s: String = s.into(); + match s.as_str() { + "planned" => Self::Planned, + "live" => Self::Live, + "ended" => Self::Ended, + _ => Self::Custom(s), + } + } +} + /// Live Event Host pub struct LiveEventHost { /// Host public key