Git · dadoum.zone
Repositories
Help
Report an Issue
apple-crates
Code
Commits
Branches
Tags
Search
Tree:
2ab8bf0
Branches
Tags
main
apple-crates
grandslam
src
grandslam
app.rs
Add instructions for agents, and a few minor API adjustments and additions.
Dadoum
commited
2ab8bf0
at 2026-10-27 03:57:30
app.rs
Blame
History
Raw
use super::account::{AccountHTTPSession, ResponseTokenError, header_token, pe_header_token}; use adi::proxy::ADIResult; use plist::Dictionary; use plist_macros::dict; use reqwest::header::HeaderMap; use reqwest::{Method, RequestBuilder}; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Token<T: AppToken> { pub duration: u64, // #[serde(rename = "cts")] // pub start_epoch_millis: u64, #[serde(rename = "expiry")] pub expiry_epoch_millis: u64, pub token: T, } /// An app token for a service identifier supplied at runtime. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct RawToken { pub duration: u64, #[serde(rename = "expiry")] pub expiry_epoch_millis: u64, pub token: String, } impl<T: AppToken> From<RawToken> for Token<T> { fn from(token: RawToken) -> Self { Self { duration: token.duration, expiry_epoch_millis: token.expiry_epoch_millis, token: T::from(token.token), } } } pub trait AppToken: From<String> + AsRef<str> { const APP_TOKEN_IDENTIFIER: &'static str; } #[derive(Debug, Clone)] pub struct TokenBag(pub(super) Dictionary); impl TokenBag { /// Extract app, heartbeat and password-equivalent tokens. Missing headers produce /// an empty bag. pub fn from_response_headers(headers: &HeaderMap) -> Result<Self, ResponseTokenError> { let mut tokens = Dictionary::new(); for value in headers.get_all("x-apple-pe-token") { let (service, token) = pe_header_token(value, "x-apple-pe-token")?; let value = dict! { "token": token.token, "duration": token.duration, "expiry": token.expiry_epoch_millis, }; if tokens.insert(service, value.into()).is_some() { return Err(ResponseTokenError::DuplicateService); } } for name in ["x-apple-gs-token", "x-apple-hb-token"] { for value in headers.get_all(name) { let (service, token) = header_token(value, name)?; let value = dict! { "token": token.token, "duration": token.duration, "expiry": token.expiry_epoch_millis, }; if tokens.insert(service, value.into()).is_some() { return Err(ResponseTokenError::DuplicateService); } } } Ok(Self(tokens)) } pub fn entries(&self) -> Result<std::collections::BTreeMap<String, RawToken>, plist::Error> { self.0 .iter() .map(|(service, value)| Ok((service.clone(), plist::from_value(value)?))) .collect() } pub fn get<T: AppToken>(&self) -> Option<Token<T>> { let token: RawToken = plist::from_value(self.0.get(T::APP_TOKEN_IDENTIFIER)?).ok()?; Some(token.into()) } } #[derive(Clone)] pub struct AppHTTPSession<'lt, 'adi, T: AppToken> { pub http_session: AccountHTTPSession<'lt, 'adi>, pub token: Token<T>, } impl<'lt, 'adi, T: AppToken> AppHTTPSession<'lt, 'adi, T> { pub fn new(http_session: AccountHTTPSession<'lt, 'adi>, token: Token<T>) -> Self { Self { http_session, token, } } pub fn simple_request_builder(&self, method: Method, url: &str) -> RequestBuilder { self.http_session.simple_request_builder(method, url) } pub fn anisette_request_builder(&self, method: Method, url: &str) -> ADIResult<RequestBuilder> { self.http_session.anisette_request_builder(method, url) } pub fn account_request_builder(&self, method: Method, url: &str) -> ADIResult<RequestBuilder> { self.http_session.account_request_builder(method, url) } pub fn app_request_builder(&self, method: Method, url: &str) -> ADIResult<RequestBuilder> { Ok(self .account_request_builder(method, url)? .header("Content-Type", "text/x-xml-plist") .header("Accept", "text/x-xml-plist") .header("X-Apple-App-Info", T::APP_TOKEN_IDENTIFIER) .header("X-Apple-GS-Token", self.token.token.as_ref())) } }