From b4dbcdd29c7f07e0ef435cddd8e2017dda0d9e10 Mon Sep 17 00:00:00 2001 From: Philip Kristoffersen Date: Mon, 21 Mar 2022 21:34:27 +0100 Subject: [PATCH] Download official steam images as backup (#53) --- src/steamgriddb/downloader.rs | 203 ++++++++++++++++++++++++---------- src/steamgriddb/image_type.rs | 11 ++ 2 files changed, 157 insertions(+), 57 deletions(-) diff --git a/src/steamgriddb/downloader.rs b/src/steamgriddb/downloader.rs index 985f3bb..1b90e4c 100644 --- a/src/steamgriddb/downloader.rs +++ b/src/steamgriddb/downloader.rs @@ -4,6 +4,7 @@ use std::path::PathBuf; use std::{collections::HashMap, path::Path}; use futures::{stream, StreamExt}; +use serde::{Deserialize, Serialize}; use std::error::Error; use steamgriddb_api::query_parameters::GridDimentions; // 0.3.1 @@ -74,6 +75,33 @@ pub async fn download_images_for_users<'b>( } } +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct PublicGameResponseMetadata { + store_asset_mtime: Option, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct PublicGameResponseSteam { + id: String, + metadata: Option, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct PublicGameResponsePlatforms { + steam: Option, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct PublicGameResponseData { + platforms: Option, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct PublicGameResponse { + success: bool, + data: Option, +} + async fn search_fo_to_download( known_images: Vec, user_data_folder: &str, @@ -124,7 +152,7 @@ async fn search_fo_to_download( ]; let mut to_download = vec![]; for image_type in types { - let mut images_needed = shortcuts + let images_needed = shortcuts .iter() .filter(|s| search_results.contains_key(&s.app_id)) .filter(|s| !known_images.contains(&image_type.file_name(s.app_id))); @@ -133,64 +161,41 @@ async fn search_fo_to_download( .filter_map(|s| search_results.get(&s.app_id)) .copied() .collect(); - use steamgriddb_api::query_parameters::AnimtionType; - use steamgriddb_api::query_parameters::QueryType::*; - let anymation_type = if download_animated { - Some(&[AnimtionType::Animated][..]) - } else { - None - }; - let big_picture_dims = [GridDimentions::D920x430, GridDimentions::D460x215]; - use steamgriddb_api::query_parameters::GridQueryParameters; - let big_picture_parameters = GridQueryParameters { - dimentions: Some(&big_picture_dims), - types: anymation_type, - ..Default::default() - }; - - use steamgriddb_api::query_parameters::HeroQueryParameters; - let hero_parameters = HeroQueryParameters { - types: anymation_type, - ..Default::default() - }; - - let grid_parameters = GridQueryParameters { - types: anymation_type, - ..Default::default() - }; - - use steamgriddb_api::query_parameters::LogoQueryParameters; - let logo_parameters = LogoQueryParameters { - types: anymation_type, - ..Default::default() - }; - - let query_type = match image_type { - ImageType::Hero => Hero(Some(hero_parameters)), - ImageType::BigPicture => Grid(Some(big_picture_parameters)), - ImageType::Grid => Grid(Some(grid_parameters)), - ImageType::WideGrid => Grid(Some(big_picture_parameters)), - ImageType::Logo => Logo(Some(logo_parameters)), - }; - - match client - .get_images_for_ids(image_ids.as_slice(), &query_type) - .await - { + let shortcuts: Vec<&ShortcutOwned> = images_needed.collect(); + let image_search_result = + get_images_for_ids(client, &image_ids, &image_type, download_animated).await; + match image_search_result { Ok(images) => { - for image in images { - if let Some(shortcut) = images_needed.next() { - if let Ok(image) = image { - let grid_folder = Path::new(user_data_folder).join("config/grid"); - let path = grid_folder.join(image_type.file_name(shortcut.app_id)); - to_download.push(ToDownload { - path, - url: image.url, - }); + let grid_folder = Path::new(user_data_folder).join("config").join("grid"); + let images = images + .iter() + .enumerate() + .map(|(index, image)| (image, shortcuts[index], image_ids[index])); + let download_for_this_type = stream::iter(images) + .filter_map(|(image, shortcut, game_id)| { + let path = grid_folder.join(image_type.file_name(shortcut.app_id)); + async move { + let image_url = match image { + Ok(img) => Some(img.url.clone()), + Err(_) => get_steam_image_url(game_id, &image_type).await, + }; + if let Some(url) = image_url { + Some(ToDownload { + path, + url, + app_name: shortcut.app_name.clone(), + image_type: image_type.clone(), + }) + } else { + None + } } - } - } + }) + .collect::>() + .await; + + to_download.extend(download_for_this_type); } Err(err) => println!("Error getting images: {}", err), } @@ -198,8 +203,90 @@ async fn search_fo_to_download( Ok(to_download) } +async fn get_images_for_ids( + client: &Client, + image_ids: &[usize], + image_type: &ImageType, + download_animated: bool, +) -> Result< + Vec>, + Box, +> { + use steamgriddb_api::query_parameters::AnimtionType; + use steamgriddb_api::query_parameters::QueryType::*; + let anymation_type = if download_animated { + Some(&[AnimtionType::Animated][..]) + } else { + None + }; + let big_picture_dims = [GridDimentions::D920x430, GridDimentions::D460x215]; + use steamgriddb_api::query_parameters::GridQueryParameters; + let big_picture_parameters = GridQueryParameters { + dimentions: Some(&big_picture_dims), + types: anymation_type, + ..Default::default() + }; + use steamgriddb_api::query_parameters::HeroQueryParameters; + let hero_parameters = HeroQueryParameters { + types: anymation_type, + ..Default::default() + }; + let grid_parameters = GridQueryParameters { + types: anymation_type, + ..Default::default() + }; + use steamgriddb_api::query_parameters::LogoQueryParameters; + let logo_parameters = LogoQueryParameters { + types: anymation_type, + ..Default::default() + }; + let query_type = match image_type { + ImageType::Hero => Hero(Some(hero_parameters)), + ImageType::BigPicture => Grid(Some(big_picture_parameters)), + ImageType::Grid => Grid(Some(grid_parameters)), + ImageType::WideGrid => Grid(Some(big_picture_parameters)), + ImageType::Logo => Logo(Some(logo_parameters)), + }; + + let image_search_result = client.get_images_for_ids(image_ids, &query_type).await; + image_search_result +} + +async fn get_steam_image_url(game_id: usize, image_type: &ImageType) -> Option { + let steamgriddb_page_url = format!("https://www.steamgriddb.com/api/public/game/{}/", game_id); + let response = reqwest::get(steamgriddb_page_url).await; + match response { + Ok(response) => { + let text_response = response.json::().await; + match text_response { + Ok(response) => { + let game_id = response + .data + .clone() + .map(|d| d.platforms.map(|p| p.steam.map(|s| s.id))); + let mtime = response.data.map(|d| { + d.platforms + .map(|p| p.steam.map(|s| s.metadata.map(|m| m.store_asset_mtime))) + }); + if let (Some(Some(Some(steam_app_id))), Some(Some(Some(Some(Some(mtime)))))) = + (game_id, mtime) + { + return Some(image_type.steam_url(steam_app_id, mtime)); + } + } + Err(_) => (), + } + } + Err(_) => (), + } + return None; +} + async fn download_to_download(to_download: &ToDownload) -> Result<(), Box> { - println!("Downloading {} to {:?}", to_download.url, to_download.path); + println!( + "Downloading {:?} for {} to {:?}", + to_download.image_type, to_download.app_name, to_download.path + ); let path = &to_download.path; let url = &to_download.url; let mut file = File::create(path).unwrap(); @@ -212,4 +299,6 @@ async fn download_to_download(to_download: &ToDownload) -> Result<(), Box format!("{}_bigpicture.png", app_id), } } + + pub fn steam_url>(&self,steam_app_id:S,mtime:u64) -> String{ + let steam_app_id = steam_app_id.as_ref(); + match self{ + ImageType::Hero => format!("https://cdn.cloudflare.steamstatic.com/steam/apps/{}/library_hero.jpg?t={}",steam_app_id,mtime), + ImageType::Grid => format!("https://cdn.cloudflare.steamstatic.com/steam/apps/{}/library_600x900_2x.jpg?t={}",steam_app_id,mtime), + ImageType::WideGrid => format!("https://cdn.cloudflare.steamstatic.com/steam/apps/{}/header.jpg?t={}",steam_app_id,mtime), + ImageType::Logo => format!("https://cdn.cloudflare.steamstatic.com/steam/apps/{}/logo.png?t={}",steam_app_id,mtime), + ImageType::BigPicture => format!("https://cdn.cloudflare.steamstatic.com/steam/apps/{}/header.jpg?t={}",steam_app_id,mtime), + } + } }