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