~nicohman/gogapi-rs

0d2a926ecdc80fd820d5bbb92cb29de490a66394 — nicohman 5 years ago ab72b83 + 198182b
Merge branch 'login'
5 files changed, 208 insertions(+), 85 deletions(-)

M Cargo.toml
M src/error.rs
M src/lib.rs
M src/token.rs
M tests/lib.rs
M Cargo.toml => Cargo.toml +3 -0
@@ 17,3 17,6 @@ regex = "1"
log = "0.4"
curl = "0.4"
select = "0.4.2"
user_agent = "0.6.5"
time = "0.1.42"
cookie = "0.11.0"
\ No newline at end of file

M src/error.rs => src/error.rs +4 -0
@@ 26,5 26,9 @@ error_chain! {
            description("the credentials provided were incorrect")
            display("the credentials provided were incorrect")
        }
        SessionNetwork(err: ::user_agent::ReqwestSessionError) {
            description("session error")
            display("session error")
        }
    }
}

M src/lib.rs => src/lib.rs +3 -0
@@ 9,11 9,14 @@ extern crate serde_json;
extern crate error_chain;
#[macro_use]
extern crate log;
extern crate cookie;
extern crate curl;
extern crate regex;
extern crate reqwest;
extern crate select;
extern crate serde;
extern crate time;
extern crate user_agent;
mod containers;
/// Provides error-handling logic
mod error;

