example_custom_events/
main.rs1#![recursion_limit = "256"]
2
3use std::{
14 env,
15 process::exit,
16 sync::{
17 Arc,
18 atomic::{AtomicU64, Ordering},
19 },
20};
21
22use matrix_sdk::{
23 Client, Room, RoomState,
24 config::SyncSettings,
25 event_handler::Ctx,
26 ruma::{
27 OwnedEventId,
28 events::{
29 macros::EventContent,
30 room::{
31 member::StrippedRoomMemberEvent,
32 message::{MessageType, OriginalSyncRoomMessageEvent},
33 },
34 },
35 },
36};
37use serde::{Deserialize, Serialize};
38use tokio::time::{Duration, sleep};
39
40#[derive(Clone, Debug, Deserialize, Serialize, EventContent)]
44#[ruma_event(type = "rs.matrix-sdk.example.ping", kind = MessageLike)]
45pub struct PingEventContent {}
46
47#[derive(Clone, Debug, Deserialize, Serialize, EventContent)]
48#[ruma_event(type = "rs.matrix-sdk.example.ack", kind = MessageLike)]
49pub struct AckEventContent {
50 ping_id: OwnedEventId,
52}
53
54#[derive(Debug, Default, Clone)]
57pub struct CustomContext {
58 ping_counter: Arc<AtomicU64>,
59}
60
61async fn on_regular_room_message(event: OriginalSyncRoomMessageEvent, room: Room) {
69 if room.state() != RoomState::Joined {
70 return;
71 }
72 let MessageType::Text(text_content) = event.content.msgtype else { return };
73
74 if text_content.body.contains("!ping") {
75 let content = PingEventContent {};
76
77 println!("sending ping");
78 room.send(content).await.unwrap();
79 println!("ping sent");
80 }
81}
82
83async fn on_ping_event(event: SyncPingEvent, room: Room, context: Ctx<CustomContext>) {
85 if room.state() != RoomState::Joined {
86 return;
87 }
88
89 let event_id = event.event_id().to_owned();
90 let ping_number = context.ping_counter.fetch_add(1, Ordering::SeqCst);
91
92 let content = AckEventContent { ping_id: event_id };
94 println!("sending ack for ping no {ping_number}");
95 room.send(content).await.unwrap();
96
97 println!("ack sent");
98}
99
100async fn sync_loop(client: Client) -> anyhow::Result<()> {
103 client.add_event_handler(on_stripped_state_member);
105 let response = client.sync_once(SyncSettings::default()).await.unwrap();
106
107 client.add_event_handler(on_regular_room_message);
110 client.add_event_handler(on_ping_event);
112 client.add_event_handler_context(CustomContext::default());
114
115 let settings = SyncSettings::default().token(response.next_batch);
116 client.sync(settings).await?; Ok(())
119}
120
121async fn login_and_sync(
124 homeserver_url: String,
125 username: &str,
126 password: &str,
127) -> anyhow::Result<()> {
128 let client = Client::builder().homeserver_url(homeserver_url).build().await?;
129 client
130 .matrix_auth()
131 .login_username(username, password)
132 .initial_device_display_name("getting started bot")
133 .await?;
134
135 println!("logged in as {username}");
137 sync_loop(client).await
138}
139
140async fn on_stripped_state_member(
143 room_member: StrippedRoomMemberEvent,
144 client: Client,
145 room: Room,
146) {
147 if room_member.state_key != client.user_id().unwrap() {
148 return;
150 }
151
152 tokio::spawn(async move {
153 println!("Autojoining room {}", room.room_id());
154 let mut delay = 2;
155
156 while let Err(err) = room.join().await {
157 eprintln!("Failed to join room {} ({err:?}), retrying in {delay}s", room.room_id());
161
162 sleep(Duration::from_secs(delay)).await;
163 delay *= 2;
164
165 if delay > 3600 {
166 eprintln!("Can't join room {} ({err:?})", room.room_id());
167 break;
168 }
169 }
170
171 println!("Successfully joined room {}", room.room_id());
172 });
173}
174
175#[tokio::main]
176async fn main() -> anyhow::Result<()> {
177 tracing_subscriber::fmt::init();
180
181 let (Some(homeserver_url), Some(username), Some(password)) =
183 (env::args().nth(1), env::args().nth(2), env::args().nth(3))
184 else {
185 eprintln!("Usage: {} <homeserver_url> <username> <password>", env::args().next().unwrap());
186 exit(1)
188 };
189
190 login_and_sync(homeserver_url, &username, &password).await?;
192 Ok(())
193}