All basic functions plus guide README to use
This commit is contained in:
146
README.md
Normal file
146
README.md
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
# EasyNostr
|
||||||
|
|
||||||
|
A simple and functional Rust crate for building Nostr applications. This library handles the complexity of keys, relays, and protocol details (NIPs), giving you easy-to-use functions for a social media app.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
- **Identity**: Easy management of keys (nsec/npub).
|
||||||
|
- **Social Feed**: View timeline of friends or global random posts.
|
||||||
|
- **Posting**: Publish text notes (Kind 1).
|
||||||
|
- **Contacts**: Follow users and retrieve your contact list.
|
||||||
|
- **Direct Messages**: Send and receive encrypted private messages (NIP-17 & NIP-04).
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
### 1. Initialize the Client
|
||||||
|
First, you need to create an instance of `EasyNostr` using your secret key (nsec).
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use easy_nostr::EasyNostr;
|
||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<()> {
|
||||||
|
// Your secret key (nsec)
|
||||||
|
let my_secret_key = "nsec1...";
|
||||||
|
|
||||||
|
// Initialize client
|
||||||
|
let client = EasyNostr::new(my_secret_key).await?;
|
||||||
|
|
||||||
|
// Connect to Relays
|
||||||
|
client.add_relays(vec![
|
||||||
|
"wss://relay.damus.io",
|
||||||
|
"wss://nos.lol",
|
||||||
|
"wss://relay.primal.net"
|
||||||
|
]).await?;
|
||||||
|
|
||||||
|
println!("Connected and ready!");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Capabilities & Usage
|
||||||
|
|
||||||
|
### 📝 Publishing Posts
|
||||||
|
Post a simple text message to the network.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let event_id = client.post_text("Hello Nostr world!").await?;
|
||||||
|
println!("Posted! ID: {}", event_id.to_bech32()?);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 📰 Reading Feeds
|
||||||
|
You can read posts from people you follow (Timeline) or random posts from the network.
|
||||||
|
|
||||||
|
**Get Timeline (Followed Users):**
|
||||||
|
```rust
|
||||||
|
// List of public keys (npubs) you want to see posts from
|
||||||
|
let following = vec!["npub1...", "npub1..."];
|
||||||
|
|
||||||
|
let posts = client.get_timeline(following).await?;
|
||||||
|
|
||||||
|
for post in posts {
|
||||||
|
println!("@{} wrote: {}", post.author.to_bech32()?, post.content);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Get Global Feed (Random/Recent):**
|
||||||
|
```rust
|
||||||
|
let random_posts = client.get_random_posts().await?;
|
||||||
|
|
||||||
|
for post in random_posts {
|
||||||
|
println!("New Post: {}", post.content);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**The `Post` Structure:**
|
||||||
|
When you get posts, you receive a `Post` struct:
|
||||||
|
- `post.id`: The unique Event ID.
|
||||||
|
- `post.author`: The PublicKey of the author.
|
||||||
|
- `post.content`: The text content.
|
||||||
|
- `post.created_at`: The timestamp.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 👥 Managing Contacts (Followers)
|
||||||
|
Manage who you follow.
|
||||||
|
|
||||||
|
**Follow a User:**
|
||||||
|
```rust
|
||||||
|
let jack_dorsey = "npub1sg6plzptd64u62a878hep2kd8856nmmhd0x479jp6bir725uqqks698teq";
|
||||||
|
// Context: (NPub, Optional Nickname)
|
||||||
|
client.create_contact(jack_dorsey, Some("Jack".to_string())).await?;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Get Your Contact List:**
|
||||||
|
```rust
|
||||||
|
let my_contacts = client.get_contacts().await?;
|
||||||
|
|
||||||
|
for contact in my_contacts {
|
||||||
|
println!("Following: {:?}", contact.public_key.to_bech32()?);
|
||||||
|
if let Some(alias) = &contact.alias {
|
||||||
|
println!(" - Alias: {}", alias);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🔒 Direct Messages (DMs)
|
||||||
|
Send and receive encrypted private chats. Supports modern GiftWraps (NIP-17).
|
||||||
|
|
||||||
|
**Send a DM:**
|
||||||
|
```rust
|
||||||
|
let friend_npub = "npub1...";
|
||||||
|
client.send_private_message(friend_npub, "Hey, this is secret!").await?;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Read DMs:**
|
||||||
|
```rust
|
||||||
|
let friend_npub = "npub1...";
|
||||||
|
let messages = client.get_private_messages(friend_npub).await?;
|
||||||
|
|
||||||
|
for msg in messages {
|
||||||
|
let direction = if msg.is_incoming { "Received" } else { "Sent" };
|
||||||
|
println!("[{}] {}", direction, msg.content);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**The `Message` Structure:**
|
||||||
|
- `msg.sender`: PublicKey of the sender.
|
||||||
|
- `msg.content`: Decrypted text.
|
||||||
|
- `msg.is_incoming`: `true` if you received it, `false` if you sent it.
|
||||||
|
- `msg.created_at`: Use `msg.created_at.as_secs()` to get the unix timestamp.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
Ensure you have the following in your `Cargo.toml`. `easy-nostr` re-exports most things you need, but you might need `anyhow` and `tokio`.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[dependencies]
|
||||||
|
nostr-sdk = { version = "0.44.1", features = ["all-nips", "nip44"] }
|
||||||
|
tokio = { version = "1.48.0", features = ["full"] }
|
||||||
|
anyhow = "1.0.100"
|
||||||
|
```
|
||||||
19
big_promts.md
Normal file
19
big_promts.md
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
Du bist erfahrener Rust entwickler und hällst den code simpel aber funktional. Achte darauf die richtigen versionen zu verwenden: nostr-sdk = { version = "0.44.1", features = ["all-nips", "nip44"] }
|
||||||
|
tokio = { version = "1.48.0", features = ["full"] }
|
||||||
|
secp256k1 = "0.27"
|
||||||
|
anyhow = "1.0.100"
|
||||||
|
Kommentiere alles in einfachem English im code fürs verständnis. Programmiere nichts unnötiges sondern nur wesentliche sachen damit es funktioniert. Baue nun folgendes ein, passe dich auf meine Codestruktur an: Der Feed / Timeline (füge hinzu das einen Feed abrufen kann also posts von leuten sieht denen man folgt und eine funktion in der lib.rs mit der man zufällige posts laden kann); Erstelle noch eine funktion in der lib.rs das man Text posts selber machen kann. Teile alles wieder auf in @src/nips und @src/functions und die lib.rs @lib.rs Also konkret deine aufgabe jetzt: Aufgabe: Implementiere eine Feed-/Timeline-Logik und eine Post-Funktion.
|
||||||
|
|
||||||
|
lib.rs: Zentrale Einstiegspunkte.
|
||||||
|
|
||||||
|
src/functions: Logik für get_feed (Posts von gefolgten Personen) und get_random_posts.
|
||||||
|
|
||||||
|
src/nips: Definition der Datenstrukturen (Post-Typen).
|
||||||
|
|
||||||
|
Features:
|
||||||
|
|
||||||
|
Funktion zum Erstellen eines Text-Posts.
|
||||||
|
|
||||||
|
Funktion zum Abrufen eines Feeds (basierend auf einer Liste von gefolgten IDs).
|
||||||
|
|
||||||
|
Funktion für zufällige Posts.
|
||||||
35
production_test.log
Normal file
35
production_test.log
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
warning: use of deprecated method `nostr_sdk::Timestamp::as_u64`: Use `as_secs` instead
|
||||||
|
--> src/bin/testall.rs:40:63
|
||||||
|
|
|
||||||
|
40 | ...w().as_u64());
|
||||||
|
| ^^^^^^
|
||||||
|
|
|
||||||
|
= note: `#[warn(deprecated)]` on by default
|
||||||
|
|
||||||
|
warning: `easy-nostr` (bin "testall") generated 1 warning
|
||||||
|
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.10s
|
||||||
|
Running `target/debug/testall`
|
||||||
|
=== EasyNostr Production Test Suite ===
|
||||||
|
[INFO] Identity: npub1jx2cnrlnpvy9gxgfnkqf30l0u3jkgjmp76jcvgxw5uuf5lqynduqz9n2me
|
||||||
|
|
||||||
|
[STEP 1] Connecting to Relays...
|
||||||
|
[OK] Connected.
|
||||||
|
|
||||||
|
[STEP 2] Following Self (for robust timeline testing)...
|
||||||
|
[OK] Followed self.
|
||||||
|
|
||||||
|
[STEP 3] Publishing Post...
|
||||||
|
[OK] Posted: note10xlp2za0p0ug3v49ggve2gv2lzfmgltzk84a7qkueskeau9kj5nshcfu20
|
||||||
|
|
||||||
|
[STEP 4] verifying Timeline (expecting own post)...
|
||||||
|
[OK] Found our post in timeline after 2s!
|
||||||
|
|
||||||
|
[STEP 5] Testing Direct Messages...
|
||||||
|
... attempt 1/5: DM not yet visible...
|
||||||
|
... attempt 2/5: DM not yet visible...
|
||||||
|
... attempt 3/5: DM not yet visible...
|
||||||
|
... attempt 4/5: DM not yet visible...
|
||||||
|
... attempt 5/5: DM not yet visible...
|
||||||
|
[ERR] DM propagation failed.
|
||||||
|
|
||||||
|
=== Test Complete ===
|
||||||
31
production_test_v2.log
Normal file
31
production_test_v2.log
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
Compiling easy-nostr v0.1.0 (/home/malxte/Documents/Programming/Rust/Crates/easy-nostr)
|
||||||
|
warning: use of deprecated method `nostr_sdk::Timestamp::as_u64`: Use `as_secs` instead
|
||||||
|
--> src/bin/testall.rs:40:63
|
||||||
|
|
|
||||||
|
40 | let secret_code = format!("Test Run {}", Timestamp::now().as_u64());
|
||||||
|
| ^^^^^^
|
||||||
|
|
|
||||||
|
= note: `#[warn(deprecated)]` on by default
|
||||||
|
|
||||||
|
warning: `easy-nostr` (bin "testall") generated 1 warning
|
||||||
|
Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.40s
|
||||||
|
Running `target/debug/testall`
|
||||||
|
=== EasyNostr Production Test Suite ===
|
||||||
|
[INFO] Identity: npub1tfqs84ttgcw8z424dsrzzkchmee9s22hzpahxxxmyrg3z82zwmusmq4aga
|
||||||
|
|
||||||
|
[STEP 1] Connecting to Relays...
|
||||||
|
[OK] Connected.
|
||||||
|
|
||||||
|
[STEP 2] Following Self (for robust timeline testing)...
|
||||||
|
[OK] Followed self.
|
||||||
|
|
||||||
|
[STEP 3] Publishing Post...
|
||||||
|
[OK] Posted: note1p79vn2myyd29adw3dk0t0vuxk50ng0fkqk4yhhq34u085j4k65rqq40c0q
|
||||||
|
|
||||||
|
[STEP 4] verifying Timeline (expecting own post)...
|
||||||
|
[OK] Found our post in timeline after 2s!
|
||||||
|
|
||||||
|
[STEP 5] Testing Direct Messages...
|
||||||
|
[OK] Found DM after 2s!
|
||||||
|
|
||||||
|
=== Test Complete ===
|
||||||
93
src/bin/testall.rs
Normal file
93
src/bin/testall.rs
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
use easy_nostr::EasyNostr;
|
||||||
|
use nostr_sdk::prelude::*;
|
||||||
|
use std::time::Duration;
|
||||||
|
use tokio::time::sleep;
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> anyhow::Result<()> {
|
||||||
|
println!("=== EasyNostr Production Test Suite ===");
|
||||||
|
|
||||||
|
// 1. Identity
|
||||||
|
let keys = Keys::generate();
|
||||||
|
let nsec = keys.secret_key().to_bech32()?;
|
||||||
|
let npub = keys.public_key().to_bech32()?;
|
||||||
|
|
||||||
|
println!("[INFO] Identity: {}", npub);
|
||||||
|
|
||||||
|
// 2. Init
|
||||||
|
let ez = EasyNostr::new(&nsec).await?;
|
||||||
|
|
||||||
|
// 3. Relays (More reliable set)
|
||||||
|
println!("\n[STEP 1] Connecting to Relays...");
|
||||||
|
let relays = vec![
|
||||||
|
"wss://relay.damus.io",
|
||||||
|
"wss://nos.lol",
|
||||||
|
"wss://relay.primal.net",
|
||||||
|
];
|
||||||
|
ez.add_relays(relays).await?;
|
||||||
|
// Give relays a moment to fully welcome us
|
||||||
|
sleep(Duration::from_secs(2)).await;
|
||||||
|
println!(" [OK] Connected.");
|
||||||
|
|
||||||
|
// 4. Follow Self (Critical for Timeline Test reliability)
|
||||||
|
println!("\n[STEP 2] Following Self (for robust timeline testing)...");
|
||||||
|
ez.create_contact(&npub, Some("Me Myself".to_string()))
|
||||||
|
.await?;
|
||||||
|
println!(" [OK] Followed self.");
|
||||||
|
|
||||||
|
// 5. Publish Post
|
||||||
|
println!("\n[STEP 3] Publishing Post...");
|
||||||
|
let secret_code = format!("Test Run {}", Timestamp::now().as_secs());
|
||||||
|
let event_id = ez
|
||||||
|
.post_text(&format!("Hello Nostr! {}", secret_code))
|
||||||
|
.await?;
|
||||||
|
println!(" [OK] Posted: {}", event_id.to_bech32()?);
|
||||||
|
|
||||||
|
// 6. Verify Timeline (with Retry)
|
||||||
|
println!("\n[STEP 4] verifying Timeline (expecting own post)...");
|
||||||
|
let mut found_post = false;
|
||||||
|
for i in 1..=5 {
|
||||||
|
sleep(Duration::from_secs(2)).await; // Wait for propagation
|
||||||
|
let posts = ez.get_timeline(vec![npub.clone()]).await?;
|
||||||
|
|
||||||
|
// Look for our specific post
|
||||||
|
if posts.iter().any(|p| p.content.contains(&secret_code)) {
|
||||||
|
println!(" [OK] Found our post in timeline after {}s!", i * 2);
|
||||||
|
found_post = true;
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
println!(" ... attempt {}/5: Post not yet visible...", i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found_post {
|
||||||
|
println!(" [ERR] Verified Failed: Post did not appear in timeline.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7. DM Test (with Retry)
|
||||||
|
println!("\n[STEP 5] Testing Direct Messages...");
|
||||||
|
let dm_msg = format!("Secret DM {}", secret_code);
|
||||||
|
ez.send_private_message(&npub, &dm_msg).await?;
|
||||||
|
|
||||||
|
let mut found_dm = false;
|
||||||
|
for i in 1..=5 {
|
||||||
|
sleep(Duration::from_secs(2)).await;
|
||||||
|
let dms = ez.get_private_messages(&npub).await?;
|
||||||
|
|
||||||
|
if dms.iter().any(|m| m.content == dm_msg) {
|
||||||
|
println!(" [OK] Found DM after {}s!", i * 2);
|
||||||
|
found_dm = true;
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
println!(" ... attempt {}/5: DM not yet visible...", i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found_dm {
|
||||||
|
println!(" [ERR] DM propagation failed.");
|
||||||
|
// Non-fatal, DMs are slower
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("\n=== Test Complete ===");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
75
src/functions/feed.rs
Normal file
75
src/functions/feed.rs
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
use crate::nips::nip01::Post;
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
use nostr_sdk::prelude::*;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
/// Fetches a timeline/feed of posts from specific users (Followed users).
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `client` - The Nostr client.
|
||||||
|
/// * `followed_pubkeys` - A list of hex strings (bech32 also works if parsed correctly, but we assume hex or wait for parsing) representing the users to follow.
|
||||||
|
///
|
||||||
|
/// The function parses the keys, creates a filter, and returns a list of sorted Posts.
|
||||||
|
pub async fn get_followed_feed(
|
||||||
|
client: &Client,
|
||||||
|
followed_pubkeys: Vec<String>,
|
||||||
|
) -> Result<Vec<Post>> {
|
||||||
|
let mut keys = Vec::new();
|
||||||
|
for key in followed_pubkeys {
|
||||||
|
// We try to parse each key. If one fails, we just ignore it or log it (here we context it).
|
||||||
|
let pk = PublicKey::parse(&key).context(format!("Invalid public key: {}", key))?;
|
||||||
|
keys.push(pk);
|
||||||
|
}
|
||||||
|
|
||||||
|
if keys.is_empty() {
|
||||||
|
return Ok(Vec::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a filter for Text Notes (Kind 1) from these authors
|
||||||
|
let filter = Filter::new().kind(Kind::TextNote).authors(keys).limit(50); // Limit to 50 posts for now
|
||||||
|
|
||||||
|
let timeout = Duration::from_secs(10);
|
||||||
|
let events = client.fetch_events(filter, timeout).await?;
|
||||||
|
|
||||||
|
// Convert Events to our Post struct
|
||||||
|
let mut posts: Vec<Post> = events
|
||||||
|
.into_iter()
|
||||||
|
.map(|e| Post {
|
||||||
|
id: e.id,
|
||||||
|
author: e.pubkey,
|
||||||
|
content: e.content.clone(),
|
||||||
|
created_at: e.created_at,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// Sort by created_at descending (newest first)
|
||||||
|
posts.sort_by(|a, b| b.created_at.cmp(&a.created_at));
|
||||||
|
|
||||||
|
Ok(posts)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fetches random (recent) posts from the connected Relays (Global Feed).
|
||||||
|
///
|
||||||
|
/// This is useful for discovery.
|
||||||
|
pub async fn get_random_posts(client: &Client) -> Result<Vec<Post>> {
|
||||||
|
// Filter for any Text Note, limit 20
|
||||||
|
let filter = Filter::new().kind(Kind::TextNote).limit(20);
|
||||||
|
|
||||||
|
let timeout = Duration::from_secs(10);
|
||||||
|
let events = client.fetch_events(filter, timeout).await?;
|
||||||
|
|
||||||
|
let mut posts: Vec<Post> = events
|
||||||
|
.into_iter()
|
||||||
|
.map(|e| Post {
|
||||||
|
id: e.id,
|
||||||
|
author: e.pubkey,
|
||||||
|
content: e.content.clone(),
|
||||||
|
created_at: e.created_at,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// Sort by newest first
|
||||||
|
posts.sort_by(|a, b| b.created_at.cmp(&a.created_at));
|
||||||
|
|
||||||
|
Ok(posts)
|
||||||
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
pub mod new;
|
pub mod create_contact;
|
||||||
pub mod messages;
|
pub mod feed;
|
||||||
pub mod relays;
|
|
||||||
pub mod get_contacts;
|
pub mod get_contacts;
|
||||||
pub mod create_contact;
|
pub mod messages;
|
||||||
|
pub mod new;
|
||||||
|
pub mod publish;
|
||||||
|
pub mod relays;
|
||||||
|
|||||||
13
src/functions/publish.rs
Normal file
13
src/functions/publish.rs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
use crate::nips::nip01;
|
||||||
|
use anyhow::Result;
|
||||||
|
use nostr_sdk::prelude::*;
|
||||||
|
|
||||||
|
/// Publishes a text post to the network.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `client` - The Nostr client.
|
||||||
|
/// * `content` - The text content of the post.
|
||||||
|
pub async fn post_text_note(client: &Client, content: &str) -> Result<EventId> {
|
||||||
|
// We delegate the actual protocol work to the nip01 module
|
||||||
|
nip01::publish_text(client, content).await
|
||||||
|
}
|
||||||
30
src/lib.rs
30
src/lib.rs
@@ -1,11 +1,12 @@
|
|||||||
pub mod nips;
|
pub mod functions;
|
||||||
pub mod functions; // Das neue Modul registrieren
|
pub mod nips; // Das neue Modul registrieren
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
|
|
||||||
// Wir importieren die Funktionen, um Tipparbeit zu sparen
|
// Wir importieren die Funktionen, um Tipparbeit zu sparen
|
||||||
use crate::functions::{new, messages, relays, get_contacts, create_contact};
|
use crate::functions::{create_contact, feed, get_contacts, messages, new, publish, relays};
|
||||||
|
use crate::nips::nip01::Post;
|
||||||
use crate::nips::nip17::Message;
|
use crate::nips::nip17::Message;
|
||||||
|
|
||||||
pub struct EasyNostr {
|
pub struct EasyNostr {
|
||||||
@@ -21,7 +22,11 @@ impl EasyNostr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Sendet eine verschlüsselte DM (NIP-17 Wrapper)
|
/// Sendet eine verschlüsselte DM (NIP-17 Wrapper)
|
||||||
pub async fn send_private_message(&self, receiver_pubkey: &str, message: &str) -> Result<EventId> {
|
pub async fn send_private_message(
|
||||||
|
&self,
|
||||||
|
receiver_pubkey: &str,
|
||||||
|
message: &str,
|
||||||
|
) -> Result<EventId> {
|
||||||
messages::send_private_message(&self.client, receiver_pubkey, message).await
|
messages::send_private_message(&self.client, receiver_pubkey, message).await
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,4 +48,19 @@ impl EasyNostr {
|
|||||||
pub async fn create_contact(&self, npub: &str, nickname: Option<String>) -> Result<EventId> {
|
pub async fn create_contact(&self, npub: &str, nickname: Option<String>) -> Result<EventId> {
|
||||||
create_contact::create_contact(&self.client, npub, nickname).await
|
create_contact::create_contact(&self.client, npub, nickname).await
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// Veröffentlicht einen Text-Post (Kind 1)
|
||||||
|
pub async fn post_text(&self, content: &str) -> Result<EventId> {
|
||||||
|
publish::post_text_note(&self.client, content).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ruft den Feed der gefolgten User ab
|
||||||
|
pub async fn get_timeline(&self, followed_pubkeys: Vec<String>) -> Result<Vec<Post>> {
|
||||||
|
feed::get_followed_feed(&self.client, followed_pubkeys).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ruft zufällige (aktuelle) Posts ab (Global Feed)
|
||||||
|
pub async fn get_random_posts(&self) -> Result<Vec<Post>> {
|
||||||
|
feed::get_random_posts(&self.client).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,9 +1,19 @@
|
|||||||
|
use anyhow::Result;
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use anyhow::Result; // <--- Hinzufügen
|
|
||||||
|
|
||||||
// Das Result hier bezieht sich jetzt auf anyhow::Result
|
/// A simplified structure for a Text Post (Kind 1)
|
||||||
|
/// Contains the ID, Author, content, and timestamp.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Post {
|
||||||
|
pub id: EventId,
|
||||||
|
pub author: PublicKey,
|
||||||
|
pub content: String,
|
||||||
|
pub created_at: Timestamp,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Publishes a text note (Kind 1) to the connected relays.
|
||||||
pub async fn publish_text(client: &Client, content: &str) -> Result<EventId> {
|
pub async fn publish_text(client: &Client, content: &str) -> Result<EventId> {
|
||||||
let builder = EventBuilder::text_note(content);
|
let builder = EventBuilder::text_note(content);
|
||||||
let output = client.send_event_builder(builder).await?;
|
let output = client.send_event_builder(builder).await?;
|
||||||
Ok(*output.id())
|
Ok(*output.id())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use nostr_sdk::prelude::*;
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use nostr_sdk::prelude::*;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
/// UI Message Struktur
|
/// UI Message Struktur
|
||||||
@@ -38,8 +38,12 @@ pub async fn get_dm_messages(client: &Client, contact_npub: &str) -> Result<Vec<
|
|||||||
.limit(100);
|
.limit(100);
|
||||||
|
|
||||||
// --- Abrufen: zwei einzelne fetches, dann Ergebnisse mergen ---
|
// --- Abrufen: zwei einzelne fetches, dann Ergebnisse mergen ---
|
||||||
let mut events = client.fetch_events(filter_in, Duration::from_secs(10)).await?;
|
let mut events = client
|
||||||
let more_events = client.fetch_events(filter_out, Duration::from_secs(10)).await?;
|
.fetch_events(filter_in, Duration::from_secs(10))
|
||||||
|
.await?;
|
||||||
|
let more_events = client
|
||||||
|
.fetch_events(filter_out, Duration::from_secs(10))
|
||||||
|
.await?;
|
||||||
events.extend(more_events.into_iter());
|
events.extend(more_events.into_iter());
|
||||||
|
|
||||||
let mut messages: Vec<Message> = Vec::new();
|
let mut messages: Vec<Message> = Vec::new();
|
||||||
@@ -49,7 +53,10 @@ pub async fn get_dm_messages(client: &Client, contact_npub: &str) -> Result<Vec<
|
|||||||
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;
|
||||||
if rumor.pubkey == contact_pubkey && rumor.kind == Kind::TextNote {
|
// Allow TextNote (1) or ChatMessage (14)
|
||||||
|
if rumor.pubkey == contact_pubkey
|
||||||
|
&& (rumor.kind == Kind::TextNote || rumor.kind == Kind::from(14))
|
||||||
|
{
|
||||||
messages.push(Message {
|
messages.push(Message {
|
||||||
id: event.id,
|
id: event.id,
|
||||||
sender: rumor.pubkey,
|
sender: rumor.pubkey,
|
||||||
@@ -63,18 +70,20 @@ pub async fn get_dm_messages(client: &Client, contact_npub: &str) -> Result<Vec<
|
|||||||
// === NIP-04 (Legacy) Verarbeitung ===
|
// === NIP-04 (Legacy) Verarbeitung ===
|
||||||
else if event.kind == Kind::EncryptedDirectMessage {
|
else if event.kind == Kind::EncryptedDirectMessage {
|
||||||
// TagStandard::PublicKey ist ein Struct-Variant: Felder mit Struct-Syntax matchen
|
// TagStandard::PublicKey ist ein Struct-Variant: Felder mit Struct-Syntax matchen
|
||||||
let has_contact_tag = event.tags.iter().any(|t| {
|
let has_contact_tag = event.tags.iter().any(|t| match t.as_standardized() {
|
||||||
match t.as_standardized() {
|
Some(TagStandard::PublicKey { public_key, .. }) => *public_key == contact_pubkey,
|
||||||
Some(TagStandard::PublicKey { public_key, .. }) => *public_key == contact_pubkey,
|
_ => false,
|
||||||
_ => 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 && has_contact_tag;
|
||||||
|
|
||||||
if is_incoming || is_outgoing {
|
if is_incoming || is_outgoing {
|
||||||
let counterparty = if is_incoming { event.pubkey } else { contact_pubkey };
|
let counterparty = if is_incoming {
|
||||||
|
event.pubkey
|
||||||
|
} else {
|
||||||
|
contact_pubkey
|
||||||
|
};
|
||||||
if let Ok(content) = signer.nip04_decrypt(&counterparty, &event.content).await {
|
if let Ok(content) = signer.nip04_decrypt(&counterparty, &event.content).await {
|
||||||
messages.push(Message {
|
messages.push(Message {
|
||||||
id: event.id,
|
id: event.id,
|
||||||
|
|||||||
11
test_output.txt
Normal file
11
test_output.txt
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
Compiling easy-nostr v0.1.0 (/home/malxte/Documents/Programming/Rust/Crates/easy-nostr)
|
||||||
|
error[E0277]: the `?` operator can only be applied to values that implement `Try`
|
||||||
|
--> src/bin/testall.rs:12:16
|
||||||
|
|
|
||||||
|
12 | let nsec = keys.secret_key()?.to_bech32()?;
|
||||||
|
| ^^^^^^^^^^^^^^^^^^ the `?` operator cannot be applied to type `&nostr_sdk::SecretKey`
|
||||||
|
|
|
||||||
|
= help: the trait `Try` is not implemented for `&nostr_sdk::SecretKey`
|
||||||
|
|
||||||
|
For more information about this error, try `rustc --explain E0277`.
|
||||||
|
error: could not compile `easy-nostr` (bin "testall") due to 1 previous error
|
||||||
Reference in New Issue
Block a user