Skip to content

Commit eb1127b

Browse files
feat: enhance error handling and improve logging in main and stream modules
1 parent c94483e commit eb1127b

3 files changed

Lines changed: 55 additions & 44 deletions

File tree

assets/bars.gif

-189 KB
Loading

src/main.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,13 @@ async fn create_client (home_dir: &Path, proxies: &[String], state: &AppState) -
4646
}
4747
}
4848
}
49-
let path = home_dir.join(format!("{}.json", client.login.clone().unwrap()));
49+
let path = home_dir.join(format!("{}.json", client.login.clone().expect("Login is required")));
5050
let path = Path::new(&path);
5151
if !path.exists() {
5252
client.save_file(&path).await?;
5353
}
5454
let client = TwitchClient::load_from_file(&path, &random_proxy).await?;
55-
let login = client.login.clone().unwrap_or_default();
55+
let login = client.login.clone().expect("Login is required");
5656

5757
let mut accounts = state.accounts.lock().await;
5858
let already_exists = if let Some(accs) = &*accounts {
@@ -471,7 +471,7 @@ async fn drop_sync(clients: Vec<Arc<TwitchClient>>, tx: Sender<String>, cache_pa
471471
} else {
472472
let mut cache = state.drop_cache.lock().await;
473473
let cache_str = retry!(fs::read_to_string(&cache_path));
474-
let cache_vec: HashMap<String, HashSet<String>> = serde_json::from_str(&cache_str).unwrap();
474+
let cache_vec: HashMap<String, HashSet<String>> = serde_json::from_str(&cache_str).expect("Failed to deserialize cache from JSON");
475475
*cache = cache_vec;
476476
drop(cache);
477477
}
@@ -493,7 +493,7 @@ async fn drop_sync(clients: Vec<Arc<TwitchClient>>, tx: Sender<String>, cache_pa
493493

494494
//bar
495495
let bar = bars.add(ProgressBar::new(1));
496-
bar.set_style(ProgressStyle::with_template("[{bar:40.cyan/blue}] {percent:.1}% ({pos}/{len} min) {msg}").unwrap());
496+
bar.set_style(ProgressStyle::with_template("[{bar:40.cyan/blue}] {percent:.1}% ({pos}/{len} min) {msg}").expect("Failed to create progress bar style"));
497497
bar.set_message("Initialization...");
498498
bar.enable_steady_tick(Duration::from_millis(500));
499499

src/stream.rs

Lines changed: 51 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::{collections::{BinaryHeap, HashMap, HashSet, VecDeque}, error::Error, sync::Arc, time::Duration};
1+
use std::{collections::{BinaryHeap, HashMap, HashSet, VecDeque}, sync::Arc, time::Duration};
22

33
use tokio::sync::{Mutex, Notify, watch::Receiver};
44

@@ -96,7 +96,7 @@ pub async fn filter_streams (client: Arc<TwitchClient>, campaigns_arc: Arc<Mutex
9696
*video_ids_lock = video_vec;
9797
drop(video_ids_lock);
9898
debug!("Drop video_ids_lock");
99-
spawn_ws(client.access_token.clone().unwrap(), state.clone()).await;
99+
spawn_ws(client.access_token.clone().expect("Access token is required"), state.clone()).await;
100100

101101
tokio::spawn(async move {
102102
loop {
@@ -202,7 +202,7 @@ async fn spawn_ws (auth_token: String, state: Arc<AppState>) {
202202
"auth_token": auth_token
203203
}
204204
});
205-
let payload = serde_json::to_string(&payload).unwrap();
205+
let payload = serde_json::to_string(&payload).expect("json! macro production is guaranteed to be serializable");
206206
let payload = tokio_tungstenite::tungstenite::Message::Text(payload.into());
207207
write.send(payload).await.unwrap_or_else(|e| error!("Failed to send payload to WebSocket: {e}"));
208208
send_channels.extend(new_channels);
@@ -217,7 +217,7 @@ async fn spawn_ws (auth_token: String, state: Arc<AppState>) {
217217
"auth_token": auth_token
218218
}
219219
});
220-
let payload = serde_json::to_string(&payload).unwrap();
220+
let payload = serde_json::to_string(&payload).expect("json! macro production is guaranteed to be serializable");
221221
let payload = tokio_tungstenite::tungstenite::Message::Text(payload.into());
222222
write.send(payload).await.unwrap_or_else(|e| error!("Failed to send payload to WebSocket: {e}"));
223223
for delete in delete_channels {
@@ -229,39 +229,58 @@ async fn spawn_ws (auth_token: String, state: Arc<AppState>) {
229229
match msg {
230230
Ok(Message::Text(text)) => {
231231
debug!("Received WebSocket message: {text}");
232-
if text.contains("\"type\":\"PING\"") {
233-
debug!("Received PING from WebSocket, sending PONG");
234-
let pong = Message::Text("{\"type\":\"PONG\"}".into());
235-
write.send(pong).await.unwrap();
236-
}
237-
let json: Value = serde_json::from_str(&text).unwrap();
238-
if let Some(err) = json.get("error").and_then(|e| e.as_str()) {
239-
if !err.is_empty() {
240-
error!("{err}")
232+
let json: Value = match serde_json::from_str(&text) {
233+
Ok(v) => v,
234+
Err(e) => {
235+
error!("Failed to parse JSON text: {e}");
236+
continue;
241237
}
242-
continue;
243-
} else {
244-
let data = check_json(&json, "data").unwrap_or_else(|e| {error!("{e}"); &Value::Null});
245-
let message = check_json(&data, "message").unwrap_or_else(|e| {error!("{e}"); &Value::Null}).as_str().unwrap_or_default();
246-
let topic = check_json(data, "topic").unwrap_or_else(|e| {error!("{e}"); &Value::Null }).as_str().unwrap_or_default();
247-
let message_json: Value = serde_json::from_str(&message).unwrap();
248-
249-
if let Some(viewers) = message_json.get("viewers").and_then(|s| s.as_u64()) {
250-
debug!("Stream {} has {} viewers", topic, viewers);
251-
if viewers == 0 {
252-
if let Some(id_str) = topic.split('.').last() {
253-
let mut channel_pool = state.channel_pool.lock().await;
254-
if let Some(to_remove) = channel_pool.iter().find(|channel| channel.channel_id == id_str).cloned() {
255-
channel_pool.remove(&to_remove);
238+
};
239+
let msg_type = json.get("type").and_then(|t| t.as_str()).unwrap_or("");
240+
match msg_type {
241+
"PING" => {
242+
debug!("Received PING from WebSocket, sending PONG");
243+
let pong = Message::Text("{\"type\":\"PONG\"}".into());
244+
let _ = write.send(pong).await;
245+
continue;
246+
},
247+
"PONG" => {continue;}
248+
"RECCONECT" => {
249+
warn!("Received RECONNECT from WebSocket, reconnecting...");
250+
break;
251+
},
252+
"RESPONSE" => {
253+
if let Some(err) = json.get("error").and_then(|e| e.as_str()) {
254+
if !err.is_empty() {
255+
error!("WebSocket RESPONSE error: {err}");
256+
}
257+
}
258+
continue;
259+
},
260+
"MESSAGE" => {
261+
if let Some(data) = json.get("data") {
262+
let topic = data.get("topic").and_then(|t| t.as_str()).unwrap_or_default();
263+
let message_str = data.get("message").and_then(|m| m.as_str()).unwrap_or_default();
264+
if let Ok(message_json) = serde_json::from_str::<Value>(message_str) {
265+
if let Some(viewers) = message_json.get("viewers").and_then(|v| v.as_u64()) {
266+
debug!("Stream {} has {} viewers", topic, viewers);
267+
if viewers == 0 {
268+
if let Some(id_str) = topic.split('.').last() {
269+
let mut channel_pool = state.channel_pool.lock().await;
270+
channel_pool.retain(|channel| channel.channel_id != id_str);
271+
send_channels.retain(|channel| channel.channel_id != id_str);
272+
}
273+
}
274+
} else {
275+
debug!("No viewers field in message for topic {}", topic);
256276
}
257-
send_channels.retain(|channel| channel.channel_id != id_str );
277+
} else {
278+
error!("Failed to parse message JSON for topic {}", topic);
258279
}
259280
}
260-
} else {
261-
debug!("No viewers field in message for topic {}", topic);
262-
}
281+
},
282+
_ => { debug!("Received unknown message type: {}", msg_type) }
263283
}
264-
265284
},
266285
Ok(Message::Ping(ping)) => write.send(Message::Pong(ping)).await.unwrap_or_else(|e| error!("Failed to send PONG to WebSocket: {e}")),
267286
Ok(_) => {},
@@ -279,14 +298,6 @@ async fn spawn_ws (auth_token: String, state: Arc<AppState>) {
279298
});
280299
}
281300

282-
fn check_json<'a>(v: &'a Value, data: &str) -> Result<&'a Value, Box<dyn Error>> {
283-
if let Some(key) = v.get(&data) {
284-
return Ok(key);
285-
} else {
286-
return Err(format!("Failed to find '{}' in JSON", data))?;
287-
}
288-
}
289-
290301
#[derive(PartialEq, Eq, Clone)]
291302
struct Priority {
292303
priority: u32,

0 commit comments

Comments
 (0)