atagen

macltok

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