mirror of
https://github.com/damus-io/notecrumbs.git
synced 2024-09-29 18:10:43 +00:00
notecrumbs: initial commit
This commit is contained in:
commit
503c4a6e36
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
/target
|
||||||
|
/.direnv
|
||||||
|
data.mdb
|
||||||
|
lock.mdb
|
||||||
|
.build-result
|
||||||
|
.buildcmd
|
1
.rustfmt.toml
Normal file
1
.rustfmt.toml
Normal file
@ -0,0 +1 @@
|
|||||||
|
edition = "2021"
|
21
Cargo.toml
Normal file
21
Cargo.toml
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
[package]
|
||||||
|
name = "notecrumbs"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
#nostrdb = "0.1.2"
|
||||||
|
hyper = { version = "1", features = ["full"] }
|
||||||
|
tokio = { version = "1", features = ["full"] }
|
||||||
|
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" }
|
||||||
|
hex = "0.4.3"
|
||||||
|
egui = "0.21.0"
|
||||||
|
egui_skia = { version = "0.4.0", features = ["cpu_fix"] }
|
||||||
|
skia-safe = "0.58.0"
|
12
README.md
Normal file
12
README.md
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
# notecrumbs
|
||||||
|
|
||||||
|
A nostr opengraph server build on [nostrdb][nostrdb], [egui][egui], and
|
||||||
|
[skia][egui-skia]. It renders notes using the CPU
|
||||||
|
|
||||||
|
WIP!
|
||||||
|
|
||||||
|
[nostrdb]: https://github.com/damus-io/nostrdb
|
||||||
|
[egui]: https://github.com/emilk/egui
|
||||||
|
[egui-skia]: https://github.com/lucasmerlin/egui_skia
|
||||||
|
|
5
shell.nix
Normal file
5
shell.nix
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{ pkgs ? import <nixpkgs> {} }:
|
||||||
|
with pkgs;
|
||||||
|
mkShell {
|
||||||
|
nativeBuildInputs = [ gdb cargo rustc rustfmt libiconv pkg-config fontconfig freetype ];
|
||||||
|
}
|
60
src/error.rs
Normal file
60
src/error.rs
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
use nostr_sdk::nips::nip19;
|
||||||
|
use std::array::TryFromSliceError;
|
||||||
|
use std::error::Error as StdError;
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Error {
|
||||||
|
Nip19(nip19::Error),
|
||||||
|
Http(hyper::http::Error),
|
||||||
|
Nostrdb(nostrdb::Error),
|
||||||
|
SliceErr,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<TryFromSliceError> for Error {
|
||||||
|
fn from(_: TryFromSliceError) -> Self {
|
||||||
|
Error::SliceErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<nip19::Error> for Error {
|
||||||
|
fn from(err: nip19::Error) -> Self {
|
||||||
|
Error::Nip19(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<hyper::http::Error> for Error {
|
||||||
|
fn from(err: hyper::http::Error) -> Self {
|
||||||
|
Error::Http(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<nostrdb::Error> for Error {
|
||||||
|
fn from(err: nostrdb::Error) -> Self {
|
||||||
|
Error::Nostrdb(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implementing `Display`
|
||||||
|
impl fmt::Display for Error {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Error::Nip19(e) => write!(f, "Nip19 error: {}", e),
|
||||||
|
Error::Http(e) => write!(f, "HTTP error: {}", e),
|
||||||
|
Error::Nostrdb(e) => write!(f, "Nostrdb error: {}", e),
|
||||||
|
Error::SliceErr => write!(f, "Array slice error"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implementing `StdError`
|
||||||
|
impl StdError for Error {
|
||||||
|
fn source(&self) -> Option<&(dyn StdError + 'static)> {
|
||||||
|
match self {
|
||||||
|
Error::Nip19(e) => Some(e),
|
||||||
|
Error::Http(e) => Some(e),
|
||||||
|
Error::Nostrdb(e) => Some(e),
|
||||||
|
Error::SliceErr => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1
src/lib.rs
Normal file
1
src/lib.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
171
src/main.rs
Normal file
171
src/main.rs
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
use std::net::SocketAddr;
|
||||||
|
|
||||||
|
use http_body_util::Full;
|
||||||
|
use hyper::body::Bytes;
|
||||||
|
use hyper::header;
|
||||||
|
use hyper::server::conn::http1;
|
||||||
|
use hyper::service::service_fn;
|
||||||
|
use hyper::{Request, Response, StatusCode};
|
||||||
|
use hyper_util::rt::TokioIo;
|
||||||
|
use log::info;
|
||||||
|
use tokio::net::TcpListener;
|
||||||
|
|
||||||
|
use crate::error::Error;
|
||||||
|
use nostr_sdk::nips::nip19::Nip19;
|
||||||
|
use nostr_sdk::prelude::*;
|
||||||
|
use nostrdb::{Config, Ndb, Note, Transaction};
|
||||||
|
|
||||||
|
mod error;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct Context {
|
||||||
|
ndb: Ndb,
|
||||||
|
//font_data: egui::FontData,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nip19_evid(nip19: &Nip19) -> Option<EventId> {
|
||||||
|
match nip19 {
|
||||||
|
Nip19::Event(ev) => Some(ev.event_id),
|
||||||
|
Nip19::EventId(evid) => Some(*evid),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
fn setup_fonts(font_data: &egui::FontData, ctx: &egui::Context) {
|
||||||
|
let mut fonts = egui::FontDefinitions::default();
|
||||||
|
|
||||||
|
// Install my own font (maybe supporting non-latin characters).
|
||||||
|
// .ttf and .otf files supported.
|
||||||
|
fonts
|
||||||
|
.font_data
|
||||||
|
.insert("my_font".to_owned(), font_data.clone());
|
||||||
|
|
||||||
|
// Put my font first (highest priority) for proportional text:
|
||||||
|
fonts
|
||||||
|
.families
|
||||||
|
.entry(egui::FontFamily::Proportional)
|
||||||
|
.or_default()
|
||||||
|
.insert(0, "my_font".to_owned());
|
||||||
|
|
||||||
|
// Tell egui to use these fonts:
|
||||||
|
ctx.set_fonts(fonts);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
fn render_note<'a>(_app_ctx: &Context, note: &'a Note) -> Vec<u8> {
|
||||||
|
use egui::{FontId, RichText};
|
||||||
|
use egui_skia::{rasterize, RasterizeOptions};
|
||||||
|
use skia_safe::EncodedImageFormat;
|
||||||
|
|
||||||
|
let mut surface = rasterize(
|
||||||
|
(1200, 630),
|
||||||
|
|ctx| {
|
||||||
|
//setup_fonts(&app_ctx.font_data, ctx);
|
||||||
|
|
||||||
|
egui::CentralPanel::default().show(&ctx, |ui| {
|
||||||
|
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)));
|
||||||
|
});
|
||||||
|
})
|
||||||
|
});
|
||||||
|
},
|
||||||
|
Some(RasterizeOptions {
|
||||||
|
pixels_per_point: 1.0,
|
||||||
|
frames_before_screenshot: 1,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
surface
|
||||||
|
.image_snapshot()
|
||||||
|
.encode_to_data(EncodedImageFormat::PNG)
|
||||||
|
.expect("expected image")
|
||||||
|
.as_bytes()
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
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 evid = match nip19_evid(&nip19) {
|
||||||
|
Some(evid) => evid,
|
||||||
|
None => {
|
||||||
|
return Ok(Response::builder()
|
||||||
|
.status(StatusCode::NOT_FOUND)
|
||||||
|
.body(Full::new(Bytes::from("\n")))?)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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)
|
||||||
|
))))?);
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
return Ok(Response::builder()
|
||||||
|
.status(StatusCode::INTERNAL_SERVER_ERROR)
|
||||||
|
.body(Full::new(Bytes::from(format!("{}\n", err))))?);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let data = render_note(&ctx, ¬e);
|
||||||
|
|
||||||
|
Ok(Response::builder()
|
||||||
|
.header(header::CONTENT_TYPE, "image/png")
|
||||||
|
.body(Full::new(Bytes::from(data)))?)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
|
env_logger::init();
|
||||||
|
|
||||||
|
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
|
||||||
|
|
||||||
|
// We create a TcpListener and bind it to 127.0.0.1:3000
|
||||||
|
let listener = TcpListener::bind(addr).await?;
|
||||||
|
info!("Listening on 127.0.0.1:3000");
|
||||||
|
|
||||||
|
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 */
|
||||||
|
};
|
||||||
|
|
||||||
|
// We start a loop to continuously accept incoming connections
|
||||||
|
loop {
|
||||||
|
let (stream, _) = listener.accept().await?;
|
||||||
|
|
||||||
|
// Use an adapter to access something implementing `tokio::io` traits as if they implement
|
||||||
|
// `hyper::rt` IO traits.
|
||||||
|
let io = TokioIo::new(stream);
|
||||||
|
|
||||||
|
let ctx_copy = ctx.clone();
|
||||||
|
|
||||||
|
// Spawn a tokio task to serve multiple connections concurrently
|
||||||
|
tokio::task::spawn(async move {
|
||||||
|
// Finally, we bind the incoming connection to our `hello` service
|
||||||
|
if let Err(err) = http1::Builder::new()
|
||||||
|
// `service_fn` converts our function in a `Service`
|
||||||
|
.serve_connection(io, service_fn(|req| serve(&ctx_copy, req)))
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
println!("Error serving connection: {:?}", err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user