diff --git a/src-tauri/src/cmd/media_unlock_checker/bahamut.rs b/src-tauri/src/cmd/media_unlock_checker/bahamut.rs index 6a5707bec..21239e896 100644 --- a/src-tauri/src/cmd/media_unlock_checker/bahamut.rs +++ b/src-tauri/src/cmd/media_unlock_checker/bahamut.rs @@ -5,7 +5,6 @@ use regex::Regex; use reqwest::{Client, cookie::Jar}; use super::UnlockItem; -use super::utils::{country_code_to_emoji, get_local_date_string}; pub(super) async fn check_bahamut_anime(client: &Client) -> UnlockItem { let cookie_store = Arc::new(Jar::default()); @@ -54,12 +53,7 @@ pub(super) async fn check_bahamut_anime(client: &Client) -> UnlockItem { }; if device_id.is_empty() { - return UnlockItem { - name: "Bahamut Anime".to_string(), - status: "Failed".to_string(), - region: None, - check_time: Some(get_local_date_string()), - }; + return UnlockItem::checked("Bahamut Anime", "Failed", None); } let url = format!("https://ani.gamer.com.tw/ajax/token.php?adID=89422&sn=37783&device={device_id}"); @@ -79,12 +73,7 @@ pub(super) async fn check_bahamut_anime(client: &Client) -> UnlockItem { }; if token_result.is_none() { - return UnlockItem { - name: "Bahamut Anime".to_string(), - status: "No".to_string(), - region: None, - check_time: Some(get_local_date_string()), - }; + return UnlockItem::checked("Bahamut Anime", "No", None); } let region = match client_with_cookies.get("https://ani.gamer.com.tw/").send().await { @@ -92,8 +81,7 @@ pub(super) async fn check_bahamut_anime(client: &Client) -> UnlockItem { Ok(body) => match Regex::new(r#"data-geo="([^"]+)"#) { Ok(region_re) => region_re.captures(&body).and_then(|caps| caps.get(1)).map(|m| { let country_code = m.as_str(); - let emoji = country_code_to_emoji(country_code); - format!("{emoji}{country_code}") + UnlockItem::region_label(country_code) }), Err(e) => { logging!( @@ -110,10 +98,5 @@ pub(super) async fn check_bahamut_anime(client: &Client) -> UnlockItem { Err(_) => None, }; - UnlockItem { - name: "Bahamut Anime".to_string(), - status: "Yes".to_string(), - region, - check_time: Some(get_local_date_string()), - } + UnlockItem::checked("Bahamut Anime", "Yes", region) } diff --git a/src-tauri/src/cmd/media_unlock_checker/bilibili.rs b/src-tauri/src/cmd/media_unlock_checker/bilibili.rs index 72c8697c5..0264b28ed 100644 --- a/src-tauri/src/cmd/media_unlock_checker/bilibili.rs +++ b/src-tauri/src/cmd/media_unlock_checker/bilibili.rs @@ -2,7 +2,6 @@ use reqwest::Client; use serde_json::Value; use super::UnlockItem; -use super::utils::get_local_date_string; pub(super) async fn check_bilibili_china_mainland(client: &Client) -> UnlockItem { let url = "https://api.bilibili.com/pgc/player/web/playurl?avid=82846771&qn=0&type=&otype=json&ep_id=307247&fourk=1&fnver=0&fnval=16&module=bangumi"; @@ -24,26 +23,11 @@ pub(super) async fn check_bilibili_china_mainland(client: &Client) -> UnlockItem }) .unwrap_or("Failed"); - UnlockItem { - name: "哔哩哔哩大陆".to_string(), - status: status.to_string(), - region: None, - check_time: Some(get_local_date_string()), - } + UnlockItem::checked("哔哩哔哩大陆", status, None) } - Err(_) => UnlockItem { - name: "哔哩哔哩大陆".to_string(), - status: "Failed".to_string(), - region: None, - check_time: Some(get_local_date_string()), - }, - }, - Err(_) => UnlockItem { - name: "哔哩哔哩大陆".to_string(), - status: "Failed".to_string(), - region: None, - check_time: Some(get_local_date_string()), + Err(_) => UnlockItem::checked("哔哩哔哩大陆", "Failed", None), }, + Err(_) => UnlockItem::checked("哔哩哔哩大陆", "Failed", None), } } @@ -67,25 +51,10 @@ pub(super) async fn check_bilibili_hk_mc_tw(client: &Client) -> UnlockItem { }) .unwrap_or("Failed"); - UnlockItem { - name: "哔哩哔哩港澳台".to_string(), - status: status.to_string(), - region: None, - check_time: Some(get_local_date_string()), - } + UnlockItem::checked("哔哩哔哩港澳台", status, None) } - Err(_) => UnlockItem { - name: "哔哩哔哩港澳台".to_string(), - status: "Failed".to_string(), - region: None, - check_time: Some(get_local_date_string()), - }, - }, - Err(_) => UnlockItem { - name: "哔哩哔哩港澳台".to_string(), - status: "Failed".to_string(), - region: None, - check_time: Some(get_local_date_string()), + Err(_) => UnlockItem::checked("哔哩哔哩港澳台", "Failed", None), }, + Err(_) => UnlockItem::checked("哔哩哔哩港澳台", "Failed", None), } } diff --git a/src-tauri/src/cmd/media_unlock_checker/chatgpt.rs b/src-tauri/src/cmd/media_unlock_checker/chatgpt.rs index 8c441a345..ca17351be 100644 --- a/src-tauri/src/cmd/media_unlock_checker/chatgpt.rs +++ b/src-tauri/src/cmd/media_unlock_checker/chatgpt.rs @@ -3,7 +3,6 @@ use std::collections::HashMap; use reqwest::Client; use super::UnlockItem; -use super::utils::{country_code_to_emoji, get_local_date_string}; pub(super) async fn check_chatgpt_combined(client: &Client) -> Vec { let mut results = Vec::new(); @@ -23,10 +22,7 @@ pub(super) async fn check_chatgpt_combined(client: &Client) -> Vec { } } - map.get("loc").map(|loc| { - let emoji = country_code_to_emoji(loc); - format!("{emoji}{loc}") - }) + map.get("loc").map(|loc| UnlockItem::region_label(loc)) } else { None } @@ -76,19 +72,9 @@ pub(super) async fn check_chatgpt_combined(client: &Client) -> Vec { Err(_) => "Failed", }; - results.push(UnlockItem { - name: "ChatGPT iOS".to_string(), - status: ios_status.to_string(), - region: region.clone(), - check_time: Some(get_local_date_string()), - }); + results.push(UnlockItem::checked("ChatGPT iOS", ios_status, region.clone())); - results.push(UnlockItem { - name: "ChatGPT Web".to_string(), - status: web_status.to_string(), - region, - check_time: Some(get_local_date_string()), - }); + results.push(UnlockItem::checked("ChatGPT Web", web_status, region)); results } diff --git a/src-tauri/src/cmd/media_unlock_checker/claude.rs b/src-tauri/src/cmd/media_unlock_checker/claude.rs index 525b9a7fe..2e8000206 100644 --- a/src-tauri/src/cmd/media_unlock_checker/claude.rs +++ b/src-tauri/src/cmd/media_unlock_checker/claude.rs @@ -1,7 +1,6 @@ use reqwest::Client; use super::UnlockItem; -use super::utils::{country_code_to_emoji, get_local_date_string}; const BLOCKED_CODES: [&str; 10] = ["AF", "BY", "CN", "CU", "HK", "IR", "KP", "MO", "RU", "SY"]; @@ -21,40 +20,19 @@ pub(super) async fn check_claude(client: &Client) -> UnlockItem { } if let Some(code) = country_code { - let emoji = country_code_to_emoji(&code); let status = if BLOCKED_CODES.contains(&code.as_str()) { "No" } else { "Yes" }; - UnlockItem { - name: "Claude".to_string(), - status: status.to_string(), - region: Some(format!("{emoji}{code}")), - check_time: Some(get_local_date_string()), - } + UnlockItem::checked_region("Claude", status, &code) } else { - UnlockItem { - name: "Claude".to_string(), - status: "Failed".to_string(), - region: None, - check_time: Some(get_local_date_string()), - } + UnlockItem::checked("Claude", "Failed", None) } } - Err(_) => UnlockItem { - name: "Claude".to_string(), - status: "Failed".to_string(), - region: None, - check_time: Some(get_local_date_string()), - }, - }, - Err(_) => UnlockItem { - name: "Claude".to_string(), - status: "Failed".to_string(), - region: None, - check_time: Some(get_local_date_string()), + Err(_) => UnlockItem::checked("Claude", "Failed", None), }, + Err(_) => UnlockItem::checked("Claude", "Failed", None), } } diff --git a/src-tauri/src/cmd/media_unlock_checker/disney_plus.rs b/src-tauri/src/cmd/media_unlock_checker/disney_plus.rs index 1686d3727..1ce61dcaf 100644 --- a/src-tauri/src/cmd/media_unlock_checker/disney_plus.rs +++ b/src-tauri/src/cmd/media_unlock_checker/disney_plus.rs @@ -4,12 +4,51 @@ use reqwest::Client; use clash_verge_logging::{Type, logging}; use super::UnlockItem; -use super::utils::{country_code_to_emoji, get_local_date_string}; + +const DISNEY_PLUS: &str = "Disney+"; +const AUTH_HEADER: &str = "Bearer ZGlzbmV5JmJyb3dzZXImMS4wLjA.Cu56AgSfBTDag5NiRA81oLHkDZfu5L3CKadnefEAY84"; + +fn disney_item(status: impl Into, region: Option) -> UnlockItem { + UnlockItem::checked(DISNEY_PLUS, status, region) +} + +async fn fetch_main_page_region(client: &Client) -> Option { + let body = client + .get("https://www.disneyplus.com/") + .send() + .await + .ok()? + .text() + .await + .ok()?; + let region_re = match Regex::new(r#"region"\s*:\s*"([^"]+)"#) { + Ok(region_re) => region_re, + Err(e) => { + logging!( + error, + Type::Network, + "Failed to compile Disney+ main page region regex: {}", + e + ); + return None; + } + }; + region_re + .captures(&body) + .and_then(|caps| caps.get(1).map(|m| m.as_str().to_string())) +} + +async fn disney_region_from_main_page(client: &Client) -> Option { + let region = fetch_main_page_region(client).await?; + Some(disney_item( + "Yes", + Some(format!("{} (from main page)", UnlockItem::region_label(®ion))), + )) +} #[allow(clippy::cognitive_complexity)] pub(super) async fn check_disney_plus(client: &Client) -> UnlockItem { let device_api_url = "https://disney.api.edge.bamgrid.com/devices"; - let auth_header = "Bearer ZGlzbmV5JmJyb3dzZXImMS4wLjA.Cu56AgSfBTDag5NiRA81oLHkDZfu5L3CKadnefEAY84"; let device_req_body = serde_json::json!({ "deviceFamily": "browser", @@ -20,52 +59,27 @@ pub(super) async fn check_disney_plus(client: &Client) -> UnlockItem { let device_result = client .post(device_api_url) - .header("authorization", auth_header) + .header("authorization", AUTH_HEADER) .header("content-type", "application/json; charset=UTF-8") .json(&device_req_body) .send() .await; - if device_result.is_err() { - return UnlockItem { - name: "Disney+".to_string(), - status: "Failed (Network Connection)".to_string(), - region: None, - check_time: Some(get_local_date_string()), - }; - } - let device_response = match device_result { Ok(response) => response, - Err(e) => { - logging!(error, Type::Network, "Failed to get Disney+ device response: {}", e); - return UnlockItem { - name: "Disney+".to_string(), - status: "Failed (Network Connection)".to_string(), - region: None, - check_time: Some(get_local_date_string()), - }; + Err(_) => { + return disney_item("Failed (Network Connection)", None); } }; if device_response.status().as_u16() == 403 { - return UnlockItem { - name: "Disney+".to_string(), - status: "No (IP Banned By Disney+)".to_string(), - region: None, - check_time: Some(get_local_date_string()), - }; + return disney_item("No (IP Banned By Disney+)", None); } let device_body = match device_response.text().await { Ok(body) => body, Err(_) => { - return UnlockItem { - name: "Disney+".to_string(), - status: "Failed (Error: Cannot read response)".to_string(), - region: None, - check_time: Some(get_local_date_string()), - }; + return disney_item("Failed (Error: Cannot read response)", None); } }; @@ -78,12 +92,7 @@ pub(super) async fn check_disney_plus(client: &Client) -> UnlockItem { "Failed to compile assertion regex for Disney+: {}", e ); - return UnlockItem { - name: "Disney+".to_string(), - status: "Failed (Regex Error)".to_string(), - region: None, - check_time: Some(get_local_date_string()), - }; + return disney_item("Failed (Regex Error)", None); } }; let assertion = match re.captures(&device_body) { @@ -92,27 +101,11 @@ pub(super) async fn check_disney_plus(client: &Client) -> UnlockItem { }; if assertion.is_none() { - return UnlockItem { - name: "Disney+".to_string(), - status: "Failed (Error: Cannot extract assertion)".to_string(), - region: None, - check_time: Some(get_local_date_string()), - }; + return disney_item("Failed (Error: Cannot extract assertion)", None); } let token_url = "https://disney.api.edge.bamgrid.com/token"; - let assertion_str = match assertion { - Some(assertion) => assertion, - None => { - logging!(error, Type::Network, "No assertion found for Disney+"); - return UnlockItem { - name: "Disney+".to_string(), - status: "Failed (No Assertion)".to_string(), - region: None, - check_time: Some(get_local_date_string()), - }; - } - }; + let assertion_str = assertion.unwrap_or_default(); let token_body = [ ("grant_type", "urn:ietf:params:oauth:grant-type:token-exchange"), ("latitude", "0"), @@ -124,31 +117,16 @@ pub(super) async fn check_disney_plus(client: &Client) -> UnlockItem { let token_result = client .post(token_url) - .header("authorization", auth_header) + .header("authorization", AUTH_HEADER) .header("content-type", "application/x-www-form-urlencoded") .form(&token_body) .send() .await; - if token_result.is_err() { - return UnlockItem { - name: "Disney+".to_string(), - status: "Failed (Network Connection)".to_string(), - region: None, - check_time: Some(get_local_date_string()), - }; - } - let token_response = match token_result { Ok(response) => response, - Err(e) => { - logging!(error, Type::Network, "Failed to get Disney+ token response: {}", e); - return UnlockItem { - name: "Disney+".to_string(), - status: "Failed (Network Connection)".to_string(), - region: None, - check_time: Some(get_local_date_string()), - }; + Err(_) => { + return disney_item("Failed (Network Connection)", None); } }; let token_status = token_response.status(); @@ -156,22 +134,12 @@ pub(super) async fn check_disney_plus(client: &Client) -> UnlockItem { let token_body_text = match token_response.text().await { Ok(body) => body, Err(_) => { - return UnlockItem { - name: "Disney+".to_string(), - status: "Failed (Error: Cannot read token response)".to_string(), - region: None, - check_time: Some(get_local_date_string()), - }; + return disney_item("Failed (Error: Cannot read token response)", None); } }; if token_body_text.contains("forbidden-location") || token_body_text.contains("403 ERROR") { - return UnlockItem { - name: "Disney+".to_string(), - status: "No (IP Banned By Disney+)".to_string(), - region: None, - check_time: Some(get_local_date_string()), - }; + return disney_item("No (IP Banned By Disney+)", None); } let token_json: Result = serde_json::from_str(&token_body_text); @@ -198,16 +166,14 @@ pub(super) async fn check_disney_plus(client: &Client) -> UnlockItem { }; if refresh_token.is_none() { - return UnlockItem { - name: "Disney+".to_string(), - status: format!( + return disney_item( + format!( "Failed (Error: Cannot extract refresh token, status: {}, response: {})", token_status.as_u16(), token_body_text.chars().take(100).collect::() + "..." ), - region: None, - check_time: Some(get_local_date_string()), - }; + None, + ); } let graphql_url = "https://disney.api.edge.bamgrid.com/graph/v1/device/graphql"; @@ -219,21 +185,12 @@ pub(super) async fn check_disney_plus(client: &Client) -> UnlockItem { let graphql_result = client .post(graphql_url) - .header("authorization", auth_header) + .header("authorization", AUTH_HEADER) .header("content-type", "application/json") .body(graphql_payload) .send() .await; - if graphql_result.is_err() { - return UnlockItem { - name: "Disney+".to_string(), - status: "Failed (Network Connection)".to_string(), - region: None, - check_time: Some(get_local_date_string()), - }; - } - let preview_check = client.get("https://disneyplus.com").send().await; let is_unavailable = match preview_check { @@ -246,14 +203,8 @@ pub(super) async fn check_disney_plus(client: &Client) -> UnlockItem { let graphql_response = match graphql_result { Ok(response) => response, - Err(e) => { - logging!(error, Type::Network, "Failed to get Disney+ GraphQL response: {}", e); - return UnlockItem { - name: "Disney+".to_string(), - status: "Failed (Network Connection)".to_string(), - region: None, - check_time: Some(get_local_date_string()), - }; + Err(_) => { + return disney_item("Failed (Network Connection)", None); } }; let graphql_status = graphql_response.status(); @@ -271,58 +222,27 @@ pub(super) async fn check_disney_plus(client: &Client) -> UnlockItem { }; if graphql_body_text.is_empty() || graphql_status.as_u16() >= 400 { - let region_from_main = match client.get("https://www.disneyplus.com/").send().await { - Ok(response) => match response.text().await { - Ok(body) => match Regex::new(r#"region"\s*:\s*"([^"]+)"#) { - Ok(region_re) => region_re - .captures(&body) - .and_then(|caps| caps.get(1).map(|m| m.as_str().to_string())), - Err(e) => { - logging!( - error, - Type::Network, - "Failed to compile Disney+ main page region regex: {}", - e - ); - None - } - }, - Err(_) => None, - }, - Err(_) => None, - }; - - if let Some(region) = region_from_main { - let emoji = country_code_to_emoji(®ion); - return UnlockItem { - name: "Disney+".to_string(), - status: "Yes".to_string(), - region: Some(format!("{emoji}{region} (from main page)")), - check_time: Some(get_local_date_string()), - }; + if let Some(item) = disney_region_from_main_page(client).await { + return item; } if graphql_body_text.is_empty() { - return UnlockItem { - name: "Disney+".to_string(), - status: format!( + return disney_item( + format!( "Failed (GraphQL error: empty response, status: {})", graphql_status.as_u16() ), - region: None, - check_time: Some(get_local_date_string()), - }; + None, + ); } - return UnlockItem { - name: "Disney+".to_string(), - status: format!( + return disney_item( + format!( "Failed (GraphQL error: {}, status: {})", graphql_body_text.chars().take(50).collect::() + "...", graphql_status.as_u16() ), - region: None, - check_time: Some(get_local_date_string()), - }; + None, + ); } let region_re = match Regex::new(r#""countryCode"\s*:\s*"([^"]+)"#) { @@ -334,12 +254,7 @@ pub(super) async fn check_disney_plus(client: &Client) -> UnlockItem { "Failed to compile Disney+ countryCode regex: {}", e ); - return UnlockItem { - name: "Disney+".to_string(), - status: "Failed (Regex Error)".to_string(), - region: None, - check_time: Some(get_local_date_string()), - }; + return disney_item("Failed (Regex Error)", None); } }; let region_code = region_re @@ -355,12 +270,7 @@ pub(super) async fn check_disney_plus(client: &Client) -> UnlockItem { "Failed to compile Disney+ supported location regex: {}", e ); - return UnlockItem { - name: "Disney+".to_string(), - status: "Failed (Regex Error)".to_string(), - region: None, - check_time: Some(get_local_date_string()), - }; + return disney_item("Failed (Regex Error)", None); } }; let in_supported_location = supported_re @@ -368,101 +278,29 @@ pub(super) async fn check_disney_plus(client: &Client) -> UnlockItem { .and_then(|caps| caps.get(1).map(|m| m.as_str() == "true")); if region_code.is_none() { - let region_from_main = match client.get("https://www.disneyplus.com/").send().await { - Ok(response) => match response.text().await { - Ok(body) => match Regex::new(r#"region"\s*:\s*"([^"]+)"#) { - Ok(region_re) => region_re - .captures(&body) - .and_then(|caps| caps.get(1).map(|m| m.as_str().to_string())), - Err(e) => { - logging!( - error, - Type::Network, - "Failed to compile Disney+ main page region regex: {}", - e - ); - None - } - }, - Err(_) => None, - }, - Err(_) => None, - }; - - if let Some(region) = region_from_main { - let emoji = country_code_to_emoji(®ion); - return UnlockItem { - name: "Disney+".to_string(), - status: "Yes".to_string(), - region: Some(format!("{emoji}{region} (from main page)")), - check_time: Some(get_local_date_string()), - }; + if let Some(item) = disney_region_from_main_page(client).await { + return item; } - return UnlockItem { - name: "Disney+".to_string(), - status: "No".to_string(), - region: None, - check_time: Some(get_local_date_string()), - }; + return disney_item("No", None); } - let region = match region_code { - Some(code) => code, - None => { - logging!(error, Type::Network, "No region code found for Disney+"); - return UnlockItem { - name: "Disney+".to_string(), - status: "No".to_string(), - region: None, - check_time: Some(get_local_date_string()), - }; - } - }; + let region = region_code.unwrap_or_default(); if region == "JP" { - let emoji = country_code_to_emoji("JP"); - return UnlockItem { - name: "Disney+".to_string(), - status: "Yes".to_string(), - region: Some(format!("{emoji}{region}")), - check_time: Some(get_local_date_string()), - }; + return UnlockItem::checked_region(DISNEY_PLUS, "Yes", ®ion); } if is_unavailable { - return UnlockItem { - name: "Disney+".to_string(), - status: "No".to_string(), - region: None, - check_time: Some(get_local_date_string()), - }; + return disney_item("No", None); } match in_supported_location { - Some(false) => { - let emoji = country_code_to_emoji(®ion); - UnlockItem { - name: "Disney+".to_string(), - status: "Soon".to_string(), - region: Some(format!("{emoji}{region}(即将上线)")), - check_time: Some(get_local_date_string()), - } - } - Some(true) => { - let emoji = country_code_to_emoji(®ion); - UnlockItem { - name: "Disney+".to_string(), - status: "Yes".to_string(), - region: Some(format!("{emoji}{region}")), - check_time: Some(get_local_date_string()), - } - } - None => UnlockItem { - name: "Disney+".to_string(), - status: format!("Failed (Error: Unknown region status for {region})"), - region: None, - check_time: Some(get_local_date_string()), - }, + Some(false) => disney_item( + "Soon", + Some(format!("{}(即将上线)", UnlockItem::region_label(®ion))), + ), + Some(true) => UnlockItem::checked_region(DISNEY_PLUS, "Yes", ®ion), + None => disney_item(format!("Failed (Error: Unknown region status for {region})"), None), } } diff --git a/src-tauri/src/cmd/media_unlock_checker/gemini.rs b/src-tauri/src/cmd/media_unlock_checker/gemini.rs index b2843b81b..2aad077b6 100644 --- a/src-tauri/src/cmd/media_unlock_checker/gemini.rs +++ b/src-tauri/src/cmd/media_unlock_checker/gemini.rs @@ -1,19 +1,13 @@ use reqwest::Client; use super::UnlockItem; -use super::utils::{country_code_to_emoji, get_local_date_string}; const BLOCKED_CODES: [&str; 9] = ["CHN", "RUS", "BLR", "CUB", "IRN", "PRK", "SYR", "HKG", "MAC"]; const REGION_MARKER: &str = ",2,1,200,\""; pub(super) async fn check_gemini(client: &Client) -> UnlockItem { let url = "https://gemini.google.com"; - let failed = || UnlockItem { - name: "Gemini".to_string(), - status: "Failed".to_string(), - region: None, - check_time: Some(get_local_date_string()), - }; + let failed = || UnlockItem::checked("Gemini", "Failed", None); let response = match client.get(url).send().await { Ok(r) => r, @@ -34,14 +28,8 @@ pub(super) async fn check_gemini(client: &Client) -> UnlockItem { match country_code { Some(code) => { - let emoji = country_code_to_emoji(code); let status = if BLOCKED_CODES.contains(&code) { "No" } else { "Yes" }; - UnlockItem { - name: "Gemini".to_string(), - status: status.to_string(), - region: Some(format!("{emoji}{code}")), - check_time: Some(get_local_date_string()), - } + UnlockItem::checked_region("Gemini", status, code) } None => failed(), } diff --git a/src-tauri/src/cmd/media_unlock_checker/netflix.rs b/src-tauri/src/cmd/media_unlock_checker/netflix.rs index 229148c86..dd6d38469 100644 --- a/src-tauri/src/cmd/media_unlock_checker/netflix.rs +++ b/src-tauri/src/cmd/media_unlock_checker/netflix.rs @@ -4,7 +4,12 @@ use serde_json::Value; use clash_verge_logging::{Type, logging}; use super::UnlockItem; -use super::utils::{country_code_to_emoji, get_local_date_string}; + +const NETFLIX: &str = "Netflix"; + +fn netflix_item(status: impl Into, region: Option) -> UnlockItem { + UnlockItem::checked(NETFLIX, status, region) +} pub(super) async fn check_netflix(client: &Client) -> UnlockItem { let cdn_result = check_netflix_cdn(client).await; @@ -23,12 +28,7 @@ pub(super) async fn check_netflix(client: &Client) -> UnlockItem { if let Err(e) = &result1 { logging!(error, Type::Network, "Netflix请求错误: {e}"); - return UnlockItem { - name: "Netflix".to_string(), - status: "Failed".to_string(), - region: None, - check_time: Some(get_local_date_string()), - }; + return netflix_item("Failed", None); } let result2 = client @@ -39,24 +39,14 @@ pub(super) async fn check_netflix(client: &Client) -> UnlockItem { if let Err(e) = &result2 { logging!(error, Type::Network, "Netflix请求错误: {e}"); - return UnlockItem { - name: "Netflix".to_string(), - status: "Failed".to_string(), - region: None, - check_time: Some(get_local_date_string()), - }; + return netflix_item("Failed", None); } let status1 = match result1 { Ok(response) => response.status().as_u16(), Err(e) => { logging!(error, Type::Network, "Failed to get Netflix response 1: {}", e); - return UnlockItem { - name: "Netflix".to_string(), - status: "Failed".to_string(), - region: None, - check_time: Some(get_local_date_string()), - }; + return netflix_item("Failed", None); } }; @@ -64,31 +54,16 @@ pub(super) async fn check_netflix(client: &Client) -> UnlockItem { Ok(response) => response.status().as_u16(), Err(e) => { logging!(error, Type::Network, "Failed to get Netflix response 2: {}", e); - return UnlockItem { - name: "Netflix".to_string(), - status: "Failed".to_string(), - region: None, - check_time: Some(get_local_date_string()), - }; + return netflix_item("Failed", None); } }; if status1 == 404 && status2 == 404 { - return UnlockItem { - name: "Netflix".to_string(), - status: "Originals Only".to_string(), - region: None, - check_time: Some(get_local_date_string()), - }; + return netflix_item("Originals Only", None); } if status1 == 403 || status2 == 403 { - return UnlockItem { - name: "Netflix".to_string(), - status: "No".to_string(), - region: None, - check_time: Some(get_local_date_string()), - }; + return netflix_item("No", None); } if status1 == 200 || status1 == 301 || status2 == 200 || status2 == 301 { @@ -106,41 +81,19 @@ pub(super) async fn check_netflix(client: &Client) -> UnlockItem { let parts: Vec<&str> = location_str.split('/').collect(); if parts.len() >= 4 { let region_code = parts[3].split('-').next().unwrap_or("unknown"); - let emoji = country_code_to_emoji(region_code); - return UnlockItem { - name: "Netflix".to_string(), - status: "Yes".to_string(), - region: Some(format!("{emoji}{region_code}")), - check_time: Some(get_local_date_string()), - }; + return UnlockItem::checked_region(NETFLIX, "Yes", region_code); } } - let emoji = country_code_to_emoji("us"); - UnlockItem { - name: "Netflix".to_string(), - status: "Yes".to_string(), - region: Some(format!("{emoji}{}", "us")), - check_time: Some(get_local_date_string()), - } + UnlockItem::checked_region(NETFLIX, "Yes", "us") } Err(e) => { logging!(error, Type::Network, "获取Netflix区域信息失败: {e}"); - UnlockItem { - name: "Netflix".to_string(), - status: "Yes (但无法获取区域)".to_string(), - region: None, - check_time: Some(get_local_date_string()), - } + netflix_item("Yes (但无法获取区域)", None) } } } else { - UnlockItem { - name: "Netflix".to_string(), - status: format!("Failed (状态码: {status1}_{status2}"), - region: None, - check_time: Some(get_local_date_string()), - } + netflix_item(format!("Failed (状态码: {status1}_{status2}"), None) } } @@ -150,12 +103,7 @@ async fn check_netflix_cdn(client: &Client) -> UnlockItem { match client.get(url).timeout(std::time::Duration::from_secs(30)).send().await { Ok(response) => { if response.status().as_u16() == 403 { - return UnlockItem { - name: "Netflix".to_string(), - status: "No (IP Banned By Netflix)".to_string(), - region: None, - check_time: Some(get_local_date_string()), - }; + return netflix_item("No (IP Banned By Netflix)", None); } match response.json::().await { @@ -165,41 +113,20 @@ async fn check_netflix_cdn(client: &Client) -> UnlockItem { && let Some(location) = targets[0].get("location") && let Some(country) = location.get("country").and_then(|c| c.as_str()) { - let emoji = country_code_to_emoji(country); - return UnlockItem { - name: "Netflix".to_string(), - status: "Yes".to_string(), - region: Some(format!("{emoji}{country}")), - check_time: Some(get_local_date_string()), - }; + return UnlockItem::checked_region(NETFLIX, "Yes", country); } - UnlockItem { - name: "Netflix".to_string(), - status: "Unknown".to_string(), - region: None, - check_time: Some(get_local_date_string()), - } + netflix_item("Unknown", None) } Err(e) => { logging!(error, Type::Network, "解析Fast.com API响应失败: {e}"); - UnlockItem { - name: "Netflix".to_string(), - status: "Failed (解析错误)".to_string(), - region: None, - check_time: Some(get_local_date_string()), - } + netflix_item("Failed (解析错误)", None) } } } Err(e) => { logging!(error, Type::Network, "Fast.com API请求失败: {e}"); - UnlockItem { - name: "Netflix".to_string(), - status: "Failed (CDN API)".to_string(), - region: None, - check_time: Some(get_local_date_string()), - } + netflix_item("Failed (CDN API)", None) } } } diff --git a/src-tauri/src/cmd/media_unlock_checker/prime_video.rs b/src-tauri/src/cmd/media_unlock_checker/prime_video.rs index ff356e880..d23d46cfa 100644 --- a/src-tauri/src/cmd/media_unlock_checker/prime_video.rs +++ b/src-tauri/src/cmd/media_unlock_checker/prime_video.rs @@ -4,32 +4,22 @@ use reqwest::Client; use clash_verge_logging::{Type, logging}; use super::UnlockItem; -use super::utils::{country_code_to_emoji, get_local_date_string}; + +const PRIME_VIDEO: &str = "Prime Video"; + +fn prime_video_item(status: impl Into, region: Option) -> UnlockItem { + UnlockItem::checked(PRIME_VIDEO, status, region) +} pub(super) async fn check_prime_video(client: &Client) -> UnlockItem { let url = "https://www.primevideo.com"; let result = client.get(url).send().await; - if result.is_err() { - return UnlockItem { - name: "Prime Video".to_string(), - status: "Failed (Network Connection)".to_string(), - region: None, - check_time: Some(get_local_date_string()), - }; - } - let response = match result { Ok(response) => response, - Err(e) => { - logging!(error, Type::Network, "Failed to get Prime Video response: {}", e); - return UnlockItem { - name: "Prime Video".to_string(), - status: "Failed (Network Connection)".to_string(), - region: None, - check_time: Some(get_local_date_string()), - }; + Err(_) => { + return prime_video_item("Failed (Network Connection)", None); } }; @@ -46,12 +36,7 @@ pub(super) async fn check_prime_video(client: &Client) -> UnlockItem { "Failed to compile Prime Video region regex: {}", e ); - return UnlockItem { - name: "Prime Video".to_string(), - status: "Failed (Regex Error)".to_string(), - region: None, - check_time: Some(get_local_date_string()), - }; + return prime_video_item("Failed (Regex Error)", None); } }; let region_code = region_re @@ -59,45 +44,19 @@ pub(super) async fn check_prime_video(client: &Client) -> UnlockItem { .and_then(|caps| caps.get(1).map(|m| m.as_str().to_string())); if is_blocked { - return UnlockItem { - name: "Prime Video".to_string(), - status: "No (Service Not Available)".to_string(), - region: None, - check_time: Some(get_local_date_string()), - }; + return prime_video_item("No (Service Not Available)", None); } if let Some(region) = region_code { - let emoji = country_code_to_emoji(®ion); - return UnlockItem { - name: "Prime Video".to_string(), - status: "Yes".to_string(), - region: Some(format!("{emoji}{region}")), - check_time: Some(get_local_date_string()), - }; + return UnlockItem::checked_region(PRIME_VIDEO, "Yes", ®ion); } if !is_blocked { - return UnlockItem { - name: "Prime Video".to_string(), - status: "Failed (Error: PAGE ERROR)".to_string(), - region: None, - check_time: Some(get_local_date_string()), - }; + return prime_video_item("Failed (Error: PAGE ERROR)", None); } - UnlockItem { - name: "Prime Video".to_string(), - status: "Failed (Error: Unknown Region)".to_string(), - region: None, - check_time: Some(get_local_date_string()), - } + prime_video_item("Failed (Error: Unknown Region)", None) } - Err(_) => UnlockItem { - name: "Prime Video".to_string(), - status: "Failed (Error: Cannot read response)".to_string(), - region: None, - check_time: Some(get_local_date_string()), - }, + Err(_) => prime_video_item("Failed (Error: Cannot read response)", None), } } diff --git a/src-tauri/src/cmd/media_unlock_checker/spotify.rs b/src-tauri/src/cmd/media_unlock_checker/spotify.rs index 88cdea234..17805fa7a 100644 --- a/src-tauri/src/cmd/media_unlock_checker/spotify.rs +++ b/src-tauri/src/cmd/media_unlock_checker/spotify.rs @@ -1,7 +1,6 @@ use reqwest::{Client, Url}; use super::UnlockItem; -use super::utils::{country_code_to_emoji, get_local_date_string}; pub(super) async fn check_spotify(client: &Client) -> UnlockItem { let url = "https://www.spotify.com/api/content/v1/country-selector?platform=web&format=json"; @@ -15,19 +14,9 @@ pub(super) async fn check_spotify(client: &Client) -> UnlockItem { let region = extract_region(&final_url).or_else(|| extract_region_from_body(&body)); let status = determine_status(status_code.as_u16(), &body); - UnlockItem { - name: "Spotify".to_string(), - status: status.to_string(), - region, - check_time: Some(get_local_date_string()), - } + UnlockItem::checked("Spotify", status, region) } - Err(_) => UnlockItem { - name: "Spotify".to_string(), - status: "Failed".to_string(), - region: None, - check_time: Some(get_local_date_string()), - }, + Err(_) => UnlockItem::checked("Spotify", "Failed", None), } } @@ -58,8 +47,7 @@ fn extract_region(url: &Url) -> Option { let country_code = first_segment.split('-').next().unwrap_or(first_segment); let upper = country_code.to_uppercase(); - let emoji = country_code_to_emoji(&upper); - Some(format!("{emoji}{upper}")) + Some(UnlockItem::region_label(&upper)) } fn extract_region_from_body(body: &str) -> Option { @@ -70,8 +58,7 @@ fn extract_region_from_body(body: &str) -> Option { if let Some(end) = rest.find('"') { let code = rest[..end].to_uppercase(); if !code.is_empty() { - let emoji = country_code_to_emoji(&code); - return Some(format!("{emoji}{code}")); + return Some(UnlockItem::region_label(&code)); } } } diff --git a/src-tauri/src/cmd/media_unlock_checker/tiktok.rs b/src-tauri/src/cmd/media_unlock_checker/tiktok.rs index c3bb46ac1..af320a639 100644 --- a/src-tauri/src/cmd/media_unlock_checker/tiktok.rs +++ b/src-tauri/src/cmd/media_unlock_checker/tiktok.rs @@ -4,7 +4,6 @@ use regex::Regex; use reqwest::Client; use super::UnlockItem; -use super::utils::{country_code_to_emoji, get_local_date_string}; pub(super) async fn check_tiktok(client: &Client) -> UnlockItem { let trace_url = "https://www.tiktok.com/cdn-cgi/trace"; @@ -38,12 +37,7 @@ pub(super) async fn check_tiktok(client: &Client) -> UnlockItem { } } - UnlockItem { - name: "TikTok".to_string(), - status, - region, - check_time: Some(get_local_date_string()), - } + UnlockItem::checked("TikTok", status, region) } fn determine_status(status: u16, body: &str) -> &'static str { @@ -78,8 +72,7 @@ fn extract_region_from_body(body: &str) -> Option { let raw = matched.as_str(); let country_code = raw.split('-').next().unwrap_or(raw).to_uppercase(); if !country_code.is_empty() { - let emoji = country_code_to_emoji(&country_code); - return Some(format!("{emoji}{country_code}")); + return Some(UnlockItem::region_label(&country_code)); } } diff --git a/src-tauri/src/cmd/media_unlock_checker/types.rs b/src-tauri/src/cmd/media_unlock_checker/types.rs index fcb3ea23a..5f21e8477 100644 --- a/src-tauri/src/cmd/media_unlock_checker/types.rs +++ b/src-tauri/src/cmd/media_unlock_checker/types.rs @@ -1,5 +1,7 @@ use serde::{Deserialize, Serialize}; +use super::utils::{country_code_to_emoji, get_local_date_string}; + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct UnlockItem { pub name: String, @@ -9,6 +11,24 @@ pub struct UnlockItem { } impl UnlockItem { + pub fn checked(name: &str, status: impl Into, region: Option) -> Self { + Self { + name: name.to_string(), + status: status.into(), + region, + check_time: Some(get_local_date_string()), + } + } + + pub fn checked_region(name: &str, status: impl Into, country_code: &str) -> Self { + Self::checked(name, status, Some(Self::region_label(country_code))) + } + + pub fn region_label(country_code: &str) -> String { + let emoji = country_code_to_emoji(country_code); + format!("{emoji}{country_code}") + } + pub fn pending(name: &str) -> Self { Self { name: name.to_string(), diff --git a/src-tauri/src/cmd/media_unlock_checker/youtube.rs b/src-tauri/src/cmd/media_unlock_checker/youtube.rs index 0277509d4..510437ce8 100644 --- a/src-tauri/src/cmd/media_unlock_checker/youtube.rs +++ b/src-tauri/src/cmd/media_unlock_checker/youtube.rs @@ -4,16 +4,10 @@ use reqwest::Client; use clash_verge_logging::{Type, logging}; use super::UnlockItem; -use super::utils::{country_code_to_emoji, get_local_date_string}; pub(super) async fn check_youtube_premium(client: &Client) -> UnlockItem { let url = "https://www.youtube.com/premium?hl=en"; - let item = |status: &str, region: Option| UnlockItem { - name: "YouTube Premium".to_string(), - status: status.to_string(), - region, - check_time: Some(get_local_date_string()), - }; + let item = |status: &str, region: Option| UnlockItem::checked("YouTube Premium", status, region); match client.get(url).send().await { Ok(response) => { @@ -38,10 +32,7 @@ pub(super) async fn check_youtube_premium(client: &Client) -> UnlockItem { None } }) - .map(|country_code| { - let emoji = country_code_to_emoji(&country_code); - format!("{emoji}{country_code}") - }); + .map(|country_code| UnlockItem::region_label(&country_code)); let status = if body_lower.contains("youtube premium is not available in your country") || body_lower.contains("premium is not available in your country")