Merge branch '7-access-token-expired' into 'master'
automatically refresh invalid grants Closes #7 See merge request !8
This commit is contained in:
commit
db1c5af255
@ -1,5 +1,8 @@
|
||||
## 0.3.1 (2017-05-05)
|
||||
- automatically check if grant is still valid and refresh if necessary (fixes #7)
|
||||
|
||||
## 0.3.0 (2017-05-04)
|
||||
- move hashes and content info into seperate files as to not clutter up the main config file.
|
||||
- move hashes and content info into seperate files as to not clutter up the main config file
|
||||
|
||||
## 0.2.4 (2017-03-23)
|
||||
- fix a bug in serial key parsing
|
||||
|
8
Cargo.lock
generated
8
Cargo.lock
generated
@ -1,9 +1,9 @@
|
||||
[root]
|
||||
name = "gog-sync"
|
||||
version = "0.3.0"
|
||||
version = "0.3.1"
|
||||
dependencies = [
|
||||
"chrono 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"clap 2.23.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"clap 2.24.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"curl 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"env_logger 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@ -53,7 +53,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "2.23.3"
|
||||
version = "2.24.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@ -429,7 +429,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
"checksum atty 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d912da0db7fa85514874458ca3651fe2cddace8d0b0505571dbdcd41ab490159"
|
||||
"checksum bitflags 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1370e9fc2a6ae53aea8b7a5110edbd08836ed87c88736dfabccade1c2b44bff4"
|
||||
"checksum chrono 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d9123be86fd2a8f627836c235ecdf331fdd067ecf7ac05aa1a68fbcf2429f056"
|
||||
"checksum clap 2.23.3 (registry+https://github.com/rust-lang/crates.io-index)" = "f57e9b63057a545ad2ecd773ea61e49422ed1b1d63d74d5da5ecaee55b3396cd"
|
||||
"checksum clap 2.24.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f31c42d0cecb245c1a0bee00ef433eb1bf253897fe472b6a3f4202e9dbbc4b25"
|
||||
"checksum curl 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c90e1240ef340dd4027ade439e5c7c2064dd9dc652682117bd50d1486a3add7b"
|
||||
"checksum curl-sys 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)" = "23e7e544dc5e1ba42c4a4a678bd47985e84b9c3f4d3404c29700622a029db9c3"
|
||||
"checksum dtoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "80c8b71fd71146990a9742fc06dcbbde19161a267e0ad4e572c35162f4578c90"
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "gog-sync"
|
||||
version = "0.3.0"
|
||||
version = "0.3.1"
|
||||
authors = ["Sebastian Hugentobler <sebastian@vanwa.ch>"]
|
||||
description = "Synchronizes a GOG library with a local folder."
|
||||
documentation = "https://docs.rs/crate/gog-sync"
|
||||
|
106
src/gog.rs
106
src/gog.rs
@ -24,6 +24,7 @@ use std::fs::File;
|
||||
use std::io;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// Wraps `ConfigError`, `HttpError`, `serde_json::Error`, and `io::Error`.
|
||||
#[derive(Debug)]
|
||||
@ -76,8 +77,6 @@ impl From<io::Error> for GogError {
|
||||
pub struct Gog<'a> {
|
||||
client_id: String,
|
||||
client_secret: String,
|
||||
redirect_uri: String,
|
||||
games_uri: String,
|
||||
http_client: &'a mut Http,
|
||||
}
|
||||
|
||||
@ -87,8 +86,6 @@ impl<'a> Gog<'a> {
|
||||
Gog {
|
||||
client_id: String::from("46899977096215655"),
|
||||
client_secret: String::from("9d85c43b1482497dbbce61f6e4aa173a433796eeae2ca8c5f6129f2dc4de46d9"),
|
||||
redirect_uri: String::from("https://embed.gog.com/on_login_success?origin=client"),
|
||||
games_uri: String::from("https://embed.gog.com/user/data/games"),
|
||||
http_client: http_client,
|
||||
}
|
||||
}
|
||||
@ -98,6 +95,15 @@ impl<'a> Gog<'a> {
|
||||
/// will get an url which you have to open in a browser. There you have to
|
||||
/// login and copy the code parameter from the next page into the prompt.
|
||||
pub fn login(&mut self) -> Result<(), GogError> {
|
||||
let token = self.refresh_token_from_file()?;
|
||||
|
||||
let auth_header = format!("Authorization: Bearer {token}", token = token.access_token);
|
||||
self.http_client.add_header(auth_header.as_str())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn refresh_token_from_file(&mut self) -> Result<Token, GogError> {
|
||||
let config = ConfigFiles::new();
|
||||
let mut token: Token = match config.load("token.json") {
|
||||
Ok(value) => value,
|
||||
@ -113,10 +119,7 @@ impl<'a> Gog<'a> {
|
||||
|
||||
config.save("token.json", &token)?;
|
||||
|
||||
let auth_header = format!("Authorization: Bearer {token}", token = token.access_token);
|
||||
self.http_client.add_header(auth_header.as_str())?;
|
||||
|
||||
Ok(())
|
||||
Ok(token)
|
||||
}
|
||||
|
||||
/// Syncs the contents of a gog account with a local folder.
|
||||
@ -214,7 +217,7 @@ impl<'a> Gog<'a> {
|
||||
let data_uri = format!("https://embed.gog.com{}", data.manual_url);
|
||||
|
||||
info!("downloading {} for {}...", &data.manual_url, &content.title);
|
||||
let filename = self.http_client.download(data_uri.as_str(), &data_root)?;
|
||||
let filename = self.api_request_download(data_uri.as_str(), &data_root)?;
|
||||
|
||||
let data_info = DataInfo {
|
||||
hash: data_hash,
|
||||
@ -222,13 +225,15 @@ impl<'a> Gog<'a> {
|
||||
language: data.language,
|
||||
};
|
||||
|
||||
content_info.data.insert(data.manual_url.clone(), data_info);
|
||||
content_info
|
||||
.data
|
||||
.insert(data.manual_url.clone(), data_info);
|
||||
ConfigFiles::save_to_path(&content_info_path, &content_info)?;
|
||||
}
|
||||
|
||||
for extra in content.extras {
|
||||
let extra_hash_saved = match content_info_saved.extras
|
||||
.get(&content_id.to_string()) {
|
||||
let extra_hash_saved =
|
||||
match content_info_saved.extras.get(&content_id.to_string()) {
|
||||
Some(value) => value.hash,
|
||||
None => u64::min_value(),
|
||||
};
|
||||
@ -243,7 +248,7 @@ impl<'a> Gog<'a> {
|
||||
let extra_uri = format!("https://embed.gog.com{}", extra.manual_url);
|
||||
|
||||
info!("downloading {} for {}...", &extra.name, &content.title);
|
||||
let filename = self.http_client.download(extra_uri.as_str(), &content_root)?;
|
||||
let filename = self.api_request_download(extra_uri.as_str(), &content_root)?;
|
||||
|
||||
let extra_info = ExtraInfo {
|
||||
hash: extra_hash,
|
||||
@ -251,7 +256,9 @@ impl<'a> Gog<'a> {
|
||||
name: extra.name,
|
||||
};
|
||||
|
||||
content_info.extras.insert(extra.manual_url.clone(), extra_info);
|
||||
content_info
|
||||
.extras
|
||||
.insert(extra.manual_url.clone(), extra_info);
|
||||
ConfigFiles::save_to_path(&content_info_path, &content_info)?;
|
||||
}
|
||||
}
|
||||
@ -259,8 +266,8 @@ impl<'a> Gog<'a> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_code(&self) -> Result<String, GogError> {
|
||||
let auth_uri = self.auth_uri(self.client_id.as_str(), self.redirect_uri.as_str());
|
||||
fn get_code(&mut self) -> Result<String, GogError> {
|
||||
let auth_uri = self.auth_uri(self.client_id.as_str(), self.redirect_uri().as_str());
|
||||
|
||||
println!("Open the following url in a browser, login to your account and paste the \
|
||||
resulting code parameter.");
|
||||
@ -282,12 +289,9 @@ impl<'a> Gog<'a> {
|
||||
let token_uri = self.token_uri(self.client_id.as_str(),
|
||||
self.client_secret.as_str(),
|
||||
code,
|
||||
self.redirect_uri.as_str());
|
||||
self.redirect_uri().as_str());
|
||||
|
||||
let token_response = match self.http_client.get(token_uri.as_str()) {
|
||||
Ok(value) => value,
|
||||
Err(error) => return Err(GogError::HttpError(error)),
|
||||
};
|
||||
let token_response = self.api_request_get(token_uri.as_str())?;
|
||||
|
||||
match serde_json::from_str(&token_response) {
|
||||
Ok(value) => Ok(value),
|
||||
@ -300,10 +304,7 @@ impl<'a> Gog<'a> {
|
||||
self.client_secret.as_str(),
|
||||
refresh_token);
|
||||
|
||||
let token_response = match self.http_client.get(token_refresh_uri.as_str()) {
|
||||
Ok(value) => value,
|
||||
Err(error) => return Err(GogError::HttpError(error)),
|
||||
};
|
||||
let token_response = self.api_request_get(token_refresh_uri.as_str())?;
|
||||
|
||||
match serde_json::from_str(&token_response) {
|
||||
Ok(value) => Ok(value),
|
||||
@ -312,7 +313,8 @@ impl<'a> Gog<'a> {
|
||||
}
|
||||
|
||||
fn get_content_ids(&mut self) -> Result<Vec<u64>, GogError> {
|
||||
let response = self.http_client.get(self.games_uri.as_str())?;
|
||||
let games_uri = self.games_uri();
|
||||
let response = self.api_request_get(games_uri.as_str())?;
|
||||
let content_ids_raw: Value = serde_json::from_str(response.as_str())?;
|
||||
let content_ids_serde = &content_ids_raw["owned"];
|
||||
|
||||
@ -347,7 +349,8 @@ impl<'a> Gog<'a> {
|
||||
let mut raw_cd_keys_fix = raw_cd_keys.to_owned();
|
||||
|
||||
if raw_cd_keys_fix.contains("<span>") {
|
||||
raw_cd_keys_fix = raw_cd_keys_fix.replace("</span><span>", ":")
|
||||
raw_cd_keys_fix = raw_cd_keys_fix
|
||||
.replace("</span><span>", ":")
|
||||
.replace("<span>", "")
|
||||
.replace("</span>", "");
|
||||
}
|
||||
@ -379,6 +382,41 @@ impl<'a> Gog<'a> {
|
||||
return cd_keys;
|
||||
}
|
||||
|
||||
fn api_request_ensure_token(&mut self, response: &str) -> Result<bool, GogError> {
|
||||
let response_json: Value = match serde_json::from_str(response) {
|
||||
Ok(value) => value,
|
||||
Err(_) => return Ok(false),
|
||||
};
|
||||
|
||||
if response_json.is_object() && response_json.as_object().unwrap().contains_key("error") &&
|
||||
response_json["error"] == "invalid_grant" {
|
||||
debug!("invalid grant, refreshing token...");
|
||||
self.refresh_token_from_file()?;
|
||||
|
||||
Ok(true)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
fn api_request_get(&mut self, uri: &str) -> Result<String, GogError> {
|
||||
let response = self.http_client.get(uri)?;
|
||||
if self.api_request_ensure_token(&response)? {
|
||||
Ok(self.http_client.get(uri)?)
|
||||
} else {
|
||||
Ok(response)
|
||||
}
|
||||
}
|
||||
|
||||
fn api_request_download(&mut self, uri: &str, path: &PathBuf) -> Result<String, GogError> {
|
||||
let response = self.http_client.download(uri, path)?;
|
||||
if self.api_request_ensure_token(&response)? {
|
||||
Ok(self.http_client.download(uri, path)?)
|
||||
} else {
|
||||
Ok(response)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_content(&mut self,
|
||||
content_id: u64,
|
||||
os_filters: &Vec<String>,
|
||||
@ -398,7 +436,11 @@ impl<'a> Gog<'a> {
|
||||
|
||||
let downloads = &content_raw["downloads"];
|
||||
|
||||
if content_raw.is_object() && !content_raw.as_object().unwrap().contains_key("forumLink") {
|
||||
if content_raw.is_object() &&
|
||||
!content_raw
|
||||
.as_object()
|
||||
.unwrap()
|
||||
.contains_key("forumLink") {
|
||||
return Err(GogError::Error("No forumLink property"));
|
||||
}
|
||||
|
||||
@ -500,6 +542,14 @@ impl<'a> Gog<'a> {
|
||||
Ok(content)
|
||||
}
|
||||
|
||||
fn games_uri(&self) -> String {
|
||||
String::from("https://embed.gog.com/user/data/games")
|
||||
}
|
||||
|
||||
fn redirect_uri(&self) -> String {
|
||||
String::from("https://embed.gog.com/on_login_success?origin=client")
|
||||
}
|
||||
|
||||
fn auth_uri(&self, client_id: &str, redirect_uri: &str) -> String {
|
||||
format!("https://auth.gog.\
|
||||
com/auth?client_id={client_id}&redirect_uri={redirect_uri}&response_type=code&layout=client2",
|
||||
|
@ -28,7 +28,7 @@ fn main() {
|
||||
env_logger::init().unwrap();
|
||||
|
||||
let matches = App::new("Gog Synchronizer")
|
||||
.version("0.3.0")
|
||||
.version("0.3.1")
|
||||
.author("Sebastian Hugentobler <sebastian@vanwa.ch>")
|
||||
.about("Synchronizes your gog library to a local folder.")
|
||||
.arg(Arg::with_name("game-storage")
|
||||
|
Loading…
Reference in New Issue
Block a user