use nostr_sdk::prelude::*;
use serde_json::{json, Value};
use oxigraph::model::Quad;
use oxigraph::store::Store;
use oxjsonld::{
JsonLdLoadDocumentOptions, JsonLdParser as OxJsonLdParser,
JsonLdRemoteDocument,
};
use ::url::Url;
use urn::UrnBuilder;
use crate::err::LDError;
use crate::ldevents::{EVKIND_JSONLD, EVKIND_JSONLD_META0, EVKIND_JSONLD_SUBS};
use crate::loaders::Contexts;
use crate::niri::{content_iri_for_value, ToNamedNode};
pub fn event_to_jsonld(event: &Event) -> Result<String, LDError> {
let mut obj: Value = if event.kind == EVKIND_JSONLD
|| event.kind == EVKIND_JSONLD_META0
|| event.kind == EVKIND_JSONLD_SUBS
|| event.kind == Kind::Metadata
|| event.kind == Kind::ContactList
|| event.kind == Kind::Repost
{
let mut object: Value = serde_json::from_str(event.as_json().as_str())
.map_err(|_| LDError::JsonParseError)?;
if let Ok(mut content) =
serde_json::from_str::<Value>(event.content.as_str())
.map_err(|_| LDError::JsonParseError)
{
if event.kind == Kind::ContactList {
for (key, value) in content.as_object_mut().unwrap() {
value["@type"] = json!("Relay");
if let Ok(mut url) = Url::parse(key) {
url.set_fragment(Some(&event.pubkey.to_hex()));
value["@id"] = json!(url.to_string());
value["relay_url"] = json!(key);
}
}
}
if content.get("@id").is_none() {
content["@id"] = json!(content_iri_for_value(content.clone())?);
}
object["content"] = content.clone();
}
object
} else {
serde_json::from_str(event.as_json().as_str())
.map_err(|_| LDError::JsonParseError)?
};
obj["@context"] = json!("http://nostralink.org/nostr");
obj["@type"] = json!("Event");
obj["url"] = json!(event.id.named_node()?.into_string());
obj["nip21"] = json!(event.pubkey.named_node()?.into_string());
let mut rfmt: Vec<Value> = Vec::new();
for tags in obj["tags"].as_array().iter() {
for (i, tagv) in tags.iter().enumerate() {
let Some(tag) = tagv.as_array() else { continue };
if tag.len() == 0 {
continue;
}
let urn = UrnBuilder::new("nostr-tag", &event.id.to_hex())
.f_component(Some(format!("{}", i).as_str()))
.build()
.map_err(|_| LDError::URNError)?;
let mut ld_tag = json!({});
ld_tag["@id"] = json!(urn.as_str());
ld_tag["@type"] = json!("Tag");
for (idx, v) in tag.into_iter().enumerate() {
if idx == 0 {
ld_tag["tag_letter"] = json!(v);
} else {
ld_tag[format!("tag_value_{}", (idx - 1))] = json!(v);
}
}
rfmt.push(ld_tag);
}
}
obj["tags_set"] = json!(rfmt);
Ok(serde_json::to_string(&obj)
.map_err(|_| LDError::JsonSerializationError)?)
}
pub fn oxjsonld_document_loader(
url: &str,
_options: &JsonLdLoadDocumentOptions,
) -> Result<JsonLdRemoteDocument, Box<dyn std::error::Error + Send + Sync>> {
let Ok(durl) = Url::parse(url) else {
return Err(Box::from("Invalid document URL"));
};
match Contexts::get(durl.path().trim_start_matches("/")) {
Some(asset) => Ok(JsonLdRemoteDocument {
document: asset.data.into(),
document_url: url.to_string(),
}),
None => Err(Box::from("Cannot load document for URL")),
}
}
pub fn event_quads(event: &Event) -> Result<Vec<Quad>, LDError> {
Ok(OxJsonLdParser::new()
.for_reader(event_to_jsonld(&event)?.as_bytes())
.with_load_document_callback(oxjsonld_document_loader)
.into_iter()
.filter_map(|quad| quad.ok())
.collect())
}
pub fn store_event(event: &Event, store: &Store) -> Result<usize, LDError> {
let mut inserted_quads_cn = 0;
for quad in OxJsonLdParser::new()
.for_reader(event_to_jsonld(&event)?.as_bytes())
.with_load_document_callback(oxjsonld_document_loader)
{
if let Ok(evquad) = quad {
match store.insert(&evquad) {
Ok(_) => inserted_quads_cn += 1,
Err(_) => {}
}
}
}
Ok(inserted_quads_cn)
}