Skip to main content

steam_webapi_rust_sdk/store_steampowered_com/
appdetails.rs

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}