1use std::{sync::Arc, time::{SystemTime, UNIX_EPOCH}};
2use std::time::Duration;
3use futures_util::{stream::{SplitSink, SplitStream}, SinkExt, StreamExt};
4use serde_json::json;
5use tokio::{net::TcpStream, spawn, sync::Mutex, task::JoinHandle};
6use tokio_tungstenite::{connect_async, tungstenite::Message, MaybeTlsStream, WebSocketStream};
7use tokio_tungstenite::tungstenite::Utf8Bytes;
8
9use crate::{client::EventHandler, context::Context, model::user::User};
11use crate::model::ready::Ready;
12use crate::client::ClientCache;
13use crate::model::message::Message as ChatMessage;
14use crate::model::user::RelationshipStatus;
15
16type WsWriter = SplitSink<WebSocketStream<MaybeTlsStream<TcpStream>>, Message>;
18type WsReader = SplitStream<WebSocketStream<MaybeTlsStream<TcpStream>>>;
19
20pub struct WebSocket {
21 pub writer: Arc<Mutex<WsWriter>>,
22 handler: Arc<Box<dyn EventHandler>>,
23}
24
25impl WebSocket {
26 pub async fn connect(handler: Box<dyn EventHandler>, token: String) -> (Arc<WebSocket>, JoinHandle<()>) {
29
30 let (ws_stream, _) = connect_async("wss://ws.revolt.chat").await.expect("Failed to connect to WebSocket");
31 let (writer, reader) = ws_stream.split();
32
33 let writer_arc = Arc::new(Mutex::new(writer));
35
36 writer_arc.lock().await.send(Message::Text(Utf8Bytes::from(json!({
37 "type": "Authenticate",
38 "token": token
39 }).to_string()))).await.expect("Failed to send Authenticate packet");
40
41 let ws_client = Arc::new(WebSocket {
42 writer: writer_arc.clone(),
43 handler: Arc::from(handler),
44 });
45
46 let handler_token = token.clone();
47 let handler_event = ws_client.handler.clone();
48
49 let handle = spawn(async move {
50 Self::handler(reader, writer_arc, handler_token, handler_event).await;
51 });
52
53 (ws_client, handle)
54 }
55
56 async fn handler(
58 mut reader: WsReader,
59 writer: Arc<Mutex<WsWriter>>,
60 token: String,
61 event: Arc<Box<dyn EventHandler>>,
62 ) {
63 let mut bot: Option<User> = None;
64
65 let cache = ClientCache::new();
67
68 let hb_writer = writer.clone();
69 spawn(async move {
70 loop {
71 tokio::time::sleep(Duration::from_secs(30)).await;
72 let ping = json!({
73 "type": "Ping",
74 "timestamp": SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis() as u64
75 });
76 if hb_writer.lock().await.send(Message::Text(ping.to_string().into())).await.is_err() {
77 break; }
79 }
80 });
81
82 while let Some(msg_result) = reader.next().await {
84 let message = match msg_result {
85 Ok(m) => m,
86 Err(e) => {
87 eprintln!("WebSocket Error: {}", e);
88 break;
89 }
90 };
91
92 if message.is_text() {
93 let raw_text = message.to_text().unwrap_or("");
94
95 let json_value: serde_json::Value = match serde_json::from_str(raw_text) {
96 Ok(v) => v,
97 Err(e) => {
98 eprintln!("JSON Parse Error: {}", e);
99 continue;
100 }
101 };
102
103 if let Some(event_type) = json_value["type"].as_str() {
104 match event_type {
105 "Ready" => {
106 match serde_json::from_value::<Ready>(json_value.clone()) {
107 Ok(ready) => {
108
109 bot = ready.users.iter()
110 .find(|u| u.relationship == Some(RelationshipStatus::User))
111 .cloned();
112
113 if bot.is_none() {
114 eprintln!("Warning: Could not find own Bot User in Ready payload!");
115 }
116 cache.hydrate(&ready).await;
117
118 if let Some(ref bot_user) = bot {
119 let ctx = Context::new(
120 &token,
121 json_value,
122 writer.clone(),
123 bot_user.clone(),
124 cache.clone()
125 );
126 event.ready(ctx, ready).await;
127 }
128 }
129 Err(e) => eprintln!("Failed to parse Ready struct: {}", e),
130 }
131 }
132
133 "Message" => {
134 if let Some(ref bot_user) = bot {
135 let ctx = Context::new(
136 &token,
137 json_value.clone(),
138 writer.clone(),
139 bot_user.clone(),
140 cache.clone()
141 );
142
143 match serde_json::from_value::<ChatMessage>(json_value) {
144 Ok(msg) => {
145 ctx.cache.messages.insert(msg.id.clone(), msg.clone()).await;
146
147 event.message(ctx, msg).await;
148 }
149 Err(e) => eprintln!("Failed to parse Message: {}", e),
150 }
151 }
152 }
153
154 _ => {
155 }
157 }
158 }
159 } else if message.is_close() {
160 println!("Gateway closed connection.");
161 break;
162 }
163 }
164 }
165}