Skip to main content

v2_send_once/
v2_send_once.rs

1//! One-shot v2 sender for live cross-bot verification: join a community from its
2//! invite link under an ISOLATED data dir, post one message into a named channel,
3//! and exit. Pair it with `v2_test_bot` listening on another identity to prove
4//! send + receive round-trip over real relays.
5//!
6//! ```sh
7//! cargo run -p vector-sdk --example v2_send_once -- "<invite-link>" "<channel name>" "<message>"
8//! ```
9
10use vector_sdk::VectorBot;
11
12#[tokio::main]
13async fn main() -> vector_sdk::Result<()> {
14    let mut args = std::env::args().skip(1);
15    let invite = args.next().expect("arg 1: invite link");
16    let channel_name = args.next().expect("arg 2: channel name");
17    let text = args.next().expect("arg 3: message text");
18
19    let data_dir = std::env::temp_dir().join("v2_send_once_data");
20    let bot = VectorBot::builder().data_dir(&data_dir).build().await?;
21    println!("── sender online as {}", bot.npub());
22
23    println!("── joining via link…");
24    let summary = bot.core().join_community(&invite).await?;
25    let name = summary.get("name").and_then(|v| v.as_str()).unwrap_or("?");
26    println!("── joined \"{name}\"");
27
28    // The join-time fold is owner-only; admin-created channels arrive on the first
29    // control follow. Poll the local list until the target channel appears.
30    let mut channel_id: Option<String> = None;
31    for attempt in 0..10 {
32        for c in bot.core().list_communities().await {
33            if c.get("version").and_then(|v| v.as_u64()) != Some(2) {
34                continue;
35            }
36            let Some(chans) = c.get("channels").and_then(|v| v.as_array()) else { continue };
37            for ch in chans {
38                if ch.get("name").and_then(|n| n.as_str()) == Some(channel_name.as_str()) {
39                    channel_id = ch.get("channel_id").and_then(|i| i.as_str()).map(String::from);
40                }
41            }
42        }
43        if channel_id.is_some() {
44            break;
45        }
46        if attempt == 0 {
47            println!("── \"{channel_name}\" not in the join snapshot; syncing for the control fold…");
48        }
49        let _ = bot.core().sync_communities().await;
50        tokio::time::sleep(std::time::Duration::from_secs(2)).await;
51    }
52    let Some(channel_id) = channel_id else {
53        eprintln!("!! channel \"{channel_name}\" never appeared; giving up");
54        std::process::exit(1);
55    };
56    println!("── channel \"{channel_name}\" = {channel_id}");
57
58    match bot.channel(&channel_id).send(&text).await {
59        Ok(id) => println!("── sent ✅  message id {id}"),
60        Err(e) => {
61            eprintln!("!! send failed: {e}");
62            std::process::exit(1);
63        }
64    }
65    // Give the gift-wrap publish + persistence a moment to flush before exit.
66    tokio::time::sleep(std::time::Duration::from_secs(5)).await;
67    Ok(())
68}