Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Note JSON API #20

Merged
merged 5 commits into from
Jan 1, 2025
Merged
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
2 changes: 2 additions & 0 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ jobs:
runs-on: ubuntu-latest

steps:
- name: Deps
run: sudo apt-get install libfontconfig1-dev libfreetype6-dev libssl-dev
- uses: actions/checkout@v3
- name: Build
run: cargo build --verbose
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,4 @@ lru = "0.12.1"
bytes = "1.5.0"
http = "1.0.0"
html-escape = "0.2.13"
serde_json = "*"
8 changes: 8 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ pub enum Error {
NostrClient(nostr_sdk::client::Error),
Recv(RecvError),
Io(std::io::Error),
Json(serde_json::Error),
Generic(String),
Timeout(tokio::time::error::Elapsed),
Image(image::error::ImageError),
Expand Down Expand Up @@ -44,6 +45,12 @@ impl From<http::uri::InvalidUri> for Error {
}
}

impl From<serde_json::Error> for Error {
fn from(err: serde_json::Error) -> Self {
Error::Json(err)
}
}

impl From<nostr_sdk::secp256k1::Error> for Error {
fn from(err: nostr_sdk::secp256k1::Error) -> Self {
Error::Secp(err)
Expand Down Expand Up @@ -120,6 +127,7 @@ impl fmt::Display for Error {
Error::NostrClient(e) => write!(f, "Nostr client error: {}", e),
Error::NotFound => write!(f, "Not found"),
Error::Recv(e) => write!(f, "Recieve error: {}", e),
Error::Json(e) => write!(f, "json error: {e}"),
Error::InvalidNip19 => write!(f, "Invalid nip19 object"),
Error::NothingToFetch => write!(f, "No data to fetch!"),
Error::SliceErr => write!(f, "Array slice error"),
Expand Down
79 changes: 77 additions & 2 deletions src/html.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,84 @@ use crate::{
use http_body_util::Full;
use hyper::{body::Bytes, header, Request, Response, StatusCode};
use nostr_sdk::prelude::{Nip19, ToBech32};
use nostrdb::{BlockType, Blocks, Mention, Note, Transaction};
use nostrdb::{BlockType, Blocks, Filter, Mention, Ndb, Note, Transaction};
use std::io::Write;
use tracing::{error, warn};

fn blocktype_name(blocktype: &BlockType) -> &'static str {
match blocktype {
BlockType::MentionBech32 => "mention",
BlockType::Hashtag => "hashtag",
BlockType::Url => "url",
BlockType::Text => "text",
BlockType::MentionIndex => "indexed_mention",
BlockType::Invoice => "invoice",
}
}

pub fn serve_note_json(
ndb: &Ndb,
note_rd: &NoteAndProfileRenderData,
) -> Result<Response<Full<Bytes>>, Error> {
let mut body: Vec<u8> = vec![];

let note_key = match note_rd.note_rd {
NoteRenderData::Note(note_key) => note_key,
NoteRenderData::Missing(note_id) => {
warn!("missing note_id {}", hex::encode(note_id));
return Err(Error::NotFound);
}
};

let txn = Transaction::new(ndb)?;

let note = if let Ok(note) = ndb.get_note_by_key(&txn, note_key) {
note
} else {
// 404
return Err(Error::NotFound);
};

write!(body, "{{\"note\":{},\"parsed_content\":[", &note.json()?)?;

if let Ok(blocks) = ndb.get_blocks_by_key(&txn, note_key) {
for (i, block) in blocks.iter(&note).enumerate() {
if i != 0 {
write!(body, ",")?;
}
write!(
body,
"{{\"{}\":{}}}",
blocktype_name(&block.blocktype()),
serde_json::to_string(block.as_str())?
)?;
}
};

write!(body, "]")?;

if let Ok(results) = ndb.query(
&txn,
&[Filter::new()
.authors([note.pubkey()])
.kinds([0])
.limit(1)
.build()],
1,
) {
if let Some(profile_note) = results.first() {
write!(body, ",\"profile\":{}", profile_note.note.json()?)?;
}
}

writeln!(body, "}}")?;

Ok(Response::builder()
.header(header::CONTENT_TYPE, "application/json; charset=utf-8")
.status(StatusCode::OK)
.body(Full::new(Bytes::from(body)))?)
}

pub fn render_note_content(body: &mut Vec<u8>, note: &Note, blocks: &Blocks) {
for block in blocks.iter(note) {
match block.blocktype() {
Expand Down Expand Up @@ -84,7 +158,6 @@ pub fn serve_note_html(
// 5: formatted date
// 6: pfp url

let txn = Transaction::new(&app.ndb)?;
let note_key = match note_rd.note_rd {
NoteRenderData::Note(note_key) => note_key,
NoteRenderData::Missing(note_id) => {
Expand All @@ -93,6 +166,8 @@ pub fn serve_note_html(
}
};

let txn = Transaction::new(&app.ndb)?;

let note = if let Ok(note) = app.ndb.get_note_by_key(&txn, note_key) {
note
} else {
Expand Down
18 changes: 17 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,14 @@ async fn serve(
r: Request<hyper::body::Incoming>,
) -> Result<Response<Full<Bytes>>, Error> {
let is_png = r.uri().path().ends_with(".png");
let until = if is_png { 4 } else { 0 };
let is_json = r.uri().path().ends_with(".json");
let until = if is_png {
4
} else if is_json {
5
} else {
0
};

let path_len = r.uri().path().len();
let nip19 = match Nip19::from_bech32(&r.uri().path()[1..path_len - until]) {
Expand Down Expand Up @@ -166,6 +173,15 @@ async fn serve(
.header(header::CONTENT_TYPE, "image/png")
.status(StatusCode::OK)
.body(Full::new(Bytes::from(data)))?)
} else if is_json {
match render_data {
RenderData::Note(note_rd) => html::serve_note_json(&app.ndb, &note_rd),
RenderData::Profile(_profile_rd) => {
return Ok(Response::builder()
.status(StatusCode::NOT_FOUND)
.body(Full::new(Bytes::from("todo: profile json")))?);
}
}
} else {
match render_data {
RenderData::Note(note_rd) => html::serve_note_html(app, &nip19, &note_rd, r),
Expand Down
8 changes: 8 additions & 0 deletions src/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@ pub async fn find_note(
let _ = client.add_relay("wss://relay.damus.io").await;
let _ = client.add_relay("wss://nostr.wine").await;
let _ = client.add_relay("wss://nos.lol").await;
let expected_events = filters.len();

let other_relays = nip19::nip19_relays(nip19);
for relay in other_relays {
Expand All @@ -267,11 +268,18 @@ pub async fn find_note(
.stream_events(filters, Some(std::time::Duration::from_millis(2000)))
.await?;

let mut num_loops = 0;
while let Some(event) = streamed_events.next().await {
debug!("processing event {:?}", event);
if let Err(err) = ndb.process_event(&event.as_json()) {
error!("error processing event: {err}");
}

num_loops += 1;

if num_loops == expected_events {
break;
}
}

Ok(())
Expand Down
Loading