atagen

macltok

public
Star0
Watch0
49d2c9515895c13c4d06bb0dab072745da0175d6
3
ssh://brewery@pls.lobotomise.me:33/atagen/macltok.git
examples/headless.rsRust119 lines4.5 KB
1
use std::sync::Arc;
2
3
use macltok::{
4
    EncryptedSessionStore, EngineConfig, Event, PasswordLogin, Restore, Retain, RoomMembership,
5
    Session, SessionStore, StoreConfig, TimelineContent, TimelineDiff, TimelineId, TimelineItem,
6
    imbl::Vector,
7
};
8
9
#[tokio::main]
10
async fn main() -> Result<(), Box<dyn std::error::Error>> {
11
    let store_path = std::path::PathBuf::from(required("MATRIX_STORE_PATH")?);
12
    let passphrase = required("MATRIX_STORE_PASSPHRASE")?;
13
    let sessions = Arc::new(EncryptedSessionStore::file(
14
        store_path.with_extension("session"),
15
        StoreConfig::sqlite(&store_path, &passphrase),
16
    ));
17
    let session = match Session::restore(sessions.clone()).await? {
18
        Some(Restore::Resumed(session)) => session,
19
        Some(Restore::SoftLoggedOut(soft)) => {
20
            Session::login_password(
21
                soft.password_login(required("MATRIX_PASSWORD")?),
22
                sessions.clone(),
23
            )
24
            .await?
25
        }
26
        None | Some(Restore::OAuthPending) => {
27
            Session::login_password(
28
                PasswordLogin::new(
29
                    required("MATRIX_HOMESERVER")?,
30
                    required("MATRIX_USERNAME")?,
31
                    required("MATRIX_PASSWORD")?,
32
                )
33
                .with_device_display_name("macltok headless example"),
34
                sessions.clone(),
35
            )
36
            .await?
37
        }
38
    };
39
    let (engine, handle, mut events) = session.build(EngineConfig::default()).await?;
40
    let running = tokio::spawn(engine.run());
41
    let mut opened: Option<TimelineId> = None;
42
    let mut items = Vector::new();
43
    let mut stale = false;
44
45
    loop {
46
        tokio::select! {
47
            event = events.recv() => {
48
                let Some(event) = event else { break };
49
                match event {
50
                    Event::RoomsChanged if opened.is_none() => {
51
                        let Some(room) = handle
52
                            .rooms()
53
                            .await
54
                            .into_iter()
55
                            .find(|room| room.membership == RoomMembership::Joined)
56
                        else {
57
                            continue;
58
                        };
59
                        opened = Some(room.room_id.clone().into());
60
                        // answers arrive on their own, the event loop keeps draining meanwhile
61
                        let handle = handle.clone();
62
                        tokio::spawn(async move {
63
                            if let Err(error) = handle.open_room(room.room_id).await {
64
                                eprintln!("could not open {error}");
65
                            }
66
                        });
67
                    }
68
                    Event::TimelineReset { timeline_id, items: snapshot }
69
                        if opened.as_ref() == Some(&timeline_id) =>
70
                    {
71
                        items = snapshot;
72
                        stale = false;
73
                        print_messages(&items);
74
                    }
75
                    Event::TimelineChanged { timeline_id, diffs }
76
                        if !stale && opened.as_ref() == Some(&timeline_id) =>
77
                    {
78
                        if TimelineDiff::try_apply_all(diffs, &mut items).is_err() {
79
                            stale = true;
80
                            let handle = handle.clone();
81
                            tokio::spawn(async move { handle.resync(timeline_id).await });
82
                            continue;
83
                        }
84
                        print_messages(&items);
85
                    }
86
                    Event::Stopped => break,
87
                    _ => {}
88
                }
89
            }
90
            result = tokio::signal::ctrl_c() => {
91
                result?;
92
                let handle = handle.clone();
93
                tokio::spawn(async move { handle.shutdown().await });
94
            }
95
        }
96
    }
97
    let termination = running.await??;
98
    drop((handle, events));
99
    match termination.retain() {
100
        Retain::RecordAndStore => {}
101
        Retain::Store => sessions.soft_log_out().await?,
102
        Retain::Nothing => sessions.discard().await?,
103
    }
104
    Ok(())
105
}
106
107
fn print_messages(items: &Vector<TimelineItem>) {
108
    for item in items {
109
        if let TimelineItem::Event(event) = item
110
            && let TimelineContent::Message(message) = &event.content
111
        {
112
            println!("{}  {}", event.sender.name(), message.body);
113
        }
114
    }
115
}
116
117
fn required(name: &str) -> Result<String, Box<dyn std::error::Error>> {
118
    std::env::var(name).map_err(|_| format!("{name} is required").into())
119
}