-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #94 from alley-rs/dev
refactor(bilibili): bilibili 解析器用 rust 重构
- Loading branch information
Showing
12 changed files
with
575 additions
and
218 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
use reqwest::Client; | ||
|
||
use crate::error::LsarResult; | ||
use crate::parser::ParsedResult; | ||
use crate::platform::Platform; | ||
|
||
use super::cookie_verifier::CookieVerifier; | ||
use super::html_fetcher::HTMLFetcher; | ||
use super::link_parser::LinkParser; | ||
use super::room_info_fetcher::RoomInfoFetcher; | ||
use super::room_play_info_fetcher::RoomPlayInfoFetcher; | ||
|
||
pub struct BilibiliParser { | ||
room_id: u64, | ||
page_url: String, | ||
cookie: String, | ||
client: Client, | ||
} | ||
|
||
impl BilibiliParser { | ||
pub fn new(cookie: String, room_id: u64, url: Option<String>) -> Self { | ||
let page_url = url.unwrap_or_else(|| format!("https://live.bilibili.com/{}", room_id)); | ||
let client = reqwest::Client::new(); | ||
|
||
BilibiliParser { | ||
room_id, | ||
page_url, | ||
cookie, | ||
client, | ||
} | ||
} | ||
|
||
pub async fn parse(&mut self) -> LsarResult<ParsedResult> { | ||
trace!("Starting parsing process for room ID: {}", self.room_id); | ||
|
||
let cookie_verifier = CookieVerifier::new(&self.client, &self.cookie); | ||
match cookie_verifier.verify().await { | ||
Ok(username) => { | ||
info!( | ||
"Cookie verification successful. Logged in user: {}", | ||
username | ||
); | ||
} | ||
Err(e) => { | ||
error!("Cookie verification failed. Error: {}. Details: {:?}", e, e); | ||
return Err(e); | ||
} | ||
}; | ||
|
||
if self.room_id == 0 { | ||
let html_fetcher = HTMLFetcher::new(&self.client, &self.page_url); | ||
let html = match html_fetcher.fetch().await { | ||
Ok(html) => { | ||
debug!( | ||
"Fetched page HTML successfully. Length: {} characters", | ||
html.len() | ||
); | ||
html | ||
} | ||
Err(e) => { | ||
error!("Failed to fetch page HTML. Error: {}. Details: {:?}", e, e); | ||
return Err(e); | ||
} | ||
}; | ||
|
||
self.room_id = self.parse_room_id(&html)?; | ||
} | ||
|
||
let room_info_fetcher = RoomInfoFetcher::new(&self.client, self.room_id, &self.cookie); | ||
let page_info = match room_info_fetcher.fetch().await { | ||
Ok(info) => { | ||
debug!( | ||
"Fetched room info successfully. Title: {}, Anchor: {}, Category: {}", | ||
info.0, info.1, info.2 | ||
); | ||
info | ||
} | ||
Err(e) => { | ||
error!("Failed to fetch room info. Error: {}. Details: {:?}", e, e); | ||
return Err(e); | ||
} | ||
}; | ||
|
||
let room_play_info_fetcher = | ||
RoomPlayInfoFetcher::new(&self.client, self.room_id, &self.cookie); | ||
let room_play_info = match room_play_info_fetcher.fetch().await { | ||
Ok(info) => { | ||
debug!("Fetched room play info successfully"); | ||
info | ||
} | ||
Err(e) => { | ||
error!( | ||
"Failed to fetch room play info. Error: {}. Details: {:?}", | ||
e, e | ||
); | ||
return Err(e); | ||
} | ||
}; | ||
|
||
let link_parser = LinkParser::new(); | ||
let links = link_parser.parse(&room_play_info); | ||
debug!("Parsed {} stream links", links.len()); | ||
|
||
let parsed_result = ParsedResult { | ||
title: page_info.0, | ||
anchor: page_info.1, | ||
category: page_info.2, | ||
platform: Platform::Bilibili, | ||
links, | ||
room_id: self.room_id, | ||
}; | ||
|
||
info!( | ||
"Parsing completed successfully for room ID: {}", | ||
self.room_id | ||
); | ||
Ok(parsed_result) | ||
} | ||
|
||
fn parse_room_id(&self, html: &str) -> LsarResult<u64> { | ||
trace!("Parsing room ID from HTML"); | ||
let room_id = html | ||
.split(r#""defaultRoomId":""#) | ||
.nth(1) | ||
.and_then(|s| s.split('"').next()) | ||
.or_else(|| { | ||
html.split(r#""roomid":"#) | ||
.nth(1) | ||
.and_then(|s| s.split(',').next()) | ||
}) | ||
.or_else(|| { | ||
html.split(r#""roomId":"#) | ||
.nth(1) | ||
.and_then(|s| s.split(',').next()) | ||
}) | ||
.and_then(|s| s.parse::<u64>().ok()) | ||
.ok_or_else(|| { | ||
let err_msg = "Failed to parse room ID"; | ||
error!("{}", err_msg); | ||
err_msg | ||
})?; | ||
|
||
debug!("Parsed room ID: {}", room_id); | ||
Ok(room_id) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
use reqwest::Client; | ||
use serde::Deserialize; | ||
use serde_json::Value; | ||
|
||
use crate::error::LsarResult; | ||
|
||
const VERIFY_URL: &str = "https://api.bilibili.com/x/web-interface/nav"; | ||
|
||
#[derive(Debug, Deserialize)] | ||
#[serde(rename_all(deserialize = "camelCase"))] | ||
struct VerifyData { | ||
uname: Option<String>, | ||
} | ||
|
||
#[derive(Debug, Deserialize)] | ||
struct VerifyResponse { | ||
code: i32, | ||
message: String, | ||
data: VerifyData, | ||
} | ||
|
||
pub struct CookieVerifier<'a> { | ||
client: &'a Client, | ||
cookie: &'a str, | ||
} | ||
|
||
impl<'a> CookieVerifier<'a> { | ||
pub fn new(client: &'a Client, cookie: &'a str) -> Self { | ||
CookieVerifier { client, cookie } | ||
} | ||
|
||
pub async fn verify(&self) -> LsarResult<String> { | ||
debug!("Starting cookie verification process"); | ||
|
||
let response_value = self | ||
.client | ||
.get(VERIFY_URL) | ||
.header("Cookie", self.cookie) | ||
.send() | ||
.await? | ||
.json::<Value>() | ||
.await?; | ||
|
||
debug!("Cookie verification result: {}", response_value); | ||
|
||
let response: VerifyResponse = serde_json::from_value(response_value)?; | ||
|
||
if response.code != 0 { | ||
let err_msg = format!("Cookie verification failed: {}", response.message); | ||
error!("{}. Response code: {}", err_msg, response.code); | ||
|
||
// -101 未登录 | ||
if response.code == -101 && response.message == "账号未登录" { | ||
return Err("账号未登录,cookie 未设置或已失效".into()); | ||
} | ||
|
||
return Err(err_msg.into()); | ||
} | ||
|
||
let username = response.data.uname.ok_or_else(|| { | ||
let err_msg = "Username not found in verification response"; | ||
error!("{}", err_msg); | ||
err_msg | ||
})?; | ||
|
||
debug!("Cookie verification successful for user: {}", username); | ||
Ok(username) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
use reqwest::{header::USER_AGENT, Client}; | ||
use tauri::http::{HeaderMap, HeaderValue}; | ||
|
||
use crate::error::LsarResult; | ||
|
||
pub struct HTMLFetcher<'a> { | ||
client: &'a Client, | ||
url: &'a str, | ||
} | ||
|
||
impl<'a> HTMLFetcher<'a> { | ||
pub fn new(client: &'a Client, url: &'a str) -> Self { | ||
HTMLFetcher { client, url } | ||
} | ||
|
||
pub async fn fetch(&self) -> LsarResult<String> { | ||
debug!("Fetching page HTML from: {}", self.url); | ||
let mut headers = HeaderMap::new(); | ||
headers.insert("Host", HeaderValue::from_static("live.bilibili.com")); | ||
headers.insert( | ||
USER_AGENT, | ||
HeaderValue::from_static( | ||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:102.0) Gecko/20100101 Firefox/102.0", | ||
), | ||
); | ||
headers.insert("Accept", HeaderValue::from_static("text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8")); | ||
headers.insert("Accept-Language", HeaderValue::from_static("zh-CN")); | ||
headers.insert("Connection", HeaderValue::from_static("keep-alive")); | ||
headers.insert("Upgrade-Insecure-Requests", HeaderValue::from_static("1")); | ||
headers.insert("Sec-Fetch-Dest", HeaderValue::from_static("document")); | ||
headers.insert("Sec-Fetch-Mode", HeaderValue::from_static("navigate")); | ||
headers.insert("Sec-Fetch-Site", HeaderValue::from_static("none")); | ||
headers.insert("Sec-Fetch-User", HeaderValue::from_static("?1")); | ||
headers.insert("DNT", HeaderValue::from_static("1")); | ||
headers.insert("Sec-GPC", HeaderValue::from_static("1")); | ||
|
||
let response = self | ||
.client | ||
.get(self.url) | ||
.headers(headers) | ||
.send() | ||
.await | ||
.map_err(|e| { | ||
let err_msg = format!("Failed to send request: {}", e); | ||
error!("{}", err_msg); | ||
err_msg | ||
})?; | ||
|
||
let html = response.text().await.map_err(|e| { | ||
let err_msg = format!("Failed to get response text: {}", e); | ||
error!("{}", err_msg); | ||
err_msg | ||
})?; | ||
|
||
debug!( | ||
"Successfully fetched HTML. Length: {} characters", | ||
html.len() | ||
); | ||
Ok(html) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
use super::room_play_info_fetcher::Response; | ||
|
||
pub struct LinkParser; | ||
|
||
impl LinkParser { | ||
pub fn new() -> Self { | ||
LinkParser | ||
} | ||
|
||
pub fn parse(&self, info: &Response) -> Vec<String> { | ||
trace!("Starting to parse stream links"); | ||
let mut links = Vec::new(); | ||
|
||
for (stream_index, stream) in info.data.playurl_info.playurl.stream.iter().enumerate() { | ||
for (format_index, format) in stream.format.iter().enumerate() { | ||
for (codec_index, codec) in format.codec.iter().enumerate() { | ||
for (url_index, url_info) in codec.url_info.iter().enumerate() { | ||
let link = format!("{}{}{}", url_info.host, codec.base_url, url_info.extra); | ||
trace!( | ||
"Parsed link: {} (Stream: {}, Format: {}, Codec: {}, URL: {})", | ||
link, | ||
stream_index, | ||
format_index, | ||
codec_index, | ||
url_index | ||
); | ||
links.push(link); | ||
} | ||
} | ||
} | ||
} | ||
|
||
debug!("Parsed {} stream links", links.len()); | ||
links | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
use bilibili_parser::BilibiliParser; | ||
|
||
use crate::error::LsarResult; | ||
|
||
use super::ParsedResult; | ||
|
||
mod bilibili_parser; | ||
mod cookie_verifier; | ||
mod html_fetcher; | ||
mod link_parser; | ||
mod room_info_fetcher; | ||
mod room_play_info_fetcher; | ||
|
||
#[tauri::command] | ||
pub async fn parse_bilibili( | ||
room_id: u64, | ||
cookie: String, | ||
url: Option<String>, | ||
) -> LsarResult<ParsedResult> { | ||
let mut parser = BilibiliParser::new(cookie, room_id, url); | ||
|
||
match parser.parse().await { | ||
Ok(result) => { | ||
info!(target: "main", "Parsing successful. Result: {:?}", result); | ||
Ok(result) | ||
} | ||
Err(e) => { | ||
error!(target: "main", "Parsing failed: {}. Error details: {:?}", e, e); | ||
Err(e) | ||
} | ||
} | ||
} |
Oops, something went wrong.