1use async_trait::async_trait;
2use futures::{SinkExt, StreamExt};
3use serde::Deserialize;
4use serde_json::json;
5use tokio_tungstenite::{connect_async, tungstenite::protocol::Message};
6use url::Url;
7use uuid::Uuid;
8
9use config::{API_URL, TIME_OUT};
10use user::{TopUserWrapper, User};
11
12mod config;
13pub mod prelude;
14mod user;
15
16#[async_trait]
43pub trait EventHandler {
44 async fn on_message(&self, _msg: String);
45 async fn on_pong(&self);
46 async fn connection_closed(&self);
47 async fn on_ready(&self, _user: &User);
48}
49
50#[derive(Clone)]
51pub struct Client<'a, T>
52where
53 T: EventHandler + Sync,
54{
55 token: String,
56 refresh_token: String,
57 bot_token: Option<String>,
58 bot_refresh_token: Option<String>,
59 room_id: Option<&'a str>,
60 event_handler: Option<T>,
61}
62
63impl<'a, T> Client<'a, T>
64where
65 T: EventHandler + Sync,
66{
67 pub fn new(token: String, refresh_token: String) -> Self {
68 Self {
69 token,
70 refresh_token,
71 bot_token: None,
72 bot_refresh_token: None,
73 room_id: None,
74 event_handler: None,
75 }
76 }
77
78 pub async fn use_create_bot(
92 &mut self,
93 username: &'a str,
94 show_bot_tokens: bool,
95 ) -> anyhow::Result<()> {
96 let url = Url::parse(API_URL)?;
97
98 let (ws_stream, _) = connect_async(url).await.expect("Failed to connect");
99 let (mut write, mut read) = ws_stream.split();
100
101 write
102 .send(Message::Text(
103 json!(
104 {
105 "op": "auth",
106 "d": {
107 "accessToken": self.token,
108 "refreshToken": self.refresh_token,
109 "reconnectToVoice": false,
110 "currentRoomId": "",
111 "muted": true,
112 "platform": "dogehouse-rs"
113 }
114 }
115 )
116 .to_string(),
117 ))
118 .await?;
119
120 #[derive(Deserialize)]
121 struct CreateBotResponse {
122 op: String,
123 p: CreateBotResponseP,
124 }
125
126 #[derive(Deserialize)]
127 #[serde(rename_all = "camelCase")]
128 struct CreateBotResponseP {
129 api_key: serde_json::Value,
130 is_username_taken: serde_json::Value,
131 error: serde_json::Value,
132 }
133
134 write
135 .send(Message::Text(
136 json!(
137 {
138 "op": config::user::CREATE_BOT,
139 "p": {
140 "username": username
141 },
142 "ref": "[uuid]",
143 "v": "0.2.0",
144 }
145 )
146 .to_string(),
147 ))
148 .await?;
149
150 read.next().await;
152 read.next().await;
153
154 let n = read.next().await.unwrap()?.to_string();
155 let bot_response =
156 serde_json::from_str::<CreateBotResponse>(&n).expect("Error invalid bot name");
157 if bot_response.p.is_username_taken.is_boolean() {
158 return Err(anyhow::Error::msg("Bot name is taken."));
159 }
160
161 #[derive(Deserialize, Debug)]
162 #[serde(rename_all = "camelCase")]
163 struct BotAccount {
164 access_token: String,
165 refresh_token: String,
166 }
167
168 let bot_account = reqwest::Client::new()
169 .post("https://api.dogehouse.tv/bot/auth")
170 .header("content-type", "application/json")
171 .body(
172 json!(
173 {
174 "apiKey": bot_response.p.api_key.as_str()
175 }
176 )
177 .to_string(),
178 )
179 .send()
180 .await?
181 .json::<BotAccount>()
182 .await?;
183
184 if show_bot_tokens {
185 println!("Bot tokens: {:?}", &bot_account);
186 }
187
188 self.bot_token = Some(bot_account.access_token);
189 self.bot_refresh_token = Some(bot_account.refresh_token);
190 Ok(())
191 }
192
193 pub fn add_event_handler(mut self, handler: T) -> Self {
195 self.event_handler = Some(handler);
196 self
197 }
198
199 pub async fn start(&mut self, room_id: &'a str) -> anyhow::Result<()> {
200 self.room_id = Some(room_id);
201
202 let token = match self.bot_token.is_some() {
203 true => &self.bot_token.as_ref().unwrap(),
204 false => &self.token,
205 };
206
207 let refresh_token = match self.bot_refresh_token.is_some() {
208 true => &self.bot_refresh_token.as_ref().unwrap(),
209 false => &self.refresh_token,
210 };
211
212 let url = Url::parse(API_URL)?;
213
214 let (ws_stream, _) = connect_async(url).await.expect("Failed to connect");
215 let (mut write, mut read) = ws_stream.split();
216
217 write
218 .send(Message::Text(
219 json!(
220 {
221 "op": "auth",
222 "d": {
223 "accessToken": token,
224 "refreshToken": refresh_token,
225 "reconnectToVoice": false,
226 "currentRoomId": self.room_id.unwrap(),
227 "muted": true,
228 "platform": "dogehouse-rs"
229 }
230 }
231 )
232 .to_string(),
233 ))
234 .await?;
235
236 let n = read.next().await.unwrap()?.to_string();
237 println!("{}", &n);
238
239 let account: TopUserWrapper = serde_json::from_str(&n)?;
240 self.event_handler
241 .as_ref()
242 .unwrap()
243 .on_ready(&account.d.user)
244 .await;
245
246 let room_join_ref = Uuid::new_v4();
247
248 write
249 .send(Message::Text(
250 json!({
251 "op": "room:join",
252 "d": {
253 "roomId": self.room_id.unwrap()
254 },
255 "ref": room_join_ref.to_string(),
256 "v": "0.2.0",
257 })
258 .to_string(),
259 ))
260 .await?;
261
262 tokio::spawn(async move {
263 loop {
264 write.send("ping".into()).await.unwrap();
265 std::thread::sleep(std::time::Duration::new(TIME_OUT, 0));
266 }
267 });
268
269 while let Some(msg) = read.next().await {
270 let msg = msg?;
271 if msg.is_close() {
272 self.event_handler
273 .as_ref()
274 .unwrap()
275 .connection_closed()
276 .await;
277 continue;
278 } else if msg.is_binary() || msg.is_text() {
279 if msg.to_string() == "pong" {
280 self.event_handler.as_ref().unwrap().on_pong().await;
281 continue;
282 }
283 self.event_handler
284 .as_ref()
285 .unwrap()
286 .on_message(msg.to_string())
287 .await;
288 continue;
289 }
290 }
291
292 Ok(())
293 }
294}