Skip to content

Commit

Permalink
Merge pull request #94 from alley-rs/dev
Browse files Browse the repository at this point in the history
refactor(bilibili): bilibili 解析器用 rust 重构
  • Loading branch information
thep0y authored Sep 9, 2024
2 parents 47aa2f1 + aafe001 commit 923a191
Show file tree
Hide file tree
Showing 12 changed files with 575 additions and 218 deletions.
6 changes: 6 additions & 0 deletions src-tauri/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,12 @@ impl From<reqwest::Error> for HTTPError {
}
}

impl From<reqwest::Error> for LsarError {
fn from(value: reqwest::Error) -> Self {
LsarError::Http(value.into())
}
}

#[derive(Debug, Serialize, thiserror::Error)]
pub(super) enum RoomStateError {
Offline,
Expand Down
3 changes: 2 additions & 1 deletion src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ use crate::error::LsarResult;
use crate::eval::eval_result;
use crate::http::{get, post};
use crate::log::{debug, error, info, trace, warn};
use crate::parser::{parse_douyin, parse_douyu, parse_huya};
use crate::parser::{parse_bilibili, parse_douyin, parse_douyu, parse_huya};
use crate::setup::{setup_app, setup_logging};
use crate::utils::md5;

Expand Down Expand Up @@ -94,6 +94,7 @@ pub fn run() {
parse_douyu,
parse_huya,
parse_douyin,
parse_bilibili,
])
.run(tauri::generate_context!())
.expect("Error while running tauri application");
Expand Down
146 changes: 146 additions & 0 deletions src-tauri/src/parser/bilibili/bilibili_parser.rs
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)
}
}
69 changes: 69 additions & 0 deletions src-tauri/src/parser/bilibili/cookie_verifier.rs
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)
}
}
61 changes: 61 additions & 0 deletions src-tauri/src/parser/bilibili/html_fetcher.rs
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)
}
}
36 changes: 36 additions & 0 deletions src-tauri/src/parser/bilibili/link_parser.rs
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
}
}
32 changes: 32 additions & 0 deletions src-tauri/src/parser/bilibili/mod.rs
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)
}
}
}
Loading

0 comments on commit 923a191

Please sign in to comment.