get all achievement data for games

This commit is contained in:
2025-01-23 00:25:04 +01:00
parent 0cbe665b7e
commit f643fdeafd

View File

@@ -31,37 +31,11 @@ struct JsonAchievement {
defaultvalue: i32,
display_name: String,
hidden: i32,
description: String,
description: Option<String>,
icon: String,
icongray: String,
}
#[tokio::main]
async fn main() {
let client = Client::new();
let config_home = dirs::config_dir().unwrap().to_str().unwrap().to_string();
let api_key =
fs::read_to_string(format!("{config_home}/achievement-watcher/api-key.txt")).unwrap();
let api_key = api_key.trim();
let wine_home = env::var("WINEPREFIX").unwrap_or(env::var("HOME").unwrap() + ".wine");
let dirs = vec![
wine_home.clone() + "/drive_c/users/Public/Documents/Steam/CODEX",
wine_home.clone() + "/drive_c/users/Public/Documents/Steam/RUNE",
];
let ini_games = get_achievement_data_all(dirs);
println!("ini games: {:#?}", ini_games);
let game_data = get_steam_data(&client, api_key, "2310", "en").await;
println!("game_data: {:#?}", game_data);
}
async fn get_steam_data(client: &Client, key: &str, appid: &str, lang: &str) -> JsonGame {
let url = format!("https://api.steampowered.com/ISteamUserStats/GetSchemaForGame/v0002/?key={key}&appid={appid}&l={lang}&format=json");
let json = client.get(url).send().await.unwrap().text().await.unwrap();
// fs::write("out.json", &json).unwrap();
// let json = fs::read_to_string("out.json").unwrap();
serde_json::from_str(&json).unwrap()
}
#[derive(Debug)]
struct IniAchievement {
name: String,
@@ -77,6 +51,50 @@ struct IniGame {
achievements: Vec<IniAchievement>,
}
#[derive(Debug)]
struct Game {
app_id: u32,
achievements: Vec<Achievement>,
}
#[derive(Debug)]
struct Achievement {
name: String,
description: Option<String>,
achieved: bool,
hidden: bool,
icon: String,
icongray: String,
current_progress: u32,
max_progress: u32,
unlock_time: u32,
}
#[tokio::main]
async fn main() {
let client = Client::new();
let config_dir = dirs::config_dir().unwrap().to_str().unwrap().to_owned();
let cache_dir = dirs::cache_dir().unwrap().to_str().unwrap().to_owned();
let game_name_cache_path = format!("{cache_dir}/achievement-watcher/game_names.txt");
let game_name_list = get_game_name_file_cache(&game_name_cache_path);
let api_key =
fs::read_to_string(format!("{config_dir}/achievement-watcher/api-key.txt")).unwrap();
let api_key = api_key.trim();
let wine_dir = env::var("WINEPREFIX").unwrap_or(env::var("HOME").unwrap() + ".wine");
let dirs = vec![
wine_dir.clone() + "/drive_c/users/Public/Documents/Steam/CODEX",
wine_dir.clone() + "/drive_c/users/Public/Documents/Steam/RUNE",
];
let games = get_achievement_data_all(
&client,
api_key,
&game_name_cache_path,
&game_name_list,
dirs,
).await;
println!("games: {:#?}", games);
}
impl IniGame {
fn new(id: u32) -> Self {
Self {
@@ -98,7 +116,67 @@ impl IniAchievement {
}
}
fn get_achievement_data_all(dirs: Vec<String>) -> Vec<IniGame> {
async fn get_steam_data(client: &Client, key: &str, appid: u32, lang: &str) -> JsonGame {
let url = format!("https://api.steampowered.com/ISteamUserStats/GetSchemaForGame/v0002/?key={key}&appid={appid}&l={lang}&format=json");
let json = client.get(url).send().await.unwrap().text().await.unwrap();
serde_json::from_str(&json).unwrap()
}
async fn get_achievement_data_all(
client: &Client,
api_key: &str,
game_name_cache_path: &str,
game_name_list: &Vec<App>,
dirs: Vec<String>,
) -> Vec<Game> {
let mut games: Vec<Game> = Vec::new();
let ini_games = get_achievement_data_all_ini(dirs);
for ini_game in ini_games {
let mut game = Game {
app_id: ini_game.id,
achievements: Vec::new(),
};
let game_data = get_steam_data(&client, api_key, ini_game.id, "en").await;
println!(
"game: \"{}\", id: {}, total achievements: {}/{}",
get_game_name(&client, &game_name_cache_path, &game_name_list, ini_game.id)
.await
.unwrap(),
ini_game.id,
ini_game.achievements.len(),
game_data.game.available_game_stats.achievements.len(),
);
for ini_achievement in ini_game.achievements {
if ini_achievement.achieved {
for json_achievement in &game_data.game.available_game_stats.achievements {
if json_achievement.name == ini_achievement.name {
game.achievements.push(Achievement {
achieved: ini_achievement.achieved,
max_progress: ini_achievement.max_progress,
current_progress: ini_achievement.current_progress,
description: json_achievement.description.clone(),
name: json_achievement.display_name.clone(),
hidden: if json_achievement.hidden == 1 {
true
} else if json_achievement.hidden == 0 {
false
} else {
panic!("Unexpected hidden value")
},
icon: json_achievement.icon.clone(),
icongray: json_achievement.icongray.clone(),
unlock_time: ini_achievement.unlock_time,
});
}
}
}
}
games.push(game);
}
games
}
fn get_achievement_data_all_ini(dirs: Vec<String>) -> Vec<IniGame> {
let mut ini_games = Vec::new();
for dir in dirs {
let game_dirs = fs::read_dir(dir).unwrap();
@@ -109,18 +187,18 @@ fn get_achievement_data_all(dirs: Vec<String>) -> Vec<IniGame> {
.file_name()
.to_str()
.unwrap()
.to_string()
.to_owned()
.parse()
.unwrap(),
);
let game_dir_path = game_dir.path().to_str().unwrap().to_string();
let game_dir_path = game_dir.path().to_str().unwrap().to_owned();
let i = Ini::load_from_file(format!("{game_dir_path}/achievements.ini",)).unwrap();
for (sec, prop) in i.iter() {
if let Some(section) = sec {
if section == "SteamAchievements" {
continue;
}
let mut ini_achievement = IniAchievement::new(section.to_string());
let mut ini_achievement = IniAchievement::new(section.to_owned());
for (k, v) in prop.iter() {
match k {
"Achieved" => ini_achievement.achieved = v.parse::<u32>().unwrap() == 1,
@@ -138,3 +216,89 @@ fn get_achievement_data_all(dirs: Vec<String>) -> Vec<IniGame> {
}
ini_games
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
struct AppList {
applist: Apps,
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
struct Apps {
apps: Vec<App>,
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
struct App {
name: String,
appid: u32,
}
async fn get_game_name(
client: &Client,
path: &str,
game_name_list: &Vec<App>,
appid: u32,
) -> Option<String> {
use std::fs::OpenOptions;
use std::io::prelude::*;
for app in game_name_list {
if app.appid == appid {
return Some(app.name.clone());
}
}
let json = client
.get("https://api.steampowered.com/ISteamApps/GetAppList/v0002/?format=json")
.send()
.await
.unwrap()
.text()
.await
.unwrap();
let applist: AppList = serde_json::from_str(&json).unwrap();
let applist = applist.applist.apps;
for app in applist {
if app.appid == appid {
let mut file = OpenOptions::new()
.write(true)
.append(true)
.open(path)
.unwrap();
writeln!(file, "{}:{}", app.appid, app.name).unwrap();
return Some(app.name);
}
}
None
}
fn get_game_name_file_cache(path: &str) -> Vec<App> {
std::fs::read_to_string(path)
.unwrap()
.lines()
.map(|m| {
let a = m.split_once(":").unwrap();
App {
appid: a.0.parse().unwrap(),
name: a.1.to_owned(),
}
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn check_id_to_name() {
let client = Client::new();
let config_dir = dirs::config_dir().unwrap().to_str().unwrap().to_owned();
let api_key =
fs::read_to_string(format!("{config_dir}/achievement-watcher/api-key.txt")).unwrap();
let json_game = get_steam_data(&client, &api_key, 570940, "en").await;
println!("game name: {}", json_game.game.game_name);
assert!(json_game.game.game_name.to_lowercase() == "Dark Souls Remastered");
}
}