From 6e0a46f5d6c3db669dc68974890f8efeaacfeb63 Mon Sep 17 00:00:00 2001 From: cxw620 Date: Tue, 13 Dec 2022 18:28:30 +0800 Subject: [PATCH 01/18] temporarily disable retry [pchpub commit] --- src/mods/upstream_res.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mods/upstream_res.rs b/src/mods/upstream_res.rs index b23dfae3..5e7a8583 100644 --- a/src/mods/upstream_res.rs +++ b/src/mods/upstream_res.rs @@ -273,7 +273,7 @@ pub async fn get_upstream_bili_account_info( "[GET USER_INFO][U] AK {} | Get UserInfo failed -663. Upstream Reply -> {}", access_key, upstream_raw_resp_json ); - update_cached_user_info_background(access_key.to_string(), bili_runtime).await; + // update_cached_user_info_background(access_key.to_string(), bili_runtime).await; let health_report_type = HealthReportType::Others(HealthData { area_num: 0, is_200_ok: true, From 6dcdcfb0f9009a78e16daee5d398f788884474d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E4=BD=B3=E6=B4=9B?= <73123406+pchpub@users.noreply.github.com> Date: Tue, 13 Dec 2022 19:37:03 +0800 Subject: [PATCH 02/18] add: retry_num for user_info --- src/mods/background_tasks.rs | 7 ++++--- src/mods/handler.rs | 2 ++ src/mods/types.rs | 2 +- src/mods/upstream_res.rs | 13 +++++++++---- src/mods/user_info.rs | 4 ++++ 5 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/mods/background_tasks.rs b/src/mods/background_tasks.rs index 103fd684..7a125dd8 100644 --- a/src/mods/background_tasks.rs +++ b/src/mods/background_tasks.rs @@ -67,11 +67,12 @@ pub async fn update_cached_area_background( pub async fn update_cached_user_info_background( access_key: String, + retry_num: u8, bili_runtime: &BiliRuntime<'_>, ) { trace!("[BACKGROUND TASK] AK {access_key} -> Accept UserInfo Cache Refresh Task..."); let background_task_data = - BackgroundTaskType::Cache(CacheTask::UserInfoCacheRefresh(access_key)); + BackgroundTaskType::Cache(CacheTask::UserInfoCacheRefresh(access_key,retry_num)); bili_runtime.send_task(background_task_data).await } @@ -217,7 +218,7 @@ pub async fn background_task_run( } }, BackgroundTaskType::Cache(value) => match value { - CacheTask::UserInfoCacheRefresh(access_key) => { + CacheTask::UserInfoCacheRefresh(access_key,retry_num) => { let appkey = "1d8b6e7d45233436"; let appsec = "560c52ccd288fed045859ed18bffd973"; // let user_agent = "Dalvik/2.1.0 (Linux; U; Android 11; 21091116AC Build/RP1A.200720.011)"; @@ -227,7 +228,7 @@ pub async fn background_task_run( is_th: false, user_agent: &user_agent, ..Default::default() - }, true, &bili_runtime).await { + }, true, retry_num, &bili_runtime).await { Ok(new_user_info) => match get_blacklist_info(&new_user_info, bili_runtime).await { Ok(_) => Ok(()), Err(value) => Err(format!("[BACKGROUND TASK] UID {} | Refreshing blacklist info failed, ErrMsg: {}", new_user_info.uid, value.to_string())), diff --git a/src/mods/handler.rs b/src/mods/handler.rs index 4a21e614..fb4ed5c5 100644 --- a/src/mods/handler.rs +++ b/src/mods/handler.rs @@ -163,6 +163,7 @@ pub async fn handle_playurl_request(req: &HttpRequest, is_app: bool, is_th: bool params.appsec, ¶ms, false, + 1, &bili_runtime, ) .await @@ -407,6 +408,7 @@ pub async fn handle_search_request(req: &HttpRequest, is_app: bool, is_th: bool) params.appsec, ¶ms, false, + 1, &bili_runtime, ) .await diff --git a/src/mods/types.rs b/src/mods/types.rs index ce5df0e3..a482487d 100644 --- a/src/mods/types.rs +++ b/src/mods/types.rs @@ -601,7 +601,7 @@ pub enum HealthTask { HealthReport(HealthReportType), } pub enum CacheTask { - UserInfoCacheRefresh(String), + UserInfoCacheRefresh(String,u8), PlayurlCacheRefresh(PlayurlParamsStatic), ProactivePlayurlCacheRefresh, EpInfoCacheRefresh(bool, Vec), diff --git a/src/mods/upstream_res.rs b/src/mods/upstream_res.rs index 5e7a8583..dcdfc351 100644 --- a/src/mods/upstream_res.rs +++ b/src/mods/upstream_res.rs @@ -26,6 +26,7 @@ pub async fn get_upstream_bili_account_info( _appsec: &str, _is_app: bool, user_agent: &str, + retry_num: u8, bili_runtime: &BiliRuntime<'_>, ) -> Result { use rand::Rng; @@ -173,7 +174,9 @@ pub async fn get_upstream_bili_account_info( match appkey { "84956560bc028eb7" | "85eb6835b0a1034e" => { // 还是迂回更新其用户信息试一下 - update_cached_user_info_background(access_key.to_string(), bili_runtime).await; + if retry_num != 0 { + update_cached_user_info_background(access_key.to_string(), retry_num-1,bili_runtime).await; + } Err(EType::OtherError( -10403, "不兼容的APPKEY, 请升级油猴脚本或其他你正在用的客户端!", @@ -273,7 +276,9 @@ pub async fn get_upstream_bili_account_info( "[GET USER_INFO][U] AK {} | Get UserInfo failed -663. Upstream Reply -> {}", access_key, upstream_raw_resp_json ); - // update_cached_user_info_background(access_key.to_string(), bili_runtime).await; + if retry_num != 0 { + update_cached_user_info_background(access_key.to_string(), retry_num-1,bili_runtime).await; + } let health_report_type = HealthReportType::Others(HealthData { area_num: 0, is_200_ok: true, @@ -286,7 +291,7 @@ pub async fn get_upstream_bili_account_info( }, is_custom: true, custom_message: format!( - "[GET USER_INFO][U] -663错误! 理论上已修复, 仍出现此错误请提issue\nDevice: {}, APPKEY: {}, AK: {}, TS: {}", + "[GET USER_INFO][U] -663错误! 正在尝试修复\nDevice: {}, APPKEY: {}, AK: {}, TS: {}", mobi_app, appkey, access_key, ts ), }); @@ -603,7 +608,7 @@ pub async fn get_upstream_bili_playurl( if !params.is_vip { if let Ok(value) = check_vip_status_from_playurl(playurl_type, &upstream_raw_resp_json) { if value { - update_cached_user_info_background(params.access_key.to_string(), bili_runtime) + update_cached_user_info_background(params.access_key.to_string(), 1, bili_runtime) .await; match get_ep_need_vip(params.ep_id, bili_runtime).await { Some(ep_need_vip) => { diff --git a/src/mods/user_info.rs b/src/mods/user_info.rs index 45035efa..95996c3d 100644 --- a/src/mods/user_info.rs +++ b/src/mods/user_info.rs @@ -16,6 +16,7 @@ pub async fn get_user_info( appsec: &str, params: &T, force_update: bool, + retry_num: u8, bili_runtime: &BiliRuntime<'_>, ) -> Result { // detect web request @@ -41,6 +42,7 @@ pub async fn get_user_info( appsec, is_app, params.user_agent(), + retry_num, bili_runtime, ) .await @@ -88,6 +90,7 @@ pub async fn get_user_info( appsec, is_app, params.user_agent(), + retry_num, bili_runtime, ) .await @@ -354,6 +357,7 @@ pub async fn resign_user_info( params.appsec, params, false, + 1, bili_runtime, ) .await From 3ea33bd8ed352544c75a8b467c999ea8ea1be27b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E4=BD=B3=E6=B4=9B?= <73123406+pchpub@users.noreply.github.com> Date: Tue, 13 Dec 2022 21:24:58 +0800 Subject: [PATCH 03/18] change: use new api for userinfo --- src/main.rs | 9 ++++++ src/mods/upstream_res.rs | 70 ++++++++++++++++++++++++---------------- 2 files changed, 51 insertions(+), 28 deletions(-) diff --git a/src/main.rs b/src/main.rs index 8e09cd62..36590058 100644 --- a/src/main.rs +++ b/src/main.rs @@ -135,6 +135,15 @@ lazy_static! { } fn main() -> std::io::Result<()> { + // 拿来生成signed_url挺方便的 此处测试用 + // let mut req_params = "access_key=ccd0991e90ab67631b5b644142890733&appkey=783bbb7264451d82&ts=1670929680"; + // let mut signed_params = format!("{req_params}&sign="); + // let mut sign = crypto::md5::Md5::new(); + // crypto::digest::Digest::input_str(&mut sign, &format!("{req_params}2653583c8873dea268ab9386918b1d65")); + // let md5_sign = crypto::digest::Digest::result_str(&mut sign); + // signed_params.push_str(&md5_sign); + // println!("{signed_params}"); + // init log use chrono::Local; use std::io::Write; diff --git a/src/mods/upstream_res.rs b/src/mods/upstream_res.rs index dcdfc351..9d0a8c69 100644 --- a/src/mods/upstream_res.rs +++ b/src/mods/upstream_res.rs @@ -73,7 +73,7 @@ pub async fn get_upstream_bili_account_info( headers.append("x-bili-aurora-zone: sh001").unwrap(); headers.append("app-key: android64").unwrap(); - let api = "https://app.bilibili.com/x/v2/account/myinfo"; + let api = "https://app.bilibili.com/x/v2/account/mine"; let (signed_url, sign) = build_signed_url!(api, req_vec, appsec); debug!( @@ -142,33 +142,47 @@ pub async fn get_upstream_bili_account_info( }; match code { 0 => { - let output_struct = UserInfo { - code: 0, - access_key: String::from(access_key), - uid: upstream_raw_resp_json["data"]["mid"].as_u64().unwrap(), - vip_expire_time: upstream_raw_resp_json["data"]["vip"]["due_date"] - .as_u64() - .unwrap(), - expire_time: { - if ts - < upstream_raw_resp_json["data"]["vip"]["due_date"] - .as_u64() - .unwrap() - && upstream_raw_resp_json["data"]["vip"]["due_date"] - .as_u64() - .unwrap() - < ts + 25 * 24 * 60 * 60 * 1000 - { - upstream_raw_resp_json["data"]["vip"]["due_date"] - .as_u64() - .unwrap() - } else { - ts + 25 * 24 * 60 * 60 * 1000 - } - }, //用户状态25天强制更新 - }; - update_user_info_cache(&output_struct, bili_runtime).await; - Ok(output_struct) + if upstream_raw_resp_json["data"]["mid"].as_u64().unwrap_or(0) == 0 {// accesskey失效时mid为0 + let output_struct = UserInfo { + code, + expire_time: ts + 1 * 60 * 60 * 1000, // 未登录缓存1h,防止高频请求b站服务器 + ..Default::default() + }; + update_user_info_cache(&output_struct, bili_runtime).await; + error!( + "[GET USER_INFO][U] AK {} | Get UserInfo failed -101. Upstream Reply -> {}", + access_key, upstream_raw_resp_json + ); + Err(EType::UserNotLoginedError) + }else{ + let output_struct = UserInfo { + code: 0, + access_key: String::from(access_key), + uid: upstream_raw_resp_json["data"]["mid"].as_u64().unwrap(), + vip_expire_time: upstream_raw_resp_json["data"]["vip"]["due_date"] + .as_u64() + .unwrap(), + expire_time: { + if ts + < upstream_raw_resp_json["data"]["vip"]["due_date"] + .as_u64() + .unwrap() + && upstream_raw_resp_json["data"]["vip"]["due_date"] + .as_u64() + .unwrap() + < ts + 25 * 24 * 60 * 60 * 1000 + { + upstream_raw_resp_json["data"]["vip"]["due_date"] + .as_u64() + .unwrap() + } else { + ts + 25 * 24 * 60 * 60 * 1000 + } + }, //用户状态25天强制更新 + }; + update_user_info_cache(&output_struct, bili_runtime).await; + Ok(output_struct) + } } -404 => { match appkey { From ff2b7d77cc38622c7a9782dafc80a28bf65d408a Mon Sep 17 00:00:00 2001 From: cxw620 Date: Tue, 13 Dec 2022 21:32:08 +0800 Subject: [PATCH 04/18] feat: allow cdn proxy api host --- config.example.json | 2 ++ src/mods/types.rs | 14 +++++++++++++- src/mods/upstream_res.rs | 2 +- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/config.example.json b/config.example.json index f53611c6..bce184ca 100644 --- a/config.example.json +++ b/config.example.json @@ -27,6 +27,8 @@ "hk_web_search_api": "https://api.bilibili.com/x/web-interface/search/type", "th_web_search_api": "https://app.biliintl.com/intl/gateway/v2/app/search/type", "th_app_season_api": "https://app.biliintl.com/intl/gateway/v2/ogv/view/app/season", + "general_api_bilibili_com_proxy_api": "", + "general_app_bilibili_com_proxy_api": "", "th_app_season_sub_api": "https://示例.永雏塔菲.我爱你/?season_id=", "th_app_season_sub_name": "示例永雏塔菲字幕组", "th_app_season_sub_open": false, diff --git a/src/mods/types.rs b/src/mods/types.rs index a482487d..fd314e54 100644 --- a/src/mods/types.rs +++ b/src/mods/types.rs @@ -81,6 +81,10 @@ pub struct BiliConfig { pub cn_proxy_accesskey_open: bool, pub th_proxy_subtitle_url: String, pub th_proxy_subtitle_open: bool, + #[serde(default = "default_api_bilibili_com")] + pub general_api_bilibili_com_proxy_api: String, + #[serde(default = "default_app_bilibili_com")] + pub general_app_bilibili_com_proxy_api: String, pub aid: u64, pub aid_replace_open: bool, #[serde(default = "default_hashmap_false")] @@ -601,7 +605,7 @@ pub enum HealthTask { HealthReport(HealthReportType), } pub enum CacheTask { - UserInfoCacheRefresh(String,u8), + UserInfoCacheRefresh(String, u8), PlayurlCacheRefresh(PlayurlParamsStatic), ProactivePlayurlCacheRefresh, EpInfoCacheRefresh(bool, Vec), @@ -1406,6 +1410,14 @@ fn default_string() -> String { "".to_string() } +fn default_api_bilibili_com() -> String { + "api.bilibili.com".to_string() +} + +fn default_app_bilibili_com() -> String { + "app.bilibili.com".to_string() +} + pub fn random_string() -> String { let rand_sign = rand::thread_rng() .sample_iter(&rand::distributions::Alphanumeric) diff --git a/src/mods/upstream_res.rs b/src/mods/upstream_res.rs index 9d0a8c69..61988a34 100644 --- a/src/mods/upstream_res.rs +++ b/src/mods/upstream_res.rs @@ -73,7 +73,7 @@ pub async fn get_upstream_bili_account_info( headers.append("x-bili-aurora-zone: sh001").unwrap(); headers.append("app-key: android64").unwrap(); - let api = "https://app.bilibili.com/x/v2/account/mine"; + let api = format!("https://{}/x/v2/account/mine", bili_runtime.config.general_app_bilibili_com_proxy_api); let (signed_url, sign) = build_signed_url!(api, req_vec, appsec); debug!( From 5ab407bdfa999e3a9af418ff87838779a3b1b1be Mon Sep 17 00:00:00 2001 From: cxw620 Date: Tue, 13 Dec 2022 22:40:02 +0800 Subject: [PATCH 05/18] feat: advance fake ua --- src/mods/background_tasks.rs | 11 +++--- src/mods/tools.rs | 27 +------------ src/mods/types.rs | 73 ++++++++++++++++++++++++++++++++++++ src/mods/upstream_res.rs | 30 +++++++++++---- src/mods/user_info.rs | 1 - 5 files changed, 101 insertions(+), 41 deletions(-) diff --git a/src/mods/background_tasks.rs b/src/mods/background_tasks.rs index 7a125dd8..85d5e5b2 100644 --- a/src/mods/background_tasks.rs +++ b/src/mods/background_tasks.rs @@ -3,9 +3,8 @@ use super::ep_info::update_ep_vip_status_cache; use super::health::*; use super::push::send_report; use super::request::async_getwebpage; -use super::tools::build_random_useragent; use super::types::{ - Area, BackgroundTaskType, BiliRuntime, CacheTask, CacheType, EpInfo, HealthReportType, + Area, BackgroundTaskType, BiliRuntime, CacheTask, CacheType, EpInfo, FakeUA, HealthReportType, HealthTask, PlayurlParams, PlayurlParamsStatic, ReqType, }; use super::upstream_res::*; @@ -72,7 +71,7 @@ pub async fn update_cached_user_info_background( ) { trace!("[BACKGROUND TASK] AK {access_key} -> Accept UserInfo Cache Refresh Task..."); let background_task_data = - BackgroundTaskType::Cache(CacheTask::UserInfoCacheRefresh(access_key,retry_num)); + BackgroundTaskType::Cache(CacheTask::UserInfoCacheRefresh(access_key, retry_num)); bili_runtime.send_task(background_task_data).await } @@ -218,11 +217,11 @@ pub async fn background_task_run( } }, BackgroundTaskType::Cache(value) => match value { - CacheTask::UserInfoCacheRefresh(access_key,retry_num) => { + CacheTask::UserInfoCacheRefresh(access_key, retry_num) => { let appkey = "1d8b6e7d45233436"; let appsec = "560c52ccd288fed045859ed18bffd973"; // let user_agent = "Dalvik/2.1.0 (Linux; U; Android 11; 21091116AC Build/RP1A.200720.011)"; - let user_agent = build_random_useragent(); + let user_agent = FakeUA::Web.gen(); match get_user_info(&access_key, appkey, appsec, &PlayurlParams { is_app: true, is_th: false, @@ -284,7 +283,7 @@ pub async fn background_task_run( // // 没弹幕/评论区还不如去看RC-RAWS let bili_user_status_api: &str = "https://api.bilibili.com/pgc/view/web/season/user/status"; - let user_agent = build_random_useragent(); + let user_agent = FakeUA::Web.gen(); // let user_agent = "Dalvik/2.1.0 (Linux; U; Android 11; 21091116AC Build/RP1A.200720.011)"; let area_to_check = [ ( diff --git a/src/mods/tools.rs b/src/mods/tools.rs index 1d977003..0015d28f 100644 --- a/src/mods/tools.rs +++ b/src/mods/tools.rs @@ -1,10 +1,8 @@ -use log::{debug, error}; -use rand::Rng; - use super::{ request::{download, getwebpage}, types::PlayurlType, }; +use log::{debug, error}; use std::env; use std::path::PathBuf; use std::thread; @@ -432,26 +430,3 @@ pub fn vec_to_string(vec: &Vec, delimiter: &str) -> Str } } } - -pub fn build_random_useragent() -> &'static str { - let user_agents = [ - "Dalvik/2.1.0 (Linux; U; Android 13; Pixel 6 Pro Build/TQ1A.221205.011)", - "Dalvik/2.1.0 (Linux; U; Android 13; SM-S9080 Build/TP1A.220624.014)", - "Dalvik/2.1.0 (Linux; U; Android 13; 2201122C Build/TKQ1.220807.001)", - "Dalvik/2.1.0 (Linux; U; Android 12; JEF-AN00 Build/HUAWEIJEF-AN00)", - "Dalvik/2.1.0 (Linux; U; Android 12; VOG-AL10 Build/HUAWEIVOG-AL10)", - "Dalvik/2.1.0 (Linux; U; Android 12; ELS-AN00 Build/HUAWEIELS-AN00)", - "Dalvik/2.1.0 (Linux; U; Android 12; NOH-AN01 Build/HUAWEINOH-AN01)", - "Dalvik/2.1.0 (Linux; U; Android 11; SKW-A0 Build/SKYW2203210CN00MR1)", - "Dalvik/2.1.0 (Linux; U; Android 11; 21091116AC Build/RP1A.200720.011)", - "Dalvik/2.1.0 (Linux; U; Android 10; Redmi K30 MIUI/V12.0.5.0.QGHCNXM)", - "Dalvik/2.1.0 (Linux; U; Android 10; VOG-AL10 Build/HUAWEIVOG-AL10)", - "Dalvik/2.1.0 (Linux; U; Android 10; JEF-AN00 Build/HUAWEIJEF-AN00)", - "Dalvik/2.1.0 (Linux; U; Android 10; VOG-AL10 Build/HUAWEIVOG-AL10)", - "Dalvik/2.1.0 (Linux; U; Android 10; ELS-AN00 Build/HUAWEIELS-AN00)", - "Dalvik/2.1.0 (Linux; U; Android 9; BND-AL10 Build/HONORBND-AL10)", - "Dalvik/2.1.0 (Linux; U; Android 9; ALP-AL00 Build/HUAWEIALP-AL00)", - "Dalvik/2.1.0 (Linux; U; Android 9; MIX 2 MIUI/V12.0.1.0.PDECNXM)", - ]; - user_agents[rand::thread_rng().gen_range(0..=16)] -} diff --git a/src/mods/types.rs b/src/mods/types.rs index fd314e54..a956b78a 100644 --- a/src/mods/types.rs +++ b/src/mods/types.rs @@ -1568,6 +1568,79 @@ impl Area { } } +pub enum FakeUA { + Web, // 网页版, 统一使用Chrome + Mobile, // 移动UA, 移动版Chrome + App, //App的UA, Dalvik开头的类型 + Bilibili, //Bilibili的UA, 类似Mozilla/5.0 BiliDroid/{6.80.0}{ (bbcallen@gmail.com) os/android model/M2012K11AC mobi_app/android build/6800300 channel/master innerVer/6800310 osVer/12 network/2 +} + +impl FakeUA { + #[inline] + fn gen_random_phone() -> (&'static str, &'static str, &'static str) { + let phones = [ + ("13", "Pixel 6 Pro", "TQ1A.221205.011"), + ("13", "SM-S9080", "TP1A.220624.014"), + ("13", "2201122C", "TKQ1.220807.001"), + ("12", "JEF-AN00", "HUAWEIJEF-AN00"), + ("12", "VOG-AL10", "HUAWEIVOG-AL10"), + ("12", "ELS-AN00", "HUAWEIELS-AN00"), + ("12", "NOH-AN01", "HUAWEINOH-AN01"), + ("11", "SKW-A0", "SKYW2203210CN00MR1"), + ("11", "21091116AC", "RP1A.200720.011"), + ("10", "VOG-AL10", "HUAWEIVOG-AL10"), + ("10", "JEF-AN00", "HUAWEIJEF-AN00"), + ("10", "VOG-AL10", "HUAWEIVOG-AL10"), + ("10", "ELS-AN00", "HUAWEIELS-AN00"), + ("9", "BND-AL10", "HONORBND-AL10"), + ("9", "ALP-AL00", "HUAWEIALP-AL00"), + ]; + phones[rand::thread_rng().gen_range(0..=14)] + } + #[inline] + pub fn gen(&self) -> String { + match self { + FakeUA::Web => { + // 非常粗暴的做法 + format!("Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/{}.0.0.0 Safari/537.36", rand::thread_rng().gen_range(90..=108)) + } + FakeUA::Mobile => { + let phone = FakeUA::gen_random_phone(); + format!("Mozilla/5.0 (Linux; U; Android {}; {} Build/{}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/{}.0.0.0 Mobile Safari/537.36", phone.0, phone.1, phone.2, rand::thread_rng().gen_range(90..=108)) + } //虽然暂时用不到 + FakeUA::App => { + // 性能考虑才这么写, 虽然只是快一点, 100次循环format!()是7,073 ns/iter (+/- 277), String::with_capacity是2,387 ns/iter (+/- 163). + // "Dalvik/2.1.0 (Linux; U; Android 13; Pixel 6 Pro Build/TQ1A.221205.011)" + let mut user_agent = String::with_capacity(100); + let phone = FakeUA::gen_random_phone(); + user_agent.push_str("Dalvik/2.1.0 (Linux; U; Android "); + user_agent.push_str(phone.0); + user_agent.push_str("; "); + user_agent.push_str(phone.1); + user_agent.push_str(" Build/"); + user_agent.push_str(phone.2); + user_agent.push_str(")"); + user_agent + } + FakeUA::Bilibili => { + // Mozilla/5.0 BiliDroid/6.80.0 (bbcallen@gmail.com) os/android model/M2012K11AC mobi_app/android build/6800300 channel/master innerVer/6800310 osVer/12 network/2 + let mut user_agent = String::with_capacity(200); + let (android_version, _, phone_model) = FakeUA::gen_random_phone(); + user_agent.push_str( + "Mozilla/5.0 BiliDroid/6.80.0 (bbcallen@gmail.com) os/android model/", + ); + user_agent.push_str(phone_model); + user_agent.push_str( + " mobi_app/android build/6800300 channel/master innerVer/6800310 osVer/", + ); + user_agent.push_str(android_version); + user_agent.push_str("network/2"); + user_agent + } + } + } +} + /* * the following is user related struct & impl */ diff --git a/src/mods/upstream_res.rs b/src/mods/upstream_res.rs index 61988a34..7fe4ef58 100644 --- a/src/mods/upstream_res.rs +++ b/src/mods/upstream_res.rs @@ -10,7 +10,7 @@ use super::health::report_health; use super::request::async_getwebpage; use super::tools::{check_vip_status_from_playurl, get_mobi_app, remove_parameters_playurl}; use super::types::{ - Area, BiliRuntime, EType, EpInfo, HealthData, HealthReportType, PlayurlParams, ReqType, + Area, BiliRuntime, EType, EpInfo, FakeUA, HealthData, HealthReportType, PlayurlParams, ReqType, SearchParams, UpstreamReply, UserCerinfo, UserInfo, }; use crate::build_signed_url; @@ -25,7 +25,7 @@ pub async fn get_upstream_bili_account_info( _appkey: &str, _appsec: &str, _is_app: bool, - user_agent: &str, + _user_agent: &str, retry_num: u8, bili_runtime: &BiliRuntime<'_>, ) -> Result { @@ -73,7 +73,10 @@ pub async fn get_upstream_bili_account_info( headers.append("x-bili-aurora-zone: sh001").unwrap(); headers.append("app-key: android64").unwrap(); - let api = format!("https://{}/x/v2/account/mine", bili_runtime.config.general_app_bilibili_com_proxy_api); + let api = format!( + "https://{}/x/v2/account/mine", + bili_runtime.config.general_app_bilibili_com_proxy_api + ); let (signed_url, sign) = build_signed_url!(api, req_vec, appsec); debug!( @@ -85,7 +88,7 @@ pub async fn get_upstream_bili_account_info( &signed_url, bili_runtime.config.cn_proxy_accesskey_open, &bili_runtime.config.cn_proxy_accesskey_url, - user_agent, + &FakeUA::Bilibili.gen(), // 客户端请求UA和普通的不一样 "", Some(headers), ) @@ -142,7 +145,8 @@ pub async fn get_upstream_bili_account_info( }; match code { 0 => { - if upstream_raw_resp_json["data"]["mid"].as_u64().unwrap_or(0) == 0 {// accesskey失效时mid为0 + if upstream_raw_resp_json["data"]["mid"].as_u64().unwrap_or(0) == 0 { + // accesskey失效时mid为0 let output_struct = UserInfo { code, expire_time: ts + 1 * 60 * 60 * 1000, // 未登录缓存1h,防止高频请求b站服务器 @@ -154,7 +158,7 @@ pub async fn get_upstream_bili_account_info( access_key, upstream_raw_resp_json ); Err(EType::UserNotLoginedError) - }else{ + } else { let output_struct = UserInfo { code: 0, access_key: String::from(access_key), @@ -189,7 +193,12 @@ pub async fn get_upstream_bili_account_info( "84956560bc028eb7" | "85eb6835b0a1034e" => { // 还是迂回更新其用户信息试一下 if retry_num != 0 { - update_cached_user_info_background(access_key.to_string(), retry_num-1,bili_runtime).await; + update_cached_user_info_background( + access_key.to_string(), + retry_num - 1, + bili_runtime, + ) + .await; } Err(EType::OtherError( -10403, @@ -291,7 +300,12 @@ pub async fn get_upstream_bili_account_info( access_key, upstream_raw_resp_json ); if retry_num != 0 { - update_cached_user_info_background(access_key.to_string(), retry_num-1,bili_runtime).await; + update_cached_user_info_background( + access_key.to_string(), + retry_num - 1, + bili_runtime, + ) + .await; } let health_report_type = HealthReportType::Others(HealthData { area_num: 0, diff --git a/src/mods/user_info.rs b/src/mods/user_info.rs index 95996c3d..53172e7e 100644 --- a/src/mods/user_info.rs +++ b/src/mods/user_info.rs @@ -542,4 +542,3 @@ async fn get_accesskey_from_token( .await; Some((resign_info.access_key, resign_info.expire_time)) } - From ca3bec65b04256b40f8efdc0a9f49698ae0f8c0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E4=BD=B3=E6=B4=9B?= <73123406+pchpub@users.noreply.github.com> Date: Tue, 13 Dec 2022 23:43:39 +0800 Subject: [PATCH 06/18] change: add default value --- config.example.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config.example.json b/config.example.json index bce184ca..3e17cf37 100644 --- a/config.example.json +++ b/config.example.json @@ -27,8 +27,8 @@ "hk_web_search_api": "https://api.bilibili.com/x/web-interface/search/type", "th_web_search_api": "https://app.biliintl.com/intl/gateway/v2/app/search/type", "th_app_season_api": "https://app.biliintl.com/intl/gateway/v2/ogv/view/app/season", - "general_api_bilibili_com_proxy_api": "", - "general_app_bilibili_com_proxy_api": "", + "general_api_bilibili_com_proxy_api": "api.bilibili.com", + "general_app_bilibili_com_proxy_api": "app.bilibili.com", "th_app_season_sub_api": "https://示例.永雏塔菲.我爱你/?season_id=", "th_app_season_sub_name": "示例永雏塔菲字幕组", "th_app_season_sub_open": false, From 121021c072fe3e805b0d1801f2c3795096c50db9 Mon Sep 17 00:00:00 2001 From: cxw620 Date: Wed, 14 Dec 2022 00:35:10 +0800 Subject: [PATCH 07/18] feat: add more methods to get user's vip due_date --- src/mods/background_tasks.rs | 30 ++++----- src/mods/cache.rs | 1 + src/mods/upstream_res.rs | 124 ++++++++++++++++++++++++++++++++++- 3 files changed, 136 insertions(+), 19 deletions(-) diff --git a/src/mods/background_tasks.rs b/src/mods/background_tasks.rs index 85d5e5b2..6b2f1c7a 100644 --- a/src/mods/background_tasks.rs +++ b/src/mods/background_tasks.rs @@ -1,4 +1,4 @@ -use super::cache::update_cached_playurl; +use super::cache::{update_cached_playurl, update_user_info_cache}; use super::ep_info::update_ep_vip_status_cache; use super::health::*; use super::push::send_report; @@ -8,7 +8,7 @@ use super::types::{ HealthTask, PlayurlParams, PlayurlParamsStatic, ReqType, }; use super::upstream_res::*; -use super::user_info::{get_blacklist_info, get_user_info}; +use super::user_info::get_blacklist_info; use log::{debug, error, info, trace}; use serde_json::json; @@ -217,22 +217,18 @@ pub async fn background_task_run( } }, BackgroundTaskType::Cache(value) => match value { - CacheTask::UserInfoCacheRefresh(access_key, retry_num) => { - let appkey = "1d8b6e7d45233436"; - let appsec = "560c52ccd288fed045859ed18bffd973"; - // let user_agent = "Dalvik/2.1.0 (Linux; U; Android 11; 21091116AC Build/RP1A.200720.011)"; - let user_agent = FakeUA::Web.gen(); - match get_user_info(&access_key, appkey, appsec, &PlayurlParams { - is_app: true, - is_th: false, - user_agent: &user_agent, - ..Default::default() - }, true, retry_num, &bili_runtime).await { - Ok(new_user_info) => match get_blacklist_info(&new_user_info, bili_runtime).await { - Ok(_) => Ok(()), - Err(value) => Err(format!("[BACKGROUND TASK] UID {} | Refreshing blacklist info failed, ErrMsg: {}", new_user_info.uid, value.to_string())), + CacheTask::UserInfoCacheRefresh(access_key, _) => { + match get_upstream_bili_account_info_background(&access_key, &bili_runtime).await { + Ok(new_user_info) => { + update_user_info_cache(&new_user_info, &bili_runtime).await; + match get_blacklist_info(&new_user_info, bili_runtime).await { + Ok(_) => Ok(()), + Err(value) => Err(format!("[BACKGROUND TASK] UID {} | Refreshing blacklist info failed, ErrMsg: {}", new_user_info.uid, value.to_string())), + } + }, + Err(value) => { + Err(format!("[BACKGROUND TASK] ACCESS_KEY {} | Refreshing blacklist info failed, ErrMsg: {}", access_key, value.to_string())) }, - Err(value) => Err(format!("[BACKGROUND TASK] ACCESS_KEY {} | Refreshing blacklist info failed, ErrMsg: {}", access_key, value.to_string())), } } CacheTask::PlayurlCacheRefresh(params) => { diff --git a/src/mods/cache.rs b/src/mods/cache.rs index 8ebca443..7fb41576 100644 --- a/src/mods/cache.rs +++ b/src/mods/cache.rs @@ -209,6 +209,7 @@ pub async fn get_cached_user_info( .get_cache(&CacheType::UserInfo(access_key, 1145141919810)) .await { + // TODO: 处理expire_time, 主动刷新 Some(value) => Some(serde_json::from_str(&value).unwrap()), None => None, } diff --git a/src/mods/upstream_res.rs b/src/mods/upstream_res.rs index 7fe4ef58..f221a331 100644 --- a/src/mods/upstream_res.rs +++ b/src/mods/upstream_res.rs @@ -2,8 +2,8 @@ use super::background_tasks::{ update_cached_ep_vip_status_background, update_cached_user_info_background, }; use super::cache::{ - update_area_cache, update_blacklist_info_cache, update_cached_playurl, update_th_season_cache, - update_th_subtitle_cache, update_user_info_cache, + get_cached_user_info, update_area_cache, update_blacklist_info_cache, update_cached_playurl, + update_th_season_cache, update_th_subtitle_cache, update_user_info_cache, }; use super::ep_info::get_ep_need_vip; use super::health::report_health; @@ -17,6 +17,7 @@ use crate::build_signed_url; use chrono::prelude::*; use curl::easy::List; use log::{debug, error}; +use pcre2::bytes::Regex; use qstring::QString; use std::string::String; @@ -363,6 +364,125 @@ pub async fn get_upstream_bili_account_info( } } +pub async fn get_upstream_bili_account_info_background( + access_key: &str, + bili_runtime: &BiliRuntime<'_>, +) -> Result { + match get_upstream_bili_account_info(access_key, "", "", false, "", 0, bili_runtime).await { + Ok(value) => Ok(value), + Err(value) => match value { + EType::ServerReqError("-663错误, 您的账号似乎被鼠鼠风控了, 请稍后重试") => + { + let dt = Local::now(); + let ts = dt.timestamp_millis() as u64; + let cached_user_info = get_cached_user_info(access_key, bili_runtime).await; + let uid = match cached_user_info { + Some(value) => value.uid, + None => { + // TODO: 添加更多获取mid的方式 + if let Some(value) = + get_upstream_mid_from_playurl(access_key, bili_runtime).await + { + value + } else { + error!( + "[GET USER_INFO][U] AK {} | Get User's mid failed", + access_key + ); + return Err(EType::ServerGeneral); + } + } + }; + let vip_expire_time = get_upstream_vip_due_date_from_mid(uid, bili_runtime) + .await + .unwrap_or(0); + Ok(UserInfo { + code: 0, + access_key: access_key.to_owned(), + uid, + vip_expire_time, + expire_time: { + if ts < vip_expire_time && vip_expire_time < ts + 25 * 24 * 60 * 60 * 1000 { + vip_expire_time + } else { + ts + 25 * 24 * 60 * 60 * 1000 + } + }, + }) + } + _ => Err(value), + }, + } +} + +/// 从播放链接中提取mid +async fn get_upstream_mid_from_playurl( + access_key: &str, + bili_runtime: &BiliRuntime<'_>, +) -> Option { + if let Ok(playurl_string) = get_upstream_bili_playurl_background( + &mut PlayurlParams { + access_key, + ep_id: "425578", + area: "hk", + area_num: 2, + ep_need_vip: false, + user_agent: &FakeUA::App.gen(), + ..Default::default() + }, + bili_runtime, + ) + .await + { + // 感觉不太优雅... + let re = Regex::new(r#"(?m)&mid=(\d{0,})&platform"#).unwrap(); + let mid = match re.captures(playurl_string.as_bytes()) { + Ok(value) => { + if let Some(value) = value { + value + } else { + return None; + } + } + Err(value) => { + error!("REGEX CAPTURE FAILED: {:?}", value); + return None; + } + }; + let mid: u64 = String::from_utf8(mid[1].to_vec()).unwrap().parse().unwrap(); + Some(mid) + } else { + None + } +} +async fn get_upstream_vip_due_date_from_mid( + mid: u64, + bili_runtime: &BiliRuntime<'_>, +) -> Option { + let url = + format!("https://api.bilibili.com/x/space/wbi/acc/info?mid={mid}&token=&platform=web"); + match async_getwebpage( + &url, + bili_runtime.config.cn_proxy_accesskey_open, + &bili_runtime.config.cn_proxy_accesskey_url, + &FakeUA::Web.gen(), + "", + None, + ) + .await + { + Ok(data) => { + let data_json = if let Some(value) = data.json() { + value + } else { + return None; + }; + Some(data_json["data"]["vip"]["due_date"].as_u64().unwrap_or(0)) + } + Err(_) => None, + } +} + pub async fn get_upstream_blacklist_info( user_info: &UserInfo, bili_runtime: &BiliRuntime<'_>, From f7bd6fda570380fc43c7e12d76e48c57c240196d Mon Sep 17 00:00:00 2001 From: cxw620 Date: Wed, 14 Dec 2022 05:23:37 +0800 Subject: [PATCH 08/18] fix: web access_key invalid --- src/docs/error_code_define.md | 5 +- src/mods/background_tasks.rs | 65 ++++-- src/mods/cache.rs | 5 +- src/mods/handler.rs | 25 +- src/mods/tools.rs | 22 ++ src/mods/types.rs | 35 ++- src/mods/upstream_res.rs | 427 ++++++++++++++++------------------ src/mods/user_info.rs | 153 +++--------- 8 files changed, 343 insertions(+), 394 deletions(-) diff --git a/src/docs/error_code_define.md b/src/docs/error_code_define.md index 11f9e29a..77c06cdd 100644 --- a/src/docs/error_code_define.md +++ b/src/docs/error_code_define.md @@ -9,4 +9,7 @@ + + 大会员限制 + 地区限制 - + 黑名单限制 \ No newline at end of file + + 黑名单限制 + +# 自定义 ++ -999 无法获取用户mid(uid), \ No newline at end of file diff --git a/src/mods/background_tasks.rs b/src/mods/background_tasks.rs index 6b2f1c7a..7cfc0dd7 100644 --- a/src/mods/background_tasks.rs +++ b/src/mods/background_tasks.rs @@ -3,12 +3,13 @@ use super::ep_info::update_ep_vip_status_cache; use super::health::*; use super::push::send_report; use super::request::async_getwebpage; +use super::tools::get_user_mid_from_playurl; use super::types::{ - Area, BackgroundTaskType, BiliRuntime, CacheTask, CacheType, EpInfo, FakeUA, HealthReportType, - HealthTask, PlayurlParams, PlayurlParamsStatic, ReqType, + Area, BackgroundTaskType, BiliRuntime, CacheTask, CacheType, EType, EpInfo, FakeUA, + HealthReportType, HealthTask, PlayurlParams, PlayurlParamsStatic, ReqType, UserInfo, }; use super::upstream_res::*; -use super::user_info::get_blacklist_info; +use chrono::Local; use log::{debug, error, info, trace}; use serde_json::json; @@ -64,14 +65,14 @@ pub async fn update_cached_area_background( bili_runtime.send_task(background_task_data).await } +/// 不采用常规方法更新, 仅限用于未知的错误码下的刷新 pub async fn update_cached_user_info_background( access_key: String, - retry_num: u8, bili_runtime: &BiliRuntime<'_>, ) { trace!("[BACKGROUND TASK] AK {access_key} -> Accept UserInfo Cache Refresh Task..."); let background_task_data = - BackgroundTaskType::Cache(CacheTask::UserInfoCacheRefresh(access_key, retry_num)); + BackgroundTaskType::Cache(CacheTask::UserInfoCacheRefresh(access_key)); bili_runtime.send_task(background_task_data).await } @@ -217,17 +218,51 @@ pub async fn background_task_run( } }, BackgroundTaskType::Cache(value) => match value { - CacheTask::UserInfoCacheRefresh(access_key, _) => { - match get_upstream_bili_account_info_background(&access_key, &bili_runtime).await { - Ok(new_user_info) => { - update_user_info_cache(&new_user_info, &bili_runtime).await; - match get_blacklist_info(&new_user_info, bili_runtime).await { - Ok(_) => Ok(()), - Err(value) => Err(format!("[BACKGROUND TASK] UID {} | Refreshing blacklist info failed, ErrMsg: {}", new_user_info.uid, value.to_string())), - } + CacheTask::UserInfoCacheRefresh(access_key) => { + // 不管刷新是否成功 + match get_upstream_bili_playurl_background( + &mut PlayurlParams { + access_key: &access_key, + ep_id: "425578", + area: "hk", + area_num: 2, + ep_need_vip: false, + user_agent: &FakeUA::App.gen(), + ..Default::default() }, - Err(value) => { - Err(format!("[BACKGROUND TASK] ACCESS_KEY {} | Refreshing blacklist info failed, ErrMsg: {}", access_key, value.to_string())) + bili_runtime, + ) + .await + { + Ok(playurl_string) => { + let uid = if let Some(value) = get_user_mid_from_playurl(&playurl_string) { + value + } else { + // 不考虑失败的情况 + return Err("".to_owned()); + }; + let vip_expire_time = + get_upstream_bili_account_info_vip_due_date(uid, bili_runtime) + .await + .unwrap_or(0); + let new_user_info = UserInfo::new(0, &access_key, uid, vip_expire_time); + update_user_info_cache(&new_user_info, bili_runtime).await; + Ok(()) + } + Err(err_type) => match err_type { + EType::OtherError(_, "上游错误, 刷新失败") => { + // 这种情况下缓存30min防止请求过于频繁 + let new_user_info = UserInfo { + code: -500, + access_key: access_key.to_owned(), + expire_time: Local::now().timestamp_millis() as u64 + + 10 * 60 * 1000, // 暂时缓存10m, + ..Default::default() + }; + update_user_info_cache(&new_user_info, bili_runtime).await; + Ok(()) + } + _ => Ok(()), }, } } diff --git a/src/mods/cache.rs b/src/mods/cache.rs index 7fb41576..7ac3a8ff 100644 --- a/src/mods/cache.rs +++ b/src/mods/cache.rs @@ -209,7 +209,6 @@ pub async fn get_cached_user_info( .get_cache(&CacheType::UserInfo(access_key, 1145141919810)) .await { - // TODO: 处理expire_time, 主动刷新 Some(value) => Some(serde_json::from_str(&value).unwrap()), None => None, } @@ -253,7 +252,9 @@ pub async fn update_user_info_cache(new_user_info: &UserInfo, bili_runtime: &Bil .redis_set("av11301", &new_user_info.access_key, expire_time) .await; // 此处保存vip用户的access_key到本地使用, 02版本号, 刷新access_token的方法比较麻烦 - bili_runtime.redis_set("a11102", &new_user_info.access_key, expire_time).await + bili_runtime + .redis_set("a11102", &new_user_info.access_key, expire_time) + .await } else { bili_runtime .redis_set("uv01301", &new_user_info.uid.to_string(), expire_time) diff --git a/src/mods/handler.rs b/src/mods/handler.rs index fb4ed5c5..56e3022c 100644 --- a/src/mods/handler.rs +++ b/src/mods/handler.rs @@ -157,17 +157,7 @@ pub async fn handle_playurl_request(req: &HttpRequest, is_app: bool, is_th: bool }; // get user_info - let user_info = match get_user_info( - params.access_key, - params.appkey, - params.appsec, - ¶ms, - false, - 1, - &bili_runtime, - ) - .await - { + let user_info = match get_user_info(params.access_key, params.is_app, &bili_runtime).await { Ok(value) => value, Err(value) => { build_response!(value); @@ -242,6 +232,7 @@ pub async fn handle_playurl_request(req: &HttpRequest, is_app: bool, is_th: bool params.ep_id ); let resp = match get_cached_playurl(¶ms, &bili_runtime).await { + // 允许-999时用户获取缓存, 但不是VIP Ok(data) => { debug!( "[GET PLAYURL] IP {client_ip} | UID {} | AREA {} | EP {} -> Serve from cache", @@ -402,17 +393,7 @@ pub async fn handle_search_request(req: &HttpRequest, is_app: bool, is_th: bool) //为了记录accesskey to uid let uid = if is_app && (!is_th) { - match get_user_info( - params.access_key, - params.appkey, - params.appsec, - ¶ms, - false, - 1, - &bili_runtime, - ) - .await - { + match get_user_info(params.access_key, params.is_app, &bili_runtime).await { Ok(value) => { get_blacklist_info(&value, &bili_runtime) .await diff --git a/src/mods/tools.rs b/src/mods/tools.rs index 0015d28f..2c073270 100644 --- a/src/mods/tools.rs +++ b/src/mods/tools.rs @@ -3,6 +3,7 @@ use super::{ types::PlayurlType, }; use log::{debug, error}; +use pcre2::bytes::Regex; use std::env; use std::path::PathBuf; use std::thread; @@ -125,6 +126,27 @@ pub fn check_vip_status_from_playurl( } } +#[inline] +pub fn get_user_mid_from_playurl(playurl_string: &str) -> Option { + // 感觉不太优雅... + let re = Regex::new(r#"(?m)&mid=(\d{0,})&platform"#).unwrap(); + let mid = match re.captures(playurl_string.as_bytes()) { + Ok(value) => { + if let Some(value) = value { + value + } else { + return None; + } + } + Err(value) => { + error!("REGEX CAPTURE FAILED: {:?}", value); + return None; + } + }; + let mid: u64 = String::from_utf8(mid[1].to_vec()).unwrap().parse().unwrap(); + Some(mid) +} + #[inline] pub fn remove_parameters_playurl( playurl_type: &PlayurlType, diff --git a/src/mods/types.rs b/src/mods/types.rs index a956b78a..164ef0fb 100644 --- a/src/mods/types.rs +++ b/src/mods/types.rs @@ -4,7 +4,7 @@ use super::{ tools::remove_viponly_clarity, }; use async_channel::{Sender, TrySendError}; -use chrono::{FixedOffset, TimeZone, Utc}; +use chrono::{FixedOffset, Local, TimeZone, Utc}; use deadpool_redis::Pool; use rand::Rng; use serde::{Deserialize, Serialize}; @@ -605,7 +605,7 @@ pub enum HealthTask { HealthReport(HealthReportType), } pub enum CacheTask { - UserInfoCacheRefresh(String, u8), + UserInfoCacheRefresh(String), PlayurlCacheRefresh(PlayurlParamsStatic), ProactivePlayurlCacheRefresh, EpInfoCacheRefresh(bool, Vec), @@ -1676,6 +1676,33 @@ pub struct UserInfo { } impl UserInfo { + pub fn new(code:i64, access_key: &str, uid: u64, vip_expire_time: u64) -> UserInfo { + let dt = Local::now(); + let ts = dt.timestamp_millis() as u64; + UserInfo { + code, + access_key: access_key.to_owned(), + uid, + vip_expire_time, + expire_time: { + if ts < vip_expire_time && vip_expire_time < ts + 25 * 24 * 60 * 60 * 1000 { + vip_expire_time + } else { + ts + 25 * 24 * 60 * 60 * 1000 + } + }, + } + } + /// 遇到未预料的错误, 如网络问题/上游-663时可用这个 + pub fn new_unintended_error(access_key: &str) -> UserInfo { + UserInfo { + code: -999, + access_key: access_key.to_owned(), + uid: 0, + vip_expire_time: 0, + expire_time: Local::now().timestamp_millis() as u64 + 10 * 60 * 1000, //缓存10min + } + } pub fn to_json(&self) -> String { serde_json::to_string(&self).unwrap() // format!( @@ -2175,7 +2202,7 @@ impl EType { String::from("{\"code\":-10403,\"message\":\"大会员专享限制\"}") } EType::UserNotLoginedError => { - String::from("{\"code\":-101,\"message\":\"账号未登录\",\"ttl\":1}") + String::from("{\"code\":-101,\"message\":\"账号未登录, 若已登录请尝试退出重新登录\"}") } EType::InvalidReq => String::from("{\"code\":-412,\"message\":\"请求被拦截\"}"), EType::OtherError(err_code, err_msg) => { @@ -2185,7 +2212,7 @@ impl EType { format!("{{\"code\":{err_code},\"message\":\"其他上游错误: {err_msg}\"}}") } EType::UserLoginInvalid => String::from( - "{{\"code\":-61000,\"message\":\"无效的用户态, 请尝试重新登陆Bilibili\"}}", + "{{\"code\":61000,\"message\":\"无效的用户态, 请尝试重新登陆Bilibili\"}}", ), } } diff --git a/src/mods/upstream_res.rs b/src/mods/upstream_res.rs index f221a331..3d0a6917 100644 --- a/src/mods/upstream_res.rs +++ b/src/mods/upstream_res.rs @@ -2,44 +2,113 @@ use super::background_tasks::{ update_cached_ep_vip_status_background, update_cached_user_info_background, }; use super::cache::{ - get_cached_user_info, update_area_cache, update_blacklist_info_cache, update_cached_playurl, - update_th_season_cache, update_th_subtitle_cache, update_user_info_cache, + update_area_cache, update_blacklist_info_cache, update_cached_playurl, update_th_season_cache, + update_th_subtitle_cache, update_user_info_cache, }; use super::ep_info::get_ep_need_vip; use super::health::report_health; use super::request::async_getwebpage; -use super::tools::{check_vip_status_from_playurl, get_mobi_app, remove_parameters_playurl}; +use super::tools::{ + check_vip_status_from_playurl, get_user_mid_from_playurl, remove_parameters_playurl, +}; use super::types::{ Area, BiliRuntime, EType, EpInfo, FakeUA, HealthData, HealthReportType, PlayurlParams, ReqType, SearchParams, UpstreamReply, UserCerinfo, UserInfo, }; +use super::user_info::get_blacklist_info; use crate::build_signed_url; use chrono::prelude::*; use curl::easy::List; use log::{debug, error}; -use pcre2::bytes::Regex; use qstring::QString; +use rand::Rng; +use serde_json::json; use std::string::String; pub async fn get_upstream_bili_account_info( access_key: &str, - _appkey: &str, - _appsec: &str, - _is_app: bool, - _user_agent: &str, - retry_num: u8, + is_app: bool, + bili_runtime: &BiliRuntime<'_>, +) -> Result { + // 分流, 相当长时间内网页的key不可用, 除非web脚本的作者修复. + if is_app { + match get_upstream_bili_account_info_app(access_key, bili_runtime).await { + Ok(value) => Ok(value), + Err(err_type) => match err_type { + _ => Err(err_type), + }, + } + } else { + let new_user_info = UserInfo::new_unintended_error(access_key); + update_user_info_cache(&new_user_info, bili_runtime).await; + Ok(new_user_info) + } +} + +// pub async fn get_upstream_bili_account_info_background( +// access_key: &str, +// bili_runtime: &BiliRuntime<'_>, +// ) -> Result { +// match get_upstream_bili_account_info(access_key, "", "", false, "", 0, bili_runtime).await { +// Ok(value) => Ok(value), +// Err(value) => match value { +// EType::ServerReqError("-663错误, 您的账号似乎被鼠鼠风控了, 请稍后重试") => +// { +// let dt = Local::now(); +// let ts = dt.timestamp_millis() as u64; +// let cached_user_info = get_cached_user_info(access_key, bili_runtime).await; +// let uid = match cached_user_info { +// Some(value) => value.uid, +// None => { +// // TODO: 添加更多获取mid的方式 +// if let Some(value) = +// get_upstream_mid_from_playurl(access_key, bili_runtime).await +// { +// value +// } else { +// error!( +// "[GET USER_INFO][U] AK {} | Get User's mid failed", +// access_key +// ); +// return Err(EType::ServerGeneral); +// } +// } +// }; +// let vip_expire_time = get_upstream_vip_due_date_from_mid(uid, bili_runtime) +// .await +// .unwrap_or(0); +// Ok(UserInfo { +// code: 0, +// access_key: access_key.to_owned(), +// uid, +// vip_expire_time, +// expire_time: { +// if ts < vip_expire_time && vip_expire_time < ts + 25 * 24 * 60 * 60 * 1000 { +// vip_expire_time +// } else { +// ts + 25 * 24 * 60 * 60 * 1000 +// } +// }, +// }) +// } +// _ => Err(value), +// }, +// } +// } + +async fn get_upstream_bili_account_info_app( + access_key: &str, bili_runtime: &BiliRuntime<'_>, ) -> Result { - use rand::Rng; let dt = Local::now(); let ts = dt.timestamp_millis() as u64; let ts_min = dt.timestamp() as u64; let ts_min_string = ts_min.to_string(); + let appkey = "783bbb7264451d82"; - // Android请求用Android端的appkey, 网页用的ios的appkey (https://github.com/SocialSisterYi/bilibili-API-collect/issues/393#issuecomment-1288749103) - // 直接安卓的appkey得了, 61000的话用哪个appkey貌似都一样 + let appsec = "2653583c8873dea268ab9386918b1d65"; + let mobi_app = "android"; - let (_, appsec, mobi_app) = get_mobi_app(appkey); let rand_string_36 = { let words: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; let mut rng = rand::thread_rng(); @@ -66,7 +135,6 @@ pub async fn get_upstream_bili_account_info( ("ts", &ts_min_string), ]; req_vec.sort_by_key(|v| v.0); - // let req_params = qstring::QString::new(req_vec); // fix -663 error let mut headers = List::new(); @@ -79,12 +147,6 @@ pub async fn get_upstream_bili_account_info( bili_runtime.config.general_app_bilibili_com_proxy_api ); let (signed_url, sign) = build_signed_url!(api, req_vec, appsec); - - debug!( - "[GET USER_INFO][U] AK {} | RAW QUERY -> APPKEY {} TS {} APPSEC {}", - access_key, appkey, ts_min, appsec - ); - // debug!("[GET USER_INFO][U] URL {}", url); let upstream_raw_resp = match async_getwebpage( &signed_url, bili_runtime.config.cn_proxy_accesskey_open, @@ -96,7 +158,7 @@ pub async fn get_upstream_bili_account_info( .await { Ok(data) => data, - Err(value) => { + Err(_) => { error!( "[GET USER_INFO][U] AK {} | Req failed. Network Problems. RAW QUERY -> APPKEY {} TS {} APPSEC {} Use Proxy {} - {}", access_key, appkey, ts_min, appsec, bili_runtime.config.cn_proxy_accesskey_open, &bili_runtime.config.cn_proxy_accesskey_url, @@ -113,7 +175,9 @@ pub async fn get_upstream_bili_account_info( custom_message: "[GET USERINFO][U] 致命错误! 获取用户信息失败!".to_owned(), }); report_health(health_report_type, bili_runtime).await; - return Err(value); + let new_user_info = UserInfo::new_unintended_error(access_key); + update_user_info_cache(&new_user_info, bili_runtime).await; + return Ok(new_user_info); } }; @@ -121,7 +185,10 @@ pub async fn get_upstream_bili_account_info( "[GET USER_INFO][U] AK {} | Upstream Reply: {}", access_key, upstream_raw_resp ); - let upstream_raw_resp_json = upstream_raw_resp.json().unwrap(); + + let upstream_raw_resp_json = upstream_raw_resp + .json() + .unwrap_or(json!({"code": -500, "message": "[SERVER] 解析上游JSON错误"})); let code = if let Some(value) = upstream_raw_resp_json["code"].as_i64() { value } else { @@ -144,42 +211,29 @@ pub async fn get_upstream_bili_account_info( report_health(health_report_type, bili_runtime).await; return Err(EType::ServerReqError("解析用户信息失败")); }; + match code { 0 => { if upstream_raw_resp_json["data"]["mid"].as_u64().unwrap_or(0) == 0 { - // accesskey失效时mid为0 - let output_struct = UserInfo { - code, - expire_time: ts + 1 * 60 * 60 * 1000, // 未登录缓存1h,防止高频请求b站服务器 - ..Default::default() - }; - update_user_info_cache(&output_struct, bili_runtime).await; + // accesskey失效时mid为0, 缓存25d + update_user_info_cache(&UserInfo::new(code, access_key, 0, 0), bili_runtime).await; error!( "[GET USER_INFO][U] AK {} | Get UserInfo failed -101. Upstream Reply -> {}", access_key, upstream_raw_resp_json ); Err(EType::UserNotLoginedError) } else { + let vip_expire_time = upstream_raw_resp_json["data"]["vip"]["due_date"] + .as_u64() + .unwrap(); let output_struct = UserInfo { code: 0, access_key: String::from(access_key), uid: upstream_raw_resp_json["data"]["mid"].as_u64().unwrap(), - vip_expire_time: upstream_raw_resp_json["data"]["vip"]["due_date"] - .as_u64() - .unwrap(), + vip_expire_time, expire_time: { - if ts - < upstream_raw_resp_json["data"]["vip"]["due_date"] - .as_u64() - .unwrap() - && upstream_raw_resp_json["data"]["vip"]["due_date"] - .as_u64() - .unwrap() - < ts + 25 * 24 * 60 * 60 * 1000 - { - upstream_raw_resp_json["data"]["vip"]["due_date"] - .as_u64() - .unwrap() + if ts < vip_expire_time && vip_expire_time < ts + 25 * 24 * 60 * 60 * 1000 { + vip_expire_time } else { ts + 25 * 24 * 60 * 60 * 1000 } @@ -189,60 +243,56 @@ pub async fn get_upstream_bili_account_info( Ok(output_struct) } } - -404 => { - match appkey { - "84956560bc028eb7" | "85eb6835b0a1034e" => { - // 还是迂回更新其用户信息试一下 - if retry_num != 0 { - update_cached_user_info_background( - access_key.to_string(), - retry_num - 1, - bili_runtime, - ) - .await; - } - Err(EType::OtherError( - -10403, - "不兼容的APPKEY, 请升级油猴脚本或其他你正在用的客户端!", - )) - } - _ => { - error!("[GET USER_INFO][U] AK {} -> Get UserInfo failed. Invalid APPKEY -> APPKEY {} | TS {} | APPSEC {}. Upstream Reply -> {}", - access_key, appkey, ts_min, appsec, upstream_raw_resp_json - ); - let health_report_type = HealthReportType::Others(HealthData { - area_num: 0, - is_200_ok: true, - upstream_reply: UpstreamReply { - code, - message: upstream_raw_resp_json["message"].as_str().unwrap_or("null").to_owned(), - upstream_header: upstream_raw_resp.read_headers(), - proxy_open: bili_runtime.config.cn_proxy_accesskey_open, - proxy_url: bili_runtime.config.cn_proxy_accesskey_url.clone(), - }, - is_custom: true, - custom_message: format!( - "[GET USER_INFO][U] 疑似不能用于获取用户信息的APPKEY {appkey} - APPSEC {appsec}" - ), - }); - report_health(health_report_type, bili_runtime).await; - Err(EType::InvalidReq) - } - } - } - -400 => { - error!("[GET USER_INFO][U] AK {} | Get UserInfo failed -400. REQ Params -> APPKEY {} | TS {} | APPSEC {} | SIGN {}. Upstream Reply -> {}", + -3 => { + // 不应该出现签名错误, 除非B站更改签名算法 + error!("[GET USER_INFO][U] AK {} | Get UserInfo failed -3. REQ Params -> APPKEY {} | TS {} | APPSEC {} | SIGN {:?}. Upstream Reply -> {}", access_key, appkey, ts_min, appsec, sign, upstream_raw_resp_json ); - Err(EType::OtherError(-400, "可能你用的不是手机")) + Ok(UserInfo::new_unintended_error(access_key)) + } + -400 | -404 => { + // 已经指定appkey了, 不应当出现-404/-400, 除非这个appkey寄了 + error!("[GET USER_INFO][U] AK {} -> Get UserInfo failed. Invalid APPKEY -> APPKEY {} | TS {} | APPSEC {}. Upstream Reply -> {}", + access_key, appkey, ts_min, appsec, upstream_raw_resp + ); + let health_report_type = HealthReportType::Others(HealthData { + area_num: 0, + is_200_ok: true, + upstream_reply: UpstreamReply { + code, + message: upstream_raw_resp_json["message"] + .as_str() + .unwrap_or("null") + .to_owned(), + upstream_header: upstream_raw_resp.read_headers(), + proxy_open: bili_runtime.config.cn_proxy_accesskey_open, + proxy_url: bili_runtime.config.cn_proxy_accesskey_url.clone(), + }, + is_custom: true, + custom_message: format!( + "[GET USER_INFO][U] 致命错误: 不能用于获取用户信息的APPKEY {appkey} - APPSEC {appsec}" + ), + }); + report_health(health_report_type, bili_runtime).await; + Err(EType::ServerReqError("APPKEY失效")) } + // -400 => { + // // 已经指定appkey, 不应当出现此错误 + // error!("[GET USER_INFO][U] AK {} | Get UserInfo failed -400. REQ Params -> APPKEY {} | TS {} | APPSEC {} | SIGN {}. Upstream Reply -> {}", + // access_key, appkey, ts_min, appsec, sign, upstream_raw_resp_json + // ); + // Err(EType::OtherError(-400, "可能你用的不是手机")) + // } -101 => { - let output_struct = UserInfo { - code, - expire_time: ts + 1 * 60 * 60 * 1000, // 未登录缓存1h,防止高频请求b站服务器 - ..Default::default() - }; - update_user_info_cache(&output_struct, bili_runtime).await; + // let output_struct = UserInfo { + // code, + // access_key: access_key.to_owned(), + // expire_time: ts + 1 * 60 * 60 * 1000, + // ..Default::default() + // }; + // -101必定是登录失效, 观察发现也只有网页端会这样, 缓存25天 + update_user_info_cache(&UserInfo::new(code, access_key, 0, 0), bili_runtime).await; + // update_user_info_cache(&output_struct, bili_runtime).await; error!( "[GET USER_INFO][U] AK {} | Get UserInfo failed -101. Upstream Reply -> {}", access_key, upstream_raw_resp_json @@ -251,24 +301,14 @@ pub async fn get_upstream_bili_account_info( } 61000 => { // 那先看成未登录 - let output_struct = UserInfo { - code, - expire_time: ts + 10 * 60 * 1000, // 看起来不是请求过快, 并且后续应该无法使用这个accesskey获取到用户信息了,所以先暂时缓存10m - ..Default::default() - }; - update_user_info_cache(&output_struct, bili_runtime).await; + // 61000是登录失效, 观察发现后续应该无法使用这个accesskey获取到用户信息了, 缓存25天 + update_user_info_cache(&UserInfo::new(code, access_key, 0, 0), bili_runtime).await; error!( "[GET USER_INFO][U] AK {} | Get UserInfo failed 61000. Maybe AK out of date. Upstream Reply -> {}", access_key, upstream_raw_resp_json ); Err(EType::UserLoginInvalid) } - -3 => { - error!("[GET USER_INFO][U] AK {} | Get UserInfo failed -3. REQ Params -> APPKEY {} | TS {} | APPSEC {} | SIGN {:?}. Upstream Reply -> {}", - access_key, appkey, ts_min, appsec, sign, upstream_raw_resp_json - ); - Err(EType::ReqSignError) - } -412 => { error!( "[GET USER_INFO][U] AK {} | Get UserInfo failed -412. Upstream Reply -> {}", @@ -289,25 +329,25 @@ pub async fn get_upstream_bili_account_info( }, is_custom: true, custom_message: format!( - "[GET USER_INFO][U] 致命错误! 机子-412喵! \n上游返回: {upstream_raw_resp}" + "[GET USER_INFO][U] 致命错误! 机子-412喵! 上游返回: {upstream_raw_resp}" ), }); report_health(health_report_type, bili_runtime).await; Err(EType::ServerFatalError) } -663 => { + // app端的-663不应该. error!( "[GET USER_INFO][U] AK {} | Get UserInfo failed -663. Upstream Reply -> {}", access_key, upstream_raw_resp_json ); - if retry_num != 0 { - update_cached_user_info_background( - access_key.to_string(), - retry_num - 1, - bili_runtime, - ) - .await; - } + let output_struct = UserInfo { + code, + access_key: access_key.to_owned(), + expire_time: ts + 10 * 60 * 1000, // 暂时缓存10m + ..Default::default() + }; + update_user_info_cache(&output_struct, bili_runtime).await; let health_report_type = HealthReportType::Others(HealthData { area_num: 0, is_200_ok: true, @@ -320,20 +360,20 @@ pub async fn get_upstream_bili_account_info( }, is_custom: true, custom_message: format!( - "[GET USER_INFO][U] -663错误! 正在尝试修复\nDevice: {}, APPKEY: {}, AK: {}, TS: {}", + "[GET USER_INFO][U] 致命错误: 上游返回-663! 正在尝试修复\nDevice: {}, APPKEY: {}, AK: {}, TS: {}", mobi_app, appkey, access_key, ts ), }); report_health(health_report_type, bili_runtime).await; - Err(EType::ServerReqError( - "-663错误, 您的账号似乎被鼠鼠风控了, 请稍后重试", - )) + Err(EType::ServerReqError("-663错误, 被鼠鼠制裁了, 请稍后重试")) } _ => { error!("[GET USER_INFO][U] AK {} -> Get UserInfo failed. REQ Params -> APPKEY {} | TS {} | APPSEC {} | SIGN {:?}. Upstream Reply -> {}", access_key, appkey, ts_min, appsec, sign, upstream_raw_resp_json ); error!("[GET USER_INFO][U] URL {}", signed_url); + // 不采用常规方法更新, 仅限用于未知的错误码下的刷新 + update_cached_user_info_background(access_key.to_owned(), bili_runtime).await; let health_report_type = HealthReportType::Others(HealthData { area_num: 0, is_200_ok: true, @@ -349,7 +389,7 @@ pub async fn get_upstream_bili_account_info( }, is_custom: true, custom_message: format!( - "[GET USER_INFO][U] 致命错误! 未知的错误码! \n上游返回: {upstream_raw_resp}" + "[GET USER_INFO][U] 致命错误! 未知的错误码! 上游返回: {upstream_raw_resp}" ), }); report_health(health_report_type, bili_runtime).await; @@ -364,98 +404,7 @@ pub async fn get_upstream_bili_account_info( } } -pub async fn get_upstream_bili_account_info_background( - access_key: &str, - bili_runtime: &BiliRuntime<'_>, -) -> Result { - match get_upstream_bili_account_info(access_key, "", "", false, "", 0, bili_runtime).await { - Ok(value) => Ok(value), - Err(value) => match value { - EType::ServerReqError("-663错误, 您的账号似乎被鼠鼠风控了, 请稍后重试") => - { - let dt = Local::now(); - let ts = dt.timestamp_millis() as u64; - let cached_user_info = get_cached_user_info(access_key, bili_runtime).await; - let uid = match cached_user_info { - Some(value) => value.uid, - None => { - // TODO: 添加更多获取mid的方式 - if let Some(value) = - get_upstream_mid_from_playurl(access_key, bili_runtime).await - { - value - } else { - error!( - "[GET USER_INFO][U] AK {} | Get User's mid failed", - access_key - ); - return Err(EType::ServerGeneral); - } - } - }; - let vip_expire_time = get_upstream_vip_due_date_from_mid(uid, bili_runtime) - .await - .unwrap_or(0); - Ok(UserInfo { - code: 0, - access_key: access_key.to_owned(), - uid, - vip_expire_time, - expire_time: { - if ts < vip_expire_time && vip_expire_time < ts + 25 * 24 * 60 * 60 * 1000 { - vip_expire_time - } else { - ts + 25 * 24 * 60 * 60 * 1000 - } - }, - }) - } - _ => Err(value), - }, - } -} - -/// 从播放链接中提取mid -async fn get_upstream_mid_from_playurl( - access_key: &str, - bili_runtime: &BiliRuntime<'_>, -) -> Option { - if let Ok(playurl_string) = get_upstream_bili_playurl_background( - &mut PlayurlParams { - access_key, - ep_id: "425578", - area: "hk", - area_num: 2, - ep_need_vip: false, - user_agent: &FakeUA::App.gen(), - ..Default::default() - }, - bili_runtime, - ) - .await - { - // 感觉不太优雅... - let re = Regex::new(r#"(?m)&mid=(\d{0,})&platform"#).unwrap(); - let mid = match re.captures(playurl_string.as_bytes()) { - Ok(value) => { - if let Some(value) = value { - value - } else { - return None; - } - } - Err(value) => { - error!("REGEX CAPTURE FAILED: {:?}", value); - return None; - } - }; - let mid: u64 = String::from_utf8(mid[1].to_vec()).unwrap().parse().unwrap(); - Some(mid) - } else { - None - } -} -async fn get_upstream_vip_due_date_from_mid( +pub async fn get_upstream_bili_account_info_vip_due_date( mid: u64, bili_runtime: &BiliRuntime<'_>, ) -> Option { @@ -492,8 +441,13 @@ pub async fn get_upstream_blacklist_info( let ts = dt.timestamp() as u64; let uid = user_info.uid; if uid == 0 { - // 仅当出问题才会有uid=0 - return Err(EType::ServerGeneral); + return Ok(UserCerinfo { + uid: 0, + black: false, + white: false, + ban_until: 0, + status_expire_time: 0, + }); } //let user_cerinfo_str = String::new(); let user_agent = format!("biliroaming-rust-server/{}", env!("CARGO_PKG_VERSION")); @@ -752,12 +706,32 @@ pub async fn get_upstream_bili_playurl( bili_runtime, ) .await; - // check user's vip status - if !params.is_vip { + // update playurl cache + let final_data = upstream_raw_resp_json.to_string(); + update_cached_playurl(params, &final_data, bili_runtime).await; + + // check user's vip status update web user's user_info + // 是vip的用户必定是正常请求api获得了用户信息的 + // 对非VIP用户不友好, 笑 + if !params.is_vip || user_info.code == -999 { + // 处理网页用户等 + let uid = if let Some(value) = get_user_mid_from_playurl(&upstream_raw_resp.resp_content) { + value + } else { + return Err(EType::UserNotLoginedError); + }; + let vip_expire_time = get_upstream_bili_account_info_vip_due_date(uid, bili_runtime) + .await + .unwrap_or(0); + let new_user_info = UserInfo::new(0, params.access_key, uid, vip_expire_time); + update_user_info_cache(&new_user_info, bili_runtime).await; + match get_blacklist_info(&new_user_info, bili_runtime).await { + Ok(_) => (), + Err(value) => return Err(value), + } + params.is_vip = new_user_info.is_vip(); if let Ok(value) = check_vip_status_from_playurl(playurl_type, &upstream_raw_resp_json) { - if value { - update_cached_user_info_background(params.access_key.to_string(), 1, bili_runtime) - .await; + if value && (!params.is_vip) { match get_ep_need_vip(params.ep_id, bili_runtime).await { Some(ep_need_vip) => { if ep_need_vip == 1 { @@ -783,7 +757,6 @@ pub async fn get_upstream_bili_playurl( ); } } - report_health( HealthReportType::Playurl(HealthData { area_num: params.area_num, @@ -806,11 +779,7 @@ pub async fn get_upstream_bili_playurl( )); } } - // TODO: add fallback check } - // update playurl cache - let final_data = upstream_raw_resp_json.to_string(); - update_cached_playurl(params, &final_data, bili_runtime).await; debug!( "[GET PLAYURL][U] UID {} | AK {} | AREA {} | EP {} -> 获取成功", user_info.uid, @@ -1475,12 +1444,12 @@ pub async fn get_upstream_bili_ep_info( let bili_hidden_season_api = format!("https://bangumi.bilibili.com/view/web_api/season?ep_id={ep_id}"); let bili_season_api = format!("http://api.bilibili.com/pgc/view/web/season?ep_id={ep_id}"); - let user_agent = "Dalvik/2.1.0 (Linux; U; Android 11; 21091116AC Build/RP1A.200720.011)"; + let user_agent = FakeUA::App.gen(); match async_getwebpage( &bili_hidden_season_api, proxy_open, proxy_url, - user_agent, + &user_agent, "", None, ) @@ -1493,7 +1462,7 @@ pub async fn get_upstream_bili_ep_info( &bili_season_api, proxy_open, proxy_url, - user_agent, + &user_agent, "", None, ) @@ -1546,7 +1515,7 @@ pub async fn get_upstream_bili_ep_info( &bili_season_api, proxy_open, proxy_url, - user_agent, + &user_agent, "", None, ) diff --git a/src/mods/user_info.rs b/src/mods/user_info.rs index 53172e7e..25d7dfb7 100644 --- a/src/mods/user_info.rs +++ b/src/mods/user_info.rs @@ -1,8 +1,6 @@ use super::cache::{get_cached_blacklist_info, get_cached_user_info}; use super::request::{async_getwebpage, async_postwebpage}; -use super::types::{ - BiliRuntime, EType, HasIsappIsthUseragent, PlayurlParams, UserInfo, UserResignInfo, -}; +use super::types::{BiliRuntime, EType, PlayurlParams, UserInfo, UserResignInfo}; use super::upstream_res::{get_upstream_bili_account_info, get_upstream_blacklist_info}; use crate::build_signed_params; use chrono::prelude::*; @@ -10,43 +8,32 @@ use log::{debug, error, info}; // general #[inline] -pub async fn get_user_info( +pub async fn get_user_info( access_key: &str, - appkey: &str, - appsec: &str, - params: &T, - force_update: bool, - retry_num: u8, + is_app: bool, bili_runtime: &BiliRuntime<'_>, ) -> Result { - // detect web request - // let is_app = { - // if params.is_th() { - // if params.user_agent().contains("Chrome") { - // false - // } else { - // true - // } - // } else { - // params.is_app() - // } - // }; - // 既然获取userinfo不用区分网页请求,那这里就先注释了 - let is_app = params.is_app(); - - // mixed with blacklist function - if force_update { - match get_upstream_bili_account_info( - access_key, - appkey, - appsec, - is_app, - params.user_agent(), - retry_num, - bili_runtime, - ) - .await - { + match get_cached_user_info(access_key, bili_runtime).await { + Some(cached_user_info) => { + debug!( + "[GET USER_INFO] UID {} | AK {} | U.VIP {} -> Got AK {}'s user info from cache", + cached_user_info.uid, + cached_user_info.access_key, + cached_user_info.is_vip(), + access_key + ); + match cached_user_info.code { + 0 | -999 => Ok(cached_user_info), + -3 => Err(EType::ReqSignError), + -101 => Err(EType::UserNotLoginedError), + -400 | -404 => Err(EType::ServerReqError("APPKEY失效")), + -412 => Err(EType::ServerFatalError), + 61000 => Err(EType::UserLoginInvalid), + -663 => Err(EType::ServerReqError("-663错误, 被鼠鼠制裁了, 请稍后重试")), + _ => Err(EType::ServerGeneral), + } + } + None => match get_upstream_bili_account_info(access_key, is_app, bili_runtime).await { Ok(value) => { debug!( "[GET USER_INFO] UID {} | AK {} | U.VIP {} -> Got AK {}'s user info from upstream", @@ -58,74 +45,7 @@ pub async fn get_user_info( Ok(value) } Err(value) => Err(value), - } - } else { - match get_cached_user_info(access_key, bili_runtime).await { - Some(cached_user_info) => { - debug!( - "[GET USER_INFO] UID {} | AK {} | U.VIP {} -> Got AK {}'s user info from cache", - cached_user_info.uid, - cached_user_info.access_key, - cached_user_info.is_vip(), - access_key - ); - match cached_user_info.code { - 0 => Ok(cached_user_info), - -101 => Err(EType::UserNotLoginedError), - -404 => Err(EType::OtherError( - -10403, - "不兼容的APPKEY, 请升级油猴脚本或其他你正在用的客户端!", - )), - -400 => Err(EType::OtherError(-400, "可能你用的不是手机")), - -3 => Err(EType::ReqSignError), - -412 => Err(EType::ServerFatalError), - 61000 => Err(EType::UserLoginInvalid), - -663 => Err(EType::UserLoginInvalid), - _ => Err(EType::ServerGeneral), - } - } - None => match get_upstream_bili_account_info( - access_key, - appkey, - appsec, - is_app, - params.user_agent(), - retry_num, - bili_runtime, - ) - .await - { - Ok(value) => { - debug!( - "[GET USER_INFO] UID {} | AK {} | U.VIP {} -> Got AK {}'s user info from upstream", - value.uid, - value.access_key, - value.is_vip(), - access_key - ); - Ok(value) - } - Err(value) => match value { - // 也不知道为啥有一堆access_key失效了的请求 - EType::UserNotLoginedError => Err(value), - _ => { - let dt = Local::now(); - let ts = dt.timestamp_millis() as u64; - let user_info = UserInfo { - code: -1, - access_key: access_key.to_owned(), - uid: 0, - vip_expire_time: 0, - expire_time: ts + 30 * 60 * 1000, - }; - match get_blacklist_info(&user_info, bili_runtime).await { - Ok(_) => Ok(user_info), - Err(_) => Err(value), - } - } - }, - }, - } + }, } } @@ -351,22 +271,13 @@ pub async fn resign_user_info( .await .unwrap_or((params.access_key.to_string(), 1)); - let resign_user_info = match get_user_info( - &new_access_key, - params.appkey, - params.appsec, - params, - false, - 1, - bili_runtime, - ) - .await - { - Ok(value) => value, - Err(value) => { - return Err(value); - } - }; + let resign_user_info = + match get_user_info(&new_access_key, params.is_app, bili_runtime).await { + Ok(value) => value, + Err(value) => { + return Err(value); + } + }; if !params.is_vip && resign_user_info.is_vip() { // 用户不是大会员且resign accesskey是大会员时才需要替换, 否则会因为请求过多导致黑号(已经有个人的测速的key寄了) From cb70db7b21cc8355e3c2d61c00928fa044041670 Mon Sep 17 00:00:00 2001 From: cxw620 Date: Wed, 14 Dec 2022 07:23:44 +0800 Subject: [PATCH 09/18] fix: panic due to web preview --- src/mods/tools.rs | 3 +++ src/mods/upstream_res.rs | 2 ++ src/mods/user_info.rs | 4 ++-- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/mods/tools.rs b/src/mods/tools.rs index 2c073270..d4a0d7cb 100644 --- a/src/mods/tools.rs +++ b/src/mods/tools.rs @@ -78,6 +78,9 @@ pub fn check_vip_status_from_playurl( } PlayurlType::ChinaWeb => { if data["code"].as_i64().unwrap_or(233) == 0 { + if data["result"]["is_preview"].as_bool().unwrap_or(false) { + return Ok(true); + } let mut quality_need_vip: Vec = Vec::with_capacity(2); let items = if let Some(value) = data["result"]["support_formats"].as_array() { value diff --git a/src/mods/upstream_res.rs b/src/mods/upstream_res.rs index 3d0a6917..b8c6c211 100644 --- a/src/mods/upstream_res.rs +++ b/src/mods/upstream_res.rs @@ -519,6 +519,8 @@ pub async fn get_upstream_blacklist_info( .as_bool() .unwrap_or(false), status_expire_time: { + // 3376656000 + // 1671057927 match upstream_raw_resp_json["data"]["ban_until"].as_u64() { Some(ban_until) => { if ban_until > ts && ban_until < ts + 1 * 24 * 60 * 60 { diff --git a/src/mods/user_info.rs b/src/mods/user_info.rs index 25d7dfb7..fbe7942c 100644 --- a/src/mods/user_info.rs +++ b/src/mods/user_info.rs @@ -204,7 +204,7 @@ pub async fn get_blacklist_info( }; if data.white { info!( - "[GET USER_CER_INFO] UID {} | AK {} -> 在线白名单, 过期时间: {}", + "[GET USER_CER_INFO] UID {} | AK {} -> 在线白名单, 下次刷新: {}", user_info.uid, user_info.access_key, data.status_expire_time ); Ok(true) @@ -213,7 +213,7 @@ pub async fn get_blacklist_info( "[GET USER_CER_INFO] UID {} | AK {} -> 在线黑名单, {}", user_info.uid, user_info.access_key, - timestamp_to_time(&data.status_expire_time) + timestamp_to_time(&data.ban_until) ); Err(EType::UserBlacklistedError(data.ban_until as i64)) } else { From bfd67054827ca3ac4f4badc0b930975a66ef9ee5 Mon Sep 17 00:00:00 2001 From: cxw620 Date: Wed, 14 Dec 2022 07:29:42 +0800 Subject: [PATCH 10/18] feat: allow cdn proxy api host --- src/mods/upstream_res.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/mods/upstream_res.rs b/src/mods/upstream_res.rs index b8c6c211..ef9d5cdc 100644 --- a/src/mods/upstream_res.rs +++ b/src/mods/upstream_res.rs @@ -408,8 +408,10 @@ pub async fn get_upstream_bili_account_info_vip_due_date( mid: u64, bili_runtime: &BiliRuntime<'_>, ) -> Option { - let url = - format!("https://api.bilibili.com/x/space/wbi/acc/info?mid={mid}&token=&platform=web"); + let url = format!( + "https://{}/x/space/wbi/acc/info?mid={mid}&token=&platform=web", + bili_runtime.config.general_api_bilibili_com_proxy_api + ); match async_getwebpage( &url, bili_runtime.config.cn_proxy_accesskey_open, From 0875eff976dcd5a8ba34a69500d8812a6b154096 Mon Sep 17 00:00:00 2001 From: cxw620 Date: Wed, 14 Dec 2022 07:39:36 +0800 Subject: [PATCH 11/18] feat: background task for user info update --- src/mods/background_tasks.rs | 99 ++++++++++++++++++++---------------- 1 file changed, 55 insertions(+), 44 deletions(-) diff --git a/src/mods/background_tasks.rs b/src/mods/background_tasks.rs index 7cfc0dd7..7aacb008 100644 --- a/src/mods/background_tasks.rs +++ b/src/mods/background_tasks.rs @@ -1,4 +1,4 @@ -use super::cache::{update_cached_playurl, update_user_info_cache}; +use super::cache::{get_cached_user_info, update_cached_playurl, update_user_info_cache}; use super::ep_info::update_ep_vip_status_cache; use super::health::*; use super::push::send_report; @@ -220,51 +220,62 @@ pub async fn background_task_run( BackgroundTaskType::Cache(value) => match value { CacheTask::UserInfoCacheRefresh(access_key) => { // 不管刷新是否成功 - match get_upstream_bili_playurl_background( - &mut PlayurlParams { - access_key: &access_key, - ep_id: "425578", - area: "hk", - area_num: 2, - ep_need_vip: false, - user_agent: &FakeUA::App.gen(), - ..Default::default() - }, - bili_runtime, - ) - .await + let uid = if let Some(value) = get_cached_user_info(&access_key, bili_runtime).await { - Ok(playurl_string) => { - let uid = if let Some(value) = get_user_mid_from_playurl(&playurl_string) { - value - } else { - // 不考虑失败的情况 - return Err("".to_owned()); - }; - let vip_expire_time = - get_upstream_bili_account_info_vip_due_date(uid, bili_runtime) - .await - .unwrap_or(0); - let new_user_info = UserInfo::new(0, &access_key, uid, vip_expire_time); - update_user_info_cache(&new_user_info, bili_runtime).await; - Ok(()) - } - Err(err_type) => match err_type { - EType::OtherError(_, "上游错误, 刷新失败") => { - // 这种情况下缓存30min防止请求过于频繁 - let new_user_info = UserInfo { - code: -500, - access_key: access_key.to_owned(), - expire_time: Local::now().timestamp_millis() as u64 - + 10 * 60 * 1000, // 暂时缓存10m, - ..Default::default() - }; - update_user_info_cache(&new_user_info, bili_runtime).await; - Ok(()) + value.uid + } else { + match get_upstream_bili_playurl_background( + &mut PlayurlParams { + access_key: &access_key, + ep_id: "425578", + area: "hk", + area_num: 2, + ep_need_vip: false, + user_agent: &FakeUA::App.gen(), + ..Default::default() + }, + bili_runtime, + ) + .await + { + Ok(playurl_string) => { + if let Some(value) = get_user_mid_from_playurl(&playurl_string) { + value + } else { + // 不考虑失败的情况 + return Err("[BACKGROUND TASK] | 从playurl正则匹配获取mid失败, 刷新用户信息终止".to_owned()); + } } - _ => Ok(()), - }, - } + Err(err_type) => { + match err_type { + EType::OtherError(_, "上游错误, 刷新失败") => { + // 这种情况下缓存30min防止请求过于频繁 + let new_user_info = UserInfo { + code: -500, + access_key: access_key.to_owned(), + expire_time: Local::now().timestamp_millis() as u64 + + 10 * 60 * 1000, // 暂时缓存10m, + ..Default::default() + }; + update_user_info_cache(&new_user_info, bili_runtime).await; + return Err("[BACKGROUND TASK] | 上游问题, 刷新用户信息失败" + .to_string()); + } + _ => { + return Err("[BACKGROUND TASK] | 网络问题, 刷新用户信息失败" + .to_string()) + } + } + } + } + }; + let vip_expire_time = + get_upstream_bili_account_info_vip_due_date(uid, bili_runtime) + .await + .unwrap_or(0); + let new_user_info = UserInfo::new(0, &access_key, uid, vip_expire_time); + update_user_info_cache(&new_user_info, bili_runtime).await; + Ok(()) } CacheTask::PlayurlCacheRefresh(params) => { match get_upstream_bili_playurl_background(&mut params.as_ref(), bili_runtime).await From 58da50de81dc92529c9d8caf44b3ad5cbb838dbc Mon Sep 17 00:00:00 2001 From: cxw620 Date: Wed, 14 Dec 2022 13:36:50 +0800 Subject: [PATCH 12/18] fix: appkey consistency --- src/mods/handler.rs | 56 +++++++++++++++++++------ src/mods/tools.rs | 2 +- src/mods/upstream_res.rs | 88 ++++++++++------------------------------ src/mods/user_info.rs | 17 ++++---- 4 files changed, 76 insertions(+), 87 deletions(-) diff --git a/src/mods/handler.rs b/src/mods/handler.rs index 56e3022c..f87509a2 100644 --- a/src/mods/handler.rs +++ b/src/mods/handler.rs @@ -87,7 +87,14 @@ pub async fn handle_playurl_request(req: &HttpRequest, is_app: bool, is_th: bool } // detect user's appkey - params.appkey = query.get("appkey").unwrap_or("1d8b6e7d45233436"); + params.appkey = query.get("appkey").unwrap_or_else(|| { + if params.is_app { + "1d8b6e7d45233436" + } else { + // 网页端是ios的key + "27eb53fc9058f8c3" + } + }); if let Err(_) = params.appkey_to_sec() { error!( "[GET PLAYURL] IP {client_ip} -> Detect unknown appkey: {}", @@ -142,12 +149,19 @@ pub async fn handle_playurl_request(req: &HttpRequest, is_app: bool, is_th: bool }; // detect req ep - params.ep_id = query.get("ep_id").unwrap_or(""); + params.ep_id = if let Some(value) = query.get("ep_id") { + value + } else { + build_response!(EType::InvalidReq) + }; params.cid = query.get("cid").unwrap_or(""); // detect other info params.build = query.get("build").unwrap_or("6800300"); - params.device = query.get("device").unwrap_or("android"); + params.device = + query + .get("device") + .unwrap_or_else(|| if params.is_app { "android" } else { "iphone" }); params.is_tv = match query.get("fnval") { Some(value) => match value { "130" | "0" | "2" => true, @@ -157,7 +171,14 @@ pub async fn handle_playurl_request(req: &HttpRequest, is_app: bool, is_th: bool }; // get user_info - let user_info = match get_user_info(params.access_key, params.is_app, &bili_runtime).await { + let user_info = match get_user_info( + params.access_key, + params.appkey, + params.is_app, + &bili_runtime, + ) + .await + { Ok(value) => value, Err(value) => { build_response!(value); @@ -309,10 +330,13 @@ pub async fn handle_search_request(req: &HttpRequest, is_app: bool, is_th: bool) } // detect user's appkey - params.appkey = match query.get("appkey") { - Option::Some(key) => key, - _ => "1d8b6e7d45233436", - }; + params.appkey = query.get("appkey").unwrap_or_else(|| { + if params.is_app { + "1d8b6e7d45233436" + } else { + "27eb53fc9058f8c3" + } + }); if let Err(_) = params.appkey_to_sec() { error!( "[GET SEARCH] IP {client_ip} | Detect unknown appkey: {}", @@ -362,10 +386,11 @@ pub async fn handle_search_request(req: &HttpRequest, is_app: bool, is_th: bool) build_response!(EType::UserNotLoginedError); } }; - - // detect other info params.build = query.get("build").unwrap_or("6800300"); - params.device = query.get("device").unwrap_or("android"); + params.device = + query + .get("device") + .unwrap_or_else(|| if params.is_app { "android" } else { "iphone" }); params.statistics = match query.get("statistics") { Some(value) => value, _ => "", @@ -393,7 +418,14 @@ pub async fn handle_search_request(req: &HttpRequest, is_app: bool, is_th: bool) //为了记录accesskey to uid let uid = if is_app && (!is_th) { - match get_user_info(params.access_key, params.is_app, &bili_runtime).await { + match get_user_info( + params.access_key, + params.appkey, + params.is_app, + &bili_runtime, + ) + .await + { Ok(value) => { get_blacklist_info(&value, &bili_runtime) .await diff --git a/src/mods/tools.rs b/src/mods/tools.rs index d4a0d7cb..fba26e49 100644 --- a/src/mods/tools.rs +++ b/src/mods/tools.rs @@ -349,7 +349,7 @@ pub fn get_mobi_app(appkey: &str) -> (&'static str, &'static str, &'static str) "27eb53fc9058f8c3" => ( "27eb53fc9058f8c3", "c2ed53a74eeefe3cf99fbd01d8c9c375", - "ios", + "iphone", // ios ), "57263273bc6b67f6" => ( "57263273bc6b67f6", diff --git a/src/mods/upstream_res.rs b/src/mods/upstream_res.rs index ef9d5cdc..3b57c706 100644 --- a/src/mods/upstream_res.rs +++ b/src/mods/upstream_res.rs @@ -17,6 +17,7 @@ use super::types::{ }; use super::user_info::get_blacklist_info; use crate::build_signed_url; +use crate::mods::tools::get_mobi_app; use chrono::prelude::*; use curl::easy::List; use log::{debug, error}; @@ -27,77 +28,32 @@ use std::string::String; pub async fn get_upstream_bili_account_info( access_key: &str, + appkey: &str, is_app: bool, bili_runtime: &BiliRuntime<'_>, ) -> Result { - // 分流, 相当长时间内网页的key不可用, 除非web脚本的作者修复. - if is_app { - match get_upstream_bili_account_info_app(access_key, bili_runtime).await { - Ok(value) => Ok(value), - Err(err_type) => match err_type { - _ => Err(err_type), - }, - } - } else { - let new_user_info = UserInfo::new_unintended_error(access_key); - update_user_info_cache(&new_user_info, bili_runtime).await; - Ok(new_user_info) + match get_upstream_bili_account_info_app(access_key, appkey, bili_runtime).await { + Ok(value) => Ok(value), + Err(err_type) => match err_type { + // web端可能appkey不可用? + EType::ServerReqError("-663错误, 被鼠鼠制裁了, 请稍后重试") => { + if !is_app { + let new_user_info = UserInfo::new_unintended_error(access_key); + update_user_info_cache(&new_user_info, bili_runtime).await; + Ok(new_user_info) + } else { + // app端再次出现-663就是寄 + Err(err_type) + } + } + _ => Err(err_type), + }, } } -// pub async fn get_upstream_bili_account_info_background( -// access_key: &str, -// bili_runtime: &BiliRuntime<'_>, -// ) -> Result { -// match get_upstream_bili_account_info(access_key, "", "", false, "", 0, bili_runtime).await { -// Ok(value) => Ok(value), -// Err(value) => match value { -// EType::ServerReqError("-663错误, 您的账号似乎被鼠鼠风控了, 请稍后重试") => -// { -// let dt = Local::now(); -// let ts = dt.timestamp_millis() as u64; -// let cached_user_info = get_cached_user_info(access_key, bili_runtime).await; -// let uid = match cached_user_info { -// Some(value) => value.uid, -// None => { -// // TODO: 添加更多获取mid的方式 -// if let Some(value) = -// get_upstream_mid_from_playurl(access_key, bili_runtime).await -// { -// value -// } else { -// error!( -// "[GET USER_INFO][U] AK {} | Get User's mid failed", -// access_key -// ); -// return Err(EType::ServerGeneral); -// } -// } -// }; -// let vip_expire_time = get_upstream_vip_due_date_from_mid(uid, bili_runtime) -// .await -// .unwrap_or(0); -// Ok(UserInfo { -// code: 0, -// access_key: access_key.to_owned(), -// uid, -// vip_expire_time, -// expire_time: { -// if ts < vip_expire_time && vip_expire_time < ts + 25 * 24 * 60 * 60 * 1000 { -// vip_expire_time -// } else { -// ts + 25 * 24 * 60 * 60 * 1000 -// } -// }, -// }) -// } -// _ => Err(value), -// }, -// } -// } - async fn get_upstream_bili_account_info_app( access_key: &str, + appkey: &str, bili_runtime: &BiliRuntime<'_>, ) -> Result { let dt = Local::now(); @@ -105,9 +61,7 @@ async fn get_upstream_bili_account_info_app( let ts_min = dt.timestamp() as u64; let ts_min_string = ts_min.to_string(); - let appkey = "783bbb7264451d82"; - let appsec = "2653583c8873dea268ab9386918b1d65"; - let mobi_app = "android"; + let (appkey, appsec, mobi_app) = get_mobi_app(appkey); let rand_string_36 = { let words: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; @@ -251,7 +205,6 @@ async fn get_upstream_bili_account_info_app( Ok(UserInfo::new_unintended_error(access_key)) } -400 | -404 => { - // 已经指定appkey了, 不应当出现-404/-400, 除非这个appkey寄了 error!("[GET USER_INFO][U] AK {} -> Get UserInfo failed. Invalid APPKEY -> APPKEY {} | TS {} | APPSEC {}. Upstream Reply -> {}", access_key, appkey, ts_min, appsec, upstream_raw_resp ); @@ -408,6 +361,7 @@ pub async fn get_upstream_bili_account_info_vip_due_date( mid: u64, bili_runtime: &BiliRuntime<'_>, ) -> Option { + // https://api.bilibili.com/x/space/acc/info?mid=114514 也可以 let url = format!( "https://{}/x/space/wbi/acc/info?mid={mid}&token=&platform=web", bili_runtime.config.general_api_bilibili_com_proxy_api diff --git a/src/mods/user_info.rs b/src/mods/user_info.rs index fbe7942c..3eed9703 100644 --- a/src/mods/user_info.rs +++ b/src/mods/user_info.rs @@ -10,6 +10,7 @@ use log::{debug, error, info}; #[inline] pub async fn get_user_info( access_key: &str, + appkey: &str, is_app: bool, bili_runtime: &BiliRuntime<'_>, ) -> Result { @@ -33,19 +34,21 @@ pub async fn get_user_info( _ => Err(EType::ServerGeneral), } } - None => match get_upstream_bili_account_info(access_key, is_app, bili_runtime).await { - Ok(value) => { - debug!( + None => { + match get_upstream_bili_account_info(access_key, appkey, is_app, bili_runtime).await { + Ok(value) => { + debug!( "[GET USER_INFO] UID {} | AK {} | U.VIP {} -> Got AK {}'s user info from upstream", value.uid, value.access_key, value.is_vip(), access_key ); - Ok(value) + Ok(value) + } + Err(value) => Err(value), } - Err(value) => Err(value), - }, + } } } @@ -272,7 +275,7 @@ pub async fn resign_user_info( .unwrap_or((params.access_key.to_string(), 1)); let resign_user_info = - match get_user_info(&new_access_key, params.is_app, bili_runtime).await { + match get_user_info(&new_access_key, params.appkey, params.is_app, bili_runtime).await { Ok(value) => value, Err(value) => { return Err(value); From 1483b74a9e34d061f470aee4c0d322a238bf6ede Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E4=BD=B3=E6=B4=9B?= <73123406+pchpub@users.noreply.github.com> Date: Wed, 14 Dec 2022 14:58:32 +0800 Subject: [PATCH 13/18] add: mid to eid --- Cargo.toml | 2 +- src/mods/tools.rs | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 9eafce50..ca45ea07 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ pcre2 = "0.2.3" ctrlc = "3.2.3" rand = "0.8.5" actix-governor = "0.4.0-beta.3" -#fake-useragent = "0.1.3" +base64 = "0.20.0" urlencoding = "2.1.2" lazy_static = "1.4.0" log = "0.4" diff --git a/src/mods/tools.rs b/src/mods/tools.rs index fba26e49..cb08ad3b 100644 --- a/src/mods/tools.rs +++ b/src/mods/tools.rs @@ -455,3 +455,12 @@ pub fn vec_to_string(vec: &Vec, delimiter: &str) -> Str } } } + +pub fn mid_to_eid(mid: &str) -> String { + let mid: Vec<(char,usize)> = mid.chars().zip(0..).collect(); + let mut eid = Vec::with_capacity(mid.len()); + for (single_char,index) in &mid{ + eid.push(*single_char as u8 ^ "ad1va46a7lza".as_bytes()[index % 12] as u8); + } + base64::encode(eid) +} \ No newline at end of file From 1e604257adda972a49be77a49acf91074966490f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E4=BD=B3=E6=B4=9B?= <73123406+pchpub@users.noreply.github.com> Date: Wed, 14 Dec 2022 15:46:06 +0800 Subject: [PATCH 14/18] add: mid to eid --- src/mods/tools.rs | 162 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 159 insertions(+), 3 deletions(-) diff --git a/src/mods/tools.rs b/src/mods/tools.rs index cb08ad3b..d35a99b7 100644 --- a/src/mods/tools.rs +++ b/src/mods/tools.rs @@ -4,7 +4,7 @@ use super::{ }; use log::{debug, error}; use pcre2::bytes::Regex; -use std::env; +use std::{env, u8}; use std::path::PathBuf; use std::thread; @@ -459,8 +459,164 @@ pub fn vec_to_string(vec: &Vec, delimiter: &str) -> Str pub fn mid_to_eid(mid: &str) -> String { let mid: Vec<(char,usize)> = mid.chars().zip(0..).collect(); let mut eid = Vec::with_capacity(mid.len()); - for (single_char,index) in &mid{ + for (single_char,index) in &mid { eid.push(*single_char as u8 ^ "ad1va46a7lza".as_bytes()[index % 12] as u8); } base64::encode(eid) -} \ No newline at end of file +} + +// 有些api带eid, 这时候就可以获取到mid, 此函数作为后备方案 +pub fn eid_to_mid(eid: &str) -> Result { + fn mid_and_index_to_mid(mid: &u8,index: &usize) -> Result { + let index = index % 12; + match (mid,index) { + (81,0) => Ok('0'), + (84,1) => Ok('0'), + (1,2) => Ok('0'), + (70,3) => Ok('0'), + (81,4) => Ok('0'), + (4,5) => Ok('0'), + (6,6) => Ok('0'), + (81,7) => Ok('0'), + (7,8) => Ok('0'), + (92,9) => Ok('0'), + (74,10) => Ok('0'), + (81,11) => Ok('0'), + (80,0) => Ok('1'), + (85,1) => Ok('1'), + (0,2) => Ok('1'), + (71,3) => Ok('1'), + (80,4) => Ok('1'), + (5,5) => Ok('1'), + (7,6) => Ok('1'), + (80,7) => Ok('1'), + (6,8) => Ok('1'), + (93,9) => Ok('1'), + (75,10) => Ok('1'), + (80,11) => Ok('1'), + (83,0) => Ok('2'), + (86,1) => Ok('2'), + (3,2) => Ok('2'), + (68,3) => Ok('2'), + (83,4) => Ok('2'), + (6,5) => Ok('2'), + (4,6) => Ok('2'), + (83,7) => Ok('2'), + (5,8) => Ok('2'), + (94,9) => Ok('2'), + (72,10) => Ok('2'), + (83,11) => Ok('2'), + (82,0) => Ok('3'), + (87,1) => Ok('3'), + (2,2) => Ok('3'), + (69,3) => Ok('3'), + (82,4) => Ok('3'), + (7,5) => Ok('3'), + (5,6) => Ok('3'), + (82,7) => Ok('3'), + (4,8) => Ok('3'), + (95,9) => Ok('3'), + (73,10) => Ok('3'), + (82,11) => Ok('3'), + (85,0) => Ok('4'), + (80,1) => Ok('4'), + (5,2) => Ok('4'), + (66,3) => Ok('4'), + (85,4) => Ok('4'), + (0,5) => Ok('4'), + (2,6) => Ok('4'), + (85,7) => Ok('4'), + (3,8) => Ok('4'), + (88,9) => Ok('4'), + (78,10) => Ok('4'), + (85,11) => Ok('4'), + (84,0) => Ok('5'), + (81,1) => Ok('5'), + (4,2) => Ok('5'), + (67,3) => Ok('5'), + (84,4) => Ok('5'), + (1,5) => Ok('5'), + (3,6) => Ok('5'), + (84,7) => Ok('5'), + (2,8) => Ok('5'), + (89,9) => Ok('5'), + (79,10) => Ok('5'), + (84,11) => Ok('5'), + (87,0) => Ok('6'), + (82,1) => Ok('6'), + (7,2) => Ok('6'), + (64,3) => Ok('6'), + (87,4) => Ok('6'), + (2,5) => Ok('6'), + (0,6) => Ok('6'), + (87,7) => Ok('6'), + (1,8) => Ok('6'), + (90,9) => Ok('6'), + (76,10) => Ok('6'), + (87,11) => Ok('6'), + (86,0) => Ok('7'), + (83,1) => Ok('7'), + (6,2) => Ok('7'), + (65,3) => Ok('7'), + (86,4) => Ok('7'), + (3,5) => Ok('7'), + (1,6) => Ok('7'), + (86,7) => Ok('7'), + (0,8) => Ok('7'), + (91,9) => Ok('7'), + (77,10) => Ok('7'), + (86,11) => Ok('7'), + (89,0) => Ok('8'), + (92,1) => Ok('8'), + (9,2) => Ok('8'), + (78,3) => Ok('8'), + (89,4) => Ok('8'), + (12,5) => Ok('8'), + (14,6) => Ok('8'), + (89,7) => Ok('8'), + (15,8) => Ok('8'), + (84,9) => Ok('8'), + (66,10) => Ok('8'), + (89,11) => Ok('8'), + (88,0) => Ok('9'), + (93,1) => Ok('9'), + (8,2) => Ok('9'), + (79,3) => Ok('9'), + (88,4) => Ok('9'), + (13,5) => Ok('9'), + (15,6) => Ok('9'), + (88,7) => Ok('9'), + (14,8) => Ok('9'), + (85,9) => Ok('9'), + (67,10) => Ok('9'), + (88,11) => Ok('9'), + _ => Err(()) + } + } + let eid: Vec<(u8,usize)> = if let Ok(value) = base64::decode(eid){ + value.into_iter().zip(0..).collect() + }else{ + return Err(()); + }; + let mut mid = String::with_capacity(eid.len()); + for (single_char,index) in &eid { + mid.push({ + if let Ok(value) = mid_and_index_to_mid(single_char, index) { + value + }else{ + return Err(()); + } + }); + } + Ok(mid) +} + +// 用来生成代码段挺方便的 仅供测试 +// pub fn gen_eid_to_mid_map() -> () { +// for i in 0..10 { +// for n in 0..12 { +// println!("({},{n}) => Ok('{i}'),",i.to_string().as_str().chars().collect::>()[0] as u8 ^ "ad1va46a7lza".as_bytes()[n] as u8); +// } +// } +// () +// } \ No newline at end of file From 16dcdaefd2d021acc1be307679afcfcb24d6c1bf Mon Sep 17 00:00:00 2001 From: cxw620 Date: Wed, 14 Dec 2022 19:34:12 +0800 Subject: [PATCH 15/18] fix: fatal error on get_user_mid_from_playurl --- src/mods/upstream_res.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mods/upstream_res.rs b/src/mods/upstream_res.rs index 3b57c706..d18de512 100644 --- a/src/mods/upstream_res.rs +++ b/src/mods/upstream_res.rs @@ -671,7 +671,7 @@ pub async fn get_upstream_bili_playurl( // check user's vip status update web user's user_info // 是vip的用户必定是正常请求api获得了用户信息的 // 对非VIP用户不友好, 笑 - if !params.is_vip || user_info.code == -999 { + if (!params.is_vip || user_info.code == -999) && (!params.is_th) { // 处理网页用户等 let uid = if let Some(value) = get_user_mid_from_playurl(&upstream_raw_resp.resp_content) { value From 516f69fc53356df176a6bebc39aa07bf77a5a19a Mon Sep 17 00:00:00 2001 From: cxw620 Date: Wed, 14 Dec 2022 20:18:54 +0800 Subject: [PATCH 16/18] fix: fatal error on get_user_mid_from_playurl --- src/mods/upstream_res.rs | 129 +++++++++++++++++++++------------------ 1 file changed, 69 insertions(+), 60 deletions(-) diff --git a/src/mods/upstream_res.rs b/src/mods/upstream_res.rs index d18de512..c1579d0f 100644 --- a/src/mods/upstream_res.rs +++ b/src/mods/upstream_res.rs @@ -671,73 +671,82 @@ pub async fn get_upstream_bili_playurl( // check user's vip status update web user's user_info // 是vip的用户必定是正常请求api获得了用户信息的 // 对非VIP用户不友好, 笑 - if (!params.is_vip || user_info.code == -999) && (!params.is_th) { - // 处理网页用户等 - let uid = if let Some(value) = get_user_mid_from_playurl(&upstream_raw_resp.resp_content) { - value - } else { - return Err(EType::UserNotLoginedError); - }; - let vip_expire_time = get_upstream_bili_account_info_vip_due_date(uid, bili_runtime) - .await - .unwrap_or(0); - let new_user_info = UserInfo::new(0, params.access_key, uid, vip_expire_time); - update_user_info_cache(&new_user_info, bili_runtime).await; - match get_blacklist_info(&new_user_info, bili_runtime).await { - Ok(_) => (), - Err(value) => return Err(value), + if !params.is_th { + if user_info.code == -999 { + // 处理网页用户等 + // 东南亚, 可能共享了服主的vip, mid是不准的 + let uid = + if let Some(value) = get_user_mid_from_playurl(&upstream_raw_resp.resp_content) { + value + } else { + return Err(EType::UserNotLoginedError); + }; + let vip_expire_time = get_upstream_bili_account_info_vip_due_date(uid, bili_runtime) + .await + .unwrap_or(0); + let new_user_info = UserInfo::new(0, params.access_key, uid, vip_expire_time); + update_user_info_cache(&new_user_info, bili_runtime).await; + match get_blacklist_info(&new_user_info, bili_runtime).await { + Ok(_) => (), + Err(value) => return Err(value), + } + params.is_vip = new_user_info.is_vip(); } - params.is_vip = new_user_info.is_vip(); - if let Ok(value) = check_vip_status_from_playurl(playurl_type, &upstream_raw_resp_json) { - if value && (!params.is_vip) { - match get_ep_need_vip(params.ep_id, bili_runtime).await { - Some(ep_need_vip) => { - if ep_need_vip == 1 { - update_cached_ep_vip_status_background( - true, - vec![EpInfo { - ep_id: params.ep_id.parse::().unwrap_or(233), - ..Default::default() - }], - bili_runtime, - ) - .await; + // 防止东南亚区共享VIP出问题 + if !params.is_vip { + if let Ok(value) = check_vip_status_from_playurl(playurl_type, &upstream_raw_resp_json) + { + if value && (!params.is_vip) { + match get_ep_need_vip(params.ep_id, bili_runtime).await { + Some(ep_need_vip) => { + if ep_need_vip == 1 { + update_cached_ep_vip_status_background( + true, + vec![EpInfo { + ep_id: params.ep_id.parse::().unwrap_or(233), + ..Default::default() + }], + bili_runtime, + ) + .await; + } + error!( + "[GET PLAYURL][U] UID {} | AK {} | AREA {} | EP {} -> 非大会员用户获取了大会员独享视频, 可能大会员状态变动或限免, 并且尝试更新ep_need_vip成功", + user_info.uid, user_info.access_key, params.area.to_ascii_uppercase(), params.ep_id + ); + } + None => { + error!( + "[GET PLAYURL][U] UID {} | AK {} | AREA {} | EP {} -> 非大会员用户获取了大会员独享视频, 可能大会员状态变动或限免, 并且尝试更新ep_need_vip失败", + user_info.uid, user_info.access_key, params.area.to_ascii_uppercase(), params.ep_id + ); } - error!( - "[GET PLAYURL][U] UID {} | AK {} | AREA {} | EP {} -> 非大会员用户获取了大会员独享视频, 可能大会员状态变动或限免, 并且尝试更新ep_need_vip成功", - user_info.uid, user_info.access_key, params.area.to_ascii_uppercase(), params.ep_id - ); - } - None => { - error!( - "[GET PLAYURL][U] UID {} | AK {} | AREA {} | EP {} -> 非大会员用户获取了大会员独享视频, 可能大会员状态变动或限免, 并且尝试更新ep_need_vip失败", - user_info.uid, user_info.access_key, params.area.to_ascii_uppercase(), params.ep_id - ); } + report_health( + HealthReportType::Playurl(HealthData { + area_num: params.area_num, + is_200_ok: true, + upstream_reply: UpstreamReply { + code, + proxy_open, + proxy_url: proxy_url.to_owned(), + ..Default::default() + }, + is_custom: true, + custom_message: format!("[GET PLAYURL][U] EP {} -> 非大会员用户获取了大会员独享视频. 可能限免, 请人工核实...", params.ep_id), + }), + bili_runtime, + ) + .await; + return Err(EType::OtherError( + -10403, + "检测到可能刚刚买了带会员, 刷新缓存中, 请稍后重试喵", + )); } - report_health( - HealthReportType::Playurl(HealthData { - area_num: params.area_num, - is_200_ok: true, - upstream_reply: UpstreamReply { - code, - proxy_open, - proxy_url: proxy_url.to_owned(), - ..Default::default() - }, - is_custom: true, - custom_message: format!("[GET PLAYURL][U] EP {} -> 非大会员用户获取了大会员独享视频. 可能限免, 请人工核实...", params.ep_id), - }), - bili_runtime, - ) - .await; - return Err(EType::OtherError( - -10403, - "检测到可能刚刚买了带会员, 刷新缓存中, 请稍后重试喵", - )); } } } + debug!( "[GET PLAYURL][U] UID {} | AK {} | AREA {} | EP {} -> 获取成功", user_info.uid, From 905565abe337d40473b53d853661f0690b7fc330 Mon Sep 17 00:00:00 2001 From: cxw620 Date: Fri, 16 Dec 2022 03:37:30 +0800 Subject: [PATCH 17/18] feat: adapt new method optaining platform --- src/mods/background_tasks.rs | 2 + src/mods/handler.rs | 22 ++- src/mods/tools.rs | 1 + src/mods/types.rs | 277 ++++++++++++++++++++++++++++++++++- src/mods/upstream_res.rs | 139 +++++++++++++++--- 5 files changed, 414 insertions(+), 27 deletions(-) diff --git a/src/mods/background_tasks.rs b/src/mods/background_tasks.rs index 7aacb008..636c7163 100644 --- a/src/mods/background_tasks.rs +++ b/src/mods/background_tasks.rs @@ -37,6 +37,8 @@ pub async fn update_cached_playurl_background( season_id: params.season_id.to_string(), build: params.build.to_string(), device: params.device.to_string(), + mobi_app: params.mobi_app.to_string(), + platform: params.platform.to_string(), is_app: params.is_app, is_tv: params.is_tv, is_th: params.is_th, diff --git a/src/mods/handler.rs b/src/mods/handler.rs index f87509a2..f57bd5b3 100644 --- a/src/mods/handler.rs +++ b/src/mods/handler.rs @@ -11,6 +11,7 @@ use super::upstream_res::{ get_upstream_bili_subtitle, }; use super::user_info::*; +use crate::mods::types::ClientType; use crate::{build_response, build_result_response}; use actix_web::http::header::ContentType; use actix_web::{HttpRequest, HttpResponse}; @@ -156,12 +157,25 @@ pub async fn handle_playurl_request(req: &HttpRequest, is_app: bool, is_th: bool }; params.cid = query.get("cid").unwrap_or(""); + // detect client_type + let client_type = + if let Some(value) = ClientType::init(params.appkey, params.is_app, params.is_th, req) { + value + } else { + build_response!(EType::InvalidReq) + }; // detect other info params.build = query.get("build").unwrap_or("6800300"); - params.device = - query - .get("device") - .unwrap_or_else(|| if params.is_app { "android" } else { "iphone" }); + params.device = query + .get("device") + .unwrap_or(client_type.device().unwrap_or("")); + params.platform = query + .get("platform") + .unwrap_or(client_type.platform().unwrap_or("")); + params.mobi_app = query + .get("platform") + .unwrap_or(client_type.mobi_app().unwrap_or("")); + params.is_tv = match query.get("fnval") { Some(value) => match value { "130" | "0" | "2" => true, diff --git a/src/mods/tools.rs b/src/mods/tools.rs index d35a99b7..392a8e58 100644 --- a/src/mods/tools.rs +++ b/src/mods/tools.rs @@ -379,6 +379,7 @@ pub fn get_mobi_app(appkey: &str) -> (&'static str, &'static str, &'static str) } } + pub fn update_server(is_auto_close: bool) { thread::spawn(move || { let mut tags = format!("v{}", env!("CARGO_PKG_VERSION")); diff --git a/src/mods/types.rs b/src/mods/types.rs index 164ef0fb..a82ec898 100644 --- a/src/mods/types.rs +++ b/src/mods/types.rs @@ -3,9 +3,11 @@ use super::{ request::{redis_get, redis_set}, tools::remove_viponly_clarity, }; +use actix_web::HttpRequest; use async_channel::{Sender, TrySendError}; use chrono::{FixedOffset, Local, TimeZone, Utc}; use deadpool_redis::Pool; +use log::error; use rand::Rng; use serde::{Deserialize, Serialize}; use std::{collections::HashMap, hash::Hash, sync::Arc}; @@ -235,6 +237,259 @@ impl<'bili_runtime> BiliRuntime<'bili_runtime> { } } +/// 识别 +pub enum ClientType { + Ai4cCreatorAndroid, + Android, + AndroidB, + AndroidBiliThings, + AndroidHD, + AndroidI, + AndroidMallTicket, + AndroidOttSdk, + AndroidTV, + AnguAndroid, + BiliLink, + BiliScan, + BstarA, + Invalid(&'static str, &'static str), + Ios, + // Iphone, //有个不知道能不能用的key对应的是Iphone + Web, + Unknown, +} +impl ClientType { + pub fn init(appkey: &str, is_app: bool, is_th: bool, req: &HttpRequest) -> Option { + let platform = if let Some(value) = req.headers().get("platform-from-biliroaming") { + value.to_str().unwrap_or("") + } else { + "" + }; + if appkey.is_empty() && platform.is_empty() { + return None; + } + if is_th { + Some(ClientType::BstarA) + } else { + if !is_app { + Some(ClientType::Web) + } else { + if platform.is_empty() { + ClientType::detect_client_type_from_appkey(appkey) + } else { + ClientType::detect_client_type_from_platform(platform) + } + } + } + } + pub fn appkey(&self) -> &'static str { + match self { + ClientType::Ai4cCreatorAndroid => "9d5889cf67e615cd", + ClientType::Android => "1d8b6e7d45233436", + ClientType::AndroidB => "07da50c9a0bf829f", + ClientType::AndroidBiliThings => "8d23902c1688a798", + ClientType::AndroidHD => "dfca71928277209b", + ClientType::AndroidI => "bb3101000e232e27", + ClientType::AndroidMallTicket => "4c6e1021617d40d9", + ClientType::AndroidOttSdk => "c034e8b74130a886", + ClientType::AndroidTV => "4409e2ce8ffd12b8", + ClientType::AnguAndroid => "50e1328c6a1075a1", + ClientType::BiliLink => "37207f2beaebf8d7", + ClientType::BiliScan => "9a75abf7de2d8947", + ClientType::BstarA => "7d089525d3611b1c", + ClientType::Web | ClientType::Ios => "27eb53fc9058f8c3", //web使用ios的appkey + ClientType::Invalid(appkey, _) => appkey, + ClientType::Unknown => "", + } + } + pub fn appsec(&self) -> &'static str { + match self { + ClientType::Ai4cCreatorAndroid => "8fd9bb32efea8cef801fd895bef2713d", + ClientType::Android => "560c52ccd288fed045859ed18bffd973", + ClientType::AndroidB => "25bdede4e1581c836cab73a48790ca6e", + ClientType::AndroidBiliThings => "710f0212e62bd499b8d3ac6e1db9302a", + ClientType::AndroidHD => "b5475a8825547a4fc26c7d518eaaa02e", + ClientType::AndroidI => "36efcfed79309338ced0380abd824ac1", + ClientType::AndroidMallTicket => "e559a59044eb2701b7a8628c86aa12ae", + ClientType::AndroidOttSdk => "e4e8966b1e71847dc4a3830f2d078523", + ClientType::AndroidTV => "59b43e04ad6965f34319062b478f83dd", + ClientType::AnguAndroid => "4d35e3dea073433cd24dd14b503d242e", + ClientType::BiliLink => "e988e794d4d4b6dd43bc0e89d6e90c43", + ClientType::BiliScan => "35ca1c82be6c2c242ecc04d88c735f31", + ClientType::BstarA => "acd495b248ec528c2eed1e862d393126", + ClientType::Web | ClientType::Ios => "c2ed53a74eeefe3cf99fbd01d8c9c375", //web使用ios的appkey + ClientType::Invalid(_, appsec) => appsec, + ClientType::Unknown => "", + // ClientType::Iphone => "8cb98205e9b2ad3669aad0fce12a4c13", + } + } + // 可能用户请求自带 + // 需要抓包获得, 等待日后完善 + // 未知的暂时返回None + pub fn mobi_app(&self) -> Option<&'static str> { + match self { + // ClientType::Ai4cCreatorAndroid => todo!(), + ClientType::Android => Some("android"), + ClientType::AndroidB => Some("android_b"), + // ClientType::AndroidBiliThings => todo!(), + // ClientType::AndroidHD => todo!(), + ClientType::AndroidI => Some("android_i"), + // ClientType::AndroidMallTicket => todo!(), + // ClientType::AndroidOttSdk => todo!(), + ClientType::AndroidTV => Some("android_tv_yst"), //TV端, 云视听小电视, ver 1.5.4 build 105301 + // ClientType::AnguAndroid => todo!(), + // ClientType::BiliLink => todo!(), + // ClientType::BiliScan => todo!(), + ClientType::BstarA => Some("bstar_a"), + ClientType::Web | ClientType::Ios => Some("iphone"), + _ => None, + } + } + pub fn device(&self) -> Option<&'static str> { + match self { + // ClientType::Ai4cCreatorAndroid => todo!(), + ClientType::Android => Some("android"), + ClientType::AndroidB => Some("android"), + // ClientType::AndroidBiliThings => todo!(), + // ClientType::AndroidHD => todo!(), + ClientType::AndroidI => Some("android"), + // ClientType::AndroidMallTicket => todo!(), + // ClientType::AndroidOttSdk => todo!(), + // ClientType::AndroidTV => Some("android"), + // ClientType::AnguAndroid => todo!(), + // ClientType::BiliLink => todo!(), + // ClientType::BiliScan => todo!(), + ClientType::BstarA => Some("android"), + ClientType::Web | ClientType::Ios => Some("iphone"), + _ => None, + } + } + pub fn platform(&self) -> Option<&'static str> { + match self { + // ClientType::Ai4cCreatorAndroid => todo!(), + ClientType::Android => Some("android"), + ClientType::AndroidB => Some("android"), + // ClientType::AndroidBiliThings => todo!(), + // ClientType::AndroidHD => todo!(), + ClientType::AndroidI => Some("android"), + // ClientType::AndroidMallTicket => todo!(), + // ClientType::AndroidOttSdk => todo!(), + ClientType::AndroidTV => Some("android"), //TV端, 云视听小电视, ver 1.5.4 build 105301 + // ClientType::AnguAndroid => todo!(), + // ClientType::BiliLink => todo!(), + // ClientType::BiliScan => todo!(), + ClientType::BstarA => Some("android"), + ClientType::Web | ClientType::Ios => Some("ios"), + _ => None, + } + } + fn detect_client_type_from_platform(platform: &str) -> Option { + match platform { + "ai4c_creator_android" => Some(ClientType::Ai4cCreatorAndroid), + "android" => Some(ClientType::Android), + "android_b" => Some(ClientType::AndroidB), + "android_bilithings" => Some(ClientType::AndroidBiliThings), + "android_hd" => Some(ClientType::AndroidHD), + "android_i" => Some(ClientType::AndroidI), + "android_mall_ticket" => Some(ClientType::AndroidMallTicket), + "android_ott_sdk" => Some(ClientType::AndroidOttSdk), + "android_tv" => Some(ClientType::AndroidTV), + "angu_android" => Some(ClientType::AnguAndroid), + "biliLink" => Some(ClientType::BiliLink), + "biliScan" => Some(ClientType::BiliScan), + "bstar_a" => Some(ClientType::BstarA), + "web" => Some(ClientType::Web), + "iphone" => Some(ClientType::Ios), + _ => { + error!("[PLATFORM] 检测到未知的platform {platform}"); + None + } + } + } + fn detect_client_type_from_appkey(appkey: &str) -> Option { + match appkey { + "9d5889cf67e615cd" => Some(ClientType::Ai4cCreatorAndroid), + "1d8b6e7d45233436" => Some(ClientType::Android), + "c1b107428d337928" => Some(ClientType::Invalid( + "c1b107428d337928", + "ea85624dfcf12d7cc7b2b3a94fac1f2c ", + )), + "783bbb7264451d82" => Some(ClientType::Invalid( + "783bbb7264451d82", + "2653583c8873dea268ab9386918b1d65", + )), + "57263273bc6b67f6" => Some(ClientType::Invalid( + "57263273bc6b67f6", + "a0488e488d1567960d3a765e8d129f90", + )), + "07da50c9a0bf829f" => Some(ClientType::AndroidB), + "7d336ec01856996b" => Some(ClientType::Invalid( + "7d336ec01856996b", + "a1ce6983bc89e20a36c37f40c4f1a0dd", + )), + "178cf125136ca8ea" => Some(ClientType::Invalid( + "178cf125136ca8ea", + "34381a26236dd1171185c0beb042e1c6", + )), + "8d23902c1688a798" => Some(ClientType::AndroidBiliThings), + "dfca71928277209b" => Some(ClientType::AndroidHD), + "bb3101000e232e27" => Some(ClientType::AndroidI), + "8e16697a1b4f8121" => Some(ClientType::Invalid( + "8e16697a1b4f8121", + "f5dd03b752426f2e623d7badb28d190a", + )), + "ae57252b0c09105d" => Some(ClientType::Invalid( + "ae57252b0c09105d", + "c75875c596a69eb55bd119e74b07cfe3", + )), + "4c6e1021617d40d9" => Some(ClientType::AndroidMallTicket), + "c034e8b74130a886" => Some(ClientType::AndroidOttSdk), + "4409e2ce8ffd12b8" => Some(ClientType::AndroidTV), + "50e1328c6a1075a1" => Some(ClientType::AnguAndroid), + "37207f2beaebf8d7" => Some(ClientType::BiliLink), + "9a75abf7de2d8947" => Some(ClientType::BiliScan), + "7d089525d3611b1c" => Some(ClientType::BstarA), + "27eb53fc9058f8c3" => Some(ClientType::Ios), + "85eb6835b0a1034e" => Some(ClientType::Invalid( + "85eb6835b0a1034e", + "2ad42749773c441109bdc0191257a664", + )), + "84956560bc028eb7" => Some(ClientType::Invalid( + "84956560bc028eb7", + "94aba54af9065f71de72f5508f1cd42e", + )), + "aae92bc66f3edfab" => Some(ClientType::Invalid( + "aae92bc66f3edfab", + "af125a0d5279fd576c1b4418a3e8276d", + )), + "bca7e84c2d947ac6" => Some(ClientType::Invalid( + "bca7e84c2d947ac6", + "60698ba2f68e01ce44738920a0ffe768", + )), + "4ebafd7c4951b366" => Some(ClientType::Invalid( + "4ebafd7c4951b366", + "8cb98205e9b2ad3669aad0fce12a4c13", + )), + "cc8617fd6961e070" => Some(ClientType::Invalid( + "cc8617fd6961e070", + "3131924b941aac971e45189f265262be", + )), + "iVGUTjsxvpLeuDCf" => Some(ClientType::Invalid( + "iVGUTjsxvpLeuDCf", + "aHRmhWMLkdeMuILqORnYZocwMBpMEOdt", + )), + "YvirImLGlLANCLvM" => Some(ClientType::Invalid( + "YvirImLGlLANCLvM", + "JNlZNgfNGKZEpaDTkCdPQVXntXhuiJEM", + )), + _ => { + error!("[APPKEY] 检测到未知的appkey {appkey}"); + None + } + } + } +} + pub enum ReqType { Playurl(Area, bool), Search(Area, bool), @@ -1676,7 +1931,7 @@ pub struct UserInfo { } impl UserInfo { - pub fn new(code:i64, access_key: &str, uid: u64, vip_expire_time: u64) -> UserInfo { + pub fn new(code: i64, access_key: &str, uid: u64, vip_expire_time: u64) -> UserInfo { let dt = Local::now(); let ts = dt.timestamp_millis() as u64; UserInfo { @@ -1776,6 +2031,8 @@ pub struct PlayurlParamsStatic { pub season_id: String, pub build: String, pub device: String, + pub mobi_app: String, + pub platform: String, // extra info pub is_app: bool, pub is_tv: bool, @@ -1808,6 +2065,8 @@ impl PlayurlParamsStatic { season_id: &self.season_id, build: &self.build, device: &self.device, + mobi_app: &self.mobi_app, + platform: &self.platform, is_app: self.is_app, is_tv: self.is_tv, is_th: self.is_th, @@ -1816,6 +2075,7 @@ impl PlayurlParamsStatic { area: &self.area, area_num: self.area_num, user_agent: &self.user_agent, + } } } @@ -1829,6 +2089,8 @@ pub struct PlayurlParams<'playurl_params> { pub season_id: &'playurl_params str, pub build: &'playurl_params str, pub device: &'playurl_params str, + pub mobi_app: &'playurl_params str, + pub platform: &'playurl_params str, // extra info pub is_app: bool, pub is_tv: bool, @@ -1870,7 +2132,9 @@ impl<'bili_playurl_params: 'playurl_params_impl, 'playurl_params_impl> Default cid: "", season_id: "", build: "6800300", - device: "android", + device: "", + mobi_app: "", + platform: "", is_app: true, is_tv: false, is_th: false, @@ -1879,6 +2143,7 @@ impl<'bili_playurl_params: 'playurl_params_impl, 'playurl_params_impl> Default area: "hk", area_num: 2, user_agent: "Dalvik/2.1.0 (Linux; U; Android 12; PFEM10 Build/SKQ1.211019.001)", + //不清楚iphone的UA } } } @@ -1929,7 +2194,7 @@ impl<'bili_playurl_params: 'playurl_params_impl, 'playurl_params_impl> "57263273bc6b67f6" => "a0488e488d1567960d3a765e8d129f90", // Android "7d336ec01856996b" => "a1ce6983bc89e20a36c37f40c4f1a0dd", // AndroidB "85eb6835b0a1034e" => "2ad42749773c441109bdc0191257a664", // unknown // 不能用于获取UserInfo, 会404 - "84956560bc028eb7" => "94aba54af9065f71de72f5508f1cd42e", // unknown // 不能用于获取UserInfo, 会404 + "84956560bc028eb7" => "94aba54af9065f71de72f5508f1cd42e", // PC UWP // 不能用于获取UserInfo, 会404 "8e16697a1b4f8121" => "f5dd03b752426f2e623d7badb28d190a", // AndroidI "aae92bc66f3edfab" => "af125a0d5279fd576c1b4418a3e8276d", // PC 投稿工具 "ae57252b0c09105d" => "c75875c596a69eb55bd119e74b07cfe3", // AndroidI @@ -2201,9 +2466,9 @@ impl EType { EType::UserNonVIPError => { String::from("{\"code\":-10403,\"message\":\"大会员专享限制\"}") } - EType::UserNotLoginedError => { - String::from("{\"code\":-101,\"message\":\"账号未登录, 若已登录请尝试退出重新登录\"}") - } + EType::UserNotLoginedError => String::from( + "{\"code\":-101,\"message\":\"账号未登录, 若已登录请尝试退出重新登录\"}", + ), EType::InvalidReq => String::from("{\"code\":-412,\"message\":\"请求被拦截\"}"), EType::OtherError(err_code, err_msg) => { format!("{{\"code\":{err_code},\"message\":\"其他错误: {err_msg}\"}}") diff --git a/src/mods/upstream_res.rs b/src/mods/upstream_res.rs index c1579d0f..2e9a3621 100644 --- a/src/mods/upstream_res.rs +++ b/src/mods/upstream_res.rs @@ -34,20 +34,32 @@ pub async fn get_upstream_bili_account_info( ) -> Result { match get_upstream_bili_account_info_app(access_key, appkey, bili_runtime).await { Ok(value) => Ok(value), - Err(err_type) => match err_type { - // web端可能appkey不可用? - EType::ServerReqError("-663错误, 被鼠鼠制裁了, 请稍后重试") => { - if !is_app { - let new_user_info = UserInfo::new_unintended_error(access_key); - update_user_info_cache(&new_user_info, bili_runtime).await; - Ok(new_user_info) - } else { - // app端再次出现-663就是寄 - Err(err_type) + Err(err_type) => { + // more method get mid + if let Some(mid) = + get_upstream_bili_account_info_ak_to_mid(access_key, bili_runtime).await + { + if let Some(vip_expire_time) = + get_upstream_bili_account_info_vip_due_date(mid, bili_runtime).await + { + return Ok(UserInfo::new(0, access_key, mid, vip_expire_time)); } + }; + match err_type { + // web端可能appkey不可用? + EType::ServerReqError("-663错误, 被鼠鼠制裁了, 请稍后重试") => { + if !is_app { + let new_user_info = UserInfo::new_unintended_error(access_key); + update_user_info_cache(&new_user_info, bili_runtime).await; + Ok(new_user_info) + } else { + // app端再次出现-663就是寄 + Err(err_type) + } + } + _ => Err(err_type), } - _ => Err(err_type), - }, + } } } @@ -135,11 +147,6 @@ async fn get_upstream_bili_account_info_app( } }; - debug!( - "[GET USER_INFO][U] AK {} | Upstream Reply: {}", - access_key, upstream_raw_resp - ); - let upstream_raw_resp_json = upstream_raw_resp .json() .unwrap_or(json!({"code": -500, "message": "[SERVER] 解析上游JSON错误"})); @@ -388,6 +395,104 @@ pub async fn get_upstream_bili_account_info_vip_due_date( } } +pub async fn get_upstream_bili_account_info_ak_to_mid( + access_key: &str, + bili_runtime: &BiliRuntime<'_>, +) -> Option { + let api = format!("https://api.bilibili.com/x/member/web/account?access_key={access_key}"); + let upstream_raw_resp = match async_getwebpage( + &api, + bili_runtime.config.cn_proxy_accesskey_open, + &bili_runtime.config.cn_proxy_accesskey_url, + &FakeUA::Web.gen(), // 客户端请求UA和普通的不一样 + "", + None, + ) + .await + { + Ok(value) => value, + Err(_) => { + error!("[GET MID FUNC] AK {access_key} | 获取mid失败, 网络问题"); + return None; + } + }; + let upstream_raw_resp_json = upstream_raw_resp.json().unwrap_or(json!({"code": -999})); + let code = upstream_raw_resp_json["code"].as_i64().unwrap_or(-999); + match code { + 0 => { + if let Some(value) = upstream_raw_resp_json["data"]["mid"].as_u64() { + Some(value) + } else { + let health_report_type = HealthReportType::Others(HealthData { + area_num: 0, + is_200_ok: true, + upstream_reply: UpstreamReply { + message: upstream_raw_resp_json["message"] + .as_str() + .unwrap_or("null") + .to_owned(), + ..Default::default() + }, + is_custom: true, + custom_message: format!( + "[GET USER_INFO][U][GET MID FUNC] 解析mid为u64失败! 上游返回: {upstream_raw_resp}" + ), + }); + report_health(health_report_type, bili_runtime).await; + error!("[GET MID FUNC] AK {access_key} | 解析mid为u64失败. 上级返回内容 -> {upstream_raw_resp}"); + None + } + } + -101 => { + // 用户未登录, 即access_key失效 + error!("[GET MID FUNC] AK {access_key} | 获取mid失败, 用户未登录. 上级返回内容 -> {upstream_raw_resp}"); + Some(0) + } + -999 => { + error!("[GET MID FUNC] AK {access_key} | 解析上级返回JSON失败 -> {upstream_raw_resp}"); + let health_report_type = HealthReportType::Others(HealthData { + area_num: 0, + is_200_ok: true, + upstream_reply: UpstreamReply { + message: upstream_raw_resp_json["message"] + .as_str() + .unwrap_or("null") + .to_owned(), + upstream_header: upstream_raw_resp.read_headers(), + ..Default::default() + }, + is_custom: true, + custom_message: format!( + "[GET USER_INFO][U][GET MID FUNC] 解析上级返回JSON失败! 上游返回: {upstream_raw_resp}" + ), + }); + report_health(health_report_type, bili_runtime).await; + None + } + _ => { + error!("[GET MID FUNC] AK {access_key} | 获取mid失败, 未知的错误码. 上级返回内容 -> {upstream_raw_resp}"); + let health_report_type = HealthReportType::Others(HealthData { + area_num: 0, + is_200_ok: true, + upstream_reply: UpstreamReply { + code, + message: upstream_raw_resp_json["message"] + .as_str() + .unwrap_or("null") + .to_owned(), + upstream_header: upstream_raw_resp.read_headers(), + ..Default::default() + }, + is_custom: true, + custom_message: format!( + "[GET USER_INFO][U][GET MID FUNC] 获取mid失败, 未知的错误码! 上游返回: {upstream_raw_resp}" + ), + }); + report_health(health_report_type, bili_runtime).await; + None + } + } +} pub async fn get_upstream_blacklist_info( user_info: &UserInfo, bili_runtime: &BiliRuntime<'_>, From 8916c2555fbfdeeefbe1f29eb4acfd3390617a51 Mon Sep 17 00:00:00 2001 From: cxw620 Date: Fri, 16 Dec 2022 03:48:53 +0800 Subject: [PATCH 18/18] fix: minor change --- src/mods/upstream_res.rs | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/mods/upstream_res.rs b/src/mods/upstream_res.rs index 2e9a3621..1d1203c1 100644 --- a/src/mods/upstream_res.rs +++ b/src/mods/upstream_res.rs @@ -641,12 +641,10 @@ pub async fn get_upstream_bili_playurl( query_vec = vec![ ("access_key", ¶ms.access_key[..]), ("appkey", params.appkey), - ("build", params.build), - ("device", params.device), + ("ep_id", params.ep_id), ("fnval", "130"), ("fnver", "0"), ("fourk", "1"), - ("platform", "android"), //("qn", query.get("qn").unwrap_or("112")), //720P 64 1080P高码率 112 ("qn", "112"), //测试了下,没会员会回落到下一档,所以没必要区分 DLNA投屏就最高一档好了,支持其他档没必要,还增加服务器负担 ("ts", &ts_string), @@ -655,24 +653,30 @@ pub async fn get_upstream_bili_playurl( query_vec = vec![ ("access_key", ¶ms.access_key[..]), ("appkey", params.appkey), - ("build", params.build), - ("device", params.device), + ("ep_id", params.ep_id), ("fnval", "4048"), ("fnver", "0"), ("fourk", "1"), - ("platform", "android"), ("qn", "125"), ("ts", &ts_string), ]; } - if params.ep_id.is_empty() { - return Err(EType::InvalidReq); - } else { - query_vec.push(("ep_id", params.ep_id)); - } if !params.cid.is_empty() { query_vec.push(("cid", params.cid)); } + if !params.build.is_empty() { + query_vec.push(("build", params.build)); + } + if !params.device.is_empty() { + query_vec.push(("device", params.device)); + } + if !params.mobi_app.is_empty() { + query_vec.push(("mobi_app", params.mobi_app)); + } + if !params.platform.is_empty() { + query_vec.push(("platform", params.platform)); + } + if params.is_th { query_vec.push(("s_locale", "zh_SG")); }