atagen

macltok

public
Star0
Watch0
31ce2453c82f470aa8b5d3861ab0eec4273afc5d
3
ssh://brewery@pls.lobotomise.me:33/atagen/macltok.git
examples/headless.rsRust93 lines3.5 KB
1
use macltok::{
2
    EngineConfig, Event, PasswordLogin, RoomMembership, Session, StoreConfig, TimelineId,
3
    TimelineItem,
4
};
5
6
#[tokio::main]
7
async fn main() -> Result<(), Box<dyn std::error::Error>> {
8
    let session = Session::login_password(
9
        PasswordLogin::new(
10
            required("MATRIX_HOMESERVER")?,
11
            required("MATRIX_USERNAME")?,
12
            required("MATRIX_PASSWORD")?,
13
        )
14
        .with_device_display_name("macltok headless example"),
15
        StoreConfig::new(
16
            required("MATRIX_STORE_PATH")?,
17
            required("MATRIX_STORE_PASSPHRASE")?,
18
        ),
19
    )
20
    .await?;
21
    let (engine, handle, mut events) = session.build(EngineConfig::default()).await?;
22
    let running = tokio::spawn(engine.run());
23
    let mut opened: Option<TimelineId> = None;
24
    let mut items = Vec::new();
25
    let mut stale = false;
26
27
    loop {
28
        tokio::select! {
29
            event = events.recv() => {
30
                let Some(event) = event else { break };
31
                match event {
32
                    Event::RoomsChanged if opened.is_none() => {
33
                        let Some(room) = handle
34
                            .rooms()
35
                            .await
36
                            .into_iter()
37
                            .find(|room| room.membership == RoomMembership::Joined)
38
                        else {
39
                            continue;
40
                        };
41
                        opened = Some(room.room_id.clone().into());
42
                        // answers arrive on their own, the event loop keeps draining meanwhile
43
                        let handle = handle.clone();
44
                        tokio::spawn(async move {
45
                            if let Err(error) = handle.open_room(room.room_id).await {
46
                                eprintln!("could not open {error}");
47
                            }
48
                        });
49
                    }
50
                    Event::TimelineReset { timeline_id, items: snapshot }
51
                        if opened.as_ref() == Some(&timeline_id) =>
52
                    {
53
                        items = snapshot;
54
                        stale = false;
55
                        print_messages(&items);
56
                    }
57
                    Event::TimelineChanged { timeline_id, diffs }
58
                        if !stale && opened.as_ref() == Some(&timeline_id) =>
59
                    {
60
                        if diffs.into_iter().any(|diff| diff.try_apply(&mut items).is_err()) {
61
                            stale = true;
62
                            let handle = handle.clone();
63
                            tokio::spawn(async move { handle.resync(timeline_id).await });
64
                            continue;
65
                        }
66
                        print_messages(&items);
67
                    }
68
                    Event::Stopped => break,
69
                    _ => {}
70
                }
71
            }
72
            result = tokio::signal::ctrl_c() => {
73
                result?;
74
                let handle = handle.clone();
75
                tokio::spawn(async move { handle.shutdown().await });
76
            }
77
        }
78
    }
79
    running.await??;
80
    Ok(())
81
}
82
83
fn print_messages(items: &[TimelineItem]) {
84
    for item in items {
85
        if let TimelineItem::Message(message) = item {
86
            println!("{}  {}", message.sender_name, message.body);
87
        }
88
    }
89
}
90
91
fn required(name: &str) -> Result<String, Box<dyn std::error::Error>> {
92
    std::env::var(name).map_err(|_| format!("{name} is required").into())
93
}