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