refactor: simplify media unlock results

This commit is contained in:
Tunglies
2026-05-18 23:14:27 +08:00
parent d95e9ffab9
commit 977783e39c
12 changed files with 167 additions and 548 deletions
@@ -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)
}
@@ -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),
}
}
@@ -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<UnlockItem> {
let mut results = Vec::new();
@@ -23,10 +22,7 @@ pub(super) async fn check_chatgpt_combined(client: &Client) -> Vec<UnlockItem> {
}
}
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<UnlockItem> {
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
}
@@ -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),
}
}
@@ -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<String>, region: Option<String>) -> UnlockItem {
UnlockItem::checked(DISNEY_PLUS, status, region)
}
async fn fetch_main_page_region(client: &Client) -> Option<String> {
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<UnlockItem> {
let region = fetch_main_page_region(client).await?;
Some(disney_item(
"Yes",
Some(format!("{} (from main page)", UnlockItem::region_label(&region))),
))
}
#[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::Value, _> = 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::<String>() + "..."
),
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(&region);
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::<String>() + "...",
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(&region);
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", &region);
}
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(&region);
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(&region);
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(&region))),
),
Some(true) => UnlockItem::checked_region(DISNEY_PLUS, "Yes", &region),
None => disney_item(format!("Failed (Error: Unknown region status for {region})"), None),
}
}
@@ -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(),
}
@@ -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<String>, region: Option<String>) -> 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::<Value>().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)
}
}
}
@@ -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<String>, region: Option<String>) -> 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(&region);
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", &region);
}
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),
}
}
@@ -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<String> {
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<String> {
@@ -70,8 +58,7 @@ fn extract_region_from_body(body: &str) -> Option<String> {
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));
}
}
}
@@ -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<String> {
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));
}
}
@@ -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<String>, region: Option<String>) -> 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<String>, 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(),
@@ -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<String>| UnlockItem {
name: "YouTube Premium".to_string(),
status: status.to_string(),
region,
check_time: Some(get_local_date_string()),
};
let item = |status: &str, region: Option<String>| 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")