Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions crates/nostr/src/event/tag/kind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ pub enum TagKind<'a> {
///
/// <https://github.com/nostr-protocol/nips/blob/master/C0.md>
Extension,
/// Fallback file source
///
/// <https://github.com/nostr-protocol/nips/blob/master/94.md>
Fallback,
/// File
File,
/// Image
Expand Down Expand Up @@ -109,6 +113,10 @@ pub enum TagKind<'a> {
Nonce,
/// Option
Option,
/// SHA-256 of the original file before any server-side transforms
///
/// <https://github.com/nostr-protocol/nips/blob/master/94.md>
Ox,
/// Payload
Payload,
/// Poll type
Expand Down Expand Up @@ -339,6 +347,7 @@ impl<'a> TagKind<'a> {
Self::Ends => "ends",
Self::Expiration => "expiration",
Self::Extension => "extension",
Self::Fallback => "fallback",
Self::File => "file",
Self::Head => "HEAD",
Self::Image => "image",
Expand All @@ -354,6 +363,7 @@ impl<'a> TagKind<'a> {
Self::Name => "name",
Self::Nonce => "nonce",
Self::Option => "option",
Self::Ox => "ox",
Self::Payload => "payload",
Self::PollType => "polltype",
Self::Preimage => "preimage",
Expand Down Expand Up @@ -418,6 +428,7 @@ impl<'a> From<&'a str> for TagKind<'a> {
"ends" => Self::Ends,
"expiration" => Self::Expiration,
"extension" => Self::Extension,
"fallback" => Self::Fallback,
"file" => Self::File,
"image" => Self::Image,
"license" => Self::License,
Expand All @@ -432,6 +443,7 @@ impl<'a> From<&'a str> for TagKind<'a> {
"name" => Self::Name,
"nonce" => Self::Nonce,
"option" => Self::Option,
"ox" => Self::Ox,
"payload" => Self::Payload,
"polltype" => Self::PollType,
"preimage" => Self::Preimage,
Expand Down
11 changes: 11 additions & 0 deletions crates/nostr/src/event/tag/standard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@ pub enum TagStandard {
},
Server(Url),
Sha256(Sha256Hash),
OriginalHash(Sha256Hash),
Size(usize),
Dim(ImageDimensions),
Magnet(String),
Expand Down Expand Up @@ -303,6 +304,10 @@ pub enum TagStandard {
///
/// <https://github.com/nostr-protocol/nips/blob/master/31.md>
Alt(String),
/// Fallback file source URL
///
/// <https://github.com/nostr-protocol/nips/blob/master/94.md>
Fallback(Url),
/// List of web URLs
Web(Vec<Url>),
Word(String),
Expand Down Expand Up @@ -491,6 +496,7 @@ impl TagStandard {
TagKind::Lnurl => Ok(Self::Lnurl(tag_1.to_string())),
TagKind::Name => Ok(Self::Name(tag_1.to_string())),
TagKind::Url => Ok(Self::Url(Url::parse(tag_1)?)),
TagKind::Fallback => Ok(Self::Fallback(Url::parse(tag_1)?)),
TagKind::Magnet => Ok(Self::Magnet(tag_1.to_string())),
TagKind::Blurhash => Ok(Self::Blurhash(tag_1.to_string())),
TagKind::Streaming => Ok(Self::Streaming(Url::parse(tag_1)?)),
Expand All @@ -513,6 +519,7 @@ impl TagStandard {
#[cfg(feature = "nip98")]
TagKind::Method => Ok(Self::Method(HttpMethod::from_str(tag_1)?)),
TagKind::Payload => Ok(Self::Payload(Sha256Hash::from_str(tag_1)?)),
TagKind::Ox => Ok(Self::OriginalHash(Sha256Hash::from_str(tag_1)?)),
TagKind::Request => Ok(Self::Request(Event::from_json(tag_1)?)),
TagKind::Word => Ok(Self::Word(tag_1.to_string())),
TagKind::Alt => Ok(Self::Alt(tag_1.to_string())),
Expand Down Expand Up @@ -715,6 +722,7 @@ impl TagStandard {
character: Alphabet::X,
uppercase: false,
}),
Self::OriginalHash(..) => TagKind::Ox,
Self::Size(..) => TagKind::Size,
Self::Dim(..) => TagKind::Dim,
Self::Magnet(..) => TagKind::Magnet,
Expand Down Expand Up @@ -749,6 +757,7 @@ impl TagStandard {
}),
Self::Protected => TagKind::Protected,
Self::Alt(..) => TagKind::Alt,
Self::Fallback(..) => TagKind::Fallback,
Self::Web(..) => TagKind::Web,
}
}
Expand Down Expand Up @@ -1015,6 +1024,7 @@ impl From<TagStandard> for Vec<String> {
TagStandard::Aes256Gcm { key, iv } => vec![tag_kind, key, iv],
TagStandard::Server(url) => vec![tag_kind, url.to_string()],
TagStandard::Sha256(hash) => vec![tag_kind, hash.to_string()],
TagStandard::OriginalHash(hash) => vec![tag_kind, hash.to_string()],
TagStandard::Size(bytes) => vec![tag_kind, bytes.to_string()],
TagStandard::Dim(dim) => vec![tag_kind, dim.to_string()],
TagStandard::Magnet(uri) => vec![tag_kind, uri],
Expand Down Expand Up @@ -1077,6 +1087,7 @@ impl From<TagStandard> for Vec<String> {
}
TagStandard::Protected => vec![tag_kind],
TagStandard::Alt(summary) => vec![tag_kind, summary],
TagStandard::Fallback(url) => vec![tag_kind, url.to_string()],
TagStandard::Web(urls) => {
let mut tag: Vec<String> = Vec::with_capacity(1 + urls.len());
tag.push(tag_kind);
Expand Down
171 changes: 171 additions & 0 deletions crates/nostr/src/nips/nip94.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ pub struct FileMetadata {
pub mime_type: String,
/// SHA256 of file
pub hash: Sha256Hash,
/// SHA-256 of the original file before any server-side transforms (`ox` tag)
pub original_hash: Option<Sha256Hash>,
/// AES 256 GCM
pub aes_256_gcm: Option<(String, String)>,
/// Size in bytes
Expand All @@ -56,6 +58,16 @@ pub struct FileMetadata {
pub magnet: Option<String>,
/// Blurhash
pub blurhash: Option<String>,
/// Thumbnail URL
pub thumb: Option<Url>,
/// Preview image URL
pub image: Option<Url>,
/// Short text summary / description
pub summary: Option<String>,
/// Alt text for accessibility
pub alt: Option<String>,
/// Fallback download URLs
pub fallback: Vec<Url>,
}

impl FileMetadata {
Expand All @@ -68,11 +80,25 @@ impl FileMetadata {
url,
mime_type: mime_type.into(),
hash,
original_hash: None,
aes_256_gcm: None,
size: None,
dim: None,
magnet: None,
blurhash: None,
thumb: None,
image: None,
summary: None,
alt: None,
fallback: Vec::new(),
}
}

/// Set SHA-256 of the original file before server-side transforms (`ox` tag)
pub fn original_hash(self, hash: Sha256Hash) -> Self {
Self {
original_hash: Some(hash),
..self
}
}

Expand Down Expand Up @@ -124,6 +150,50 @@ impl FileMetadata {
..self
}
}

/// Add thumbnail URL
pub fn thumb(self, thumb: Url) -> Self {
Self {
thumb: Some(thumb),
..self
}
}

/// Add preview image URL
pub fn image(self, image: Url) -> Self {
Self {
image: Some(image),
..self
}
}

/// Add short text summary / description
pub fn summary<S>(self, summary: S) -> Self
where
S: Into<String>,
{
Self {
summary: Some(summary.into()),
..self
}
}

/// Add alt text for accessibility
pub fn alt<S>(self, alt: S) -> Self
where
S: Into<String>,
{
Self {
alt: Some(alt.into()),
..self
}
}

/// Add a fallback download URL
pub fn add_fallback(mut self, url: Url) -> Self {
self.fallback.push(url);
self
}
}

impl From<FileMetadata> for Vec<Tag> {
Expand All @@ -132,11 +202,17 @@ impl From<FileMetadata> for Vec<Tag> {
url,
mime_type,
hash,
original_hash,
aes_256_gcm,
size,
dim,
magnet,
blurhash,
thumb,
image,
summary,
alt,
fallback,
} = metadata;

let mut tags: Vec<Tag> = Vec::with_capacity(3);
Expand All @@ -149,6 +225,12 @@ impl From<FileMetadata> for Vec<Tag> {
hash,
)));

if let Some(ox) = original_hash {
tags.push(Tag::from_standardized_without_cell(
TagStandard::OriginalHash(ox),
));
}

if let Some((key, iv)) = aes_256_gcm {
tags.push(Tag::from_standardized_without_cell(
TagStandard::Aes256Gcm { key, iv },
Expand All @@ -175,6 +257,34 @@ impl From<FileMetadata> for Vec<Tag> {
)));
}

if let Some(thumb) = thumb {
tags.push(Tag::from_standardized_without_cell(TagStandard::Thumb(
thumb, None,
)));
}

if let Some(image) = image {
tags.push(Tag::from_standardized_without_cell(TagStandard::Image(
image, None,
)));
}

if let Some(summary) = summary {
tags.push(Tag::from_standardized_without_cell(TagStandard::Summary(
summary,
)));
}

if let Some(alt) = alt {
tags.push(Tag::from_standardized_without_cell(TagStandard::Alt(alt)));
}

for url in fallback {
tags.push(Tag::from_standardized_without_cell(TagStandard::Fallback(
url,
)));
}

tags
}
}
Expand Down Expand Up @@ -218,6 +328,17 @@ impl TryFrom<Vec<Tag>> for FileMetadata {

let mut metadata = FileMetadata::new(url.clone(), mime, *sha256);

if let Some(TagStandard::OriginalHash(ox)) = value.iter().find_map(|t| {
let t = t.as_standardized();
if matches!(t, Some(TagStandard::OriginalHash(..))) {
t
} else {
None
}
}) {
metadata = metadata.original_hash(*ox);
}

if let Some(TagStandard::Aes256Gcm { key, iv }) = value.iter().find_map(|t| {
let t = t.as_standardized();
if matches!(t, Some(TagStandard::Aes256Gcm { .. })) {
Expand Down Expand Up @@ -273,6 +394,56 @@ impl TryFrom<Vec<Tag>> for FileMetadata {
metadata = metadata.blurhash(bh);
}

if let Some(TagStandard::Thumb(url, _)) = value.iter().find_map(|t| {
let t = t.as_standardized();
if matches!(t, Some(TagStandard::Thumb(..))) {
t
} else {
None
}
}) {
metadata = metadata.thumb(url.clone());
}

if let Some(TagStandard::Image(url, _)) = value.iter().find_map(|t| {
let t = t.as_standardized();
if matches!(t, Some(TagStandard::Image(..))) {
t
} else {
None
}
}) {
metadata = metadata.image(url.clone());
}

if let Some(TagStandard::Summary(s)) = value.iter().find_map(|t| {
let t = t.as_standardized();
if matches!(t, Some(TagStandard::Summary(..))) {
t
} else {
None
}
}) {
metadata = metadata.summary(s);
}

if let Some(TagStandard::Alt(s)) = value.iter().find_map(|t| {
let t = t.as_standardized();
if matches!(t, Some(TagStandard::Alt(..))) {
t
} else {
None
}
}) {
metadata = metadata.alt(s);
}

for tag in value.iter() {
if let Some(TagStandard::Fallback(url)) = tag.as_standardized() {
metadata = metadata.add_fallback(url.clone());
}
}

Ok(metadata)
}
}
Expand Down