use crate::{ BilibiliRequest, BpiClient, BpiError, BpiResponse };
use serde::{ Deserialize, Serialize };
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HistoryCursor {
pub max: u64,
pub view_at: u64,
pub business: String,
pub ps: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HistoryTab {
#[serde(rename = "type")]
pub type_name: String,
pub name: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum HistoryCovers {
Array(Vec<String>),
String(String),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HistoryDetail {
pub oid: u64,
pub epid: Option<u64>,
pub bvid: Option<String>,
pub page: Option<u32>,
pub cid: Option<u64>,
pub part: Option<String>,
pub business: String,
pub dt: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HistoryListItem {
pub title: String,
pub long_title: Option<String>,
pub cover: Option<String>,
pub covers: Option<Vec<String>>,
pub uri: Option<String>,
pub history: HistoryDetail,
pub videos: Option<u32>,
pub author_name: Option<String>,
pub author_face: Option<String>,
pub author_mid: Option<u64>,
pub view_at: u64,
pub progress: i32,
pub show_title: Option<String>,
pub duration: Option<u32>,
pub current: Option<String>,
pub total: Option<u32>,
pub new_desc: Option<String>,
pub is_finish: Option<u8>,
pub is_fav: u8,
pub kid: u64,
pub tag_name: Option<String>,
pub live_status: Option<u8>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HistoryListData {
pub cursor: HistoryCursor,
pub tab: Vec<HistoryTab>,
pub list: Vec<HistoryListItem>,
}
impl BpiClient {
pub async fn history_list(
&self,
max: Option<u64>,
business: Option<&str>,
view_at: Option<u64>,
typ: Option<&str>,
ps: Option<u32>
) -> Result<BpiResponse<HistoryListData>, BpiError> {
let mut request = self.get("https://api.bilibili.com/x/web-interface/history/cursor");
if let Some(m) = max {
request = request.query(&[("max", m)]);
}
if let Some(b) = business {
request = request.query(&[("business", b)]);
}
if let Some(v) = view_at {
request = request.query(&[("view_at", v)]);
}
if let Some(t) = typ {
request = request.query(&[("type", t)]);
}
if let Some(p) = ps {
request = request.query(&[("ps", p)]);
}
request.send_bpi("获取历史记录列表").await
}
pub async fn history_delete(
&self,
kid: &str
) -> Result<BpiResponse<serde_json::Value>, BpiError> {
let csrf = self.csrf()?;
let payload = [
("kid", kid),
("csrf", &csrf),
];
self
.post("https://api.bilibili.com/x/v2/history/delete")
.form(&payload)
.send_bpi("删除历史记录").await
}
pub async fn history_clear(&self) -> Result<BpiResponse<serde_json::Value>, BpiError> {
let csrf = self.csrf()?;
let payload = [("csrf", &csrf)];
self
.post("https://api.bilibili.com/x/v2/history/clear")
.form(&payload)
.send_bpi("清空历史记录").await
}
pub async fn history_shadow_set(
&self,
switch: bool
) -> Result<BpiResponse<serde_json::Value>, BpiError> {
let csrf = self.csrf()?;
let payload = [
("switch", switch.to_string()),
("csrf", csrf),
];
self
.post("https://api.bilibili.com/x/v2/history/shadow/set")
.form(&payload)
.send_bpi("停用历史记录").await
}
pub async fn history_shadow_get(&self) -> Result<BpiResponse<bool>, BpiError> {
self
.get("https://api.bilibili.com/x/v2/history/shadow")
.send_bpi("查询历史记录停用状态").await
}
}
#[cfg(test)]
mod tests {
use super::*;
use tracing::info;
#[tokio::test]
async fn test_history_get_list() {
let bpi = BpiClient::new();
let resp = bpi.history_list(None, None, None, None, Some(10)).await;
info!("{:?}", resp);
assert!(resp.is_ok());
let resp_data = resp.unwrap();
info!("code: {}", resp_data.code);
info!("message: {}", resp_data.message);
if let Some(data) = resp_data.data {
info!("cursor: {:?}", data.cursor);
info!("tabs: {:?}", data.tab);
info!("first item: {:?}", data.list.first());
}
}
#[tokio::test]
async fn test_history_shadow_set_and_get() {
let bpi = BpiClient::new();
let current_status_resp = bpi.history_shadow_get().await.unwrap();
let current_status = current_status_resp.data.unwrap();
let new_status = !current_status;
let set_resp = bpi.history_shadow_set(new_status).await;
info!("Set status to {}: {:?}", new_status, set_resp);
assert!(set_resp.is_ok());
let new_status_resp = bpi.history_shadow_get().await;
info!("New status: {:?}", new_status_resp);
assert!(new_status_resp.is_ok());
assert_eq!(new_status_resp.unwrap().data.unwrap(), new_status);
let set_back_resp = bpi.history_shadow_set(current_status).await;
info!("Set back to original status {}: {:?}", current_status, set_back_resp);
assert!(set_back_resp.is_ok());
}
}