mirror of
https://github.com/mikedilger/gossip.git
synced 2024-09-19 11:43:43 +00:00
NIP-35 support
This commit is contained in:
parent
f6341fed47
commit
5653bb781a
@ -164,6 +164,34 @@ impl DbPerson {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn upsert_valid_nip05(
|
||||
pubkey: PublicKeyHex,
|
||||
dns_id: String,
|
||||
dns_id_last_checked: u64,
|
||||
) -> Result<(), Error> {
|
||||
let sql = "INSERT INTO person (pubkey, dns_id, dns_id_valid, dns_id_last_checked, followed) \
|
||||
values (?, ?, 1, ?, 1) \
|
||||
ON CONFLICT(pubkey) DO UPDATE SET dns_id=?, dns_id_valid=1, dns_id_last_checked=?, followed=1";
|
||||
|
||||
spawn_blocking(move || {
|
||||
let maybe_db = GLOBALS.db.blocking_lock();
|
||||
let db = maybe_db.as_ref().unwrap();
|
||||
|
||||
let mut stmt = db.prepare(sql)?;
|
||||
stmt.execute((
|
||||
&pubkey.0,
|
||||
&dns_id,
|
||||
&dns_id_last_checked,
|
||||
&dns_id,
|
||||
&dns_id_last_checked,
|
||||
))?;
|
||||
Ok::<(), Error>(())
|
||||
})
|
||||
.await??;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub async fn delete(criteria: &str) -> Result<(), Error> {
|
||||
let sql = format!("DELETE FROM person WHERE {}", criteria);
|
||||
|
@ -213,6 +213,33 @@ impl DbPersonRelay {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn upsert_last_suggested_nip35(
|
||||
person: PublicKeyHex,
|
||||
relay: String,
|
||||
last_suggested_nip35: u64,
|
||||
) -> Result<(), Error> {
|
||||
let sql = "INSERT INTO person_relay (person, relay, last_suggested_nip35) \
|
||||
VALUES (?, ?, ?) \
|
||||
ON CONFLICT(person, relay) DO UPDATE SET last_suggested_nip35=?";
|
||||
|
||||
spawn_blocking(move || {
|
||||
let maybe_db = GLOBALS.db.blocking_lock();
|
||||
let db = maybe_db.as_ref().unwrap();
|
||||
|
||||
let mut stmt = db.prepare(sql)?;
|
||||
stmt.execute((
|
||||
&person.0,
|
||||
&relay,
|
||||
&last_suggested_nip35,
|
||||
&last_suggested_nip35,
|
||||
))?;
|
||||
Ok::<(), Error>(())
|
||||
})
|
||||
.await??;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub async fn delete(criteria: &str) -> Result<(), Error> {
|
||||
let sql = format!("DELETE FROM person_relay WHERE {}", criteria);
|
||||
|
12
src/error.rs
12
src/error.rs
@ -21,6 +21,12 @@ pub enum Error {
|
||||
#[error("Error sending mpsc: {0}")]
|
||||
MpscSend(#[from] tokio::sync::mpsc::error::SendError<BusMessage>),
|
||||
|
||||
#[error("NIP-05 public key not found")]
|
||||
Nip05NotFound,
|
||||
|
||||
#[error("NIP-35 relays not found")]
|
||||
Nip35NotFound,
|
||||
|
||||
#[error("Nostr: {0}")]
|
||||
Nostr(#[from] nostr_types::Error),
|
||||
|
||||
@ -30,12 +36,18 @@ pub enum Error {
|
||||
#[error("I/O Error: {0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
|
||||
#[error("Invalid DNS ID (nip-05 / nip-35), should be user@domain")]
|
||||
InvalidDnsId,
|
||||
|
||||
#[error("Invalid URI: {0}")]
|
||||
InvalidUri(#[from] http::uri::InvalidUri),
|
||||
|
||||
#[error("Bad integer: {0}")]
|
||||
ParseInt(#[from] std::num::ParseIntError),
|
||||
|
||||
#[error("HTTP (reqwest) error: {0}")]
|
||||
ReqwestHttpError(#[from] reqwest::Error),
|
||||
|
||||
#[error("SerdeJson Error: {0}")]
|
||||
SerdeJson(#[from] serde_json::Error),
|
||||
|
||||
|
@ -7,7 +7,7 @@ use crate::error::Error;
|
||||
use crate::globals::{Globals, GLOBALS};
|
||||
use crate::settings::Settings;
|
||||
use minion::Minion;
|
||||
use nostr_types::{Event, PrivateKey, PublicKey, PublicKeyHex, Unixtime, Url};
|
||||
use nostr_types::{Event, Nip05, PrivateKey, PublicKey, PublicKeyHex, Unixtime, Url};
|
||||
use relay_picker::{BestRelay, RelayPicker};
|
||||
use std::collections::HashMap;
|
||||
use tokio::sync::broadcast::Sender;
|
||||
@ -349,6 +349,14 @@ impl Overlord {
|
||||
"get_missing_events" => {
|
||||
self.get_missing_events().await?;
|
||||
}
|
||||
"follow_nip35" => {
|
||||
let dns_id: String = serde_json::from_str(&bus_message.json_payload)?;
|
||||
let _ = tokio::spawn(async move {
|
||||
if let Err(e) = Overlord::get_and_follow_nip35(dns_id).await {
|
||||
error!("{}", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
_ => {}
|
||||
@ -399,4 +407,61 @@ impl Overlord {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_and_follow_nip35(nip35: String) -> Result<(), Error> {
|
||||
let mut parts: Vec<&str> = nip35.split('@').collect();
|
||||
if parts.len() != 2 {
|
||||
return Err(Error::InvalidDnsId);
|
||||
}
|
||||
|
||||
let domain = parts.pop().unwrap();
|
||||
let user = parts.pop().unwrap();
|
||||
let nip05_future = reqwest::Client::new()
|
||||
.get(format!(
|
||||
"https://{}/.well-known/nostr.json?name={}",
|
||||
domain, user
|
||||
))
|
||||
.header("Host", domain)
|
||||
.send();
|
||||
let timeout_future = tokio::time::timeout(std::time::Duration::new(15, 0), nip05_future);
|
||||
let response = timeout_future.await??;
|
||||
let nip05 = response.json::<Nip05>().await?;
|
||||
Overlord::follow_nip35(nip05, user.to_string(), domain.to_string()).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn follow_nip35(nip05: Nip05, user: String, domain: String) -> Result<(), Error> {
|
||||
let dns_id = format!("{}@{}", user, domain);
|
||||
|
||||
let pubkey = match nip05.names.get(&user) {
|
||||
Some(pk) => pk,
|
||||
None => return Err(Error::Nip05NotFound),
|
||||
};
|
||||
|
||||
let relays = match nip05.relays.get(pubkey) {
|
||||
Some(relays) => relays,
|
||||
None => return Err(Error::Nip35NotFound),
|
||||
};
|
||||
|
||||
// Save person
|
||||
DbPerson::upsert_valid_nip05((*pubkey).into(), dns_id, Unixtime::now().unwrap().0 as u64)
|
||||
.await?;
|
||||
|
||||
for relay in relays.iter() {
|
||||
// Save relay
|
||||
let db_relay = DbRelay::new(relay.to_string())?;
|
||||
DbRelay::insert(db_relay).await?;
|
||||
|
||||
DbPersonRelay::upsert_last_suggested_nip35(
|
||||
(*pubkey).into(),
|
||||
relay.0.clone(),
|
||||
Unixtime::now().unwrap().0 as u64,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
info!("Followed {}@{} at {} relays", user, domain, relays.len());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -47,7 +47,8 @@ pub fn run() -> Result<(), Error> {
|
||||
#[derive(PartialEq)]
|
||||
enum Page {
|
||||
Feed,
|
||||
People,
|
||||
PeopleFollow,
|
||||
PeopleList,
|
||||
You,
|
||||
Relays,
|
||||
Settings,
|
||||
@ -62,6 +63,7 @@ struct GossipUi {
|
||||
placeholder_avatar: TextureHandle,
|
||||
draft: String,
|
||||
settings: Settings,
|
||||
nip35follow: String,
|
||||
}
|
||||
|
||||
impl GossipUi {
|
||||
@ -113,6 +115,7 @@ impl GossipUi {
|
||||
placeholder_avatar: placeholder_avatar_texture_handle,
|
||||
draft: "".to_owned(),
|
||||
settings,
|
||||
nip35follow: "".to_owned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -132,7 +135,7 @@ impl eframe::App for GossipUi {
|
||||
ui.horizontal(|ui| {
|
||||
ui.selectable_value(&mut self.page, Page::Feed, "Feed");
|
||||
ui.separator();
|
||||
ui.selectable_value(&mut self.page, Page::People, "People");
|
||||
ui.selectable_value(&mut self.page, Page::PeopleList, "People");
|
||||
ui.separator();
|
||||
ui.selectable_value(&mut self.page, Page::You, "You");
|
||||
ui.separator();
|
||||
@ -149,7 +152,8 @@ impl eframe::App for GossipUi {
|
||||
|
||||
egui::CentralPanel::default().show(ctx, |ui| match self.page {
|
||||
Page::Feed => feed::update(self, ctx, frame, ui),
|
||||
Page::People => people::update(self, ctx, frame, ui),
|
||||
Page::PeopleList => people::update(self, ctx, frame, ui),
|
||||
Page::PeopleFollow => people::update(self, ctx, frame, ui),
|
||||
Page::You => you::update(self, ctx, frame, ui),
|
||||
Page::Relays => relays::update(self, ctx, frame, ui),
|
||||
Page::Settings => settings::update(self, ctx, frame, ui, darkmode),
|
||||
|
@ -1,9 +1,44 @@
|
||||
use super::GossipUi;
|
||||
use super::{GossipUi, Page};
|
||||
use crate::comms::BusMessage;
|
||||
use crate::globals::GLOBALS;
|
||||
use eframe::egui;
|
||||
use egui::{Context, RichText, ScrollArea, TextStyle, Ui, Vec2};
|
||||
use egui::{Context, RichText, ScrollArea, TextStyle, TopBottomPanel, Ui, Vec2};
|
||||
|
||||
pub(super) fn update(app: &mut GossipUi, ctx: &Context, _frame: &mut eframe::Frame, ui: &mut Ui) {
|
||||
TopBottomPanel::top("people_menu").show(ctx, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.selectable_value(&mut app.page, Page::PeopleList, "Followed");
|
||||
ui.separator();
|
||||
ui.selectable_value(&mut app.page, Page::PeopleFollow, "Follow Someone New");
|
||||
ui.separator();
|
||||
});
|
||||
});
|
||||
|
||||
if app.page == Page::PeopleFollow {
|
||||
ui.add_space(24.0);
|
||||
|
||||
ui.add_space(8.0);
|
||||
ui.heading("Follow someone");
|
||||
ui.add_space(18.0);
|
||||
|
||||
ui.separator();
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Enter user@domain");
|
||||
ui.text_edit_singleline(&mut app.nip35follow);
|
||||
if ui.button("follow").clicked() {
|
||||
let tx = GLOBALS.to_overlord.clone();
|
||||
let _ = tx.send(BusMessage {
|
||||
target: "overlord".to_string(),
|
||||
kind: "follow_nip35".to_string(),
|
||||
json_payload: serde_json::to_string(&app.nip35follow).unwrap(),
|
||||
});
|
||||
app.nip35follow = "".to_owned();
|
||||
}
|
||||
});
|
||||
} else if app.page == Page::PeopleList {
|
||||
ui.add_space(24.0);
|
||||
|
||||
pub(super) fn update(app: &mut GossipUi, _ctx: &Context, _frame: &mut eframe::Frame, ui: &mut Ui) {
|
||||
ui.add_space(8.0);
|
||||
ui.heading("People Followed");
|
||||
ui.add_space(18.0);
|
||||
@ -49,4 +84,5 @@ pub(super) fn update(app: &mut GossipUi, _ctx: &Context, _frame: &mut eframe::Fr
|
||||
ui.separator();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user