use reqwest::{Client, RequestBuilder};
use serde::{Serialize, de::DeserializeOwned};
use std::sync::LazyLock;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use crate::error::SearchError;
pub use reqwest::{Method, Response, StatusCode};
pub const USER_AGENT: &str = "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36";
pub const COOKIE: &str = "os=pc;osver=Microsoft-Windows-10-Professional-build-16299.125-64bit;appver=2.0.3.131777;channel=netease;__remember_me=true";
const REQUEST_TIMEOUT: Duration = Duration::from_secs(60);
static HTTP_CLIENT: LazyLock<Client> = LazyLock::new(|| {
Client::builder()
.user_agent(USER_AGENT)
.timeout(REQUEST_TIMEOUT)
.build()
.expect("Failed to create HTTP client")
});
pub fn unix_seconds() -> u64 {
since_unix_epoch().as_secs()
}
pub fn unix_millis() -> u128 {
since_unix_epoch().as_millis()
}
fn since_unix_epoch() -> Duration {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("系统时钟早于 Unix 纪元")
}
pub async fn send(
method: Method,
url: &str,
headers: &[(&str, &str)],
) -> Result<Response, SearchError> {
send_request(method, url, headers, |request| request).await
}
pub async fn send_json(
url: &str,
body: &impl Serialize,
headers: &[(&str, &str)],
) -> Result<Response, SearchError> {
send_request(Method::POST, url, headers, |request| request.json(body)).await
}
pub async fn send_form(
url: &str,
form: &[(&str, &str)],
headers: &[(&str, &str)],
) -> Result<Response, SearchError> {
send_request(Method::POST, url, headers, |request| request.form(form)).await
}
pub async fn json<T: DeserializeOwned>(response: Response) -> Result<T, SearchError> {
ensure_success(&response)?;
Ok(response.json::<T>().await?)
}
pub async fn text(response: Response) -> Result<String, SearchError> {
ensure_success(&response)?;
Ok(response.text().await?)
}
async fn send_request(
method: Method,
url: &str,
headers: &[(&str, &str)],
body: impl FnOnce(RequestBuilder) -> RequestBuilder,
) -> Result<Response, SearchError> {
let mut request = HTTP_CLIENT.request(method, url);
for (key, value) in headers {
request = request.header(*key, *value);
}
Ok(body(request).send().await?)
}
fn ensure_success(response: &Response) -> Result<(), SearchError> {
let status = response.status();
if status.is_success() {
Ok(())
} else {
Err(SearchError::Status(status.as_u16()))
}
}