1use serde_json::json;
5mod containers;
6mod error;
8pub mod extract;
10pub mod gog;
12pub mod token;
14use connect::*;
15use containers::*;
16use curl::easy::{Easy2, Handler, WriteError};
17use domains::*;
18pub use error::Error;
20pub use error::ErrorKind;
21pub use error::Result;
22use extract::*;
23use gog::*;
24use product::*;
25use regex::*;
26use reqwest::blocking::{Client, Response};
27use reqwest::header::*;
28use reqwest::redirect::Policy;
29use reqwest::Method;
30use serde::de::DeserializeOwned;
31use serde_json::value::{Map, Value};
32use std::cell::RefCell;
33use std::io::BufRead;
34use std::io::BufReader;
35use std::io::Read;
36use token::Token;
37use ErrorKind::*;
38
39const GET: Method = Method::GET;
40const POST: Method = Method::POST;
41
42pub type EmptyResponse = ::std::result::Result<Response, Error>;
44macro_rules! map_p {
45 ($($js: tt)+) => {
46 Some(json!($($js)+).as_object().unwrap().clone())
47 }
48}
49
50pub struct Gog {
52 pub token: RefCell<Token>,
53 pub client: RefCell<Client>,
54 pub client_noredirect: RefCell<Client>,
55 pub auto_update: bool,
56}
57impl Gog {
58 pub fn from_login_code(code: &str) -> Gog {
60 Gog::from_token(Token::from_login_code(code).unwrap())
61 }
62
63 pub fn new(token: Token) -> Gog {
65 Gog::from_token(token)
66 }
67
68 fn from_token(token: Token) -> Gog {
69 let headers = Gog::headers_token(&token.access_token);
70 let mut client = Client::builder();
71 let mut client_re = Client::builder().redirect(Policy::none());
72 client = client.default_headers(headers.clone());
73 client_re = client_re.default_headers(headers);
74 Gog {
75 token: RefCell::new(token),
76 client: RefCell::new(client.build().unwrap()),
77 client_noredirect: RefCell::new(client_re.build().unwrap()),
78 auto_update: true,
79 }
80 }
81
82 fn update_token(&self, token: Token) {
83 let headers = Gog::headers_token(&token.access_token);
84 let client = Client::builder();
85 let client_re = Client::builder().redirect(Policy::none());
86 self.client
87 .replace(client.default_headers(headers.clone()).build().unwrap());
88 self.client_noredirect
89 .replace(client_re.default_headers(headers).build().unwrap());
90 self.token.replace(token);
91 }
92
93 pub fn uid_string(&self) -> String {
94 self.token.borrow().user_id.clone()
95 }
96
97 pub fn uid(&self) -> i64 {
98 self.token.borrow().user_id.parse().unwrap()
99 }
100
101 fn headers_token(at: &str) -> reqwest::header::HeaderMap {
102 let mut headers = reqwest::header::HeaderMap::new();
103 headers.insert(
104 "Authorization",
105 ("Bearer ".to_string() + at).parse().unwrap(),
106 );
107 headers.insert("CSRF", "csrf=true".parse().unwrap());
109 headers
110 }
111
112 fn rget(
113 &self,
114 domain: &str,
115 path: &str,
116 params: Option<Map<String, Value>>,
117 ) -> Result<Response> {
118 self.rreq(GET, domain, path, params)
119 }
120
121 fn rpost(
122 &self,
123 domain: &str,
124 path: &str,
125 params: Option<Map<String, Value>>,
126 ) -> Result<Response> {
127 self.rreq(POST, domain, path, params)
128 }
129
130 fn rreq(
131 &self,
132 method: Method,
133 domain: &str,
134 path: &str,
135 params: Option<Map<String, Value>>,
136 ) -> Result<Response> {
137 if self.token.borrow().is_expired() {
138 if self.auto_update {
139 let new_token = self.token.borrow().refresh()?;
140 self.update_token(new_token);
141 self.rreq(method, domain, path, params)
142 } else {
143 Err(ExpiredToken.into())
144 }
145 } else {
146 let mut url = domain.to_string() + path;
147 if let Some(temp_params) = params {
148 let params = temp_params;
149 if !params.is_empty() {
150 url += "?";
151 for (k, v) in params.iter() {
152 url = url + k + "=" + &v.to_string() + "&";
153 }
154 url.pop();
155 }
156 }
157 Ok(self.client.borrow().request(method, &url).send()?)
158 }
159 }
160
161 fn fget<T>(&self, domain: &str, path: &str, params: Option<Map<String, Value>>) -> Result<T>
162 where
163 T: DeserializeOwned,
164 {
165 self.freq(GET, domain, path, params)
166 }
167
168 fn _fpost<T>(&self, domain: &str, path: &str, params: Option<Map<String, Value>>) -> Result<T>
169 where
170 T: DeserializeOwned,
171 {
172 self.freq(POST, domain, path, params)
173 }
174
175 fn freq<T>(
176 &self,
177 method: Method,
178 domain: &str,
179 path: &str,
180 params: Option<Map<String, Value>>,
181 ) -> Result<T>
182 where
183 T: DeserializeOwned,
184 {
185 let res = self.rreq(method, domain, path, params)?;
186 let st = res.text()?;
187 Ok(serde_json::from_str(&st)?)
188 }
189
190 fn nfreq<T>(
191 &self,
192 method: Method,
193 domain: &str,
194 path: &str,
195 params: Option<Map<String, Value>>,
196 nested: &str,
197 ) -> Result<T>
198 where
199 T: DeserializeOwned,
200 {
201 let r: Map<String, Value> = self.freq(method, domain, path, params)?;
202 if r.contains_key(nested) {
203 Ok(serde_json::from_str(&r.get(nested).unwrap().to_string())?)
204 } else {
205 Err(MissingField(nested.to_string()).into())
206 }
207 }
208
209 fn nfget<T>(
210 &self,
211 domain: &str,
212 path: &str,
213 params: Option<Map<String, Value>>,
214 nested: &str,
215 ) -> Result<T>
216 where
217 T: DeserializeOwned,
218 {
219 self.nfreq(GET, domain, path, params, nested)
220 }
221
222 fn nfpost<T>(
223 &self,
224 domain: &str,
225 path: &str,
226 params: Option<Map<String, Value>>,
227 nested: &str,
228 ) -> Result<T>
229 where
230 T: DeserializeOwned,
231 {
232 self.nfreq(POST, domain, path, params, nested)
233 }
234
235 pub fn get_user_data(&self) -> Result<UserData> {
237 self.fget(EMBD, "/userData.json", None)
238 }
239
240 pub fn get_pub_info(&self, uid: i64, expand: Vec<String>) -> Result<PubInfo> {
242 self.fget(
243 EMBD,
244 &("/users/info/".to_string() + &uid.to_string()),
245 map_p!({
246 "expand": expand.iter().fold("".to_string(), fold_mult)
247 }),
248 )
249 }
250
251 pub fn get_games(&self) -> Result<Vec<i64>> {
253 let r: OwnedGames = self.fget(EMBD, "/user/data/games", None)?;
254 Ok(r.owned)
255 }
256
257 pub fn get_game_details(&self, game_id: i64) -> Result<GameDetails> {
259 let mut res: GameDetailsP = self.fget(
260 EMBD,
261 &("/account/gameDetails/".to_string() + &game_id.to_string() + ".json"),
262 None,
263 )?;
264 if !res.downloads.is_empty() {
265 res.downloads[0].remove(0);
266 let downloads: Downloads =
267 serde_json::from_str(&serde_json::to_string(&res.downloads[0][0])?)?;
268 Ok(res.into_details(downloads))
269 } else {
270 Err(NotAvailable.into())
271 }
272 }
273
274 pub fn download_game(&self, downloads: Vec<Download>) -> Vec<Result<Response>> {
276 downloads
277 .iter()
278 .map(|x| {
279 let mut url = BASE.to_string() + &x.manual_url;
280 let mut response;
281 loop {
282 let temp_response = self.client_noredirect.borrow().get(url).send();
283 if let Ok(temp) = temp_response {
284 response = temp;
285 let headers = response.headers();
286 if headers.contains_key("location") {
288 url = headers
289 .get("location")
290 .unwrap()
291 .to_str()
292 .unwrap()
293 .to_string();
294 } else {
295 break;
296 }
297 } else {
298 return Err(temp_response.err().unwrap().into());
299 }
300 }
301 Ok(response)
302 })
303 .collect()
304 }
305
306 pub fn hide_product(&self, game_id: i64) -> EmptyResponse {
308 self.rget(
309 EMBD,
310 &("/account/hideProduct".to_string() + &game_id.to_string()),
311 None,
312 )
313 }
314
315 pub fn reveal_product(&self, game_id: i64) -> EmptyResponse {
317 self.rget(
318 EMBD,
319 &("/account/revealProduct".to_string() + &game_id.to_string()),
320 None,
321 )
322 }
323
324 pub fn wishlist(&self) -> Result<Wishlist> {
326 self.fget(EMBD, "/user/wishlist.json", None)
327 }
328
329 pub fn add_wishlist(&self, game_id: i64) -> Result<Wishlist> {
331 self.fget(
332 EMBD,
333 &("/user/wishlist/add/".to_string() + &game_id.to_string()),
334 None,
335 )
336 }
337
338 pub fn rm_wishlist(&self, game_id: i64) -> Result<Wishlist> {
340 self.fget(
341 EMBD,
342 &("/user/wishlist/remove/".to_string() + &game_id.to_string()),
343 None,
344 )
345 }
346
347 pub fn save_birthday(&self, bday: &str) -> EmptyResponse {
349 self.rget(EMBD, &("/account/save_birthday".to_string() + bday), None)
350 }
351
352 pub fn save_country(&self, country: &str) -> EmptyResponse {
354 self.rget(EMBD, &("/account/save_country".to_string() + country), None)
355 }
356
357 pub fn save_currency(&self, currency: Currency) -> EmptyResponse {
360 self.rget(
361 EMBD,
362 &("/user/changeCurrency".to_string() + ¤cy.to_string()),
363 None,
364 )
365 }
366
367 pub fn save_language(&self, language: Language) -> EmptyResponse {
370 self.rget(
371 EMBD,
372 &("/user/changeLanguage".to_string() + &language.to_string()),
373 None,
374 )
375 }
376
377 pub fn connect_account(&self, user_id: i64) -> Result<LinkedSteam> {
379 self.fget(
380 EMBD,
381 &("/api/v1/users/".to_string() + &user_id.to_string() + "/gogLink/steam/linkedAccount"),
382 None,
383 )
384 }
385
386 pub fn connect_status(&self, user_id: i64) -> Result<ConnectStatus> {
388 let st = self
389 .rget(
390 EMBD,
391 &("/api/v1/users/".to_string()
392 + &user_id.to_string()
393 + "/gogLink/steam/exchangeableProducts"),
394 None,
395 )?
396 .text()?;
397 if let Ok(cs) = serde_json::from_str(&st) {
398 return Ok(cs);
399 } else {
400 let map: Map<String, Value> = serde_json::from_str(&st)?;
401 if let Some(items) = map.get("items") {
402 let array = items.as_array();
403 if array.is_some() && array.unwrap().is_empty() {
404 return Err(NotAvailable.into());
405 }
406 }
407 }
408 Err(MissingField("items".to_string()).into())
409 }
410
411 pub fn connect_scan(&self, user_id: i64) -> EmptyResponse {
413 self.rget(
414 EMBD,
415 &("/api/v1/users/".to_string()
416 + &user_id.to_string()
417 + "/gogLink/steam/synchronizeUserProfile"),
418 None,
419 )
420 }
421
422 pub fn connect_claim(&self, user_id: i64) -> EmptyResponse {
424 self.rget(
425 EMBD,
426 &("/api/v1/users/".to_string() + &user_id.to_string() + "/gogLink/steam/claimProducts"),
427 None,
428 )
429 }
430
431 pub fn product(&self, ids: Vec<i64>, expand: Vec<String>) -> Result<Vec<Product>> {
433 self.fget(
434 API,
435 "/products",
436 map_p!({
437 "expand": expand.iter().fold("".to_string(), fold_mult),
438 "ids": ids.iter().fold("".to_string(), |acc, x|{
439 acc + "," + &x.to_string()
440 })
441 }),
442 )
443 }
444
445 pub fn achievements(&self, product_id: i64, user_id: i64) -> Result<AchievementList> {
447 self.fget(
448 GPLAY,
449 &("/clients/".to_string()
450 + &product_id.to_string()
451 + "/users/"
452 + &user_id.to_string()
453 + "/achievements"),
454 None,
455 )
456 }
457
458 pub fn add_tag(&self, product_id: i64, tag_id: i64) -> Result<bool> {
460 let res: Result<Success> = self.fget(
461 EMBD,
462 "/account/tags/attach",
463 map_p!({
464 "product_id":product_id,
465 "tag_id":tag_id
466 }),
467 );
468 res.map(|x| x.success)
469 }
470
471 pub fn rm_tag(&self, product_id: i64, tag_id: i64) -> Result<bool> {
473 self.nfget(
474 EMBD,
475 "/account/tags/detach",
476 map_p!({
477 "product_id":product_id,
478 "tag_id":tag_id
479 }),
480 "success",
481 )
482 }
483
484 pub fn get_filtered_products(&self, params: FilterParams) -> Result<FilteredProducts> {
486 let url = reqwest::Url::parse(
488 &("https://gog.com/account/getFilteredProducts".to_string()
489 + ¶ms.to_query_string()),
490 )
491 .unwrap();
492 let path = url.path().to_string() + "?" + url.query().unwrap();
493 self.fget(EMBD, &path, None)
494 }
495
496 pub fn get_all_filtered_products(&self, params: FilterParams) -> Result<Vec<ProductDetails>> {
498 let url = reqwest::Url::parse(
499 &("https://gog.com/account/getFilteredProducts".to_string()
500 + ¶ms.to_query_string()),
501 )
502 .unwrap();
503 let mut page = 1;
504 let path = url.path().to_string() + "?" + url.query().unwrap();
505 let mut products = vec![];
506 loop {
507 let res: FilteredProducts =
508 self.fget(EMBD, &format!("{}&page={}", path, page), None)?;
509 products.push(res.products);
510 if page >= res.total_pages {
511 break;
512 } else {
513 page += 1;
514 }
515 }
516 Ok(products.into_iter().flatten().collect())
517 }
518
519 pub fn get_products(&self, params: FilterParams) -> Result<Vec<UnownedProductDetails>> {
521 let url = reqwest::Url::parse(
523 &("https://gog.com/games/ajax/filtered".to_string() + ¶ms.to_query_string()),
524 )
525 .unwrap();
526 let path = url.path().to_string() + "?" + url.query().unwrap();
527 self.nfget(EMBD, &path, None, "products")
528 }
529
530 pub fn create_tag(&self, name: &str) -> Result<i64> {
532 return self
533 .nfget(EMBD, "/account/tags/add", map_p!({ "name": name }), "id")
534 .map(|x: String| x.parse::<i64>().unwrap());
535 }
536
537 pub fn delete_tag(&self, tag_id: i64) -> Result<bool> {
539 let res: Result<StatusDel> =
540 self.fget(EMBD, "/account/tags/delete", map_p!({ "tag_id": tag_id }));
541 res.map(|x| return x.status.as_str() == "deleted")
542 }
543
544 pub fn newsletter_subscription(&self, enabled: bool) -> EmptyResponse {
546 self.rget(
547 EMBD,
548 &("/account/save_newsletter_subscription/".to_string() + &(enabled as i32).to_string()),
549 None,
550 )
551 }
552
553 pub fn promo_subscription(&self, enabled: bool) -> EmptyResponse {
555 self.rget(
556 EMBD,
557 &("/account/save_promo_subscription/".to_string() + &(enabled as i32).to_string()),
558 None,
559 )
560 }
561
562 pub fn wishlist_subscription(&self, enabled: bool) -> EmptyResponse {
564 self.rget(
565 EMBD,
566 &("/account/save_wishlist_notification/".to_string() + &(enabled as i32).to_string()),
567 None,
568 )
569 }
570
571 pub fn all_subscription(&self, enabled: bool) -> Vec<EmptyResponse> {
573 vec![
574 self.newsletter_subscription(enabled),
575 self.promo_subscription(enabled),
576 self.wishlist_subscription(enabled),
577 ]
578 }
579
580 pub fn game_ratings(&self) -> Result<Vec<(String, i64)>> {
582 let g: Map<String, Value> =
583 self.nfget(EMBD, "/user/games_rating.json", None, "games_rating")?;
584 Ok(g.iter()
585 .map(|x| (x.0.to_owned(), x.1.as_i64().unwrap()))
586 .collect::<Vec<(String, i64)>>())
587 }
588
589 pub fn voted_reviews(&self) -> Result<Vec<i64>> {
591 self.nfget(EMBD, "/user/review_votes.json", None, "reviews")
592 }
593
594 pub fn report_review(&self, review_id: i32) -> Result<bool> {
596 self.nfpost(
597 EMBD,
598 &("/reviews/report/review/".to_string() + &review_id.to_string() + ".json"),
599 None,
600 "reported",
601 )
602 }
603
604 pub fn library_background(&self, bg: ShelfBackground) -> EmptyResponse {
606 self.rpost(
607 EMBD,
608 &("/account/save_shelf_background/".to_string() + bg.as_str()),
609 None,
610 )
611 }
612
613 pub fn friends(&self) -> Result<Vec<Friend>> {
615 self.nfget(
616 CHAT,
617 &("/users/".to_string() + &self.uid_string() + "/friends"),
618 None,
619 "items",
620 )
621 }
622
623 fn get_sizes<R: Read>(&self, bufreader: &mut BufReader<R>) -> Result<(usize, usize)> {
624 let mut buffer = String::new();
625 let mut script_size = 0;
626 let mut script_bytes = 0;
627 let mut script = String::new();
628 let mut i = 1;
629 let mut filesize = 0;
630 let filesize_reg = Regex::new(r#"filesizes="(\d+)"#).unwrap();
631 let offset_reg = Regex::new(r"offset=`head -n (\d+)").unwrap();
632 loop {
633 let read = bufreader.read_line(&mut buffer).unwrap();
634 script_bytes += read;
635 if script_size != 0 && script_size > i {
636 script += &buffer;
637 } else if script_size != 0 && script_size <= i && filesize != 0 {
638 break;
639 }
640 if script_size == 0 {
641 if let Some(captures) = offset_reg.captures(&buffer) {
642 if captures.len() > 1 {
643 script_size = captures[1].to_string().parse().unwrap();
644 }
645 }
646 }
647 if filesize == 0 {
648 if let Some(captures) = filesize_reg.captures(&buffer) {
649 if captures.len() > 1 {
650 filesize = captures[1].to_string().parse().unwrap();
651 }
652 }
653 }
654 i += 1;
655 }
656 Ok((script_bytes, filesize))
657 }
658
659 pub fn download_request_range_at<H: Handler>(
661 at: impl Into<String>,
662 url: impl Into<String>,
663 handler: H,
664 start: i64,
665 end: i64,
666 ) -> Result<Easy2<H>> {
667 let url = url.into();
668 let mut easy = Easy2::new(handler);
669 easy.url(&url)?;
670 easy.range(&format!("{}-{}", start, end))?;
671 easy.follow_location(true)?;
672 let mut list = curl::easy::List::new();
673 list.append("CSRF: true")?;
674 list.append(&format!("Authentication: Bearer {}", at.into()))?;
675 easy.get(true)?;
676 easy.http_headers(list)?;
677 easy.perform()?;
678 Ok(easy)
679 }
680
681 pub fn download_request_range(
683 &self,
684 url: impl Into<String>,
685 start: i64,
686 end: i64,
687 ) -> Result<Vec<u8>> {
688 Ok(Gog::download_request_range_at(
689 self.token.borrow().access_token.as_str(),
690 url,
691 Collector(Vec::new()),
692 start,
693 end,
694 )?
695 .get_ref()
696 .0
697 .clone())
698 }
699
700 pub fn extract_data(&self, downloads: Vec<Download>) -> Result<Vec<ZipData>> {
702 let mut zips = vec![];
703 let mut responses = self.download_game(downloads.clone());
704 for down in downloads {
705 let mut url = BASE.to_string() + &down.manual_url;
706 let mut response;
707 loop {
708 if let Ok(temp_response) = self.client_noredirect.borrow().get(&url).send() {
709 response = temp_response;
710 let headers = response.headers();
711 if headers.contains_key("location") {
714 url = headers
715 .get("location")
716 .unwrap()
717 .to_str()
718 .unwrap()
719 .to_string();
720 } else {
721 break;
722 }
723 }
724 }
725 let response = responses.remove(0).expect("Couldn't get download");
726 let size = response
727 .headers()
728 .get(CONTENT_LENGTH)
729 .unwrap()
730 .to_str()
731 .expect("Couldn't convert to string")
732 .parse()
733 .unwrap();
734 let mut bufreader = BufReader::new(response);
735 let sizes = self.get_sizes(&mut bufreader)?;
736 let eocd_offset = self.get_eocd_offset(&url, size)?;
737 let off = match eocd_offset {
738 EOCDOffset::Offset(offset) => offset,
739 EOCDOffset::Offset64(offset) => offset,
740 };
741 let cd_offset;
742 let records;
743 let cd_size;
744 let central_directory = self.download_request_range(url.as_str(), off as i64, size)?;
745 let mut cd_slice = central_directory.as_slice();
746 let mut cd_reader = BufReader::new(&mut cd_slice);
747 match eocd_offset {
748 EOCDOffset::Offset(..) => {
749 let cd = CentralDirectory::from_reader(&mut cd_reader);
750 cd_offset = cd.cd_start_offset as u64;
751 records = cd.total_cd_records as u64;
752 cd_size = cd.cd_size as u64;
753 }
754 EOCDOffset::Offset64(..) => {
755 let cd = CentralDirectory64::from_reader(&mut cd_reader);
756 cd_offset = cd.cd_start as u64;
757 records = cd.cd_total;
758 cd_size = cd.cd_size;
759 }
760 };
761 let offset_beg = sizes.0 + sizes.1 + cd_offset as usize;
762 let cd = self
763 .download_request_range(
764 url.as_str(),
765 offset_beg as i64,
766 (offset_beg + cd_size as usize) as i64,
767 )
768 .unwrap();
769 let mut slice = cd.as_slice();
770 let mut full_reader = BufReader::new(&mut slice);
771 let mut files = vec![];
772 for _ in 0..records {
773 let mut entry = CDEntry::from_reader(&mut full_reader);
774 entry.start_offset = (sizes.0 + sizes.1) as u64 + entry.disk_offset.unwrap();
775 files.push(entry);
776 }
777 let len = files.len();
778 files[len - 1].end_offset = offset_beg as u64 - 1;
779 for i in 0..(len - 1) {
780 files[i].end_offset = files[i + 1].start_offset;
781 }
782 zips.push(ZipData {
783 sizes,
784 files,
785 url,
786 cd: None,
787 });
788 }
789 Ok(zips)
790 }
791
792 fn get_eocd_offset(&self, url: &str, size: i64) -> Result<EOCDOffset> {
794 let signature = 0x06054b50;
795 let signature_64 = 0x06064b50;
796 let mut offset;
797 for i in 4..size + 1 {
798 let pos = size - i;
799 let resp = self.download_request_range(url, pos, pos + 4)?;
800 let cur = pos + 4;
801 let inte = vec_to_u32(&resp);
802 if inte == signature {
803 offset = cur;
804 offset -= 4;
805 return Ok(EOCDOffset::Offset(offset as usize));
806 } else if inte == signature_64 {
807 offset = cur;
808 offset -= 4;
809 return Ok(EOCDOffset::Offset64(offset as usize));
810 }
811 }
812 Err(NotAvailable.into())
813 }
814}
815
816fn fold_mult(acc: String, now: &String) -> String {
817 acc + "," + now
818}
819
820fn vec_to_u32(data: &[u8]) -> u32 {
821 u32::from_le_bytes([data[0], data[1], data[2], data[3]])
822}
823
824pub struct Collector(pub Vec<u8>);
826
827impl Handler for Collector {
828 fn write(&mut self, data: &[u8]) -> std::result::Result<usize, WriteError> {
829 self.0.extend_from_slice(data);
830 Ok(data.len())
831 }
832}