psn_rs 0.1.0

Interact with PlayStation Network API in full Rust!
Documentation
use reqwest::header::{
    AUTHORIZATION, CONTENT_TYPE, COOKIE, HeaderMap, HeaderName, HeaderValue, LOCATION, USER_AGENT,
};
use serde::Serialize;

use crate::{PSNApiResult, PSNClient, models::TokenResponse};

impl PSNClient {
    pub async fn authenticate(&mut self) -> PSNApiResult<()> {
        let authorization_code = self.get_authorization_code().await?;
        log::debug!("got authorization code");

        self.fetch_access_code_from_authorization(authorization_code)
            .await?;

        log::debug!("successfully authenticated");
        Ok(())
    }

    async fn get_authorization_code(&mut self) -> PSNApiResult<String> {
        log::debug!("getting authorization code");

        let mut headers = HeaderMap::new();
        headers.insert(
            COOKIE,
            HeaderValue::from_str(&format!("npsso={}", self.npsso))?,
        );
        headers.insert(
            CONTENT_TYPE,
            HeaderValue::from_static("application/x-www-form-urlencoded"),
        );
        headers.insert(
            "X-Requested-With",
            HeaderValue::from_static("com.scee.psxandroid"),
        );
        headers.insert("Sec-Fetch-Dest", HeaderValue::from_static("document"));
        headers.insert("Sec-Fetch-Mode", HeaderValue::from_static("navigate"));
        headers.insert("Sec-Fetch-Site", HeaderValue::from_static("same-site"));
        headers.insert("Sec-Fetch-User", HeaderValue::from_static("?1"));

        let query = vec![
            ("access_type", "offline"),
            ("cid", self.cid.as_str()),
            ("client_id", "09515159-7237-4370-9b40-3806e67c0891"),
            ("device_base_font_size", "10"),
            ("device_profile", "mobile"),
            ("elements_visibility", "no_aclink"),
            ("enable_scheme_error_code", "true"),
            ("no_captcha", "true"),
            ("PlatformPrivacyWs1", "minimal"),
            ("redirect_uri", "com.scee.psxandroid.scecompcall://redirect"),
            ("response_type", "code"),
            ("scope", "psn:mobile.v2.core psn:clientapp"),
            ("service_entity", "urn:service-entity:psn"),
            ("service_logo", "ps"),
            ("smcid", "psapp:signin"),
            ("support_scheme", "sneiprls"),
            ("turnOnTrustedBrowser", "true"),
            ("ui", "pr"),
        ];

        let response = self
            .get(
                "https://ca.account.sony.com/api/authz/v3/oauth/authorize",
                Some(headers),
                Some(query),
            )
            .await?;

        let location_url = response.headers().get(LOCATION).unwrap().to_str().unwrap();
        let parsed_location_url = url::Url::parse(location_url)?;
        let parsed_location_query_pairs = parsed_location_url.query_pairs().collect::<Vec<_>>();
        let filtered = parsed_location_query_pairs
            .iter()
            .filter(|v| v.0 == "code")
            .collect::<Vec<_>>();

        let authorization_code = filtered.first().unwrap().1.to_string();

        Ok(authorization_code)
    }

    async fn fetch_access_code_from_authorization<S: AsRef<str>>(
        &mut self,
        authorization_code: S,
    ) -> PSNApiResult<()> {
        log::debug!("fetching access code from authorization");

        let mut headers = HeaderMap::new();
        headers.insert(
            AUTHORIZATION,
            HeaderValue::from_static(
                "Basic MDk1MTUxNTktNzIzNy00MzcwLTliNDAtMzgwNmU2N2MwODkxOnVjUGprYTV0bnRCMktxc1A=",
            ),
        );
        headers.insert(
            CONTENT_TYPE,
            HeaderValue::from_static("application/x-www-form-urlencoded"),
        );
        headers.insert(
            USER_AGENT,
            HeaderValue::from_static("com.sony.snei.np.android.sso.share.oauth.versa.USER_AGENT"),
        );
        headers.insert(
            HeaderName::from_static("x-psn-correlation-id"),
            HeaderValue::from_str(self.cid.as_str())?,
        );

        #[derive(Serialize)]
        struct Data {
            cid: String,
            code: String,
            grant_type: String,
            redirect_uri: String,
            scope: String,
            token_format: String,
        }

        let data = Data {
            cid: self.cid.clone(),
            code: authorization_code.as_ref().to_string(),
            grant_type: "authorization_code".to_string(),
            redirect_uri: "com.scee.psxandroid.scecompcall://redirect".to_string(),
            scope: "psn:mobile.v2.core psn:clientapp".to_string(),
            token_format: "jwt".to_string(),
        };

        let response = self
            .post(
                "https://ca.account.sony.com/api/authz/v3/oauth/token",
                Some(headers),
                Some(&data),
            )
            .await?
            .error_for_status()?;

        let token_response = response.json::<TokenResponse>().await?;

        self.token_response = Some(token_response);

        Ok(())
    }
}