commit dec66e4a8aa7a033441cb523f633e41e2d3ffea3
parent 7a7a04da39c84281f3e174ceabae21450997e162
Author: William Casarin <jb55@jb55.com>
Date: Sun, 17 Dec 2023 13:02:21 -0800
fetch notes from relays if we don't have them
If we don't have a cache hit, try to find the note on other relays. Once
we find it, add it to nostrdb.
Diffstat:
3 files changed, 99 insertions(+), 29 deletions(-)
diff --git a/Cargo.toml b/Cargo.toml
@@ -13,8 +13,8 @@ hyper-util = { version = "0.1", features = ["full"] }
http-body-util = "0.1"
log = "0.4.20"
env_logger = "0.10.1"
-nostrdb = "0.1.3"
-nostr-sdk = { git = "https://github.com/damus-io/nostr-sdk.git", rev = "bfd6ac4e111720dcecf8fc09d4ea76da4d971cf4" }
+nostrdb = { git = "https://github.com/damus-io/nostrdb-rs.git", rev = "0545571827bf64e06250f094d65775acd2a1165e" }
+nostr-sdk = { git = "https://github.com/damus-io/nostr-sdk.git", rev = "fc0dc7b38f5060f171228b976b9700c0135245d3" }
hex = "0.4.3"
egui = "0.21.0"
egui_skia = { version = "0.4.0", features = ["cpu_fix"] }
diff --git a/README.md b/README.md
@@ -17,7 +17,7 @@ WIP!
- [x] Local note fetching with nostrdb
- [x] Basic note rendering
-- [ ] Fetch notes from relays
+- [x] Fetch notes from relays
- [ ] Render profile pictures
- [ ] Cache profile pictures
- [ ] HTML note page
diff --git a/src/main.rs b/src/main.rs
@@ -7,20 +7,26 @@ use hyper::server::conn::http1;
use hyper::service::service_fn;
use hyper::{Request, Response, StatusCode};
use hyper_util::rt::TokioIo;
-use log::info;
+use log::{error, info, warn};
use tokio::net::TcpListener;
use crate::error::Error;
use nostr_sdk::nips::nip19::Nip19;
use nostr_sdk::prelude::*;
-use nostrdb::{Config, Ndb, Note, Transaction};
+use nostrdb::{Config, Ndb, Transaction};
+use std::time::Duration;
+
+use nostr_sdk::Kind;
mod error;
#[derive(Debug, Clone)]
struct Context {
ndb: Ndb,
- //font_data: egui::FontData,
+ keys: Keys,
+
+ /// How long do we wait for remote note requests
+ timeout: Duration,
}
fn nip19_evid(nip19: &Nip19) -> Option<EventId> {
@@ -31,8 +37,7 @@ fn nip19_evid(nip19: &Nip19) -> Option<EventId> {
}
}
-
-fn render_note<'a>(_app_ctx: &Context, note: &'a Note) -> Vec<u8> {
+fn render_note<'a>(_app_ctx: &Context, content: &'a str) -> Vec<u8> {
use egui::{FontId, RichText};
use egui_skia::{rasterize, RasterizeOptions};
use skia_safe::EncodedImageFormat;
@@ -46,7 +51,7 @@ fn render_note<'a>(_app_ctx: &Context, note: &'a Note) -> Vec<u8> {
ui.horizontal(|ui| {
ui.label(RichText::new("✏").font(FontId::proportional(120.0)));
ui.vertical(|ui| {
- ui.label(RichText::new(note.content()).font(FontId::proportional(40.0)));
+ ui.label(RichText::new(content).font(FontId::proportional(40.0)));
});
})
});
@@ -97,11 +102,57 @@ fn nip19_relays(nip19: &Nip19) -> Vec<String> {
relays
}
+async fn find_note(ctx: &Context, nip19: &Nip19) -> Result<nostr_sdk::Event, Error> {
+ let opts = Options::new().shutdown_on_drop(true);
+ let client = Client::with_opts(&ctx.keys, opts);
+
+ let _ = client.add_relay("wss://relay.damus.io").await;
+
+ let other_relays = nip19_relays(nip19);
+ for relay in other_relays {
+ let _ = client.add_relay(relay).await;
+ }
+
+ client.connect().await;
+
+ let filters = nip19_to_filters(nip19)?;
+
+ client
+ .req_events_of(filters.clone(), Some(ctx.timeout))
+ .await;
+
+ loop {
+ match client.notifications().recv().await? {
+ RelayPoolNotification::Event(_url, ev) => {
+ info!("got ev: {:?}", ev);
+ return Ok(ev);
+ }
+ RelayPoolNotification::RelayStatus { .. } => continue,
+ RelayPoolNotification::Message(_url, msg) => match msg {
+ RelayMessage::Event { event, .. } => return Ok(*event),
+ RelayMessage::EndOfStoredEvents(_) => return Err(Error::NotFound),
+ _ => continue,
+ },
+ RelayPoolNotification::Stop | RelayPoolNotification::Shutdown => {
+ return Err(Error::NotFound);
+ }
+ }
+ }
+}
+
async fn serve(
ctx: &Context,
r: Request<hyper::body::Incoming>,
) -> Result<Response<Full<Bytes>>, Error> {
- let nip19 = Nip19::from_bech32(&r.uri().to_string()[1..])?;
+ let nip19 = match Nip19::from_bech32(&r.uri().to_string()[1..]) {
+ Ok(nip19) => nip19,
+ Err(_) => {
+ return Ok(Response::builder()
+ .status(StatusCode::NOT_FOUND)
+ .body(Full::new(Bytes::from("Invalid url\n")))?);
+ }
+ };
+
let evid = match nip19_evid(&nip19) {
Some(evid) => evid,
None => {
@@ -111,21 +162,33 @@ async fn serve(
}
};
- let mut txn = Transaction::new(&ctx.ndb)?;
- let note = match ctx
- .ndb
- .get_note_by_id(&mut txn, evid.as_bytes().try_into()?)
- {
- Ok(note) => note,
- Err(nostrdb::Error::NotFound) => {
- // query relays
- return Ok(Response::builder()
- .status(StatusCode::NOT_FOUND)
- .body(Full::new(Bytes::from(format!(
- "noteid {} not found\n",
- ::hex::encode(evid)
- ))))?);
- }
+ let content = {
+ let mut txn = Transaction::new(&ctx.ndb)?;
+ ctx.ndb
+ .get_note_by_id(&mut txn, evid.as_bytes().try_into()?)
+ .map(|n| {
+ info!("cache hit {:?}", nip19);
+ n.content().to_string()
+ })
+ };
+
+ let content = match content {
+ Ok(content) => content,
+ Err(nostrdb::Error::NotFound) => match find_note(ctx, &nip19).await {
+ Ok(note) => {
+ ctx.ndb
+ .process_event(&json!(["EVENT", "s", note]).to_string());
+ note.content
+ }
+ Err(err) => {
+ return Ok(Response::builder()
+ .status(StatusCode::NOT_FOUND)
+ .body(Full::new(Bytes::from(format!(
+ "noteid {} not found\n",
+ ::hex::encode(evid)
+ ))))?);
+ }
+ },
Err(err) => {
return Ok(Response::builder()
.status(StatusCode::INTERNAL_SERVER_ERROR)
@@ -133,13 +196,20 @@ async fn serve(
}
};
- let data = render_note(&ctx, ¬e);
+ let data = render_note(&ctx, &content);
Ok(Response::builder()
.header(header::CONTENT_TYPE, "image/png")
+ .status(StatusCode::OK)
.body(Full::new(Bytes::from(data)))?)
}
+fn get_env_timeout() -> Duration {
+ let timeout_env = std::env::var("TIMEOUT_MS").unwrap_or("2000".to_string());
+ let timeout_ms: u64 = timeout_env.parse().unwrap_or(2000);
+ Duration::from_millis(timeout_ms)
+}
+
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
env_logger::init();
@@ -153,9 +223,9 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let cfg = Config::new();
let ndb = Ndb::new(".", &cfg).expect("ndb failed to open");
//let font_data = egui::FontData::from_static(include_bytes!("../fonts/NotoSans-Regular.ttf"));
- let ctx = Context {
- ndb, /*, font_data */
- };
+ let keys = Keys::generate();
+ let timeout = get_env_timeout();
+ let ctx = Context { ndb, keys, timeout };
// We start a loop to continuously accept incoming connections
loop {