use alloc::string::{String, ToString};
use alloc::vec::Vec;
use core::fmt;
use core::str::FromStr;
use secp256k1::schnorr::Signature;
use crate::types::{RelayUrl, Url};
use crate::{
Alphabet, ImageDimensions, PublicKey, SingleLetterTag, Tag, TagKind, TagStandard, Timestamp,
};
#[derive(Debug, PartialEq, Eq)]
pub enum Error {
UnknownLiveEventMarker(String),
DescriptionMissing,
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::UnknownLiveEventMarker(u) => write!(f, "Unknown marker: {u}"),
Self::DescriptionMissing => f.write_str("Event missing a description"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum LiveEventMarker {
Host,
Speaker,
Participant,
}
impl fmt::Display for LiveEventMarker {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl LiveEventMarker {
pub fn as_str(&self) -> &str {
match self {
Self::Host => "Host",
Self::Speaker => "Speaker",
Self::Participant => "Participant",
}
}
}
impl FromStr for LiveEventMarker {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"Host" => Ok(Self::Host),
"Speaker" => Ok(Self::Speaker),
"Participant" => Ok(Self::Participant),
s => Err(Error::UnknownLiveEventMarker(s.to_string())),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum LiveEventStatus {
Planned,
Live,
Ended,
Custom(String),
}
impl fmt::Display for LiveEventStatus {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl LiveEventStatus {
pub fn as_str(&self) -> &str {
match self {
Self::Planned => "planned",
Self::Live => "live",
Self::Ended => "ended",
Self::Custom(s) => s.as_str(),
}
}
}
impl<S> From<S> for LiveEventStatus
where
S: Into<String>,
{
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),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct LiveEventHost {
pub public_key: PublicKey,
pub relay_url: Option<RelayUrl>,
pub proof: Option<Signature>,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct LiveEvent {
pub id: String,
pub title: Option<String>,
pub summary: Option<String>,
pub image: Option<(Url, Option<ImageDimensions>)>,
pub hashtags: Vec<String>,
pub streaming: Option<Url>,
pub recording: Option<Url>,
pub starts: Option<Timestamp>,
pub ends: Option<Timestamp>,
pub status: Option<LiveEventStatus>,
pub current_participants: Option<u64>,
pub total_participants: Option<u64>,
pub relays: Vec<RelayUrl>,
pub host: Option<LiveEventHost>,
pub speakers: Vec<(PublicKey, Option<RelayUrl>)>,
pub participants: Vec<(PublicKey, Option<RelayUrl>)>,
}
impl LiveEvent {
pub fn new<S>(id: S) -> Self
where
S: Into<String>,
{
Self {
id: id.into(),
title: None,
summary: None,
image: None,
hashtags: Vec::new(),
streaming: None,
recording: None,
starts: None,
ends: None,
status: None,
current_participants: None,
total_participants: None,
relays: Vec::new(),
host: None,
speakers: Vec::new(),
participants: Vec::new(),
}
}
}
impl From<LiveEvent> for Vec<Tag> {
fn from(live_event: LiveEvent) -> Self {
let LiveEvent {
id,
title,
summary,
image,
hashtags,
streaming,
recording,
starts,
ends,
status,
current_participants,
total_participants,
relays,
host,
speakers,
participants,
} = live_event;
let mut tags = Vec::with_capacity(1);
tags.push(Tag::identifier(id));
if let Some(title) = title {
tags.push(Tag::from_standardized_without_cell(TagStandard::Title(
title,
)));
}
if let Some(summary) = summary {
tags.push(Tag::from_standardized_without_cell(TagStandard::Summary(
summary,
)));
}
if let Some(streaming) = streaming {
tags.push(Tag::from_standardized_without_cell(TagStandard::Streaming(
streaming,
)));
}
if let Some(status) = status {
tags.push(Tag::from_standardized_without_cell(
TagStandard::LiveEventStatus(status),
));
}
if let Some(LiveEventHost {
public_key,
relay_url,
proof,
}) = host
{
tags.push(Tag::from_standardized_without_cell(
TagStandard::PublicKeyLiveEvent {
public_key,
relay_url,
marker: LiveEventMarker::Host,
proof,
},
));
}
for (public_key, relay_url) in speakers.into_iter() {
tags.push(Tag::from_standardized_without_cell(
TagStandard::PublicKeyLiveEvent {
public_key,
relay_url,
marker: LiveEventMarker::Speaker,
proof: None,
},
));
}
for (public_key, relay_url) in participants.into_iter() {
tags.push(Tag::from_standardized_without_cell(
TagStandard::PublicKeyLiveEvent {
public_key,
relay_url,
marker: LiveEventMarker::Participant,
proof: None,
},
));
}
if let Some((image, dim)) = image {
tags.push(Tag::from_standardized_without_cell(TagStandard::Image(
image, dim,
)));
}
for hashtag in hashtags.into_iter() {
tags.push(Tag::from_standardized_without_cell(TagStandard::Hashtag(
hashtag,
)));
}
if let Some(recording) = recording {
tags.push(Tag::from_standardized_without_cell(TagStandard::Recording(
recording,
)));
}
if let Some(starts) = starts {
tags.push(Tag::from_standardized_without_cell(TagStandard::Starts(
starts,
)));
}
if let Some(ends) = ends {
tags.push(Tag::from_standardized_without_cell(TagStandard::Ends(ends)));
}
if let Some(current_participants) = current_participants {
tags.push(Tag::from_standardized_without_cell(
TagStandard::CurrentParticipants(current_participants),
));
}
if let Some(total_participants) = total_participants {
tags.push(Tag::from_standardized_without_cell(
TagStandard::TotalParticipants(total_participants),
));
}
if !relays.is_empty() {
tags.push(Tag::from_standardized_without_cell(TagStandard::Relays(
relays,
)));
}
tags
}
}
impl TryFrom<Vec<Tag>> for LiveEvent {
type Error = Error;
fn try_from(tags: Vec<Tag>) -> Result<Self, Self::Error> {
let id: &str = tags
.iter()
.find(|t| t.kind() == TagKind::SingleLetter(SingleLetterTag::lowercase(Alphabet::D)))
.and_then(|t| t.content())
.ok_or(Error::DescriptionMissing)?;
let mut live_event = LiveEvent::new(id);
for tag in tags.into_iter() {
let Some(tag) = tag.to_standardized() else {
continue;
};
match tag {
TagStandard::Title(title) => live_event.title = Some(title),
TagStandard::Summary(summary) => live_event.summary = Some(summary),
TagStandard::Streaming(url) => live_event.streaming = Some(url),
TagStandard::LiveEventStatus(status) => live_event.status = Some(status),
TagStandard::PublicKeyLiveEvent {
public_key,
relay_url,
marker,
proof,
} => match marker {
LiveEventMarker::Host => {
live_event.host = Some(LiveEventHost {
public_key,
relay_url,
proof,
})
}
LiveEventMarker::Speaker => live_event.speakers.push((public_key, relay_url)),
LiveEventMarker::Participant => {
live_event.participants.push((public_key, relay_url))
}
},
TagStandard::Image(image, dim) => live_event.image = Some((image, dim)),
TagStandard::Hashtag(hashtag) => live_event.hashtags.push(hashtag),
TagStandard::Recording(url) => live_event.recording = Some(url),
TagStandard::Starts(starts) => live_event.starts = Some(starts),
TagStandard::Ends(ends) => live_event.ends = Some(ends),
TagStandard::CurrentParticipants(n) => live_event.current_participants = Some(n),
TagStandard::TotalParticipants(n) => live_event.total_participants = Some(n),
TagStandard::Relays(mut relays) => live_event.relays.append(&mut relays),
_ => {}
}
}
Ok(live_event)
}
}