feat: improve NIP-17 DM loading logic for incoming and outgoing messages
This commit is contained in:
66
src/bin/test_message_loading.rs
Normal file
66
src/bin/test_message_loading.rs
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
use easy_nostr::EasyNostr;
|
||||||
|
use std::env;
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> anyhow::Result<()> {
|
||||||
|
// 1. YOUR PRIVATE KEY (The one who is logged in)
|
||||||
|
// You can use the HEX format OR the NSEC string. nostr-sdk handles both.
|
||||||
|
// Example (This must be YOUR secret key):
|
||||||
|
let my_private_key = env::var("NOSTR_PRIVATE_KEY").unwrap_or_else(|_| {
|
||||||
|
// This must be a SECRET key (hex or nsec), not a public key!
|
||||||
|
"hex_nsec_key".to_string()
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2. THE CONTACT'S PUBLIC KEY (The person you are chatting with)
|
||||||
|
let contact_npub = "npub1...";
|
||||||
|
|
||||||
|
println!("🚀 Initializing EasyNostr with my private key...");
|
||||||
|
// EasyNostr::new calls Keys::parse() internally, which accepts Hex or Nsec
|
||||||
|
let easy_nostr = EasyNostr::new(&my_private_key).await?;
|
||||||
|
|
||||||
|
println!("🌐 Connecting to relays...");
|
||||||
|
let relays = vec![
|
||||||
|
"wss://relay.damus.io",
|
||||||
|
"wss://nos.lol",
|
||||||
|
"wss://relay.snort.social",
|
||||||
|
"wss://relay.primal.net", // Added Primal as it's very reliable for DMs
|
||||||
|
];
|
||||||
|
easy_nostr.add_relays(relays).await?;
|
||||||
|
|
||||||
|
println!("📥 Fetching messages for contact: {}...", contact_npub);
|
||||||
|
|
||||||
|
// 3. Fetch the messages
|
||||||
|
// This will look for:
|
||||||
|
// - Messages from contact_npub to ME (decrypted with my_private_key)
|
||||||
|
// - Messages from ME to contact_npub
|
||||||
|
let messages = easy_nostr.get_private_messages(contact_npub).await?;
|
||||||
|
|
||||||
|
println!("\n--- 💬 Chat History ({}) ---", messages.len());
|
||||||
|
|
||||||
|
if messages.is_empty() {
|
||||||
|
println!("❌ No messages found.");
|
||||||
|
println!(
|
||||||
|
"Hint: Make sure the private key corresponds to the account that sent/received the messages."
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
for msg in messages {
|
||||||
|
let direction = if msg.is_incoming {
|
||||||
|
"⬅️ [IN]"
|
||||||
|
} else {
|
||||||
|
"➡️ [OUT]"
|
||||||
|
};
|
||||||
|
let time = msg.created_at.to_human_datetime();
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"{} | {} | Sender: {:.8}...\n Content: {}\n",
|
||||||
|
direction,
|
||||||
|
time,
|
||||||
|
msg.sender.to_hex(),
|
||||||
|
msg.content
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("--- End of History ---");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -1,14 +1,19 @@
|
|||||||
|
use crate::nips::nip17::{self, Message};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
// Importiere das Message Struct aus nip17!
|
|
||||||
use crate::nips::nip17::{self, Message};
|
|
||||||
|
|
||||||
pub async fn send_private_message(client: &Client, receiver_pubkey: &str, message: &str) -> Result<EventId> {
|
/// Sends a private message.
|
||||||
|
/// The SDK will automatically handle the NIP-17 (modern) or NIP-04 (legacy) logic.
|
||||||
|
pub async fn send_private_message(
|
||||||
|
client: &Client,
|
||||||
|
receiver_pubkey: &str,
|
||||||
|
message: &str,
|
||||||
|
) -> Result<EventId> {
|
||||||
let receiver = PublicKey::parse(receiver_pubkey)?;
|
let receiver = PublicKey::parse(receiver_pubkey)?;
|
||||||
nip17::send_dm(client, receiver, message).await
|
nip17::send_dm(client, receiver, message).await
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rückgabetyp ist Vec<Message>
|
/// Fetches all private messages between the user and a specific contact.
|
||||||
pub async fn get_private_messages(client: &Client, contact_npub: &str) -> Result<Vec<Message>> {
|
pub async fn get_private_messages(client: &Client, contact_npub: &str) -> Result<Vec<Message>> {
|
||||||
nip17::get_dm_messages(client, contact_npub).await
|
nip17::get_dm_messages(client, contact_npub).await
|
||||||
}
|
}
|
||||||
@@ -30,11 +30,11 @@ impl EasyNostr {
|
|||||||
messages::send_private_message(&self.client, receiver_pubkey, message).await
|
messages::send_private_message(&self.client, receiver_pubkey, message).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Retrieves history of DMs with a specific contact
|
||||||
pub async fn get_private_messages(&self, contact_npub: &str) -> Result<Vec<Message>> {
|
pub async fn get_private_messages(&self, contact_npub: &str) -> Result<Vec<Message>> {
|
||||||
messages::get_private_messages(&self.client, contact_npub).await
|
messages::get_private_messages(&self.client, contact_npub).await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fügt Relays hinzu und verbindet
|
|
||||||
pub async fn add_relays(&self, urls: Vec<&str>) -> Result<()> {
|
pub async fn add_relays(&self, urls: Vec<&str>) -> Result<()> {
|
||||||
relays::add_relays(&self.client, urls).await
|
relays::add_relays(&self.client, urls).await
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
|
use std::collections::HashSet;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
/// UI Message Struktur
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Message {
|
pub struct Message {
|
||||||
pub id: EventId,
|
pub id: EventId,
|
||||||
@@ -12,79 +12,98 @@ pub struct Message {
|
|||||||
pub is_incoming: bool,
|
pub is_incoming: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Direktnachricht senden
|
|
||||||
pub async fn send_dm(client: &Client, receiver: PublicKey, message: &str) -> Result<EventId> {
|
pub async fn send_dm(client: &Client, receiver: PublicKey, message: &str) -> Result<EventId> {
|
||||||
let output = client.send_private_msg(receiver, message, None).await?;
|
let output = client.send_private_msg(receiver, message, None).await?;
|
||||||
Ok(*output.id())
|
Ok(*output.id())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Direktnachrichten abrufen
|
// src/nips/nip17.rs
|
||||||
|
|
||||||
pub async fn get_dm_messages(client: &Client, contact_npub: &str) -> Result<Vec<Message>> {
|
pub async fn get_dm_messages(client: &Client, contact_npub: &str) -> Result<Vec<Message>> {
|
||||||
let signer = client.signer().await?;
|
let signer = client.signer().await?;
|
||||||
let my_pubkey = signer.get_public_key().await?;
|
let my_pubkey = signer.get_public_key().await?;
|
||||||
let contact_pubkey = PublicKey::parse(contact_npub)?;
|
let contact_pubkey = PublicKey::parse(contact_npub)?;
|
||||||
|
|
||||||
// --- Einzelne Filter erstellen ---
|
let search_start = Timestamp::now() - Duration::from_secs(60 * 60 * 24 * 365);
|
||||||
let filter_in = Filter::new()
|
|
||||||
|
// STRATEGY: We only fetch events where WE are the recipient (#p tag).
|
||||||
|
// In NIP-17, sent messages are only readable if we sent a copy to ourselves.
|
||||||
|
// In NIP-04, we can fetch messages we authored.
|
||||||
|
|
||||||
|
let filter_to_me = Filter::new()
|
||||||
.kinds([Kind::GiftWrap, Kind::EncryptedDirectMessage])
|
.kinds([Kind::GiftWrap, Kind::EncryptedDirectMessage])
|
||||||
.pubkey(my_pubkey)
|
.pubkey(my_pubkey) // All GiftWraps sent TO me (includes my own copies)
|
||||||
.since(Timestamp::now() - Duration::from_secs(60 * 60 * 24 * 30))
|
.since(search_start);
|
||||||
.limit(500);
|
|
||||||
|
|
||||||
let filter_out = Filter::new()
|
let filter_from_me_legacy = Filter::new()
|
||||||
.kind(Kind::EncryptedDirectMessage)
|
.kind(Kind::EncryptedDirectMessage)
|
||||||
.author(my_pubkey)
|
.author(my_pubkey) // Only for NIP-04 legacy (where author can decrypt)
|
||||||
.pubkey(contact_pubkey)
|
.pubkey(contact_pubkey)
|
||||||
.limit(100);
|
.since(search_start);
|
||||||
|
|
||||||
// --- Abrufen: zwei einzelne fetches, dann Ergebnisse mergen ---
|
|
||||||
let mut events = client
|
let mut events = client
|
||||||
.fetch_events(filter_in, Duration::from_secs(10))
|
.fetch_events(filter_to_me, Duration::from_secs(10))
|
||||||
.await?;
|
.await?
|
||||||
|
.to_vec();
|
||||||
let more_events = client
|
let more_events = client
|
||||||
.fetch_events(filter_out, Duration::from_secs(10))
|
.fetch_events(filter_from_me_legacy, Duration::from_secs(10))
|
||||||
.await?;
|
.await?
|
||||||
events.extend(more_events.into_iter());
|
.to_vec();
|
||||||
|
events.extend(more_events);
|
||||||
|
|
||||||
|
let mut seen_ids = HashSet::new();
|
||||||
let mut messages: Vec<Message> = Vec::new();
|
let mut messages: Vec<Message> = Vec::new();
|
||||||
|
|
||||||
for event in events.iter() {
|
for event in events {
|
||||||
// === NIP-17 (Gift Wrap) Verarbeitung ===
|
if !seen_ids.insert(event.id) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// === NIP-17 Processing ===
|
||||||
if event.kind == Kind::GiftWrap {
|
if event.kind == Kind::GiftWrap {
|
||||||
if let Ok(unwrapped) = client.unwrap_gift_wrap(event).await {
|
if let Ok(unwrapped) = client.unwrap_gift_wrap(&event).await {
|
||||||
let rumor = unwrapped.rumor;
|
let rumor = unwrapped.rumor;
|
||||||
// Allow TextNote (1) or ChatMessage (14)
|
|
||||||
if rumor.pubkey == contact_pubkey
|
// 1. INCOMING: Rumor author is contact
|
||||||
&& (rumor.kind == Kind::TextNote || rumor.kind == Kind::from(14))
|
let is_incoming = rumor.pubkey == contact_pubkey;
|
||||||
|
|
||||||
|
// 2. OUTGOING: Rumor is from ME, and it contains a tag pointing to the contact
|
||||||
|
// Most NIP-17 rumors have a 'p' tag inside the rumor to show who it's for.
|
||||||
|
let is_outgoing = rumor.pubkey == my_pubkey
|
||||||
|
&& rumor.tags.iter().any(|t| {
|
||||||
|
if let Some(TagStandard::PublicKey { public_key, .. }) = t.as_standardized()
|
||||||
|
{
|
||||||
|
public_key == &contact_pubkey
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (is_incoming || is_outgoing)
|
||||||
|
&& (rumor.kind == Kind::from(14) || rumor.kind == Kind::TextNote)
|
||||||
{
|
{
|
||||||
messages.push(Message {
|
messages.push(Message {
|
||||||
id: event.id,
|
id: event.id,
|
||||||
sender: rumor.pubkey,
|
sender: rumor.pubkey,
|
||||||
content: rumor.content.clone(),
|
content: rumor.content.clone(),
|
||||||
created_at: rumor.created_at,
|
created_at: rumor.created_at,
|
||||||
is_incoming: true,
|
is_incoming,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// === NIP-04 (Legacy) Verarbeitung ===
|
// === NIP-04 Processing ===
|
||||||
else if event.kind == Kind::EncryptedDirectMessage {
|
else if event.kind == Kind::EncryptedDirectMessage {
|
||||||
// TagStandard::PublicKey ist ein Struct-Variant: Felder mit Struct-Syntax matchen
|
|
||||||
let has_contact_tag = event.tags.iter().any(|t| match t.as_standardized() {
|
|
||||||
Some(TagStandard::PublicKey { public_key, .. }) => *public_key == contact_pubkey,
|
|
||||||
_ => false,
|
|
||||||
});
|
|
||||||
|
|
||||||
let is_incoming = event.pubkey == contact_pubkey;
|
let is_incoming = event.pubkey == contact_pubkey;
|
||||||
let is_outgoing = event.pubkey == my_pubkey && has_contact_tag;
|
let is_outgoing = event.pubkey == my_pubkey; // Already filtered by p-tag in the filter
|
||||||
|
|
||||||
if is_incoming || is_outgoing {
|
if is_incoming || is_outgoing {
|
||||||
let counterparty = if is_incoming {
|
let decrypt_with = if is_incoming {
|
||||||
event.pubkey
|
event.pubkey
|
||||||
} else {
|
} else {
|
||||||
contact_pubkey
|
contact_pubkey
|
||||||
};
|
};
|
||||||
if let Ok(content) = signer.nip04_decrypt(&counterparty, &event.content).await {
|
if let Ok(content) = signer.nip04_decrypt(&decrypt_with, &event.content).await {
|
||||||
messages.push(Message {
|
messages.push(Message {
|
||||||
id: event.id,
|
id: event.id,
|
||||||
sender: event.pubkey,
|
sender: event.pubkey,
|
||||||
@@ -97,8 +116,6 @@ pub async fn get_dm_messages(client: &Client, contact_npub: &str) -> Result<Vec<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Nachrichten chronologisch sortieren
|
|
||||||
messages.sort_by(|a, b| a.created_at.cmp(&b.created_at));
|
messages.sort_by(|a, b| a.created_at.cmp(&b.created_at));
|
||||||
|
|
||||||
Ok(messages)
|
Ok(messages)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user