96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 1 | use macltok::{ |
31ce2453c82f | atagen | 2026-09-24 13:07 UTC | 2 | EngineConfig, Event, PasswordLogin, RoomMembership, Session, StoreConfig, TimelineId, |
31ce2453c82f | atagen | 2026-09-24 13:07 UTC | 3 | TimelineItem, |
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 session = Session::login_password( |
31ce2453c82f | atagen | 2026-09-24 13:07 UTC | 9 | PasswordLogin::new( |
31ce2453c82f | atagen | 2026-09-24 13:07 UTC | 10 | required("MATRIX_HOMESERVER")?, |
31ce2453c82f | atagen | 2026-09-24 13:07 UTC | 11 | required("MATRIX_USERNAME")?, |
31ce2453c82f | atagen | 2026-09-24 13:07 UTC | 12 | required("MATRIX_PASSWORD")?, |
31ce2453c82f | atagen | 2026-09-24 13:07 UTC | 13 | ) |
31ce2453c82f | atagen | 2026-09-24 13:07 UTC | 14 | .with_device_display_name("macltok headless example"), |
31ce2453c82f | atagen | 2026-09-24 13:07 UTC | 15 | StoreConfig::new( |
31ce2453c82f | atagen | 2026-09-24 13:07 UTC | 16 | required("MATRIX_STORE_PATH")?, |
31ce2453c82f | atagen | 2026-09-24 13:07 UTC | 17 | required("MATRIX_STORE_PASSPHRASE")?, |
31ce2453c82f | atagen | 2026-09-24 13:07 UTC | 18 | ), |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 19 | ) |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 20 | .await?; |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 21 | let (engine, handle, mut events) = session.build(EngineConfig::default()).await?; |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 22 | let running = tokio::spawn(engine.run()); |
31ce2453c82f | atagen | 2026-09-24 13:07 UTC | 23 | let mut opened: Option<TimelineId> = None; |
31ce2453c82f | atagen | 2026-09-24 13:07 UTC | 24 | let mut items = Vec::new(); |
31ce2453c82f | atagen | 2026-09-24 13:07 UTC | 25 | let mut stale = false; |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 26 | |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 27 | loop { |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 28 | tokio::select! { |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 29 | event = events.recv() => { |
31ce2453c82f | atagen | 2026-09-24 13:07 UTC | 30 | let Some(event) = event else { break }; |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 31 | match event { |
31ce2453c82f | atagen | 2026-09-24 13:07 UTC | 32 | Event::RoomsChanged if opened.is_none() => { |
31ce2453c82f | atagen | 2026-09-24 13:07 UTC | 33 | let Some(room) = handle |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 34 | .rooms() |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 35 | .await |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 36 | .into_iter() |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 37 | .find(|room| room.membership == RoomMembership::Joined) |
31ce2453c82f | atagen | 2026-09-24 13:07 UTC | 38 | else { |
31ce2453c82f | atagen | 2026-09-24 13:07 UTC | 39 | continue; |
31ce2453c82f | atagen | 2026-09-24 13:07 UTC | 40 | }; |
31ce2453c82f | atagen | 2026-09-24 13:07 UTC | 41 | opened = Some(room.room_id.clone().into()); |
31ce2453c82f | atagen | 2026-09-24 13:07 UTC | 42 | // answers arrive on their own, the event loop keeps draining meanwhile |
31ce2453c82f | atagen | 2026-09-24 13:07 UTC | 43 | let handle = handle.clone(); |
31ce2453c82f | atagen | 2026-09-24 13:07 UTC | 44 | tokio::spawn(async move { |
31ce2453c82f | atagen | 2026-09-24 13:07 UTC | 45 | if let Err(error) = handle.open_room(room.room_id).await { |
31ce2453c82f | atagen | 2026-09-24 13:07 UTC | 46 | eprintln!("could not open {error}"); |
31ce2453c82f | atagen | 2026-09-24 13:07 UTC | 47 | } |
31ce2453c82f | atagen | 2026-09-24 13:07 UTC | 48 | }); |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 49 | } |
31ce2453c82f | atagen | 2026-09-24 13:07 UTC | 50 | Event::TimelineReset { timeline_id, items: snapshot } |
31ce2453c82f | atagen | 2026-09-24 13:07 UTC | 51 | if opened.as_ref() == Some(&timeline_id) => |
31ce2453c82f | atagen | 2026-09-24 13:07 UTC | 52 | { |
31ce2453c82f | atagen | 2026-09-24 13:07 UTC | 53 | items = snapshot; |
31ce2453c82f | atagen | 2026-09-24 13:07 UTC | 54 | stale = false; |
31ce2453c82f | atagen | 2026-09-24 13:07 UTC | 55 | print_messages(&items); |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 56 | } |
31ce2453c82f | atagen | 2026-09-24 13:07 UTC | 57 | Event::TimelineChanged { timeline_id, diffs } |
31ce2453c82f | atagen | 2026-09-24 13:07 UTC | 58 | if !stale && opened.as_ref() == Some(&timeline_id) => |
31ce2453c82f | atagen | 2026-09-24 13:07 UTC | 59 | { |
31ce2453c82f | atagen | 2026-09-24 13:07 UTC | 60 | if diffs.into_iter().any(|diff| diff.try_apply(&mut items).is_err()) { |
31ce2453c82f | atagen | 2026-09-24 13:07 UTC | 61 | stale = true; |
31ce2453c82f | atagen | 2026-09-24 13:07 UTC | 62 | let handle = handle.clone(); |
31ce2453c82f | atagen | 2026-09-24 13:07 UTC | 63 | tokio::spawn(async move { handle.resync(timeline_id).await }); |
31ce2453c82f | atagen | 2026-09-24 13:07 UTC | 64 | continue; |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 65 | } |
31ce2453c82f | atagen | 2026-09-24 13:07 UTC | 66 | print_messages(&items); |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 67 | } |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 68 | Event::Stopped => break, |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 69 | _ => {} |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 70 | } |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 71 | } |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 72 | result = tokio::signal::ctrl_c() => { |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 73 | result?; |
31ce2453c82f | atagen | 2026-09-24 13:07 UTC | 74 | let handle = handle.clone(); |
31ce2453c82f | atagen | 2026-09-24 13:07 UTC | 75 | tokio::spawn(async move { handle.shutdown().await }); |
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 | running.await??; |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 80 | Ok(()) |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 81 | } |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 82 | |
31ce2453c82f | atagen | 2026-09-24 13:07 UTC | 83 | fn print_messages(items: &[TimelineItem]) { |
31ce2453c82f | atagen | 2026-09-24 13:07 UTC | 84 | for item in items { |
31ce2453c82f | atagen | 2026-09-24 13:07 UTC | 85 | if let TimelineItem::Message(message) = item { |
31ce2453c82f | atagen | 2026-09-24 13:07 UTC | 86 | println!("{} {}", message.sender_name, message.body); |
31ce2453c82f | atagen | 2026-09-24 13:07 UTC | 87 | } |
31ce2453c82f | atagen | 2026-09-24 13:07 UTC | 88 | } |
31ce2453c82f | atagen | 2026-09-24 13:07 UTC | 89 | } |
31ce2453c82f | atagen | 2026-09-24 13:07 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 | } |