1use std::fs;
2use std::fs::{File, read_to_string};
3use std::path::Path;
4use std::io::Write;
5use serde_json::Value;
6use serde::Deserialize;
7use crate::util::get_json_filetype;
8
9#[derive(Deserialize, Debug)]
10pub struct SteamAppDetails {
11 pub app_id: i64,
12 pub name: String,
13 pub app_type: String,
14 pub supported_languages: String,
15 pub support_info: SupportInfo,
16 pub short_description: String,
17 pub screenshots: Vec<Screenshot>,
18 pub reviews: String,
19 pub required_age: i64,
20 pub release_date: ReleaseDate,
21 pub recommendations: Recommendations,
22 pub price_overview: PriceOverview,
23 pub platforms: Platforms,
24 pub pc_requirements: PcRequirements,
25 pub mac_requirements: MacRequirements,
26 pub linux_requirements: LinuxRequirements,
27 pub package_groups: Vec<PackageGroup>,
28 pub movies: Vec<Movie>,
29 pub metacritic: Metacritic,
30 pub legal_notice: String,
31 pub is_free: bool,
32 pub genres: Vec<Genre>,
33 pub fullgame: FullGame,
34 pub ext_user_account_notice: String,
35 pub drm_notice: String,
36 pub detailed_description: String,
37 pub header_image: String,
38 pub demos: Vec<Demo>,
39 pub controller_support: String,
40 pub content_descriptors: ContentDescriptors,
41 pub categories: Vec<Category>,
42 pub website: String,
43 pub background_raw: String,
44 pub background: String,
45 pub alternate_appid: String,
46 pub about_the_game: String,
47 pub achievements: Achievement,
48}
49
50#[derive(Deserialize, Debug)]
51pub struct SupportInfo {
52 pub url: String,
53 pub email: String,
54}
55
56#[derive(Deserialize, Debug)]
57pub struct Screenshot {
58 pub path_thumbnail: String,
59 pub path_full: String,
60 pub id: i64,
61}
62
63#[derive(Deserialize, Debug)]
64pub struct ReleaseDate {
65 pub date: String,
66 pub coming_soon: bool,
67}
68
69#[derive(Deserialize, Debug)]
70pub struct Recommendations {
71 pub total: i64,
72}
73
74#[derive(Deserialize, Debug)]
75pub struct PriceOverview {
76 pub recurring_sub_desc: String,
77 pub recurring_sub: i64,
78 pub initial_formatted: String,
79 pub initial: i64,
80 pub final_formatted: String,
81 pub final_price: i64,
82 pub discount_percent: i64,
83 pub currency: String,
84}
85
86#[derive(Deserialize, Debug)]
87pub struct Platforms {
88 pub windows: bool,
89 pub mac: bool,
90 pub linux: bool,
91}
92
93#[derive(Deserialize, Debug)]
94pub struct PcRequirements {
95 pub recommended: String,
96 pub minimum: String,
97}
98
99#[derive(Deserialize, Debug)]
100pub struct MacRequirements {
101 pub recommended: String,
102 pub minimum: String,
103}
104
105#[derive(Deserialize, Debug)]
106pub struct LinuxRequirements {
107 pub recommended: String,
108 pub minimum: String,
109}
110
111#[derive(Deserialize, Debug)]
112pub struct PackageGroup {
113 pub title: String,
114 pub selection_text: String,
115 pub save_text: String,
116 pub name: String,
117 pub is_recurring_subscription: String,
118 pub display_type: String,
119 pub description: String,
120 pub subs: Vec<Sub>,
121}
122
123#[derive(Deserialize, Debug)]
124pub struct Sub {
125 pub price_in_cents_with_discount: i64,
126 pub percent_savings_text: String,
127 pub percent_savings: i64,
128 pub packageid: i64,
129 pub option_text: String,
130 pub option_description: String,
131 pub is_free_license: bool,
132 pub can_get_free_license: String,
133}
134
135#[derive(Deserialize, Debug)]
136pub struct Movie {
137 pub thumbnail: String,
138 pub name: String,
139 pub id: i64,
140 pub highlight: bool,
141 pub webm: Webm,
142 pub mp4: Mp4,
143}
144
145#[derive(Deserialize, Debug)]
146pub struct Mp4 {
147 pub max: String,
148 pub _480: String,
149}
150
151#[derive(Deserialize, Debug)]
152pub struct Webm {
153 pub max: String,
154 pub dash: String,
155 pub _480: String,
156}
157
158#[derive(Deserialize, Debug)]
159pub struct Metacritic {
160 pub url: String,
161 pub score: i64,
162}
163
164#[derive(Deserialize, Debug)]
165pub struct Genre {
166 pub id: String,
167 pub description: String
168}
169
170#[derive(Deserialize, Debug)]
171pub struct FullGame {
172 pub name: String,
173 pub appid: String
174}
175
176#[derive(Deserialize, Debug)]
177pub struct Demo {
178 pub description: String,
179 pub appid: i64
180}
181
182#[derive(Deserialize, Debug)]
183pub struct ContentDescriptors {
184 pub notes: String,
185}
186
187#[derive(Deserialize, Debug)]
188pub struct Category {
189 pub id: i64,
190 pub description: String,
191}
192
193#[derive(Deserialize, Debug)]
194pub struct Achievement {
195 pub total: i64,
196 pub highlighted: Vec<Highlight>,
197}
198
199#[derive(Deserialize, Debug)]
200pub struct Highlight {
201 pub path: String,
202 pub name: String,
203}
204
205pub fn get(app_id: i64) -> Result<SteamAppDetails, String> {
206 let api_response_boxed = make_api_call(app_id);
207 if api_response_boxed.is_err() {
208 return Err(api_response_boxed.err().unwrap().to_string());
209 } else {
210 parse_api_call_result(api_response_boxed.unwrap(), app_id)
211 }
212}
213
214pub fn get_cached(app_id: i64) -> Result<SteamAppDetails, String> {
215 let filepath = get_resource_filepath(app_id);
216
217 let boxed_read = read_to_string(filepath);
218 let is_readable = boxed_read.is_ok();
219 if is_readable {
220 let cached_api_response = boxed_read.unwrap();
221 parse_api_call_result(cached_api_response, app_id)
222 } else {
223 Err("Cached resource not readable. Consider use get call to retrieve data from steam api".to_string())
224 }
225
226}
227
228pub fn make_api_call(app_id: i64) -> Result<String, String> {
229 let url = get_api_url(app_id);
230
231 let boxed_response = minreq::get(url).send();
232 if boxed_response.is_err() {
233 return Err("Operation timed out (API call)".to_string());
234 }
235
236 let raw_response : Vec<u8> = boxed_response.unwrap().into_bytes();
237
238 let response_string_boxed = String::from_utf8(raw_response);
239 if response_string_boxed.is_err() {
240 let error_message = response_string_boxed.err().unwrap().to_string();
241 if error_message == "invalid utf-8 sequence of 1 bytes from index 1" {
242 return Err("no response from API".to_string());
243 }
244 return Err("invalid utf-8 sequence".to_string());
245 }
246 let response_string: String = response_string_boxed.unwrap();
247
248 Ok(response_string)
249}
250
251pub fn get_api_url(app_id: i64) -> String {
252 let api_url = format!("https://store.steampowered.com/api/appdetails?appids={}&lang=en", app_id);
253 api_url
254}
255
256pub fn get_resource_filepath(app_id: i64) -> String {
257 let cache_dir = get_cache_dir_path(app_id);
258 let filepath = [
259 cache_dir,
260 app_id.to_string(),
261 ".".to_string(),
262 get_json_filetype(),
263 ].join("");
264 filepath
265}
266
267
268pub fn parse_api_call_result(response_string: String, app_id: i64) -> Result<SteamAppDetails, String> {
269 let mut steam_app_details = SteamAppDetails {
270 app_id: app_id,
271 name: "".to_string(),
272 app_type: "".to_string(),
273 supported_languages: "".to_string(),
274 support_info: SupportInfo {
275 url: "".to_string(),
276 email: "".to_string()
277 },
278 short_description: "".to_string(),
279 screenshots: vec![],
280 detailed_description: "".to_string(),
281 reviews: "".to_string(),
282 header_image: "".to_string(),
283 demos: vec![],
284 controller_support: "".to_string(),
285 content_descriptors: ContentDescriptors { notes: "".to_string() },
286 categories: vec![],
287 website: "".to_string(),
288 background_raw: "".to_string(),
289 background: "".to_string(),
290 alternate_appid: "".to_string(),
291 required_age: 0,
292 release_date: ReleaseDate {
293 date: "".to_string(),
294 coming_soon: false
295 },
296 recommendations: Recommendations {
297 total: 0
298 },
299 price_overview: PriceOverview {
300 recurring_sub_desc: "".to_string(),
301 recurring_sub: 0,
302 initial_formatted: "".to_string(),
303 initial: 0,
304 final_formatted: "".to_string(),
305 final_price: 0,
306 discount_percent: 0,
307 currency: "".to_string()
308 },
309 platforms: Platforms {
310 windows: false,
311 mac: false,
312 linux: false
313 },
314 pc_requirements: PcRequirements {
315 recommended: "".to_string(),
316 minimum: "".to_string()
317 },
318 mac_requirements: MacRequirements {
319 recommended: "".to_string(),
320 minimum: "".to_string()
321 },
322 linux_requirements: LinuxRequirements {
323 recommended: "".to_string(),
324 minimum: "".to_string()
325 },
326 package_groups: vec![],
327 movies: vec![],
328 metacritic: Metacritic {
329 url: "".to_string(),
330 score: 0
331 },
332 legal_notice: "".to_string(),
333 is_free: false,
334 genres: vec![],
335 fullgame: FullGame {
336 name: "".to_string(),
337 appid: "".to_string()
338 },
339 ext_user_account_notice: "".to_string(),
340 drm_notice: "".to_string(),
341 about_the_game: "".to_string(),
342 achievements: Achievement {
343 total: 0, highlighted:
344 vec![]
345 },
346 };
347
348 if response_string.len() > 0 {
349 let boxed_initial_parse = serde_json::from_str(&response_string);
350 if boxed_initial_parse.is_err() {
351 return Err(boxed_initial_parse.err().unwrap().to_string());
352 }
353 let mut json: Value = boxed_initial_parse.unwrap();
354
355 let mut app_details_wrapped = json[app_id.to_string()].take();
356
357 let mut is_success = app_details_wrapped["success".to_string()].take();
358 if is_success.take().as_bool().unwrap() == false {
359 return Err("steampowered api returned failed response".to_string());
360 }
361
362 let mut app_details : Value = app_details_wrapped["data"].take();
363
364 let boxed_website = app_details["website"].take();
365 if boxed_website.as_str().is_some() {
366 steam_app_details.website = boxed_website.as_str().unwrap().to_string();
367 }
368
369 let boxed_type = app_details["type"].take();
370 if boxed_type.as_str().is_some() {
371 steam_app_details.app_type = boxed_type.as_str().unwrap().to_string();
372 }
373
374 let boxed_supported_languages = app_details["supported_languages"].take();
375 if boxed_supported_languages.as_str().is_some() {
376 steam_app_details.supported_languages = boxed_supported_languages.as_str().unwrap().to_string();
377 }
378
379 let boxed_support_info = app_details["support_info"].take();
380 if boxed_support_info.as_object().is_some() {
381 let support_info = boxed_support_info.as_object().unwrap();
382
383 let url = support_info.get("url").unwrap().as_str().unwrap();
384 let email = support_info.get("email").unwrap().as_str().unwrap();
385
386 let support_info = SupportInfo {
387 url: url.to_string(),
388 email: email.to_string(),
389 };
390
391 steam_app_details.support_info = support_info;
392 }
393
394
395 let boxed_price_overview = app_details["price_overview"].take();
396 if boxed_price_overview.as_object().is_some() {
397 let price_overview_map = boxed_price_overview.as_object().unwrap();
398
399
400 let mut price_overview = PriceOverview {
401 recurring_sub_desc: "".to_string(),
402 recurring_sub: 0,
403 initial_formatted: "".to_string(),
404 initial: 0,
405 final_formatted: "".to_string(),
406 final_price: 0,
407 discount_percent: 0,
408 currency: "".to_string()
409 };
410
411
412 let boxed_recurring_sub_desc = price_overview_map.get("recurring_sub_desc");
413 if boxed_recurring_sub_desc.is_some() {
414 price_overview.recurring_sub_desc = boxed_recurring_sub_desc.unwrap().as_str().unwrap().to_string();
415 }
416
417
418 let boxed_initial_formatted = price_overview_map.get("initial_formatted");
419 if boxed_initial_formatted.is_some() {
420 price_overview.initial_formatted = boxed_initial_formatted.unwrap().as_str().unwrap().to_string();
421 }
422
423
424 let boxed_final_formatted = price_overview_map.get("final_formatted");
425 if boxed_initial_formatted.is_some() {
426 price_overview.final_formatted = boxed_final_formatted.unwrap().as_str().unwrap().to_string();
427 }
428
429
430 let boxed_recurring_sub = price_overview_map.get("recurring_sub");
431 if boxed_recurring_sub.is_some() {
432 price_overview.recurring_sub = boxed_recurring_sub.unwrap().as_i64().unwrap();
433 }
434
435
436 let boxed_initial = price_overview_map.get("initial");
437 if boxed_initial.is_some() {
438 price_overview.initial = boxed_initial.unwrap().as_i64().unwrap();
439 }
440
441
442 let boxed_final_price = price_overview_map.get("final");
443 if boxed_final_price.is_some() {
444 price_overview.final_price = boxed_final_price.unwrap().as_i64().unwrap();
445 }
446
447 let boxed_discount_percent = price_overview_map.get("discount_percent");
448 if boxed_discount_percent.is_some() {
449 price_overview.discount_percent = boxed_discount_percent.unwrap().as_i64().unwrap();
450 }
451
452
453 let boxed_currency = price_overview_map.get("currency");
454 if boxed_currency.is_some() {
455 price_overview.currency = boxed_currency.unwrap().as_str().unwrap().to_string();
456 }
457
458 steam_app_details.price_overview = price_overview;
459 }
460
461 let boxed_platforms = app_details["platforms"].take();
462 if boxed_platforms.as_object().is_some() {
463 let platforms_map = boxed_platforms.as_object().unwrap();
464
465 let mut platforms = Platforms {
466 windows: false,
467 mac: false,
468 linux: false
469 };
470
471 let boxed_windows = platforms_map.get("windows");
472 if boxed_windows.is_some() {
473 platforms.windows = boxed_windows.unwrap().as_bool().unwrap();
474 }
475
476 let boxed_mac = platforms_map.get("mac");
477 if boxed_mac.is_some() {
478 platforms.mac = boxed_mac.unwrap().as_bool().unwrap();
479 }
480
481 let boxed_linux = platforms_map.get("linux");
482 if boxed_linux.is_some() {
483 platforms.linux = boxed_linux.unwrap().as_bool().unwrap();
484 }
485
486
487 steam_app_details.platforms = platforms;
488 }
489
490 let boxed_pc_requirements = app_details["pc_requirements"].take();
491 if boxed_pc_requirements.as_object().is_some() {
492 let pc_requirements_map = boxed_pc_requirements.as_object().unwrap();
493
494 let mut pc_requirements = PcRequirements {
495 recommended: "".to_string(),
496 minimum: "".to_string()
497 };
498
499 let boxed_minimum = pc_requirements_map.get("minimum");
500 if boxed_minimum.is_some() {
501 pc_requirements.minimum = boxed_minimum.unwrap().as_str().unwrap().to_string();
502 }
503
504
505 let boxed_recommended = pc_requirements_map.get("recommended");
506 if boxed_recommended.is_some() {
507 pc_requirements.recommended = boxed_recommended.unwrap().as_str().unwrap().to_string();
508 }
509
510
511 steam_app_details.pc_requirements = pc_requirements;
512 }
513
514
515 let boxed_mac_requirements = app_details["mac_requirements"].take();
516 if boxed_mac_requirements.as_object().is_some() {
517 let mac_requirements_map = boxed_mac_requirements.as_object().unwrap();
518
519 let mut mac_requirements = MacRequirements {
520 recommended: "".to_string(),
521 minimum: "".to_string()
522 };
523
524 let boxed_minimum = mac_requirements_map.get("minimum");
525 if boxed_minimum.is_some() {
526 mac_requirements.minimum = boxed_minimum.unwrap().as_str().unwrap().to_string();
527 }
528
529
530 let boxed_recommended = mac_requirements_map.get("recommended");
531 if boxed_recommended.is_some() {
532 mac_requirements.recommended = boxed_recommended.unwrap().as_str().unwrap().to_string();
533 }
534
535
536 steam_app_details.mac_requirements = mac_requirements;
537 }
538
539 let boxed_linux_requirements = app_details["linux_requirements"].take();
540 if boxed_linux_requirements.as_object().is_some() {
541 let linux_requirements_map = boxed_linux_requirements.as_object().unwrap();
542
543 let mut linux_requirements = LinuxRequirements {
544 recommended: "".to_string(),
545 minimum: "".to_string()
546 };
547
548 let boxed_minimum = linux_requirements_map.get("minimum");
549 if boxed_minimum.is_some() {
550 linux_requirements.minimum = boxed_minimum.unwrap().as_str().unwrap().to_string();
551 }
552
553
554 let boxed_recommended = linux_requirements_map.get("recommended");
555 if boxed_recommended.is_some() {
556 linux_requirements.recommended = boxed_recommended.unwrap().as_str().unwrap().to_string();
557 }
558
559
560 steam_app_details.linux_requirements = linux_requirements;
561 }
562
563
564 let boxed_recommendations = app_details["recommendations"].take();
565 if boxed_recommendations.as_object().is_some() {
566 let recommendations_map = boxed_recommendations.as_object().unwrap();
567
568 let total = recommendations_map.get("total").unwrap().as_i64().unwrap();
569
570 let recommendations = Recommendations {
571 total
572 };
573
574 steam_app_details.recommendations = recommendations;
575 }
576
577 let boxed_release_date = app_details["release_date"].take();
578 if boxed_release_date.as_object().is_some() {
579 let release_date_map = boxed_release_date.as_object().unwrap();
580
581 let date = release_date_map.get("date").unwrap().as_str().unwrap();
582 let coming_soon = release_date_map.get("coming_soon").unwrap().as_bool().unwrap();
583
584 let release_date = ReleaseDate {
585 date: date.to_string(),
586 coming_soon,
587 };
588
589 steam_app_details.release_date = release_date;
590 }
591
592 let boxed_required_age = app_details["required_age"].take();
593 if boxed_required_age.as_str().is_some() {
594 let boxed_parse = boxed_required_age.as_str().unwrap().parse();
595 if boxed_parse.is_ok() {
596 steam_app_details.required_age = boxed_parse.unwrap();
597 }
598 }
599 if boxed_required_age.as_i64().is_some() {
600 steam_app_details.required_age = boxed_required_age.as_i64().unwrap();
601 }
602
603 let boxed_short_description = app_details["short_description"].take();
604 if boxed_short_description.as_str().is_some() {
605 steam_app_details.short_description = boxed_short_description.as_str().unwrap().to_string();
606 }
607
608 let boxed_screenshots = app_details["screenshots"].take();
609 if boxed_screenshots.as_array().is_some() {
610 let mut screenshoot_parsed_list : Vec<Screenshot> = vec![];
611
612 let screenshots_list = boxed_screenshots.as_array().unwrap();
613 for screenshot_val in screenshots_list {
614 let mut screenshot = Screenshot {
615 path_thumbnail: "".to_string(),
616 path_full: "".to_string(),
617 id: 0
618 };
619 screenshot.path_thumbnail = screenshot_val["path_thumbnail"].as_str().unwrap().to_string();
620 screenshot.path_full = screenshot_val["path_full"].as_str().unwrap().to_string();
621 screenshot.id = screenshot_val["id"].as_i64().unwrap();
622
623 screenshoot_parsed_list.push(screenshot);
624 }
625 steam_app_details.screenshots = screenshoot_parsed_list;
626 }
627
628 let boxed_reviews = app_details["reviews"].take();
629 if boxed_reviews.as_str().is_some() {
630 steam_app_details.reviews = boxed_reviews.as_str().unwrap().to_string();
631 }
632
633 let boxed_name = app_details["name"].take();
634 if boxed_name.as_str().is_some() {
635 steam_app_details.name = boxed_name.as_str().unwrap().to_string();
636 }
637
638 let boxed_header_image = app_details["header_image"].take();
639 if boxed_header_image.as_str().is_some() {
640 steam_app_details.header_image = boxed_header_image.as_str().unwrap().to_string();
641 }
642
643 let boxed_detailed_description = app_details["detailed_description"].take();
644 if boxed_detailed_description.as_str().is_some() {
645 steam_app_details.detailed_description = boxed_detailed_description.as_str().unwrap().to_string();
646 }
647
648 let boxed_package_groups = app_details["package_groups"].take();
649 steam_app_details.package_groups = parse_package_groups(boxed_package_groups);
650
651 let boxed_movies = app_details["movies"].take();
652 steam_app_details.movies = parse_movies(boxed_movies);
653
654 let boxed_metacritic = app_details["metacritic"].take();
655 if boxed_metacritic.as_object().is_some() {
656 let metacritic_map = boxed_metacritic.as_object().unwrap();
657 let mut metacritic = Metacritic {
658 url: "".to_string(),
659 score: 0
660 };
661
662 let boxed_url = metacritic_map.get("url");
663 if boxed_url.is_some() {
664 metacritic.url = boxed_url.unwrap().as_str().unwrap().to_string();
665 }
666
667 let boxed_score = metacritic_map.get("score");
668 if boxed_score.is_some() {
669 metacritic.score = boxed_score.unwrap().as_i64().unwrap();
670 }
671
672 steam_app_details.metacritic = metacritic;
673 }
674
675 let boxed_legal_notice = app_details["legal_notice"].take();
676 if boxed_legal_notice.as_str().is_some() {
677 steam_app_details.legal_notice = boxed_legal_notice.as_str().unwrap().to_string();
678 }
679
680 let boxed_is_free = app_details["is_free"].take();
681 if boxed_is_free.as_bool().is_some() {
682 steam_app_details.is_free = boxed_is_free.as_bool().unwrap();
683 }
684
685 let boxed_genres = app_details["genres"].take();
686 if boxed_genres.as_array().is_some() {
687 let mut genre_list: Vec<Genre> = vec![];
688 let genre_list_value = boxed_genres.as_array().unwrap();
689 for genre_item in genre_list_value {
690 let mut genre = Genre {
691 id: "".to_string(),
692 description: "".to_string()
693 };
694
695 let boxed_id = genre_item.get("id");
696 if boxed_id.is_some() {
697 genre.id = boxed_id.unwrap().as_str().unwrap().to_string();
698 }
699
700 let boxed_description = genre_item.get("description");
701 if boxed_description.is_some() {
702 genre.description = boxed_description.unwrap().as_str().unwrap().to_string();
703 }
704
705 genre_list.push(genre);
706 }
707 steam_app_details.genres = genre_list;
708 }
709
710 let boxed_fullgame = app_details["fullgame"].take();
711 if boxed_fullgame.as_object().is_some() {
712 let fullgame_item = boxed_fullgame.as_object().unwrap();
713
714 let mut fullgame = FullGame{
715 name: "".to_string(),
716 appid: "".to_string()
717 };
718
719 let boxed_name = fullgame_item.get("name");
720 if boxed_name.is_some() {
721 fullgame.name = boxed_name.unwrap().as_str().unwrap().to_string();
722 }
723
724 let boxed_appid = fullgame_item.get("appid");
725 if boxed_appid.is_some() {
726 fullgame.appid = boxed_appid.unwrap().as_str().unwrap().to_string();
727 }
728
729 steam_app_details.fullgame = fullgame;
730 }
731
732 let boxed_ext_user_account_notice = app_details["ext_user_account_notice"].take();
733 if boxed_ext_user_account_notice.as_str().is_some() {
734 steam_app_details.ext_user_account_notice = boxed_ext_user_account_notice.as_str().unwrap().to_string();
735 }
736
737 let boxed_drm_notice = app_details["drm_notice"].take();
738 if boxed_drm_notice.as_str().is_some() {
739 steam_app_details.drm_notice = boxed_drm_notice.as_str().unwrap().to_string();
740 }
741
742 let boxed_demos = app_details["demos"].take();
743 if boxed_demos.as_array().is_some() {
744 let mut demo_list: Vec<Demo> = vec![];
745
746 let demos_json_array = boxed_demos.as_array().unwrap();
747 for demo_json_item in demos_json_array {
748 let mut demo = Demo {
749 description: "".to_string(),
750 appid: 0,
751 };
752
753 let boxed_description = demo_json_item.get("description");
754 if boxed_description.is_some() {
755 demo.description = boxed_description.unwrap().as_str().unwrap().to_string();
756 }
757
758 let boxed_appid = demo_json_item.get("appid");
759 if boxed_appid.is_some() {
760 demo.appid = boxed_appid.unwrap().as_i64().unwrap();
761 }
762
763 demo_list.push(demo);
764 }
765 steam_app_details.demos = demo_list;
766
767 }
768
769 let boxed_controller_support = app_details["controller_support"].take();
770 if boxed_controller_support.as_str().is_some() {
771 steam_app_details.controller_support = boxed_controller_support.as_str().unwrap().to_string();
772 }
773
774 let boxed_content_descriptors = app_details["content_descriptors"].take();
775 if boxed_content_descriptors.as_object().is_some() {
776 let content_descriptors_json = boxed_content_descriptors.as_object().unwrap();
777
778 let mut content_descriptors = ContentDescriptors{ notes: "".to_string() };
779
780 let boxed_notes = content_descriptors_json.get("notes");
781 if boxed_notes.is_some() {
782 let unwrapped_notes = boxed_notes.unwrap();
783 if unwrapped_notes.as_str().is_some() {
784 content_descriptors.notes = unwrapped_notes.as_str().unwrap().to_string();
785 }
786 }
787 steam_app_details.content_descriptors = content_descriptors;
788 }
789
790 let boxed_categories = app_details["categories"].take();
791 if boxed_categories.as_array().is_some() {
792 let mut category_list : Vec<Category> = vec![];
793 let category_json_list = boxed_categories.as_array().unwrap();
794 for category_item in category_json_list {
795 let mut category = Category { id: 0, description: "".to_string() };
796
797 let boxed_id = category_item.get("id");
798 if boxed_id.is_some() {
799 category.id = boxed_id.unwrap().as_i64().unwrap();
800 }
801
802 let boxed_description = category_item.get("description");
803 if boxed_description.is_some() {
804 category.description = boxed_description.unwrap().as_str().unwrap().to_string();
805 }
806 category_list.push(category);
807 }
808 steam_app_details.categories = category_list;
809 }
810
811 let boxed_background_raw = app_details["background_raw"].take();
812 if boxed_background_raw.as_str().is_some() {
813 steam_app_details.background_raw = boxed_background_raw.as_str().unwrap().to_string();
814 }
815
816 let boxed_background = app_details["background"].take();
817 if boxed_background.as_str().is_some() {
818 steam_app_details.background = boxed_background.as_str().unwrap().to_string();
819 }
820
821 let boxed_alternate_appid = app_details["alternate_appid"].take();
822 if boxed_alternate_appid.as_str().is_some() {
823 steam_app_details.alternate_appid = boxed_alternate_appid.as_str().unwrap().to_string();
824 }
825
826 let boxed_about_the_game = app_details["about_the_game"].take();
827 if boxed_about_the_game.as_str().is_some() {
828 steam_app_details.about_the_game = boxed_about_the_game.as_str().unwrap().to_string();
829 }
830
831 let boxed_achievements = app_details["achievements"].take();
832 steam_app_details.achievements = parse_achievements(boxed_achievements);
833
834 }
835
836 let filepath = get_resource_filepath(app_id);
837
838 let mut file: File;
839 let directory_exists = Path::new(get_cache_dir_path(app_id).as_str()).is_dir();
840 if !directory_exists {
841 fs::create_dir_all(get_cache_dir_path(app_id)).unwrap();
842 file = File::create(filepath).unwrap();
843 } else {
844 file = File::create(filepath).unwrap();
845 }
846
847 file.write_all(response_string.as_ref()).unwrap();
848
849 Ok(steam_app_details)
850}
851
852pub fn parse_achievements(boxed_achievements: Value) -> Achievement {
853 let mut achievement = Achievement{ total: 0, highlighted: vec![] };
854
855 if boxed_achievements.as_object().is_some() {
856 let achievements_json = boxed_achievements.as_object().unwrap();
857
858 let boxed_total = achievements_json.get("total");
859 if boxed_total.is_some() {
860 achievement.total = boxed_total.unwrap().as_i64().unwrap();
861 }
862
863 let boxed_highlighted = achievements_json.get("highlighted");
864 if boxed_highlighted.is_some() {
865 let boxed_highlighted_json_array = boxed_highlighted.unwrap().as_array();
866 if boxed_highlighted_json_array.is_some() {
867 let highlighted_json_array = boxed_highlighted_json_array.unwrap();
868
869 let mut highlighted_list: Vec<Highlight> = vec![];
870 for highlighted_item in highlighted_json_array {
871 let mut highlight = Highlight { path: "".to_string(), name: "".to_string() };
872
873 let boxed_path = highlighted_item.get("path");
874 if boxed_path.is_some() {
875 highlight.path = boxed_path.unwrap().as_str().unwrap().to_string();
876 }
877
878 let boxed_highlight_name = highlighted_item.get("name");
879 if boxed_highlight_name.is_some() {
880 highlight.name = boxed_highlight_name.unwrap().as_str().unwrap().to_string();
881 }
882
883 highlighted_list.push(highlight);
884 }
885 achievement.highlighted = highlighted_list;
886 }
887 }
888 }
889 achievement
890}
891
892pub fn parse_movies(boxed_movies: Value) -> Vec<Movie> {
893 fn parse_webm(boxed_webm: Option<&Value>) -> Webm {
894 let mut webm = Webm {
895 max: "".to_string(),
896 dash: "".to_string(),
897 _480: "".to_string()
898 };
899
900 if boxed_webm.is_some() {
901 let webm_item = boxed_webm.unwrap();
902
903 let boxed_webm_max = webm_item.get("max");
904 if boxed_webm_max.is_some() {
905 webm.max = boxed_webm_max.unwrap().as_str().unwrap().to_string();
906 }
907
908 let boxed_webm_dash = webm_item.get("dash");
909 if boxed_webm_dash.is_some() {
910 webm.dash = boxed_webm_dash.unwrap().as_str().unwrap().to_string();
911 }
912
913 let boxed_webm_480 = webm_item.get("480");
914 if boxed_webm_480.is_some() {
915 webm._480 = boxed_webm_480.unwrap().as_str().unwrap().to_string();
916 }
917
918 }
919
920 webm
921 }
922
923 fn parse_mp4(boxed_mp4: Option<&Value>) -> Mp4 {
924 let mut mp4 = Mp4 {
925 max: "".to_string(),
926 _480: "".to_string()
927 };
928
929 if boxed_mp4.is_some() {
930 let webm_item = boxed_mp4.unwrap();
931
932 let boxed_webm_max = webm_item.get("max");
933 if boxed_webm_max.is_some() {
934 mp4.max = boxed_webm_max.unwrap().as_str().unwrap().to_string();
935 }
936
937
938 let boxed_webm_480 = webm_item.get("480");
939 if boxed_webm_480.is_some() {
940 mp4._480 = boxed_webm_480.unwrap().as_str().unwrap().to_string();
941 }
942
943 }
944
945 mp4
946 }
947
948
949 let mut movie_list: Vec<Movie> = vec![];
950
951 if boxed_movies.as_array().is_some() {
952 let movies_list_as_json_array = boxed_movies.as_array().unwrap();
953
954 for movie_item in movies_list_as_json_array {
955 let mut movie = Movie{
956 thumbnail: "".to_string(),
957 name: "".to_string(),
958 id: 0,
959 highlight: false,
960 webm: Webm {
961 max: "".to_string(),
962 dash: "".to_string(),
963 _480: "".to_string()
964 },
965 mp4: Mp4 {
966 max: "".to_string(),
967 _480: "".to_string() }
968 };
969
970 let boxed_thumbnail = movie_item.get("thumbnail");
971 if boxed_thumbnail.is_some() {
972 movie.thumbnail = boxed_thumbnail.unwrap().as_str().unwrap().to_string();
973 }
974
975 let boxed_name = movie_item.get("name");
976 if boxed_name.is_some() {
977 movie.name = boxed_name.unwrap().as_str().unwrap().to_string();
978 }
979
980 let boxed_id = movie_item.get("id");
981 if boxed_id.is_some() {
982 movie.id = boxed_id.unwrap().as_i64().unwrap();
983 }
984
985 let boxed_highlight = movie_item.get("highlight");
986 if boxed_highlight.is_some() {
987 movie.highlight = boxed_highlight.unwrap().as_bool().unwrap();
988 }
989
990 let boxed_webm = movie_item.get("webm");
991 movie.webm = parse_webm(boxed_webm);
992
993 let boxed_mp4 = movie_item.get("mp4");
994 movie.mp4 = parse_mp4(boxed_mp4);
995
996 movie_list.push(movie);
997 }
998
999 }
1000
1001 movie_list
1002}
1003
1004pub fn parse_package_groups(boxed_package_groups: Value) -> Vec<PackageGroup> {
1005 let mut package_group_list: Vec<PackageGroup> = vec![];
1006
1007 if boxed_package_groups.as_array().is_some() {
1008 let package_groups = boxed_package_groups.as_array().unwrap();
1009 let mut package_group = PackageGroup {
1010 title: "".to_string(),
1011 selection_text: "".to_string(),
1012 save_text: "".to_string(),
1013 name: "".to_string(),
1014 is_recurring_subscription: "".to_string(),
1015 display_type: "".to_string(),
1016 description: "".to_string(),
1017 subs: vec![]
1018 };
1019
1020 for package_group_map in package_groups {
1021 let boxed_title = package_group_map.get("title");
1022 if boxed_title.is_some() {
1023 package_group.title = boxed_title.unwrap().as_str().unwrap().to_string();
1024 }
1025
1026 let boxed_selection_text = package_group_map.get("selection_text");
1027 if boxed_selection_text.is_some() {
1028 package_group.selection_text = boxed_selection_text.unwrap().as_str().unwrap().to_string();
1029 }
1030
1031 let boxed_name = package_group_map.get("name");
1032 if boxed_name.is_some() {
1033 package_group.name = boxed_name.unwrap().as_str().unwrap().to_string();
1034 }
1035
1036 let boxed_save_text = package_group_map.get("save_text");
1037 if boxed_save_text.is_some() {
1038 package_group.save_text = boxed_save_text.unwrap().as_str().unwrap().to_string();
1039 }
1040
1041 let boxed_is_recurring_subscription = package_group_map.get("is_recurring_subscription");
1042 if boxed_is_recurring_subscription.is_some() {
1043 package_group.is_recurring_subscription = boxed_is_recurring_subscription.unwrap().as_str().unwrap().to_string();
1044 }
1045
1046 let boxed_display_type = package_group_map.get("display_type");
1047 if boxed_display_type.is_some() {
1048 let display_type_as_str = boxed_display_type.unwrap().as_str();
1049 if display_type_as_str.is_some() {
1050 package_group.display_type = display_type_as_str.unwrap().to_string();
1051 }
1052
1053 let display_type_as_i64 = boxed_display_type.unwrap().as_i64();
1054 if display_type_as_i64.is_some() {
1055 package_group.display_type = display_type_as_i64.unwrap().to_string();
1056 }
1057 }
1058
1059 let boxed_description = package_group_map.get("description");
1060 if boxed_description.is_some() {
1061 package_group.description = boxed_description.unwrap().as_str().unwrap().to_string();
1062 }
1063
1064 package_group.subs = parse_package_groups_subs(&package_group_map);
1065 }
1066
1067 package_group_list.push(package_group);
1068 }
1069
1070 package_group_list
1071}
1072
1073pub fn parse_package_groups_subs(package_group: &Value) -> Vec<Sub> {
1074 let mut sub_list: Vec<Sub> = vec![];
1075
1076 let sub_as_array = package_group.get("subs").take();
1077 if sub_as_array.is_some() {
1078
1079 let sub_list_value = sub_as_array.unwrap().as_array().unwrap();
1080
1081 for sub_item in sub_list_value {
1082
1083 let mut sub = Sub {
1084 price_in_cents_with_discount: 0,
1085 percent_savings_text: "".to_string(),
1086 percent_savings: 0,
1087 packageid: 0,
1088 option_text: "".to_string(),
1089 option_description: "".to_string(),
1090 is_free_license: false,
1091 can_get_free_license: "".to_string()
1092 };
1093
1094 let boxed_price_in_cents_with_discount = sub_item.get("price_in_cents_with_discount");
1095 if boxed_price_in_cents_with_discount.is_some() {
1096 sub.price_in_cents_with_discount = boxed_price_in_cents_with_discount.unwrap().as_i64().unwrap();
1097 }
1098
1099 let boxed_packageid = sub_item.get("packageid");
1100 if boxed_packageid.is_some() {
1101 sub.packageid = boxed_packageid.unwrap().as_i64().unwrap();
1102 }
1103 sub_list.push(sub);
1104 }
1105 }
1106
1107 sub_list
1108}
1109
1110pub fn get_cache_dir_path(app_id: i64) -> String {
1111 let interface = "steampowered";
1112 let method = "appdetails";
1113 let number_of_entries_per_bucket = 10000;
1114 let bucket = app_id / number_of_entries_per_bucket;
1115
1116 [
1117 "steam-webapi-cache".to_string(),
1118 "/".to_string(),
1119 interface.to_string(),
1120 "/".to_string(),
1121 method.to_string(),
1122 "/".to_string(),
1123 bucket.to_string(),
1124 "/".to_string(),
1125 app_id.to_string(),
1126 "/".to_string()
1127 ].join("")
1128}