fuck it we have branches

This commit is contained in:
Jack Chakany 2025-01-04 09:53:07 -05:00
parent ca7d2319ea
commit d75fa95a7d
12 changed files with 300 additions and 57 deletions

45
Cargo.lock generated
View file

@ -170,6 +170,12 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04"
[[package]]
name = "android-tzdata"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
[[package]]
name = "android_system_properties"
version = "0.1.5"
@ -871,6 +877,21 @@ dependencies = [
"libc",
]
[[package]]
name = "chrono"
version = "0.4.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825"
dependencies = [
"android-tzdata",
"iana-time-zone",
"js-sys",
"num-traits",
"serde",
"wasm-bindgen",
"windows-targets 0.52.5",
]
[[package]]
name = "clang-sys"
version = "1.8.1"
@ -1998,6 +2019,7 @@ name = "hoot-app"
version = "0.1.0"
dependencies = [
"bitcoin",
"chrono",
"eframe",
"egui_extras",
"egui_tabs",
@ -2033,6 +2055,29 @@ version = "1.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9"
[[package]]
name = "iana-time-zone"
version = "0.1.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"wasm-bindgen",
"windows-core",
]
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
dependencies = [
"cc",
]
[[package]]
name = "icrate"
version = "0.0.4"

View file

@ -29,6 +29,7 @@ serde = "1.0.204"
serde_json = "1.0.121"
pollster = "0.4.0"
bitcoin = { version = "0.32.5", features = ["serde", "rand-std"] }
chrono = { version = "0.4.39", features = ["serde"] }
[target.'cfg(target_os = "macos")'.dependencies]
security-framework = "3.0.0"

View file

