fuck it we have branches
This commit is contained in:
parent
ca7d2319ea
commit
d75fa95a7d
12 changed files with 300 additions and 57 deletions
45
Cargo.lock
generated
45
Cargo.lock
generated
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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 {
|
||||
|
|
40
src/main.rs
40
src/main.rs
|
@ -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(),
|
||||
|
|
|
@ -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
7
src/nostr/event/kind.rs
Normal file
|
@ -0,0 +1,7 @@
|
|||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[repr(u32)]
|
||||
pub enum EventKind {
|
||||
ProfileMetadata = 0,
|
||||
MailEvent = 1059,
|
||||
Custom(u64),
|
||||
}
|
|
@ -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());
|
||||
|
|
11
src/nostr/event/tag/kind.rs
Normal file
11
src/nostr/event/tag/kind.rs
Normal 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,
|
||||
}
|
|
@ -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
183
src/nostr/filter.rs
Normal 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()
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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)]
|
||||
|
|
Loading…
Add table
Reference in a new issue