M src/token.rs => src/token.rs +197 -84
@@ 4,6 4,10 @@ use reqwest;
use select::{document::*, predicate::*};
use serde_json;
use std::time::SystemTime;
use user_agent::*;
fn convert_rsession(err: ::user_agent::ReqwestSessionError) -> crate::error::Error {
    ErrorKind::SessionNetwork(err).into()
}
/// An OAuth token. Will usually expire after an hour.
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Token {


@@ 28,14 32,24 @@ fn cur_date() -> u64 {
}
impl Token {
    /// Creates a token from a response from /token
    pub fn from_response(response: &str) -> Result<Token> {
        println!("{}", response);
        Ok(serde_json::from_str(response)?)
    pub fn from_response(response: impl Into<String>) -> Result<Token> {
        Ok(serde_json::from_str(response.into().as_str())?)
    }
    /// Fetches a token using a login code
    pub fn from_login_code(code: &str) -> Result<Token> {
        let mut res = reqwest::get(&("https://auth.gog.com/token?client_id=46899977096215655&client_secret=9d85c43b1482497dbbce61f6e4aa173a433796eeae2ca8c5f6129f2dc4de46d9&grant_type=authorization_code&redirect_uri=https%3A%2F%2Fembed.gog.com%2Fon_login_success%3Forigin%3Dclient&layout=client2&code=".to_string()+&code+""))?;
        Token::from_response(&res.text()?)
    pub fn from_login_code(code: impl Into<String>) -> Result<Token> {
        let mut res = reqwest::get(&("https://auth.gog.com/token?client_id=46899977096215655&client_secret=9d85c43b1482497dbbce61f6e4aa173a433796eeae2ca8c5f6129f2dc4de46d9&grant_type=authorization_code&redirect_uri=https%3A%2F%2Fembed.gog.com%2Fon_login_success%3Forigin%3Dclient&layout=client2&code=".to_string()+&code.into()+""))?;
        let text = res.text()?;
        println!("{:?}", text);
        Token::from_response(text)
    }
    pub fn from_home_code(code: impl Into<String>) -> Result<Token> {
        let url = format!("https://auth.gog.com/token?client_id=46899977096215655&client_secret=9d85c43b1482497dbbce61f6e4aa173a433796eeae2ca8c5f6129f2dc4de46d9&grant_type=authorization_code&redirect_uri=https%3A%2F%2Fwww.gog.com%2Fon_login_success&layout=client2&code={}", code.into());
        println!("URL:{}", url);
        let mut res = reqwest::get(&url)?;
        println!("{:?}", res);
        let text = res.text()?;
        println!("{:?}", text);
        Token::from_response(text)
    }
    /// Checks if token has expired
    pub fn is_expired(&self) -> bool {


@@ 46,105 60,204 @@ impl Token {
        let mut res = reqwest::get(&("https://auth.gog.com/token?client_id=46899977096215655&client_secret=9d85c43b1482497dbbce61f6e4aa173a433796eeae2ca8c5f6129f2dc4de46d9&grant_type=refresh_token&redirect_uri=https://embed.gog.com/on_login_success?origin=client&refresh_token=".to_string()+&self.refresh_token))?;
        Ok(serde_json::from_str(&res.text()?)?)
    }
    pub fn login(username: impl Into<String>, password: impl Into<String>) -> Result<Token> {
    pub fn login<F>(
        username: impl Into<String>,
        password: impl Into<String>,
        two_step_token_fn: F,
    ) -> Result<Token>
    where
        F: Fn() -> String,
    {
        let (username, password) = (username.into(), password.into());
        let garegex =
            Regex::new(r"var galaxyAccounts = new GalaxyAccounts\('(.+)','(.+)'\)").unwrap();
        let client = reqwest::Client::new();
        let gsregex = Regex::new(r"(galaxy-login-s=.+;) expires").unwrap();
        let mut client = ReqwestSession::new(
            reqwest::ClientBuilder::new()
                .redirect(reqwest::RedirectPolicy::none())
                .build()
                .unwrap(),
        );
        let mut normal_client = ReqwestSession::new(reqwest::ClientBuilder::new().build().unwrap());
        info!("Fetching GOG home page to get auth url");
        let mut result = client.get("https://gog.com").send()?;

        let text = result.text().expect("Couldn't get home page text");
        let mut result = normal_client
            .get("https://gog.com")
            .map_err(convert_rsession)?;
        println!("{:?}", result);
        let text = result
            .text()
            .expect("Couldn't get home page text")
            .to_owned()
            .to_string();
        if let Some(captures) = garegex.captures(&text) {
            let auth_url = captures[1].to_string();
            println!("Auth URl: {}", auth_url);
            info!("Got URL, requesting auth page");
            let mut aresult = client.get(&auth_url).send()?;
            let mut aresult = client.get(&auth_url).map_err(convert_rsession)?;
            while aresult.status().is_redirection() {
                println!("Redirect!");
                let mut next_url = aresult
                    .headers()
                    .get("Location")
                    .unwrap()
                    .to_str()
                    .unwrap()
                    .to_string();
                println!("{:?}", aresult);
                aresult = client
                    .get(reqwest::Url::parse(&next_url).unwrap())
                    .map_err(convert_rsession)?
            }
            println!("{:?}", aresult);
            info!("Auth page request successful");
            let atext = aresult.text().expect("Couldn't get auth page text");
            let document = Document::from(atext.as_str());
            info!("Checking for captchas");
            let gcaptcha = document.find(Class("g-recaptcha"));
            if gcaptcha.count() > 0 {
                error!("Captcha detected. Wait and try again.");
                Err(NotAvailable.into())
            } else {
                let mut login_id = document.find(Attr("id", "login__token"));
                if let Some(input) = login_id.next() {
                    info!("Got login ID");
                    let lid = input
            let gcaptcha = document.find(Attr("defer", ""));
            for poss in gcaptcha {
                if poss.html().contains("recaptcha") {
                    error!("Captcha detected. Wait and try again.");
                    println!("Captcha");
                    return Err(NotAvailable.into());
                }
            }
            let mut login_id = document.find(Attr("id", "login__token"));
            if let Some(input) = login_id.next() {
                info!("Got login ID");
                let lid = input
                    .attr("value")
                    .expect("Login id field has no value.")
                    .to_string();
                info!("Searching home page text with regex for url");
                let mut form_parameters = std::collections::HashMap::new();
                form_parameters.insert("login[username]", username);
                form_parameters.insert("login[password]", password);
                form_parameters.insert("login[login]", String::default());
                form_parameters.insert("login[login_flow]", "default".to_string());
                form_parameters.insert("login[_token]", lid);
                println!("{:?}", form_parameters);
                let check_url = reqwest::Url::parse("https://login.gog.com/login_check").unwrap();
                let mut request = client
                    .client
                    .post_request(&check_url)
                    .form(&form_parameters);
                let mut cookies_processed: Vec<cookie::Cookie> = client
                    .store
                    .get_request_cookies(&check_url)
                    .cloned()
                    .collect();
                request = request.add_cookies(cookies_processed.iter().collect());
                println!("{:?}", request);
                let mut login_response = request.send()?;
                println!("{:?}", login_response);
                while login_response.status().is_redirection() {
                    println!("Redirect!");
                    let mut next_url = login_response
                        .headers()
                        .get("Location")
                        .unwrap()
                        .to_str()
                        .unwrap()
                        .to_string();
                    println!("{:?}", login_response);
                    login_response = client
                        .client
                        .get_request(&reqwest::Url::parse(&next_url).unwrap())
                        .add_cookies(cookies_processed.iter().collect())
                        .send()?;
                }
                let login_text = login_response.text().expect("Couldn't fetch login text");
                let login_doc = Document::from(login_text.as_str());
                let mut two_step_search =
                    login_doc.find(Attr("id", "second_step_authentication__token"));
                let url = login_response.url().clone();
                if let Some(two_node) = two_step_search.next() {
                    warn!("Two step authentication token needed.");
                    println!("Two step token required. This is not implmented yet.");
                    let two_token_secret = two_node
                        .attr("value")
                        .expect("Login id field has no value.")
                        .expect("No two step token found")
                        .to_string();
                    info!("Searching home page text with regex for url");
                    let mut form_parameters = std::collections::HashMap::new();
                    form_parameters.insert("login[username]", username);
                    form_parameters.insert("login[password]", password);
                    form_parameters.insert("login[login]", "".to_string());
                    form_parameters.insert("login[_token]", lid);
                    let two_token = two_step_token_fn();
                    if two_token.len() < 4 {
                        return Err(MissingField("Token too short".to_string()).into());
                    }
                    let mut token_iter = two_token.chars().map(|x| x.to_string());
                    let mut token_parameters = std::collections::HashMap::new();
                    token_parameters.insert(
                        "second_step_authentication[token][letter_1]",
                        token_iter.next().unwrap(),
                    );
                    token_parameters.insert(
                        "second_step_authentication[token][letter_2]",
                        token_iter.next().unwrap(),
                    );
                    token_parameters.insert(
                        "second_step_authentication[token][letter_3]",
                        token_iter.next().unwrap(),
                    );
                    token_parameters.insert(
                        "second_step_authentication[token][letter_4]",
                        token_iter.next().unwrap(),
                    );
                    token_parameters.insert("second_step_authentication[send]", String::default());
                    token_parameters.insert("second_step_authentication[_token]", two_token_secret);
                    let mut login_response = client
                        .post("https://login.gog.com/login")
                        .form(&form_parameters)
                        .client
                        .post_request(&url)
                        .add_cookies(cookies_processed.iter().collect())
                        .send()?;
                    let mut cookie_headers = reqwest::header::HeaderMap::new();
                    let mut url;
                    loop {
                        if login_response.status().is_redirection() {
                            {
                                let login_headers = login_response.headers();
                                for cookie in login_headers.get_all("set-cookie") {
                                    cookie_headers.append(
                                        "Cookie",
                                        reqwest::header::HeaderValue::from_str(&format!(
                                            "{};",
                                            cookie.to_str().unwrap()
                                        ))
                                        .unwrap(),
                                    );
                                }
                                url = login_headers
                                    .get("location")
                                    .unwrap()
                                    .to_str()
                                    .unwrap()
                                    .to_string();
                            }
                            login_response =
                                client.get(&url).headers(cookie_headers.clone()).send()?;
                        } else {
                            break;
                        }
                    }
                    let login_text = login_response.text().expect("Couldn't fetch login text");
                    let login_doc = Document::from(login_text.as_str());
                    let mut two_step_search =
                        login_doc.find(Attr("id", "second_step_authentication__token"));
                    let url = login_response.url();
                    if let Some(two_node) = two_step_search.next() {
                        info!("Two step authentication token needed.");
                        let two_token = two_node
                            .attr("value")
                            .expect("No two step token found")
                    while login_response.status().is_redirection() {
                        println!("Redirect!");
                        let mut next_url = login_response
                            .headers()
                            .get("Location")
                            .unwrap()
                            .to_str()
                            .unwrap()
                            .to_string();
                        Err(IncorrectCredentials.into())
                        println!("{:?}", login_response);
                        login_response = client
                            .client
                            .get_request(&reqwest::Url::parse(&next_url).unwrap())
                            .add_cookies(cookies_processed.iter().collect())
                            .send()?;
                    }
                    if url.as_str().contains("on_login_success") {
                        println!("{:?}", url);
                        let code = url
                            .query_pairs()
                            .filter(|(k, _v)| k == "code")
                            .map(|x| x.1)
                            .next()
                            .unwrap();
                        Token::from_home_code(code)
                    } else {
                        if url.as_str().contains("on_login_success") {
                            println!("{:?}", url);
                            let code = url
                                .query_pairs()
                                .filter(|(k, _v)| k == "code")
                                .map(|x| x.1)
                                .next()
                                .unwrap();
                            Token::from_login_code(&code)
                        } else {
                            error!("Login failed.");
                            Err(IncorrectCredentials.into())
                        }
                        println!("{:?}", url);
                        println!("{:?}", login_response);
                        error!("Login failed.");
                        Err(IncorrectCredentials.into())
                    }
                } else {
                    Err(MissingField("login id".to_string()).into())
                    println!("{:?}", cookies_processed);
                    if url.as_str().contains("on_login_success") {
                        println!("{:?}", url);
                        let code = url
                            .query_pairs()
                            .filter(|(k, _v)| k == "code")
                            .map(|x| x.1)
                            .next()
                            .unwrap();
                        Token::from_home_code(code)
                    } else {
                        println!("{:?}", url);
                        println!("{:?}", login_response);
                        error!("Login failed.");
                        Err(IncorrectCredentials.into())
                    }
                }
            } else {
                Err(MissingField("login id".to_string()).into())
            }
        } else {
            Err(MissingField("auth url".to_string()).into())

M tests/lib.rs => tests/lib.rs +1 -1
@@ 14,7 14,7 @@ fn get_gog() -> Gog {
        .unwrap()
        .read_to_string(&mut token_json)
        .unwrap();
    let mut token = Token::from_response(&token_json).unwrap();
    let mut token = Token::from_response(token_json.as_str()).unwrap();
    token = token.refresh().unwrap();
    Gog::new(token)
}