use serde::{ Deserialize, Serialize };
use crate::{ BilibiliRequest, BpiClient, BpiError, BpiResponse };
#[derive(Debug, Serialize, Clone, Deserialize)]
pub struct UpStatData {
#[serde(rename = "inc_coin")]
pub inc_coin: i64,
#[serde(rename = "inc_elec")]
pub inc_elec: i64,
#[serde(rename = "inc_fav")]
pub inc_fav: i64,
#[serde(rename = "inc_like")]
pub inc_like: i64,
#[serde(rename = "inc_share")]
pub inc_share: i64,
#[serde(rename = "incr_click")]
pub incr_click: i64,
#[serde(rename = "incr_dm")]
pub incr_dm: i64,
#[serde(rename = "incr_fans")]
pub incr_fans: i64,
#[serde(rename = "incr_reply")]
pub incr_reply: i64,
#[serde(rename = "total_click")]
pub total_click: i64,
#[serde(rename = "total_coin")]
pub total_coin: i64,
#[serde(rename = "total_dm")]
pub total_dm: i64,
#[serde(rename = "total_elec")]
pub total_elec: i64,
#[serde(rename = "total_fans")]
pub total_fans: i64,
#[serde(rename = "total_fav")]
pub total_fav: i64,
#[serde(rename = "total_like")]
pub total_like: i64,
#[serde(rename = "total_reply")]
pub total_reply: i64,
#[serde(rename = "total_share")]
pub total_share: i64,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ArchiveCompareItem {
pub aid: i64,
pub bvid: String,
pub cover: String,
pub title: String,
pub pubtime: i64,
pub duration: i64,
pub stat: Stat,
#[serde(rename = "is_only_self")]
pub is_only_self: bool,
#[serde(rename = "hour_stat")]
pub hour_stat: HourStat,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Stat {
#[serde(rename = "not_ready_field")]
pub not_ready_field: serde_json::Value,
pub play: i64,
pub vt: i64,
#[serde(rename = "full_play_ratio")]
pub full_play_ratio: i64,
#[serde(rename = "play_viewer_rate")]
pub play_viewer_rate: i64,
#[serde(rename = "play_viewer_rate_med")]
pub play_viewer_rate_med: i64,
#[serde(rename = "play_fan_rate")]
pub play_fan_rate: i64,
#[serde(rename = "play_fan_rate_med")]
pub play_fan_rate_med: i64,
#[serde(rename = "active_fans_rate")]
pub active_fans_rate: i64,
#[serde(rename = "active_fans_med")]
pub active_fans_med: i64,
#[serde(rename = "tm_rate")]
pub tm_rate: i64,
#[serde(rename = "tm_rate_med")]
pub tm_rate_med: i64,
#[serde(rename = "tm_fan_simi_rate_med")]
pub tm_fan_simi_rate_med: i64,
#[serde(rename = "tm_viewer_simi_rate_med")]
pub tm_viewer_simi_rate_med: i64,
#[serde(rename = "tm_fan_rate")]
pub tm_fan_rate: i64,
#[serde(rename = "tm_viewer_rate")]
pub tm_viewer_rate: i64,
#[serde(rename = "tm_pass_rate")]
pub tm_pass_rate: i64,
#[serde(rename = "tm_fan_pass_rate")]
pub tm_fan_pass_rate: i64,
#[serde(rename = "tm_viewer_pass_rate")]
pub tm_viewer_pass_rate: i64,
#[serde(rename = "crash_rate")]
pub crash_rate: i64,
#[serde(rename = "crash_rate_med")]
pub crash_rate_med: i64,
#[serde(rename = "crash_fan_simi_rate_med")]
pub crash_fan_simi_rate_med: i64,
#[serde(rename = "crash_viewer_simi_rate_med")]
pub crash_viewer_simi_rate_med: i64,
#[serde(rename = "crash_fan_rate")]
pub crash_fan_rate: i64,
#[serde(rename = "crash_viewer_rate")]
pub crash_viewer_rate: i64,
#[serde(rename = "interact_rate")]
pub interact_rate: i64,
#[serde(rename = "interact_rate_med")]
pub interact_rate_med: i64,
#[serde(rename = "interact_fan_simi_rate_med")]
pub interact_fan_simi_rate_med: i64,
#[serde(rename = "interact_viewer_simi_rate_med")]
pub interact_viewer_simi_rate_med: i64,
#[serde(rename = "interact_fan_rate")]
pub interact_fan_rate: i64,
#[serde(rename = "interact_viewer_rate")]
pub interact_viewer_rate: i64,
#[serde(rename = "avg_play_time")]
pub avg_play_time: i64,
#[serde(rename = "avg_play_time_int")]
pub avg_play_time_int: i64,
#[serde(rename = "total_new_attention_cnt")]
pub total_new_attention_cnt: i64,
#[serde(rename = "play_trans_fan_rate")]
pub play_trans_fan_rate: i64,
#[serde(rename = "play_trans_fan_rate_med")]
pub play_trans_fan_rate_med: i64,
pub like: i64,
pub comment: i64,
pub dm: i64,
pub fav: i64,
pub coin: i64,
pub share: i64,
#[serde(rename = "unfollow")]
pub unfollow: i64,
#[serde(rename = "tm_star")]
pub tm_star: i64,
#[serde(rename = "tm_viewer_star")]
pub tm_viewer_star: i64,
#[serde(rename = "tm_fan_star")]
pub tm_fan_star: i64,
#[serde(rename = "crash_p50")]
pub crash_p50: i64,
#[serde(rename = "crash_viewer_p50")]
pub crash_viewer_p50: i64,
#[serde(rename = "crash_fan_p50")]
pub crash_fan_p50: i64,
#[serde(rename = "interact_p50")]
pub interact_p50: i64,
#[serde(rename = "interact_viewer_p50")]
pub interact_viewer_p50: i64,
#[serde(rename = "interact_fan_p50")]
pub interact_fan_p50: i64,
#[serde(rename = "play_trans_fan_p50")]
pub play_trans_fan_p50: i64,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct HourStat {
#[serde(rename = "not_ready_field")]
pub not_ready_field: serde_json::Value,
pub play: i64,
pub vt: i64,
pub like: i64,
pub comment: i64,
pub dm: i64,
pub fav: i64,
pub coin: i64,
pub share: i64,
#[serde(rename = "tm_pass_rate")]
pub tm_pass_rate: i64,
#[serde(rename = "interact_rate")]
pub interact_rate: i64,
#[serde(rename = "tm_star")]
pub tm_star: i64,
}
#[derive(Debug, Serialize, Clone, Deserialize)]
pub struct ArchiveCompareData {
pub list: Vec<ArchiveCompareItem>,
}
#[derive(Debug, Serialize, Clone, Deserialize)]
pub struct UpArticleStatData {
pub view: i64,
pub reply: i64,
pub like: i64,
pub coin: i64,
pub fav: i64,
pub share: i64,
#[serde(rename = "incr_view")]
pub incr_view: i64,
#[serde(rename = "incr_reply")]
pub incr_reply: i64,
#[serde(rename = "incr_like")]
pub incr_like: i64,
#[serde(rename = "incr_coin")]
pub incr_coin: i64,
#[serde(rename = "incr_fav")]
pub incr_fav: i64,
#[serde(rename = "incr_share")]
pub incr_share: i64,
}
#[derive(Debug, Serialize, Clone, Deserialize)]
pub struct VideoTrendItem {
pub date_key: i64,
pub total_inc: i64,
}
#[derive(Debug, Serialize, Clone, Deserialize)]
pub struct ArticleTrendItem {
pub date_key: i64,
pub total_inc: i64,
}
#[derive(Debug, Serialize, Clone, Deserialize)]
pub struct PageSource {
pub dynamic: i64,
pub other: i64,
#[serde(rename = "related_video")]
pub related_video: i64,
pub search: i64,
pub space: i64,
pub tenma: i64,
}
#[derive(Debug, Serialize, Clone, Deserialize)]
pub struct PlayProportion {
pub android: i64,
pub h5: i64,
pub ios: i64,
pub out: i64,
pub pc: i64,
}
#[derive(Debug, Serialize, Clone, Deserialize)]
pub struct PlaySourceData {
pub page_source: PageSource,
pub play_proportion: PlayProportion,
}
#[derive(Debug, Serialize, Clone, Deserialize)]
pub struct Period {
pub module_one: Option<String>,
pub module_two: Option<String>,
pub module_three: Option<String>,
pub module_four: Option<String>,
}
pub type ViewerAreaMap = std::collections::HashMap<String, i64>;
#[derive(Debug, Serialize, Clone, Deserialize)]
pub struct ViewerArea {
pub fan: ViewerAreaMap,
pub not_fan: ViewerAreaMap,
}
#[derive(Debug, Serialize, Clone, Deserialize)]
pub struct ViewerBaseDetail {
pub male: i64,
pub female: i64,
#[serde(rename = "age_one")]
pub age_one: i64,
#[serde(rename = "age_two")]
pub age_two: i64,
#[serde(rename = "age_three")]
pub age_three: i64,
#[serde(rename = "age_four")]
pub age_four: i64,
#[serde(rename = "plat_pc")]
pub plat_pc: i64,
#[serde(rename = "plat_h5")]
pub plat_h5: i64,
#[serde(rename = "plat_out")]
pub plat_out: i64,
#[serde(rename = "plat_ios")]
pub plat_ios: i64,
#[serde(rename = "plat_android")]
pub plat_android: i64,
#[serde(rename = "plat_other_app")]
pub plat_other_app: i64,
}
#[derive(Debug, Serialize, Clone, Deserialize)]
pub struct ViewerBase {
pub fan: ViewerBaseDetail,
pub not_fan: ViewerBaseDetail,
}
#[derive(Debug, Serialize, Clone, Deserialize)]
pub struct ViewerData {
pub period: Period,
pub viewer_area: ViewerArea,
pub viewer_base: ViewerBase,
}
impl BpiClient {
pub async fn up_stat(&self) -> Result<BpiResponse<UpStatData>, BpiError> {
self
.get("https://member.bilibili.com/x/web/index/stat")
.send_bpi("获取UP主视频状态数据").await
}
pub async fn up_archive_compare(
&self,
t: Option<i64>,
size: Option<i64>
) -> Result<BpiResponse<ArchiveCompareData>, BpiError> {
let mut req = self.get("https://member.bilibili.com/x/web/data/archive_diagnose/compare");
if let Some(t) = t {
req = req.query(&[("t", t)]);
}
if let Some(size) = size {
req = req.query(&[("size", size)]);
}
req.send_bpi("获取UP主视频数据比较").await
}
pub async fn up_article_stat(&self) -> Result<BpiResponse<UpArticleStatData>, BpiError> {
self
.get("https://member.bilibili.com/x/web/data/article")
.send_bpi("获取UP主专栏状态数据").await
}
pub async fn up_video_trend(
&self,
type_code: i64
) -> Result<BpiResponse<Vec<VideoTrendItem>>, BpiError> {
self
.get("https://member.bilibili.com/x/web/data/pandect")
.query(&[("type", type_code)])
.send_bpi("获取UP主视频数据增量趋势").await
}
pub async fn up_article_trend(
&self,
type_code: i64
) -> Result<BpiResponse<Vec<ArticleTrendItem>>, BpiError> {
self
.get("https://member.bilibili.com/x/web/data/article/thirty")
.query(&[("type", type_code)])
.send_bpi("获取UP主专栏数据增量趋势").await
}
#[allow(dead_code)]
async fn up_play_source(&self) -> Result<BpiResponse<PlaySourceData>, BpiError> {
self
.get("https://member.bilibili.com/x/web/data/playsource")
.with_bilibili_headers()
.send_bpi("获取播放来源占比情况").await
}
pub async fn up_viewer_data(&self) -> Result<BpiResponse<ViewerData>, BpiError> {
self.get("https://member.bilibili.com/x/web/data/base").send_bpi("获取播放分布情况").await
}
}
#[cfg(test)]
mod tests {
use super::*;
use tracing::info;
#[tokio::test]
async fn test_up_stat() -> Result<(), Box<BpiError>> {
let bpi = BpiClient::new();
let data = bpi.up_stat().await?.into_data()?;
info!("UP主视频状态数据: {:?}", data);
Ok(())
}
#[tokio::test]
async fn test_archive_compare() -> Result<(), Box<BpiError>> {
let bpi = BpiClient::new();
let data = bpi.up_archive_compare(None, Some(3)).await?.into_data()?;
info!("UP主视频数据比较: {:?}", data);
Ok(())
}
#[tokio::test]
async fn test_up_article_stat() -> Result<(), Box<BpiError>> {
let bpi = BpiClient::new();
let data = bpi.up_article_stat().await?.into_data()?;
info!("UP主专栏状态数据: {:?}", data);
Ok(())
}
#[tokio::test]
async fn test_video_trend() -> Result<(), Box<BpiError>> {
let bpi = BpiClient::new();
let data = bpi.up_video_trend(1).await?.into_data()?; info!("UP主视频数据增量趋势: {:?}", data);
Ok(())
}
#[tokio::test]
async fn test_article_trend() -> Result<(), Box<BpiError>> {
let bpi = BpiClient::new();
let data = bpi.up_article_trend(1).await?.into_data()?; info!("UP主专栏数据增量趋势: {:?}", data);
Ok(())
}
#[tokio::test]
async fn test_viewer_data() -> Result<(), Box<BpiError>> {
let bpi = BpiClient::new();
let data = bpi.up_viewer_data().await?.into_data()?;
info!("播放分布情况: {:?}", data);
Ok(())
}
}