use crate::common::cache::Cache;
use crate::config::client_config::ProxyClient;
use crate::config::provider_config::{ApiConfig, ProviderConfig};
use crate::error::ProviderApiError;
use crate::provider_api::provider_api_trait::ProviderApiTrait;
use redis::aio::ConnectionManager;
use reqwest::Client as ReqwestClient;
use reqwest::{Method, Request, Url};
use serde::{Deserialize, Serialize};
use std::fmt::Display;
type Result<T> = core::result::Result<T, ProviderApiError>;
#[derive(Clone)]
pub struct BosLifeApi {
pub config: ProviderConfig,
pub client: ReqwestClient,
pub cached_auth_token: Cache<String, String>,
pub cached_sub_profile: Cache<String, String>,
pub cached_sub_url: Cache<String, String>,
pub cached_sub_logs: Cache<String, BosLifeLogs>,
}
impl BosLifeApi {
pub fn new(config: ProviderConfig, redis: Option<ConnectionManager>) -> Self {
let client = ReqwestClient::builder()
.cookie_store(true)
.use_rustls_tls()
.build()
.expect("Failed to create Reqwest client");
let duration = std::time::Duration::from_secs(60 * 60); let cached_sub_profile = Cache::new(redis.clone(), 10, duration);
let cached_auth_token = Cache::new(redis.clone(), 10, duration);
let cached_sub_url = Cache::new(redis.clone(), 10, duration);
let cached_sub_logs = Cache::new(redis.clone(), 10, duration);
Self {
config,
client,
cached_auth_token,
cached_sub_profile,
cached_sub_url,
cached_sub_logs,
}
}
}
impl ProviderApiTrait for BosLifeApi {
fn api_config(&self) -> &ApiConfig {
&self.config.api_config
}
fn build_raw_url(&self, client: ProxyClient) -> Url {
let mut sub_url = self.config.sub_url.clone();
sub_url.query_pairs_mut().append_pair("flag", client.into());
sub_url
}
fn client(&self) -> &reqwest::Client {
&self.client
}
fn login_request(&self) -> Result<Request> {
let builder = self.client.request(Method::POST, self.api_config().login_url()).form(&[
("email", self.api_config().credential.username.clone()),
("password", self.api_config().credential.password.clone()),
]);
builder.build().map_err(|e| ProviderApiError::BuildRequestError {
name: "[BosLife 登录请求]".to_string(),
source: e,
})
}
fn get_sub_request(&self, auth_token: impl AsRef<str>) -> Result<Request> {
let builder = self
.client
.request(Method::GET, self.api_config().get_sub_url())
.header("Authorization", auth_token.as_ref());
builder.build().map_err(|e| ProviderApiError::BuildRequestError {
name: "[BosLife 获取订阅请求]".to_string(),
source: e,
})
}
fn reset_sub_request(&self, auth_token: impl AsRef<str>) -> Result<Request> {
let builder = self
.client
.request(Method::POST, self.api_config().reset_sub_url())
.header("Authorization", auth_token.as_ref());
builder.build().map_err(|e| ProviderApiError::BuildRequestError {
name: "[BosLife 重置订阅请求]".to_string(),
source: e,
})
}
fn get_sub_logs_request(&self, auth_token: impl AsRef<str>) -> Result<Request> {
let url = self
.api_config()
.sub_logs_url()
.ok_or(ProviderApiError::Other("订阅日志接口未配置".to_string()))?;
let builder = self
.client
.request(Method::GET, url)
.header("Authorization", auth_token.as_ref());
builder.build().map_err(|e| ProviderApiError::BuildRequestError {
name: "[BosLife 获取订阅日志请求]".to_string(),
source: e,
})
}
fn cached_profile(&self) -> &Cache<String, String> {
&self.cached_sub_profile
}
fn cached_auth_token(&self) -> &Cache<String, String> {
&self.cached_auth_token
}
fn cached_sub_url(&self) -> &Cache<String, String> {
&self.cached_sub_url
}
fn cached_sub_logs(&self) -> &Cache<String, BosLifeLogs> {
&self.cached_sub_logs
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BosLifeLog {
pub user_id: u64,
pub ip: String,
pub location: String,
pub isp: String,
pub host: String,
pub ua: String,
pub created_at: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BosLifeLogs(pub Vec<BosLifeLog>);
impl Display for BosLifeLogs {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let string = serde_json::to_string_pretty(self).expect("JSON serialization failed");
write!(f, "{string}")
}
}
impl From<String> for BosLifeLogs {
fn from(value: String) -> Self {
serde_json::from_str(&value).expect("JSON deserialization failed")
}
}