1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
//! Channel events representing things that happen on a messaging platform.
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use super::identity::{ChannelUser, ConversationId};
use super::message::{ChannelMessage, MessageId, ThreadId};
#[cfg(feature = "channels-webrtc")]
use super::webrtc::{
session::{IceConnectionState, PeerConnectionState, SignalingState, WebRtcSessionId},
track::{DataChannelMessage, TrackDirection, TrackId},
};
/// An event from a messaging channel.
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub enum ChannelEvent {
/// A new message was received.
MessageReceived(ChannelMessage),
/// An existing message was edited.
MessageEdited(ChannelMessage),
/// A message was deleted.
MessageDeleted {
/// The ID of the deleted message.
message_id: MessageId,
/// The conversation the message was in.
conversation: ConversationId,
},
/// A reaction was added to a message.
ReactionAdded {
/// The message that was reacted to.
message_id: MessageId,
/// The user who added the reaction.
user: ChannelUser,
/// The emoji used for the reaction.
emoji: String,
},
/// A reaction was removed from a message.
ReactionRemoved {
/// The message the reaction was removed from.
message_id: MessageId,
/// The user who removed the reaction.
user: ChannelUser,
/// The emoji that was removed.
emoji: String,
},
/// A user started typing in a conversation.
TypingStarted {
/// The conversation where typing is occurring.
conversation: ConversationId,
/// The user who started typing.
user: ChannelUser,
},
/// A user's presence status changed.
PresenceChanged {
/// The user whose presence changed.
user: ChannelUser,
/// The new presence status.
status: PresenceStatus,
},
/// A new thread was created from a message.
ThreadCreated {
/// The parent message that spawned the thread.
parent_message_id: MessageId,
/// The ID of the newly created thread.
thread_id: ThreadId,
},
// ── WebRTC signaling & media events (requires the `webrtc` feature) ──────
/// A local ICE candidate was gathered or a remote candidate was received.
#[cfg(feature = "channels-webrtc")]
IceCandidate {
/// The WebRTC session this candidate belongs to.
session_id: WebRtcSessionId,
/// Serialized ICE candidate (RFC 5245 candidate-attribute string).
candidate: String,
/// SDP media line identifier (e.g. "audio", "video", "data").
sdp_mid: Option<String>,
/// SDP media line index.
sdp_mline_index: Option<u16>,
/// The conversation context.
conversation: ConversationId,
},
/// A remote peer sent an SDP offer for a new WebRTC session.
#[cfg(feature = "channels-webrtc")]
SdpOffer {
/// The WebRTC session this offer initiates.
session_id: WebRtcSessionId,
/// Full SDP offer body.
sdp: String,
/// The conversation context.
conversation: ConversationId,
},
/// A remote peer replied with an SDP answer.
#[cfg(feature = "channels-webrtc")]
SdpAnswer {
/// The WebRTC session this answer completes.
session_id: WebRtcSessionId,
/// Full SDP answer body.
sdp: String,
/// The conversation context.
conversation: ConversationId,
},
/// A remote media track was added to the PeerConnection.
#[cfg(feature = "channels-webrtc")]
TrackAdded {
/// The WebRTC session this track belongs to.
session_id: WebRtcSessionId,
/// Unique identifier for the track.
track_id: TrackId,
/// "audio" or "video".
kind: String,
/// Negotiated codec MIME type (e.g. "audio/opus", "video/VP8").
codec: Option<String>,
/// The send/receive direction of the track.
direction: TrackDirection,
/// The conversation context.
conversation: ConversationId,
},
/// A remote media track was removed or ended.
#[cfg(feature = "channels-webrtc")]
TrackRemoved {
/// The WebRTC session this track belongs to.
session_id: WebRtcSessionId,
/// Unique identifier for the removed track.
track_id: TrackId,
/// The conversation context.
conversation: ConversationId,
},
/// A message arrived on a WebRTC DataChannel.
#[cfg(feature = "channels-webrtc")]
WebRtcDataChannel {
/// The WebRTC session this channel belongs to.
session_id: WebRtcSessionId,
/// The DataChannel label.
channel_label: String,
/// The received message payload.
message: DataChannelMessage,
/// The conversation context.
conversation: ConversationId,
},
/// The PeerConnection state changed (e.g. Connected, Disconnected, Failed).
#[cfg(feature = "channels-webrtc")]
PeerConnectionStateChanged {
/// The WebRTC session whose state changed.
session_id: WebRtcSessionId,
/// The new PeerConnection state.
state: PeerConnectionState,
/// The conversation context.
conversation: ConversationId,
},
/// The ICE connection state changed (e.g. Checking, Connected, Failed).
#[cfg(feature = "channels-webrtc")]
IceConnectionStateChanged {
/// The WebRTC session whose ICE state changed.
session_id: WebRtcSessionId,
/// The new ICE connection state.
state: IceConnectionState,
/// The conversation context.
conversation: ConversationId,
},
/// ICE candidate gathering has completed; no further candidates will be gathered.
#[cfg(feature = "channels-webrtc")]
IceGatheringComplete {
/// The WebRTC session that finished ICE gathering.
session_id: WebRtcSessionId,
/// The conversation context.
conversation: ConversationId,
},
/// The SDP signaling state changed.
#[cfg(feature = "channels-webrtc")]
SignalingStateChanged {
/// The WebRTC session whose signaling state changed.
session_id: WebRtcSessionId,
/// The new signaling state.
state: SignalingState,
/// The conversation context.
conversation: ConversationId,
},
}
/// A user's presence status on the platform.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub enum PresenceStatus {
/// User is online and active.
Online,
/// User is away or idle.
Away,
/// User has enabled do-not-disturb mode.
DoNotDisturb,
/// User is offline.
Offline,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::channels::identity::ConversationId;
use crate::channels::message::MessageId;
#[test]
fn channel_event_serde_roundtrip() {
let event = ChannelEvent::MessageDeleted {
message_id: MessageId::new("msg-123"),
conversation: ConversationId {
platform: "discord".to_string(),
channel_id: "general".to_string(),
server_id: None,
},
};
let json = serde_json::to_string(&event).unwrap();
let deserialized: ChannelEvent = serde_json::from_str(&json).unwrap();
match deserialized {
ChannelEvent::MessageDeleted {
message_id,
conversation,
} => {
assert_eq!(message_id, MessageId::new("msg-123"));
assert_eq!(conversation.channel_id, "general");
}
_ => panic!("expected MessageDeleted variant"),
}
}
#[test]
fn presence_status_serde_roundtrip() {
let status = PresenceStatus::DoNotDisturb;
let json = serde_json::to_string(&status).unwrap();
let deserialized: PresenceStatus = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized, status);
}
}