more watcher work, TODO: watch directories instead of files
This commit is contained in:
94
src/lib.rs
94
src/lib.rs
@@ -26,61 +26,73 @@ pub struct Achievement {
|
||||
pub unlock_time: u32,
|
||||
}
|
||||
|
||||
pub async fn get_achievement_data(
|
||||
client: &Client,
|
||||
api_key: &str,
|
||||
app_id: u32,
|
||||
game_name_cache_path: &str,
|
||||
game_name_list: &Vec<App>,
|
||||
) -> Game {
|
||||
pub struct Context {
|
||||
pub api_key: String,
|
||||
pub game_name_cache_path: String,
|
||||
pub unlocked_icon_path: String,
|
||||
pub game_name_list: Vec<App>,
|
||||
pub dirs: Vec<String>,
|
||||
}
|
||||
|
||||
pub async fn get_achievement_data(client: &Client, app_id: u32, context: &Context) -> Option<Game> {
|
||||
let mut game = Game {
|
||||
name: steam::get_game_name(client, game_name_cache_path, game_name_list, app_id)
|
||||
.await
|
||||
.unwrap(),
|
||||
name: steam::get_game_name(
|
||||
client,
|
||||
&context.game_name_cache_path,
|
||||
&context.game_name_list,
|
||||
app_id,
|
||||
)
|
||||
.await
|
||||
.unwrap(),
|
||||
app_id,
|
||||
achievements: Vec::new(),
|
||||
};
|
||||
let game_data = steam::get_steam_data(client, api_key, app_id, "en").await;
|
||||
let game_data = steam::get_steam_data(client, &context.api_key, app_id, "en").await;
|
||||
let ini_data = steam::get_achievement_data_all_ini(&context.dirs);
|
||||
let ini_game = ini_data.iter().find(|m| m.id == app_id)?;
|
||||
for json_achievement in &game_data.game.available_game_stats.achievements {
|
||||
game.achievements.push(Achievement {
|
||||
achieved: false,
|
||||
max_progress: 0,
|
||||
current_progress: 0,
|
||||
unlock_time: 0,
|
||||
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(),
|
||||
});
|
||||
for ini_achievement in &ini_game.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,
|
||||
unlock_time: ini_achievement.unlock_time,
|
||||
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(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
game
|
||||
Some(game)
|
||||
}
|
||||
|
||||
pub 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> {
|
||||
pub async fn get_achievement_data_all(client: &Client, context: &Context) -> Vec<Game> {
|
||||
let mut games: Vec<Game> = Vec::new();
|
||||
let ini_games = steam::get_achievement_data_all_ini(dirs);
|
||||
let ini_games = steam::get_achievement_data_all_ini(&context.dirs);
|
||||
for ini_game in ini_games {
|
||||
let mut game = Game {
|
||||
app_id: ini_game.id,
|
||||
achievements: Vec::new(),
|
||||
name: steam::get_game_name(client, game_name_cache_path, game_name_list, ini_game.id)
|
||||
.await
|
||||
.unwrap(),
|
||||
name: steam::get_game_name(
|
||||
client,
|
||||
&context.game_name_cache_path,
|
||||
&context.game_name_list,
|
||||
ini_game.id,
|
||||
)
|
||||
.await
|
||||
.unwrap(),
|
||||
};
|
||||
let game_data = steam::get_steam_data(client, api_key, ini_game.id, "en").await;
|
||||
let game_data = steam::get_steam_data(client, &context.api_key, ini_game.id, "en").await;
|
||||
for json_achievement in &game_data.game.available_game_stats.achievements {
|
||||
let mut found_in_ini = false;
|
||||
for ini_achievement in &ini_game.achievements {
|
||||
|
||||
84
src/main.rs
84
src/main.rs
@@ -1,3 +1,4 @@
|
||||
use achievement_watcher::Context;
|
||||
use reqwest::Client;
|
||||
use std::{env, fs};
|
||||
|
||||
@@ -7,49 +8,60 @@ mod watch;
|
||||
#[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 context = setup();
|
||||
let mut games = achievement_watcher::get_achievement_data_all(&client, &context).await;
|
||||
let args: Vec<String> = std::env::args().skip(1).collect();
|
||||
if args.is_empty() {
|
||||
ui::ui(games).unwrap();
|
||||
} else if args.len() == 3 {
|
||||
match args[0].as_str() {
|
||||
"-w" => watch::watch_file(&args[1], args[2].parse().unwrap(), &context, &mut games)
|
||||
.await
|
||||
.unwrap(),
|
||||
_ => panic!("wrong arg"),
|
||||
}
|
||||
} else {
|
||||
panic!("wrong arg count");
|
||||
}
|
||||
}
|
||||
|
||||
fn setup() -> Context {
|
||||
let config_dir =
|
||||
dirs::config_dir().unwrap().to_str().unwrap().to_owned() + "/achievement-watcher";
|
||||
let cache_dir =
|
||||
dirs::cache_dir().unwrap().to_str().unwrap().to_owned() + "/achievement-watcher";
|
||||
match std::fs::exists(&config_dir) {
|
||||
Ok(false) => std::fs::create_dir(&config_dir).unwrap(),
|
||||
Err(e) => eprintln!("error in setup: config dir: {e}"),
|
||||
Ok(true) => (),
|
||||
}
|
||||
match std::fs::exists(&cache_dir) {
|
||||
Ok(false) => std::fs::create_dir(&cache_dir).unwrap(),
|
||||
Err(e) => eprintln!("error in setup: cache dir: {e}"),
|
||||
Ok(true) => (),
|
||||
}
|
||||
let game_name_cache_path = format!("{cache_dir}/game_names.txt");
|
||||
let unlocked_icon_path = format!("{cache_dir}/unlocked.png");
|
||||
let game_name_list = achievement_watcher::util::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_path = format!("{config_dir}/api-key.txt");
|
||||
let api_key = match fs::read_to_string(&api_key_path) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
eprintln!("Failed to read api key: {e} from: {api_key_path}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
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 = achievement_watcher::get_achievement_data_all(
|
||||
&client,
|
||||
api_key,
|
||||
&game_name_cache_path,
|
||||
&game_name_list,
|
||||
Context {
|
||||
api_key: api_key.to_owned(),
|
||||
dirs,
|
||||
)
|
||||
.await;
|
||||
// for game in &games {
|
||||
// for achievement in game.achievements.iter() {
|
||||
// let image = client
|
||||
// .get(&achievement.icon)
|
||||
// .send()
|
||||
// .await
|
||||
// .unwrap()
|
||||
// .bytes()
|
||||
// .await
|
||||
// .unwrap();
|
||||
// fs::write(format!("icons/{}_{}.jpg", game.name, achievement.name), image).unwrap();
|
||||
// }
|
||||
// break;
|
||||
// }
|
||||
let args: Vec<String> = std::env::args().skip(1).collect();
|
||||
if args.is_empty() {
|
||||
ui::ui(games).unwrap();
|
||||
} else if args.len() == 3 {
|
||||
match args[0].as_str() {
|
||||
// "-w" => watch::watch_file(&args[1], args[2].parse().unwrap()).unwrap(),
|
||||
_ => todo!(),
|
||||
}
|
||||
} else {
|
||||
panic!("wrong arg count");
|
||||
game_name_list,
|
||||
game_name_cache_path,
|
||||
unlocked_icon_path,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ use std::fs;
|
||||
pub struct JsonGame {
|
||||
pub game: JsonGameContent,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct JsonGameContent {
|
||||
@@ -128,7 +129,7 @@ pub async fn get_game_name(
|
||||
None
|
||||
}
|
||||
|
||||
pub fn get_achievement_data_all_ini(dirs: Vec<String>) -> Vec<IniGame> {
|
||||
pub 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();
|
||||
|
||||
@@ -14,9 +14,9 @@ pub fn get_game_name_file_cache(path: &str) -> Vec<App> {
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn send_notification(icon_path: &str, achievement_name: &str, achievement_description: &str) {
|
||||
pub fn send_notification(game_name: &str, achievement_name: &str, achievement_description: Option<&str>, icon_path: Option<&str>) {
|
||||
std::process::Command::new("notify-send")
|
||||
.args(["-i", icon_path, achievement_name, achievement_description])
|
||||
.args(["-a", game_name, "-i", icon_path.unwrap_or(""), achievement_name, achievement_description.unwrap_or("")])
|
||||
.spawn()
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
80
src/watch.rs
80
src/watch.rs
@@ -1,11 +1,15 @@
|
||||
use achievement_watcher::steam;
|
||||
use notify::{Event, RecursiveMode, Result, Watcher};
|
||||
use achievement_watcher::{util, Achievement, Context, Game};
|
||||
use notify::{event::AccessKind, Event, EventKind, RecursiveMode, Result, Watcher};
|
||||
use reqwest::Client;
|
||||
use std::path::Path;
|
||||
use std::sync::mpsc;
|
||||
use reqwest::Client;
|
||||
use achievement_watcher::steam::App;
|
||||
|
||||
pub fn watch_file(path: &str, app_id: u32, api_key: &str, game_name_cache_path: &str, game_name_list: Vec<App>) -> Result<()> {
|
||||
pub async fn watch_file(
|
||||
path: &str,
|
||||
app_id: u32,
|
||||
context: &Context,
|
||||
games: &mut Vec<Game>,
|
||||
) -> Result<()> {
|
||||
let (tx, rx) = mpsc::channel::<Result<Event>>();
|
||||
|
||||
let mut watcher = notify::recommended_watcher(tx)?;
|
||||
@@ -15,10 +19,12 @@ pub fn watch_file(path: &str, app_id: u32, api_key: &str, game_name_cache_path:
|
||||
for res in rx {
|
||||
match res {
|
||||
Ok(event) => {
|
||||
println!("event: {:?}", event);
|
||||
let ini_game = steam::parse_ini_file(path, app_id);
|
||||
let json_game = steam::get_steam_data(&client, api_key, app_id, "en");
|
||||
let result = achievement_watcher::get_achievement_data(&client, api_key, app_id, game_name_cache_path, &game_name_list);
|
||||
println!("event: {:#?}", event);
|
||||
if let EventKind::Access(AccessKind::Close(notify::event::AccessMode::Write)) =
|
||||
event.kind
|
||||
{
|
||||
check_for_new_achievement(&client, games, app_id, context).await;
|
||||
}
|
||||
}
|
||||
Err(e) => panic!("watch error: {:?}", e),
|
||||
}
|
||||
@@ -26,3 +32,59 @@ pub fn watch_file(path: &str, app_id: u32, api_key: &str, game_name_cache_path:
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn check_for_new_achievement(
|
||||
client: &Client,
|
||||
games: &mut Vec<Game>,
|
||||
app_id: u32,
|
||||
context: &Context,
|
||||
) {
|
||||
let new = achievement_watcher::get_achievement_data(&client, app_id, context)
|
||||
.await
|
||||
.unwrap();
|
||||
let old = games.iter().find(|m| m.app_id == app_id);
|
||||
if let Some(old) = old {
|
||||
let indexes = compare_achievements(&new.achievements, &old.achievements);
|
||||
for i in indexes {
|
||||
let new_achievement = &new.achievements[i];
|
||||
println!("new achievement: {:#?}", new_achievement);
|
||||
let jpg_icon: &[u8] = &client
|
||||
.get(&new_achievement.icon)
|
||||
.send()
|
||||
.await
|
||||
.unwrap()
|
||||
.bytes()
|
||||
.await
|
||||
.unwrap();
|
||||
let image: image::DynamicImage = image::load_from_memory(jpg_icon).unwrap();
|
||||
image
|
||||
.save_with_format(&context.unlocked_icon_path, image::ImageFormat::Png)
|
||||
.unwrap();
|
||||
util::send_notification(
|
||||
&new.name,
|
||||
&new_achievement.name,
|
||||
new_achievement.description.as_deref(),
|
||||
Some(&context.unlocked_icon_path),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
println!("\n\nWATCHER: did not find the game");
|
||||
}
|
||||
let u_old = games.iter_mut().find(|m| m.app_id == app_id).unwrap();
|
||||
*u_old = new;
|
||||
}
|
||||
|
||||
fn compare_achievements(new: &Vec<Achievement>, old: &Vec<Achievement>) -> Vec<usize> {
|
||||
let mut indexes = Vec::new();
|
||||
for (i, new_a) in new.iter().enumerate() {
|
||||
if old
|
||||
.iter()
|
||||
.find(|m| m.name == new_a.name && !m.achieved && new_a.achieved)
|
||||
.is_some()
|
||||
{
|
||||
println!("\n\nWATCHER: found new achievement {}", new_a.name);
|
||||
indexes.push(i);
|
||||
}
|
||||
}
|
||||
indexes
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user