96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 1 | use macltok::{ |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 2 | Command, CommandKind, EngineConfig, Event, LifecycleCommand, PasswordLogin, RoomCommand, |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 3 | RoomMembership, Session, StoreConfig, TimelineCommand, |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 4 | }; |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 5 | |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 6 | #[tokio::main] |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 7 | async fn main() -> Result<(), Box<dyn std::error::Error>> { |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 8 | let homeserver = required("MATRIX_HOMESERVER")?; |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 9 | let username = required("MATRIX_USERNAME")?; |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 10 | let password = required("MATRIX_PASSWORD")?; |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 11 | let store_path = required("MATRIX_STORE_PATH")?; |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 12 | let store_passphrase = required("MATRIX_STORE_PASSPHRASE")?; |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 13 | let session = Session::login_password( |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 14 | PasswordLogin { |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 15 | homeserver, |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 16 | username, |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 17 | password, |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 18 | device_display_name: Some("macltok headless example".into()), |
fa9114fcf75f | atagen | 2026-09-24 12:51 UTC | 19 | device_id: None, |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 20 | }, |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 21 | StoreConfig::new(store_path, store_passphrase), |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 22 | ) |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 23 | .await?; |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 24 | let (engine, handle, mut events) = session.build(EngineConfig::default()).await?; |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 25 | let running = tokio::spawn(engine.run()); |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 26 | let mut room_open_id = None; |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 27 | let mut resync_id = None; |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 28 | let mut timeline_items = Vec::new(); |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 29 | |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 30 | loop { |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 31 | tokio::select! { |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 32 | event = events.recv() => { |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 33 | let Some(event) = event else { break; }; |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 34 | match event { |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 35 | Event::RoomsChanged if room_open_id.is_none() => { |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 36 | if let Some(room) = handle |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 37 | .rooms() |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 38 | .await |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 39 | .into_iter() |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 40 | .find(|room| room.membership == RoomMembership::Joined) |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 41 | { |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 42 | room_open_id = Some(handle |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 43 | .send(Command::Room(RoomCommand::Open(room.room_id))) |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 44 | .await?); |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 45 | } |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 46 | } |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 47 | Event::TimelineReset { items, .. } => { |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 48 | timeline_items = items; |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 49 | resync_id = None; |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 50 | } |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 51 | Event::TimelineChanged { timeline_id, diffs } if resync_id.is_none() => { |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 52 | for diff in diffs { |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 53 | if diff.try_apply(&mut timeline_items).is_err() { |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 54 | resync_id = Some(handle.send(Command::Timeline( |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 55 | TimelineCommand::Resync { timeline_id }, |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 56 | )).await?); |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 57 | break; |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 58 | } |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 59 | } |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 60 | if resync_id.is_none() { |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 61 | for item in &timeline_items { |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 62 | if let macltok::TimelineItem::Message(message) = item { |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 63 | println!("{} {}", message.sender_name, message.body); |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 64 | } |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 65 | } |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 66 | } |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 67 | } |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 68 | Event::CommandSucceeded { id, command: CommandKind::Room } |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 69 | if room_open_id == Some(id) => println!("room ready"), |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 70 | Event::CommandFailed(error) if room_open_id == Some(error.id) |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 71 | || resync_id == Some(error.id) => return Err(error.message.into()), |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 72 | Event::CommandFailed(error) => { |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 73 | eprintln!("{:?} {}", error.command, error.message); |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 74 | } |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 75 | Event::Stopped => break, |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 76 | _ => {} |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 77 | } |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 78 | } |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 79 | result = tokio::signal::ctrl_c() => { |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 80 | result?; |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 81 | handle |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 82 | .send(Command::Lifecycle(LifecycleCommand::Shutdown)) |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 83 | .await?; |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 84 | } |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 85 | } |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 86 | } |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 87 | running.await??; |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 88 | Ok(()) |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 89 | } |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 90 | |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 91 | fn required(name: &str) -> Result<String, Box<dyn std::error::Error>> { |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 92 | std::env::var(name).map_err(|_| format!("{name} is required").into()) |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 93 | } |