@ -1,8 +1,9 @@
use nostr::{Event, EventBuilder, Keys, Kind, PublicKey, Tag, TagKind, TagStandard};
use crate::nostr::{Event, EventBuilder, EventKind, Tag};
use bitcoin::secp256k1::{PublicKey, Keypair};
use std::collections::HashMap;
use pollster::FutureExt as _;
pub const MAIL_EVENT_KIND: u16 = 1059;
pub const MAIL_EVENT_KIND: u32 = 1059;
pub struct MailMessage {
pub to: Vec<PublicKey>,
@ -13,24 +14,28 @@ pub struct MailMessage {
}
impl MailMessage {
pub fn to_events(&mut self, sending_keys: &Keys) -> HashMap<PublicKey, Event> {
pub fn to_events(&mut self, sending_keys: &Keypair) -> HashMap<PublicKey, Event> {
let mut pubkeys_to_send_to: Vec<PublicKey> = Vec::new();
let mut tags: Vec<Tag> = Vec::new();
for pubkey in &self.to {
tags.push(Tag::public_key(*pubkey));
tags.push(Tag::new_with_values(vec!["p".to_string(), *pubkey.to_hex().as_str()]));
pubkeys_to_send_to.push(*pubkey);
}
for pubkey in &self.cc {
tags.push(Tag::custom(TagKind::p(), vec![pubkey.to_hex().as_str(), "cc"]));
tags.push(Tag::new_with_values(vec!["p".to_string(), *pubkey.to_hex().as_str(), "cc".to_string()]));
pubkeys_to_send_to.push(*pubkey);
}
tags.push(Tag::from_standardized(TagStandard::Subject(self.subject.clone())));
tags.push(Tag::new_with_values(vec!["subject".to_string(), self.subject.clone()]));
let base_event = EventBuilder::new(Kind::Custom(MAIL_EVENT_KIND), &self.content)
.tags(tags);
let base_event = EventBuilder::new()
.kind(EventKind::MailEvent)
.content(&self.content)
.tags(tags)
.build();
base_event.pubkey = sending_keys.clone().public_key();
let mut event_list: HashMap<PublicKey, Event> = HashMap::new();
for pubkey in pubkeys_to_send_to {

View file

@ -112,7 +112,7 @@ fn update_app(app: &mut Hoot, ctx: &egui::Context) {
if app.account_manager.loaded_keys.len() > 0 {
let mut gw_sub = relay::Subscription::default();
let filter = nostr::Filter::new().kind(nostr::Kind::Custom(mail_event::MAIL_EVENT_KIND)).custom_tag(nostr::SingleLetterTag { character: nostr::Alphabet::P, uppercase: false }, app.account_manager.loaded_keys.clone().into_iter().map(|keys| keys.public_key()));
let filter = nostr::Filter::new().add_kind(mail_event::MAIL_EVENT_KIND).add_tag("p", app.account_manager.loaded_keys.clone().into_iter().map(|keys| keys.public_key()));
gw_sub.filter(filter);
// TODO: fix error handling
@ -209,36 +209,6 @@ fn render_app(app: &mut Hoot, ctx: &egui::Context) {
.insert(egui::Id::new(rand::random::<u32>()), state);
}
if ui.button("Send Test Event").clicked() {
let temp_keys = nostr::Keys::generate();
// todo: lmao
let new_event = nostr::EventBuilder::text_note("GFY!")
.sign_with_keys(&temp_keys)
.unwrap();
let event_json = crate::relay::ClientMessage::Event { event: new_event };
let _ = &app
.relays
.send(ewebsock::WsMessage::Text(
serde_json::to_string(&event_json).unwrap(),
))
.unwrap();
}
if ui.button("Get kind 1 notes").clicked() {
let mut filter = nostr::types::Filter::new();
filter = filter.kind(nostr::Kind::TextNote);
let mut sub = crate::relay::Subscription::default();
sub.filter(filter);
let c_msg = crate::relay::ClientMessage::from(sub);
let _ = &app
.relays
.send(ewebsock::WsMessage::Text(
serde_json::to_string(&c_msg).unwrap(),
))
.unwrap();
}
ui.label(format!("total events rendered: {}", app.events.len()));
TableBuilder::new(ui)
@ -263,7 +233,7 @@ fn render_app(app: &mut Hoot, ctx: &egui::Context) {
ui.checkbox(&mut false, "");
});
row.col(|ui| {
ui.label(event.pubkey.to_string());
ui.label(event.pubkey.expect("I NEED PUBLIC KEY").to_string());
});
row.col(|ui| {
ui.label(event.content.clone());
@ -274,7 +244,7 @@ fn render_app(app: &mut Hoot, ctx: &egui::Context) {
if row.response().clicked() {
println!("clicked: {}", event.content.clone());
app.focused_post = event.id.to_string();
app.focused_post = event.id.expect("WE SHOULD HAVE AN EVENT ID").to_string();
app.page = Page::Post;
}
});
@ -291,14 +261,14 @@ fn render_app(app: &mut Hoot, ctx: &egui::Context) {
let gift_wrapped_event = app
.events
.iter()
.find(|&x| x.id.to_string() == app.focused_post)
.find(|&x| x.id.expect("there should be id").to_string() == app.focused_post)
.expect("event id should be present inside event list");
let event_to_display = app.account_manager.unwrap_gift_wrap(gift_wrapped_event).expect("we should be able to unwrap an event we recieved");
ui.heading("View Message");
ui.label(format!("Content: {}", event_to_display.rumor.content));
ui.label(match &event_to_display.rumor.tags.find(nostr::TagKind::Subject) {
ui.label(match &event_to_display.rumor.tags.find(nostr::event::tag::kind::TagKind::Subject) {
Some(s) => match s.content() {
Some(c) => format!("Subject: {}", c.to_string()),
None => "Subject: None".to_string(),

View file

@ -1,6 +1,8 @@
use serde::{Serialize, Deserialize};
type Bytes = [u8; 32];
#[derive(Debug, Default, Clone, PartialEq, Eq, Hash)]
#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct EventId {
inner: Bytes,
}
@ -17,6 +19,13 @@ impl EventId {
}
}
impl std::fmt::Display for EventId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use bitcoin::hex::DisplayHex;
write!(f, "{}", self.inner.to_lower_hex_string())
}
}
impl From<String> for EventId {
fn from(s: String) -> Self {
let mut inner = [0u8; 32];

7
src/nostr/event/kind.rs Normal file
View file

@ -0,0 +1,7 @@
#[derive(Debug, PartialEq, Eq)]
#[repr(u32)]
pub enum EventKind {
ProfileMetadata = 0,
MailEvent = 1059,
Custom(u64),
}

View file

@ -1,10 +1,12 @@
use bitcoin::secp256k1::{Message, PublicKey, SecretKey, Keypair, schnorr::Signature, Secp256k1, hashes::{Hash, sha256}};
use serde_json::json;
mod tag;
use tag::{Tag, list::Tags};
mod id;
use id::EventId;
pub mod tag;
pub use tag::{Tag, list::Tags};
pub mod id;
pub use id::EventId;
pub mod kind;
pub use kind::EventKind;
#[derive(Debug, PartialEq, Eq)]
pub enum EventBuilderError {
@ -14,7 +16,7 @@ pub enum EventBuilderError {
#[derive(Debug, Default)]
pub struct EventBuilder {
pub created_at: Option<i64>,
pub kind: Option<u32>,
pub kind: Option<EventKind>,
pub tags: Tags,
pub content: String,
}
@ -24,7 +26,12 @@ impl EventBuilder {
Self::default()
}
pub fn kind(mut self, kind: u32) -> Self {
pub fn gift_wrap(sender_keypair: &Keypair, recipient_pubkey: &PublicKey, event: Event) -> Result<Event, EventBuilderError> {
event.pubkey = Some(sender_keypair.clone().public_key());
event.id = Some(event.compute_id());
}
pub fn kind(mut self, kind: EventKind) -> Self {
self.kind = Some(kind);
self
}
@ -62,7 +69,7 @@ impl EventBuilder {
Ok(Event {
created_at: self.created_at.unwrap(),
kind: self.kind.unwrap(),
kind: self.kind.unwrap().into(),
tags: self.tags.clone(),
content: self.content.clone(),
id: None,
@ -105,6 +112,10 @@ impl Event {
pub fn sign(&mut self, keypair: &Keypair) -> Result<(), String> {
let secp = Secp256k1::new();
if self.pubkey.is_none() {
self.pubkey = Some(keypair.public_key());
}
let id = self.compute_id();
self.id = Some(id.clone());
let message = Message::from_digest(*id.as_bytes());

View file

@ -0,0 +1,11 @@
use serde::{Serialize, Deserialize};
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum TagKind {
#[serde(rename = "t")]
Tag,
#[serde(rename = "p")]
Pubkey,
#[serde(rename = "subject")]
Subject,
}

View file

@ -1,6 +1,7 @@
use serde::{Serialize, Deserialize};
pub mod list;
pub mod kind;
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Tag(Vec<String>);

183
src/nostr/filter.rs Normal file
View file

@ -0,0 +1,183 @@
use std::collections::BTreeMap;
use chrono::{DateTime, Utc, serde::ts_seconds_option};
use super::EventId;
use serde::ser::{SerializeMap, Serializer};
use serde::{Serialize, Deserialize};
type GenericTags = BTreeMap<String, Vec<String>>;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Filter {
#[serde(skip_serializing_if = "Option::is_none")]
pub ids: Option<Vec<EventId>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub authors: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub kinds: Option<Vec<u32>>,
#[serde(with = "ts_seconds_option", skip_serializing_if = "Option::is_none")]
pub since: Option<DateTime<Utc>>,
#[serde(with = "ts_seconds_option", skip_serializing_if = "Option::is_none")]
pub until: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub limit: Option<usize>,
#[serde(flatten, serialize_with = "serialize_generic_tags")]
pub generic_tags: GenericTags,
}
impl Filter {
pub fn new() -> Self {
Self::default()
}
pub fn set_ids(mut self, ids: Vec<EventId>) -> Self {
self.ids = Some(ids);
self
}
pub fn unset_ids(mut self) -> Self {
self.ids = None;
self
}
pub fn add_id(mut self, id: EventId) -> Self {
self.ids.get_or_insert_with(Vec::new).push(id);
self
}
pub fn remove_id(mut self, id: &EventId) -> Self {
if let Some(ids) = &mut self.ids {
ids.retain(|x| x != id);
}
self
}
pub fn set_authors(mut self, authors: Vec<String>) -> Self {
self.authors = Some(authors);
self
}
pub fn unset_authors(mut self) -> Self {
self.authors = None;
self
}
pub fn add_author(mut self, author: String) -> Self {
self.authors.get_or_insert_with(Vec::new).push(author);
self
}
pub fn remove_author(mut self, author: &str) -> Self {
if let Some(authors) = &mut self.authors {
authors.retain(|x| x != author);
}
self
}
pub fn set_kinds(mut self, kinds: Vec<u32>) -> Self {
self.kinds = Some(kinds);
self
}
pub fn unset_kinds(mut self) -> Self {
self.kinds = None;
self
}
pub fn add_kind(mut self, kind: u32) -> Self {
self.kinds.get_or_insert_with(Vec::new).push(kind);
self
}
pub fn remove_kind(mut self, kind: u32) -> Self {
if let Some(kinds) = &mut self.kinds {
kinds.retain(|&x| x != kind);
}
self
}
pub fn set_since(mut self, since: DateTime<Utc>) -> Self {
self.since = Some(since);
self
}
pub fn unset_since(mut self) -> Self {
self.since = None;
self
}
pub fn set_until(mut self, until: DateTime<Utc>) -> Self {
self.until = Some(until);
self
}
pub fn unset_until(mut self) -> Self {
self.until = None;
self
}
pub fn set_limit(mut self, limit: usize) -> Self {
self.limit = Some(limit);
self
}
pub fn unset_limit(mut self) -> Self {
self.limit = None;
self
}
pub fn add_tag(mut self, key: &str, value: Vec<String>) -> Self {
self.generic_tags.insert(key.to_owned(), value);
self
}
pub fn remove_tag(mut self, key: &str, value: &str) -> Self {
if let Some(values) = self.generic_tags.get_mut(key) {
values.retain(|x| x != value);
if values.is_empty() {
self.generic_tags.remove(key);
}
}
self
}
pub fn clear_tag(mut self, key: &str) -> Self {
self.generic_tags.remove(key);
self
}
pub fn clear_all_tags(mut self) -> Self {
self.generic_tags.clear();
self
}
}
impl Default for Filter {
fn default() -> Self {
Filter {
ids: None,
authors: None,
kinds: None,
since: None,
until: None,
limit: None,
generic_tags: GenericTags::new(),
}
}
}
fn serialize_generic_tags<S>(tags: &GenericTags, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut map = serializer.serialize_map(Some(tags.len()))?;
for (key, value) in tags {
map.serialize_entry(key, value)?;
}
map.end()
}

View file

@ -1,2 +1,4 @@
pub mod event;
pub use event::{Event, EventBuilder};
pub use event::{Event, EventBuilder, EventId, Tag, Tags, EventKind};
pub mod filter;
pub use filter::Filter;

View file

@ -1,10 +1,8 @@
use ewebsock::{WsMessage, WsEvent};
use nostr::types::Filter;
use nostr::Event;
use serde::de::{SeqAccess, Visitor};
use crate::nostr::Filter;
use crate::nostr::Event;
use serde::ser::SerializeSeq;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::fmt::{self};
use serde::{Serialize, Serializer};
use crate::error;
#[derive(Debug, Eq, PartialEq)]