1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3
4use super::{PowerLevels, Presence};
5
6#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
7#[serde(rename_all = "snake_case")]
8pub enum EventKind {
9 RoomMessage,
10 RoomMember,
11 RoomAliases,
12 RoomCanonicalAlias,
13 RoomCreate,
14 RoomJoinRules,
15 RoomPowerLevels,
16 RoomRedaction,
17 RoomPinnedEvents,
18 RoomAvatar,
19 RoomName,
20 RoomTopic,
21 SpaceChild,
22 RoomEncryption,
23 RoomEncrypted,
24 Presence,
25 Custom(String),
26}
27
28impl Default for EventKind {
29 fn default() -> Self {
30 Self::Custom("m.custom".to_owned())
31 }
32}
33
34#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
35pub struct Event {
36 #[serde(default)]
37 pub event_id: String,
38 #[serde(rename = "type")]
39 pub kind: EventKind,
40 #[serde(default)]
41 pub sender: String,
42 #[serde(default)]
43 pub origin_server_ts: i64,
44 #[serde(default)]
45 pub content: Value,
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
49pub struct RoomEvent {
50 pub room_id: String,
51 #[serde(flatten)]
52 pub event: Event,
53}
54
55#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
56pub struct StateEvent {
57 #[serde(flatten)]
58 pub room_event: RoomEvent,
59 pub state_key: String,
60}
61
62#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
63#[serde(rename_all = "snake_case")]
64pub enum Membership {
65 Invite,
66 Join,
67 Knock,
68 #[default]
69 Leave,
70 Ban,
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
74pub struct MembershipEvent {
75 pub user_id: String,
76 pub membership: Membership,
77}
78
79impl MembershipEvent {
80 pub fn effective_membership(&self) -> Membership {
81 match self.membership {
82 Membership::Ban => Membership::Leave,
83 other => other,
84 }
85 }
86}
87
88#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
89pub struct MessageEvent {
90 pub msgtype: String,
91 pub body: String,
92 #[serde(default)]
93 pub formatted_body: Option<String>,
94 #[serde(default)]
95 pub format: Option<String>,
96 #[serde(default)]
97 pub url: Option<String>,
98 #[serde(default)]
99 pub info: Option<serde_json::Value>,
100 #[serde(default)]
101 pub file: Option<EncryptedFile>,
102}
103
104#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
106pub struct FileInfo {
107 #[serde(default)]
108 pub mimetype: Option<String>,
109 #[serde(default)]
110 pub size: Option<u64>,
111}
112
113#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
115pub struct ThumbnailInfo {
116 #[serde(flatten)]
117 #[serde(default)]
118 pub info: FileInfo,
119 #[serde(default)]
120 pub width: Option<u64>,
121 #[serde(default)]
122 pub height: Option<u64>,
123 #[serde(default)]
124 pub orientation: Option<u64>,
125}
126
127#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
129pub struct ImageInfo {
130 #[serde(flatten)]
131 #[serde(default)]
132 pub info: FileInfo,
133 #[serde(default)]
134 pub width: Option<u64>,
135 #[serde(default)]
136 pub height: Option<u64>,
137 #[serde(default)]
138 pub orientation: Option<u64>,
139}
140
141#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
143pub struct VideoInfo {
144 #[serde(flatten)]
145 #[serde(default)]
146 pub info: ImageInfo,
147 #[serde(default)]
148 pub duration: Option<u64>,
149}
150
151#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
153pub struct AudioInfo {
154 #[serde(flatten)]
155 #[serde(default)]
156 pub info: FileInfo,
157 #[serde(default)]
158 pub duration: Option<u64>,
159}
160
161#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
163pub struct LocationInfo {
164 #[serde(default)]
165 pub uri: Option<String>,
166 #[serde(default)]
167 pub description: Option<String>,
168}
169
170#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
172pub struct EncryptedFile {
173 pub url: String,
174 pub key: JsonWebKey,
175 pub iv: String,
176 #[serde(default)]
177 pub hashes: std::collections::HashMap<String, String>,
178 pub version: String,
179}
180
181#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
183pub struct JsonWebKey {
184 pub kty: String,
185 #[serde(default)]
186 pub alg: Option<String>,
187 pub k: String,
188 #[serde(default)]
189 pub ext: Option<bool>,
190}
191
192#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
193pub struct AliasesEvent {
194 #[serde(default)]
195 pub aliases: Vec<String>,
196}
197
198#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
199pub struct CanonicalAliasEvent {
200 #[serde(default)]
201 pub alias: Option<String>,
202 #[serde(default)]
203 pub alt_aliases: Vec<String>,
204}
205
206#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
207pub struct CreateEvent {
208 pub creator: String,
209 #[serde(default)]
210 pub room_version: Option<String>,
211}
212
213#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
214#[serde(rename_all = "snake_case")]
215pub enum JoinRule {
216 Public,
217 #[default]
218 Invite,
219 Knock,
220 Private,
221}
222
223#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
224pub struct JoinRulesEvent {
225 pub join_rule: JoinRule,
226}
227
228#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
229pub struct PowerLevelsEvent {
230 #[serde(flatten)]
231 pub power_levels: PowerLevels,
232}
233
234#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
235pub struct RedactionEvent {
236 pub redacts: String,
237 #[serde(default)]
238 pub reason: Option<String>,
239}
240
241#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
242pub struct PinnedEventsEvent {
243 #[serde(default)]
244 pub pinned: Vec<String>,
245}
246
247#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
248pub struct RoomAvatarEvent {
249 #[serde(default)]
250 pub url: Option<String>,
251}
252
253#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
254pub struct RoomNameEvent {
255 #[serde(default)]
256 pub name: Option<String>,
257}
258
259#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
260pub struct RoomTopicEvent {
261 #[serde(default)]
262 pub topic: Option<String>,
263}
264
265#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
266pub struct SpaceChildEvent {
267 pub child_room_id: String,
268 #[serde(default)]
269 pub via: Vec<String>,
270}
271
272#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
273pub struct EncryptionEvent {
274 pub algorithm: String,
275 #[serde(default)]
276 pub rotation_period_ms: Option<u64>,
277}
278
279#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
280pub struct EncryptedRoomEvent {
281 pub algorithm: String,
282 pub sender_key: String,
283 pub ciphertext: String,
284}
285
286#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
287pub struct PresenceEvent {
288 pub user_id: String,
289 pub presence: Presence,
290 #[serde(default)]
291 pub status_msg: Option<String>,
292}
293
294#[derive(Debug, thiserror::Error)]
295pub enum InvalidEventError {
296 #[error("missing field: {0}")]
297 MissingField(&'static str),
298 #[error("invalid event kind")]
299 InvalidType,
300 #[error("invalid json: {0}")]
301 InvalidJson(#[from] serde_json::Error),
302}
303
304#[derive(Debug, Clone, thiserror::Error, Serialize, Deserialize, PartialEq, Eq)]
306#[error("Event {event_id} was redacted")]
307pub struct EventRedactedError {
308 pub event_id: String,
310 #[serde(skip_serializing_if = "Option::is_none")]
312 pub reason: Option<String>,
313}
314
315impl EventRedactedError {
316 pub fn new(event_id: impl Into<String>) -> Self {
317 Self {
318 event_id: event_id.into(),
319 reason: None,
320 }
321 }
322
323 pub fn with_reason(event_id: impl Into<String>, reason: impl Into<String>) -> Self {
324 Self {
325 event_id: event_id.into(),
326 reason: Some(reason.into()),
327 }
328 }
329}
330
331pub mod converter {
332 use serde_json::Value;
333
334 use super::{Event, EventKind, InvalidEventError, RoomEvent};
335
336 pub fn parse_event(json: &Value) -> Result<Event, InvalidEventError> {
337 let kind_raw = json
338 .get("type")
339 .and_then(Value::as_str)
340 .ok_or(InvalidEventError::MissingField("type"))?;
341 let kind = match kind_raw {
342 "m.room.message" => EventKind::RoomMessage,
343 "m.room.member" => EventKind::RoomMember,
344 "m.room.aliases" => EventKind::RoomAliases,
345 "m.room.canonical_alias" => EventKind::RoomCanonicalAlias,
346 "m.room.create" => EventKind::RoomCreate,
347 "m.room.join_rules" => EventKind::RoomJoinRules,
348 "m.room.power_levels" => EventKind::RoomPowerLevels,
349 "m.room.redaction" => EventKind::RoomRedaction,
350 "m.room.pinned_events" => EventKind::RoomPinnedEvents,
351 "m.room.avatar" => EventKind::RoomAvatar,
352 "m.room.name" => EventKind::RoomName,
353 "m.room.topic" => EventKind::RoomTopic,
354 "m.space.child" => EventKind::SpaceChild,
355 "m.room.encryption" => EventKind::RoomEncryption,
356 "m.room.encrypted" => EventKind::RoomEncrypted,
357 "m.presence" => EventKind::Presence,
358 other => EventKind::Custom(other.to_owned()),
359 };
360
361 Ok(Event {
362 event_id: json
363 .get("event_id")
364 .and_then(Value::as_str)
365 .unwrap_or_default()
366 .to_owned(),
367 kind,
368 sender: json
369 .get("sender")
370 .and_then(Value::as_str)
371 .unwrap_or_default()
372 .to_owned(),
373 origin_server_ts: json
374 .get("origin_server_ts")
375 .and_then(Value::as_i64)
376 .unwrap_or_default(),
377 content: json.get("content").cloned().unwrap_or(Value::Null),
378 })
379 }
380
381 pub fn parse_room_event(
382 json: &Value,
383 room_id: impl Into<String>,
384 ) -> Result<RoomEvent, InvalidEventError> {
385 Ok(RoomEvent {
386 room_id: room_id.into(),
387 event: parse_event(json)?,
388 })
389 }
390}