use crate::models::{SessionResponse, SessionResponseWrapper};
use chrono::{DateTime, Duration, Local};
use http::Extensions;
use reqwest_middleware::{
reqwest::{
header::{AUTHORIZATION, CONTENT_TYPE, USER_AGENT},
Request, Response,
},
Middleware, Next, Result,
};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Session {
access_token: Option<String>,
refresh_token: String,
expiry: Option<DateTime<Local>>,
}
impl Session {
pub fn is_valid(&self, expiry_tolerance: i64) -> bool {
self.access_token.is_some()
|| self
.expiry
.is_some_and(|d| d.timestamp() - Local::now().timestamp() > expiry_tolerance)
}
}
pub struct SessionManager {
pub(crate) session: Arc<RwLock<Session>>,
}
impl SessionManager {
pub fn new(token: String) -> Self {
Self {
session: Arc::new(RwLock::new(Session {
access_token: None,
refresh_token: token,
expiry: None,
})),
}
}
pub fn restore(session: Session) -> Self {
Self {
session: Arc::new(RwLock::new(session)),
}
}
pub async fn refresh_token(&self, session: &mut Session) -> Result<()> {
let time = Local::now().format("%y-%m-%dT%H:%m:%s+00:00").to_string();
let req = reqwest_middleware::reqwest::Client::new()
.post("https://oauth.secure.pixiv.net/auth/token")
.body(
[
("grant_type", "refresh_token"),
("refresh_token", &session.refresh_token),
("client_id", "MOBrBDS8blbauoSck0ZfDbtuzpyT"),
("client_secret", "lsACyCD94FhDUtGTXi3QzcFE2uU1hqtDaKeqrdwj"),
("get_secure_url", "1"),
]
.iter()
.map(|p| format!("{}={}", p.0, p.1))
.collect::<Vec<_>>()
.join("&"),
)
.headers(
(&HashMap::from([
(
CONTENT_TYPE.to_string(),
"application/x-www-form-urlencoded".to_string(),
),
(
USER_AGENT.to_string(),
"PixivIOSApp/7.6.2 (iOS 12.2; iPhone9,1)".to_string(),
),
(
"x-client-hash".to_string(),
format!(
"{:02x}",
md5::compute(
format!(
"{}{}\n",
time,
"28c1fdd170a5204386cb1313c7077b34f83e4aaf4aa829ce78c231e05b0bae2c"
)
.as_str()
)
),
),
("x-client-time".to_string(), time),
]))
.try_into()
.unwrap(),
)
.send()
.await?
.json::<SessionResponseWrapper>()
.await?;
*session = Session::from(req.response);
Ok(())
}
}
impl From<SessionResponse> for Session {
fn from(value: SessionResponse) -> Self {
Self {
access_token: Some(value.access_token).clone(),
refresh_token: value.refresh_token.clone(),
expiry: Some(Local::now() + Duration::seconds(value.expires_in)),
}
}
}
#[async_trait::async_trait]
impl Middleware for SessionManager {
async fn handle(
&self,
mut req: Request,
extensions: &mut Extensions,
next: Next<'_>,
) -> Result<Response> {
let valid = {
let session = self.session.read().await;
session.is_valid(30)
};
if !valid {
let mut session_rw = self.session.write().await;
if !session_rw.is_valid(30) {
self.refresh_token(&mut session_rw).await?;
}
}
if let Some(access_token) = &self.session.read().await.access_token {
req.headers_mut().insert(
AUTHORIZATION,
format!("Bearer {}", access_token).try_into().unwrap(),
);
}
next.run(req, extensions).await
}
}