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
//! 基于 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)]
struct BiliResponse<T> {
    code: i64,

    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 {
            return Err(Error::StatusCode(response.status()));
        }
        let response_text = response.text().await?;
        let this: Self = serde_json::from_str(&response_text)?;
        if this.code != 0 {
            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<T: DeserializeOwned> {
    fn bili_data(self) -> Pin<Box<dyn Future<Output = Result<T>> + Send>>;
}
impl<T: DeserializeOwned> BiliResponseExt<T> for Response {
    fn bili_data(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};