Skip to main content

nextcloud_talk/
message.rs

1//! Chat message model for Nextcloud Talk.
2
3use serde::{Deserialize, Serialize};
4
5/// A chat message within a Nextcloud Talk conversation.
6#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
7pub struct ChatMessage {
8    /// Unique identifier of the chat message.
9    #[serde(deserialize_with = "crate::wire::lossy_u64")]
10    pub id: u64,
11    /// Token of the conversation this message belongs to.
12    pub token: String,
13    /// Actor type of the sender (e.g. `"users"`, `"bots"`, `"guests"`).
14    #[serde(rename = "actorType", alias = "actor_type")]
15    pub actor_type: String,
16    /// Actor identifier of the sender (e.g. user ID).
17    #[serde(rename = "actorId", alias = "actor_id")]
18    pub actor_id: String,
19    /// Display name of the sender.
20    #[serde(rename = "actorDisplayName", alias = "actor_display_name")]
21    pub actor_display_name: String,
22    /// Unix timestamp when the message was posted (in seconds).
23    #[serde(deserialize_with = "crate::wire::lossy_i64")]
24    pub timestamp: i64,
25    /// Text content of the message.
26    pub message: String,
27    /// System message identifier if this is a system event, otherwise empty.
28    #[serde(rename = "systemMessage", alias = "system_message", default)]
29    pub system_message: String,
30}
31
32impl ChatMessage {
33    /// Creates a new [`ChatMessage`].
34    #[expect(clippy::too_many_arguments)]
35    pub fn new(
36        id: u64,
37        token: impl Into<String>,
38        actor_type: impl Into<String>,
39        actor_id: impl Into<String>,
40        actor_display_name: impl Into<String>,
41        timestamp: i64,
42        message: impl Into<String>,
43        system_message: impl Into<String>,
44    ) -> Self {
45        Self {
46            id,
47            token: token.into(),
48            actor_type: actor_type.into(),
49            actor_id: actor_id.into(),
50            actor_display_name: actor_display_name.into(),
51            timestamp,
52            message: message.into(),
53            system_message: system_message.into(),
54        }
55    }
56
57    /// Returns `true` if this message represents a system event.
58    pub fn is_system_message(&self) -> bool {
59        !self.system_message.is_empty()
60    }
61}
62
63#[cfg(test)]
64mod tests {
65    use super::*;
66
67    #[test]
68    fn chat_message_deserialization_from_talk_v1_json() {
69        let json = r#"{
70            "id": 1024,
71            "token": "room_abc123",
72            "actorType": "users",
73            "actorId": "alice",
74            "actorDisplayName": "Alice Cooper",
75            "timestamp": 1718000000,
76            "message": "Hello everyone!",
77            "systemMessage": ""
78        }"#;
79
80        let msg: ChatMessage = serde_json::from_str(json).expect("valid chat message json");
81        assert_eq!(msg.id, 1024);
82        assert_eq!(msg.token, "room_abc123");
83        assert_eq!(msg.actor_type, "users");
84        assert_eq!(msg.actor_id, "alice");
85        assert_eq!(msg.actor_display_name, "Alice Cooper");
86        assert_eq!(msg.timestamp, 1718000000);
87        assert_eq!(msg.message, "Hello everyone!");
88        assert_eq!(msg.system_message, "");
89        assert!(!msg.is_system_message());
90    }
91
92    #[test]
93    fn chat_message_system_message() {
94        let json = r#"{
95            "id": "2048",
96            "token": "room_xyz",
97            "actor_type": "users",
98            "actor_id": "system",
99            "actor_display_name": "System",
100            "timestamp": "1718000050",
101            "message": "Alice joined the conversation",
102            "system_message": "user_joined"
103        }"#;
104
105        let msg: ChatMessage = serde_json::from_str(json).expect("valid chat message json");
106        assert_eq!(msg.id, 2048);
107        assert_eq!(msg.token, "room_xyz");
108        assert_eq!(msg.system_message, "user_joined");
109        assert!(msg.is_system_message());
110    }
111}