use nostr_sdk::prelude::*;
use serde_json::{json, Value};
use sophia::api::prelude::*;
use sophia::api::serializer::{Stringifier, TripleSerializer};
use sophia::inmem::graph::FastGraph;
use sophia::turtle::serializer::{nt::NtSerializer, turtle::TurtleSerializer};
use sophia_jsonld::loader::{ChainLoader, HttpLoader};
use sophia_jsonld::loader_factory::ClosureLoaderFactory;
use sophia_jsonld::parser::{JsonLdParser, JsonLdQuadSource};
use sophia_jsonld::JsonLdOptions;
use sophia_jsonld::JsonLdQuadSource::Quads;
use ::url::Url;
use urn::UrnBuilder;
use crate::err::LDError;
use crate::ldbuilder::NostraObject;
use crate::ldevents::{EVKIND_JSONLD, EVKIND_JSONLD_META0, EVKIND_JSONLD_SUBS};
use crate::loaders::static_contexts_loader;
use crate::niri::{content_iri_for_value, ToNamedNode};
use crate::parser::{hybrid_parser, HybridLoader};
pub async fn ttlify_event(event: &Event) -> Result<String, LDError> {
ttlify_event_with_parser(event, &hybrid_parser()).await
}
pub async fn ttlify_event_with_parser(
event: &Event,
parser: &JsonLdParser<
ClosureLoaderFactory<HybridLoader, impl Fn() -> HybridLoader>,
>,
) -> Result<String, LDError> {
let mut g = FastGraph::new();
let mut turtlizer = TurtleSerializer::new_stringifier();
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);
let json_ld_str = serde_json::to_string(&obj)
.map_err(|_| LDError::JsonSerializationError)?;
match parser.async_parse_str(json_ld_str.as_str()).await {
Quads(quads) => {
for quad in quads {
let triple = quad.into_triple();
g.insert(triple.s(), triple.p(), triple.o())
.map_err(|_| LDError::TripleInsertError)?;
}
turtlizer
.serialize_graph(&g)
.map_err(|_| LDError::TTLSerializationError)?;
return Ok(turtlizer.to_string());
}
JsonLdQuadSource::Err(err) => {
eprintln!("{err:?}");
Err(LDError::JsonLDParseError)
}
}
}
pub async fn ntify_event(event: &Event) -> Result<String, LDError> {
ntify_event_with_parser(event, &hybrid_parser()).await
}
pub async fn ntify_event_with_parser(
event: &Event,
parser: &JsonLdParser<
ClosureLoaderFactory<HybridLoader, impl Fn() -> HybridLoader>,
>,
) -> Result<String, LDError> {
let mut nt_stringifier = NtSerializer::new_stringifier();
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);
let json_ld_str = serde_json::to_string(&obj)
.map_err(|_| LDError::JsonSerializationError)?;
let quads = parser.async_parse_str(json_ld_str.as_str()).await;
Ok(nt_stringifier
.serialize_triples(quads.to_triples())
.map_err(|_| LDError::NTSerializationError)?
.to_string())
}
pub fn ttlify_event_sync(event: &Event) -> Result<String, LDError> {
let Ok(rt) = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
else {
return Err(LDError::TokioRuntimeError);
};
rt.block_on(ttlify_event(event))
}
pub fn ttlify_event_sync_with_parser(
event: &Event,
parser: &JsonLdParser<
ClosureLoaderFactory<HybridLoader, impl Fn() -> HybridLoader>,
>,
) -> Result<String, LDError> {
let Ok(rt) = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
else {
return Err(LDError::TokioRuntimeError);
};
rt.block_on(ttlify_event_with_parser(event, &parser))
}
pub fn ntify_event_sync_with_parser(
event: &Event,
parser: &JsonLdParser<
ClosureLoaderFactory<HybridLoader, impl Fn() -> HybridLoader>,
>,
) -> Result<String, LDError> {
let Ok(rt) = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
else {
return Err(LDError::TokioRuntimeError);
};
rt.block_on(ntify_event_with_parser(event, &parser))
}
pub fn ttlify_nobject(
mut obj: Box<dyn NostraObject>,
) -> Result<String, LDError> {
let options = JsonLdOptions::new().with_document_loader_closure(|| {
ChainLoader::new(static_contexts_loader(), HttpLoader::default())
});
let parser = JsonLdParser::new_with_options(options);
let mut g = FastGraph::new();
let mut turtlizer = TurtleSerializer::new_stringifier();
let Ok(rt) = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
else {
return Err(LDError::TokioRuntimeError);
};
let value = obj.value().map_err(|_| LDError::JsonLDParseError)?;
match rt.block_on(parser.async_parse_str(&value.to_string())) {
Quads(quads) => {
for quad in quads {
let triple = quad.into_triple();
g.insert(triple.s(), triple.p(), triple.o())
.map_err(|_| LDError::TripleInsertError)?;
}
turtlizer
.serialize_graph(&g)
.map_err(|_| LDError::TTLSerializationError)?;
return Ok(turtlizer.to_string());
}
JsonLdQuadSource::Err(_err) => {
return Err(LDError::JsonLDParseError);
}
}
}
#[tokio::test]
async fn test_rdfify_nostr_note() -> Result<(), Box<dyn std::error::Error>> {
use crate::prelude::*;
let keys = Keys::generate();
let event = EventBuilder::text_note("Hello Nostr").sign(&keys).await?;
let ttl = ttlify_event(&event).await.unwrap();
let object: Value = serde_json::from_str(event.as_json().as_str()).unwrap();
println!("{}", serde_json::to_string_pretty(&object).unwrap());
println!("{ttl}");
Ok(())
}