nostralink 0.2.1

Linked data library for nostr
Documentation
//! Embed JSON-LD in nostr events

use sophia_jsonld::loader::HttpLoader;
use sophia_jsonld::parser::{JsonLdParser, JsonLdQuadSource};
use sophia_jsonld::JsonLdOptions;
use sophia_jsonld::JsonLdQuadSource::Quads;

use sophia::api::prelude::*;
use sophia::inmem::graph::FastGraph;
use sophia::turtle::serializer::turtle::TurtleSerializer;
use sophia_api::prelude::Quad;

use sha256::digest;
use urn::UrnBuilder;

use nostr::prelude::*;
use nostr::Keys;
use nostr_sdk::prelude::*;

use serde_json::{json, Value};

use crate::err::LDError;

/// Events with a ttl (turtle) payload (UNUSED)
pub const EVKIND_TURTLE: Kind = Kind::Custom(7100);

/// JSON-LD events: Events with a stringified json-ld payload
pub const EVKIND_JSONLD: Kind = Kind::Custom(7101);

/// JSON-LD Meta: Events with a stringified json-ld payload, for metadata
pub const EVKIND_JSONLD_META0: Kind = Kind::Custom(7102);

/// JSON-LD Subcription events
pub const EVKIND_JSONLD_SUBS: Kind = Kind::Custom(7103);

/// Creates a JSON-LD EventBuilder from a serde [`Value`]
pub fn jsonld_event_builder(
    mut value: Value,
    evkind: Option<Kind>,
) -> EventBuilder {
    let content_digest = digest(value.to_string());

    let urn = UrnBuilder::new("nostr-content", &content_digest)
        .build()
        .unwrap();

    value["@id"] = json!(urn.as_str());

    let kind = match evkind {
        Some(k) => k,
        None => EVKIND_JSONLD,
    };

    EventBuilder::new(kind, value.to_string())
}

/// Builds a signed JSON-LD event from a serde [`Value`]
pub async fn build_jsonld_event_from_value(
    keys: &Keys,
    mut value: Value,
) -> Result<Event, LDError> {
    let content_digest = digest(value.to_string());

    let urn = UrnBuilder::new("nostr-content", &content_digest)
        .build()
        .map_err(|_| LDError::EventBuildError)?;

    value["@id"] = json!(urn.as_str());

    Ok(EventBuilder::new(EVKIND_JSONLD, value.to_string())
        .sign(keys)
        .await
        .map_err(|_| LDError::EventBuildError)?)
}

pub async fn build_ttl_event_from_value(
    keys: &Keys,
    mut value: Value,
) -> Result<Event, LDError> {
    let json_ld_s = value.to_string();

    let event_id = EventId::new(
        &keys.public_key(),
        &Timestamp::now(),
        &EVKIND_TURTLE,
        &Tags::new(),
        json_ld_s.as_str(),
    );
    let event_uri = event_id
        .to_nostr_uri()
        .map_err(|_| LDError::EventBuildError)?;

    value["@id"] = json!(event_uri);

    build_ttl_event(keys, &value.to_string()).await
}

pub async fn build_ttl_event(
    keys: &Keys,
    json_ld_str: &String,
) -> Result<Event, LDError> {
    let options =
        JsonLdOptions::new().with_default_document_loader::<HttpLoader>();
    let parser = JsonLdParser::new_with_options(options);

    let mut g = FastGraph::new();
    let mut turtlizer = TurtleSerializer::new_stringifier();

    let event_id = EventId::new(
        &keys.public_key(),
        &Timestamp::now(),
        &EVKIND_TURTLE,
        &Tags::new(),
        json_ld_str,
    );
    let event_uri = event_id
        .to_nostr_uri()
        .map_err(|_| LDError::EventBuildError)?;

    match parser.async_parse_str(json_ld_str.as_str()).await {
        Quads(quads) => {
            for quad in quads {
                let triple = quad.into_triple();
                let _ = g.insert(triple.s(), triple.p(), triple.o());
            }

            let _ = turtlizer
                .serialize_graph(&g)
                .map_err(|_| LDError::TTLSerializationError)?;

            let tag_vals = vec![event_uri];

            let ev = EventBuilder::new(EVKIND_TURTLE, turtlizer.to_string())
                .tag(Tag::custom(
                    TagKind::SingleLetter(
                        SingleLetterTag::from_char('g').unwrap(),
                    ),
                    tag_vals,
                ))
                .sign(keys)
                .await
                .map_err(|_| LDError::EventBuildError)?;

            Ok(ev)
        }
        JsonLdQuadSource::Err(_error) => {
            eprintln!("=> {:?}", _error);
            Err(LDError::JsonLDParseError)
        }
    }
}

/// [`Filter`] for JSON-LD objects
pub fn ld_events_filter() -> Filter {
    Filter::new().kind(EVKIND_JSONLD)
}

/// [`Filter`] for JSON-LD metadata events
pub fn ld_metadata_events_filter() -> Filter {
    Filter::new()
        .kind(EVKIND_JSONLD_META0)
        .kind(EVKIND_JSONLD_SUBS)
}