gpipipi 0.2.1

a rust crate for the google play api
Documentation
use crate::{
    DeviceProperties,
    api::{
        endpoint::Requester,
        login::{AuthToken, checkin, toc, upload_device},
    },
    client::{Client, ClientRequester, async_doc},
    error::{LoginError, PropsError},
};

/// a logged in google play client that only owns an auth token, one that was generated from an
/// aas token outside of this crate
///
/// auth tokens expire way faster than aas tokens do, usually within the hour, and this client has
/// no aas token to generate a new one with, so requests start failing with google errors once it's
/// gone, either hand it a fresh token with [`ShortLivedClient::set_auth_token`] or use a
/// [`LongLivedClient`] instead
///
/// to create one, see [`ShortLivedClient::login`], the requests themselves live on [`Client`]
pub struct ShortLivedClient {
    props: DeviceProperties,
    auth_token: AuthToken,

    checkin_token: String,
    config_token: String,
    dfe_cookie: String,
    gsf: i64,
}

impl ShortLivedClient {
    /// creates a new client from an existing auth token, acting as the given device props,
    /// **AND accepting Terms of Service if required!!**
    ///
    /// the token has to be an auth token, the one google hands back for
    /// `oauth2:https://www.googleapis.com/auth/googleplay`, not the aas token it was generated
    /// from, for that one use [`LongLivedClient::login`]
    ///
    /// # Examples
    #[doc = async_doc!(true, r#"let mut props_map = gpipipi::PropsMap::parse_aurora_config(PROPS)?;
let props = props_map.remove("arm").ok_or("No 'arm' device props!")?;
let client = gpipipi::ShortLivedClient::login(auth_token, props).await?;"#)]
    ///
    /// # Errors
    /// - `Checkin` - failed to checkin into google
    /// - `Upload` - failed to upload device props to google
    /// - `Toc` - failed to fetch the terms of service / accept them, usually a dead auth token
    /// - `NoDeviceToken` - google returned no `device_checkin_consistency_token`
    /// - `NoAndroidId` - google returned no `android_id`
    /// - `NoConfigToken` - google returned no `upload_device_config_token`
    pub async fn login<A: Into<String>>(
        auth_token: A,
        props: DeviceProperties,
    ) -> Result<Self, LoginError> {
        let checkin_response = checkin(&props).await?;
        let gsf = checkin_response
            .android_id
            .ok_or(LoginError::NoAndroidId())?
            .cast_signed();

        let checkin_token = checkin_response
            .device_checkin_consistency_token
            .ok_or(LoginError::NoDeviceToken())?;

        let upload_response = upload_device(&props, gsf, &checkin_token).await?;
        let config_token = upload_response
            .upload_device_config_token
            .ok_or(LoginError::NoConfigToken())?;

        // google is the only one that knows how much is left of a token we didn't fetch,
        // so it's always sent and google decides when it stopped being valid
        let auth_token = AuthToken::without_expire(auth_token.into());
        let dfe_cookie = toc(&auth_token, gsf, &checkin_token, &props).await?;

        Ok(Self {
            props,
            auth_token,
            checkin_token,
            config_token,
            dfe_cookie,
            gsf,
        })
    }

    /// swaps in a new auth token, for when the current one expired
    ///
    /// the rest of the login state stays as it is, so this doesn't do any requests
    ///
    /// # Examples
    #[doc = async_doc!(true, r#"let client = gpipipi::ShortLivedClient::login(&auth_token, props).await?;
client.set_auth_token(auth_token);"#)]
    pub fn set_auth_token<A: Into<String>>(&self, auth_token: A) {
        self.auth_token.set(auth_token.into(), None);
    }
}

impl ClientRequester for ShortLivedClient {
    async fn requester(&self) -> Result<Requester<'_>, PropsError> {
        Requester::new()
            .checkin_token(&self.checkin_token)
            .config_token(&self.config_token)
            .auth_token(&self.auth_token)
            .dfe_cookie(&self.dfe_cookie)
            .props(&self.props)
            .gsf(self.gsf)
            .default_headers()
            .await
    }
}

impl Client for ShortLivedClient {}