notedeck

One damus client to rule them all
git clone git://jb55.com/notedeck
Log | Files | Refs | README | LICENSE

commit 00d66515339a8971345af713b133aeaf21ec8028
parent 6741ea8a012df06ab7121bc632f2f4f565b70377
Author: kernelkind <kernelkind@gmail.com>
Date:   Mon, 23 Jun 2025 18:12:57 -0400

send kind 3 event

Signed-off-by: kernelkind <kernelkind@gmail.com>
Co-authored-by: Jakub Gladysz <jakub.gladysz@protonmail.com>
Co-authored-by: William Casarin <jb55@jb55.com>

Diffstat:
Mcrates/notedeck_columns/src/profile.rs | 112+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 110 insertions(+), 2 deletions(-)

diff --git a/crates/notedeck_columns/src/profile.rs b/crates/notedeck_columns/src/profile.rs @@ -1,8 +1,9 @@ use std::collections::HashMap; -use enostr::{FullKeypair, Pubkey, RelayPool}; -use nostrdb::{Ndb, Note, NoteBuildOptions, NoteBuilder}; +use enostr::{FilledKeypair, FullKeypair, Pubkey, RelayPool}; +use nostrdb::{Ndb, Note, NoteBuildOptions, NoteBuilder, Transaction}; +use notedeck::{Accounts, ContactState}; use tracing::info; use crate::{nav::RouterAction, profile_state::ProfileState, route::Route}; @@ -64,6 +65,24 @@ impl ProfileAction { } } } + + fn send_follow_user_event( + ndb: &Ndb, + pool: &mut RelayPool, + accounts: &Accounts, + target_key: &Pubkey, + ) { + send_kind_3_event(ndb, pool, accounts, FollowAction::Follow(target_key)); + } + + fn send_unfollow_user_event( + ndb: &Ndb, + pool: &mut RelayPool, + accounts: &Accounts, + target_key: &Pubkey, + ) { + send_kind_3_event(ndb, pool, accounts, FollowAction::Unfollow(target_key)); + } } pub fn builder_from_note<F>(note: Note<'_>, skip_tag: Option<F>) -> NoteBuilder<'_> @@ -95,3 +114,92 @@ where builder } + +enum FollowAction<'a> { + Follow(&'a Pubkey), + Unfollow(&'a Pubkey), +} + +fn send_kind_3_event(ndb: &Ndb, pool: &mut RelayPool, accounts: &Accounts, action: FollowAction) { + let Some(kp) = accounts.get_selected_account().key.to_full() else { + return; + }; + + let txn = Transaction::new(ndb).expect("txn"); + + let ContactState::Received { + contacts: _, + note_key, + } = accounts.get_selected_account().data.contacts.get_state() + else { + return; + }; + + let contact_note = match ndb.get_note_by_key(&txn, *note_key).ok() { + Some(n) => n, + None => { + tracing::error!("Somehow we are in state ContactState::Received but the contact note key doesn't exist"); + return; + } + }; + + if contact_note.kind() != 3 { + tracing::error!("Something very wrong just occured. The key for the supposed contact note yielded a note which was not a contact..."); + return; + } + + let builder = match action { + FollowAction::Follow(pubkey) => { + builder_from_note(contact_note, None::<fn(&nostrdb::Tag<'_>) -> bool>) + .start_tag() + .tag_str("p") + .tag_str(&pubkey.hex()) + } + FollowAction::Unfollow(pubkey) => builder_from_note( + contact_note, + Some(|tag: &nostrdb::Tag<'_>| { + if tag.count() < 2 { + return false; + } + + let Some("p") = tag.get_str(0) else { + return false; + }; + + let Some(cur_val) = tag.get_id(1) else { + return false; + }; + + cur_val == pubkey.bytes() + }), + ), + }; + + send_note_builder(builder, ndb, pool, kp); +} + +fn send_note_builder(builder: NoteBuilder, ndb: &Ndb, pool: &mut RelayPool, kp: FilledKeypair) { + let note = builder + .sign(&kp.secret_key.secret_bytes()) + .build() + .expect("build note"); + + let raw_msg = format!("[\"EVENT\",{}]", note.json().unwrap()); + + let _ = ndb.process_event_with( + raw_msg.as_str(), + nostrdb::IngestMetadata::new().client(true), + ); + info!("sending {}", raw_msg); + pool.send(&enostr::ClientMessage::raw(raw_msg)); +} + +fn construct_new_contact_list<'a>(pk: &'a Pubkey) -> NoteBuilder<'a> { + NoteBuilder::new() + .content("") + .kind(3) + .options(NoteBuildOptions::default()) + .start_tag() + .tag_str("p") + .tag_str(&pk.hex()) +}