1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
//! 基于 HTTP 的各种请求
//!
//! # Example
//! ```
//! use biliapi::Request;
//! # tokio_test::block_on(async {
//! let client = biliapi::connection::new_client().unwrap();
//! let info = biliapi::requests::InfoByRoom::request(&client, 1).await.unwrap();
//! // 拿到长房号
//! assert_eq!(info.room_info.room_id, 5440);
//! # });
//! ```
//!
mod prelude {
    pub use reqwest::{Client, Response, StatusCode};
    pub use serde::de::DeserializeOwned;
    pub use std::{future::Future, pin::Pin};

    pub use crate::{Error, Result};

    pub(super) use super::BiliResponseExt;
    pub use super::{Request, RequestResponse};
}

use prelude::*;

/// 哔哩哔哩返回的 http 原始 response 对应的结构
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct BiliResponse<T> {
    code: i64,

    #[serde(default)]
    message: String,

    // 不知道干啥的
    // ttl: i64
    #[serde(bound(deserialize = "T: serde::Deserialize<'de>"))]
    #[serde(default = "Option::default")]
    data: Option<T>,
}
impl<T: DeserializeOwned> BiliResponse<T> {
    #[allow(unused)]
    pub fn into_data(self) -> Option<T> {
        self.data
    }
    pub async fn from_response(response: Response) -> Result<T> {
        if response.status() != StatusCode::OK {
            let status = response.status();
            #[cfg(debug_assertions)]
            debug!(
                "status = {:?}, response text = {:?}",
                status,
                response.text().await
            );

            return Err(Error::StatusCode(status));
        }
        let response_text = response.text().await?;
        let this: Self = serde_json::from_str(&response_text).map_err(|e| {
            debug!("response text = {}", response_text);
            e
        })?;
        if this.code != 0 {
            debug!("response text = {}", response_text);
            return Err(Error::BiliCustom {
                code: this.code,
                message: this.message,
            });
        }
        match this.data {
            Some(data) => Ok(data),
            None => Err(Error::DataNotFound),
        }
    }
}

/// 这个 trait 允许直接对 [`Response`] 调用 `bili_data().await`
///
/// ```no_run
/// use biliapi::requests::BiliResponseExt;
///
/// # async fn dox() -> Result<(), Box<dyn std::error::Error>> {
/// let response: reqwest::Response = reqwest::Client::new()
///     .get("https://example.com")
///     .send().await?;
///
/// let data: i32 = response.bili_data().await?;
/// # Ok(()) }
/// ```
///
pub trait BiliResponseExt {
    fn bili_data<T: DeserializeOwned>(self) -> Pin<Box<dyn Future<Output = Result<T>> + Send>>;
}
impl BiliResponseExt for Response {
    fn bili_data<T: DeserializeOwned>(self) -> Pin<Box<dyn Future<Output = Result<T>> + Send>> {
        Box::pin(async move { BiliResponse::<T>::from_response(self).await })
    }
}

/// API 接口的实现 trait
///
/// 所有对 bilibili 的请求都应该实现这个 trait,如
/// ```no_run
/// use biliapi::requests::{Request, BiliResponseExt, RequestResponse};
/// use serde::Deserialize;
/// use reqwest::Client;
///
/// #[derive(Debug, Deserialize)]
/// struct SomeApi {
///     pub field: i32
/// }
/// impl Request for SomeApi {
///     type Args = i64;
///     fn request(client: &Client, args: i64) -> RequestResponse<Self> {
///         let request = client.get("https://api.bilibili.com/some/api")
///             .query(&[("id", args)])
///             .send();
///         Box::pin(async move {
///             // 这里需要引入 `BiliResponseExt`
///             request.await?.bili_data().await
///         })
///
///     }
/// }
/// ```
pub trait Request: DeserializeOwned {
    /// 请求对应的参数
    type Args;

    /// 请求的实现
    fn request(client: &Client, args: Self::Args) -> RequestResponse<Self>;
}
/// [`Request`] trait 返回结果的封装,本质就是 `Pin<Box<dyn Future<Output = Result<T>>>>`
pub type RequestResponse<T> = Pin<Box<dyn Future<Output = Result<T>> + Send>>;

mod room_info;
pub use room_info::{InfoByRoom, RoomInfo};

mod danmu_info;
pub use danmu_info::{DanmuInfo, DanmuServer};

mod video_info;
pub use video_info::{VideoInfo, VideoPage, VideoStat};

mod login;
pub use login::{CheckQrLogin, QrLoginRequest};

mod uploader_stat;
pub use uploader_stat::UploaderStat;

mod user_info;
pub use user_info::UserInfo;

mod vote_info;
pub use vote_info::VoteInfo;

mod my_account_info;
pub use my_account_info::MyAccountInfo;