use clap::Parser;
use regex::Regex;
use sf_api::{gamestate::GameState, session::*, sso::SFAccount};
#[tokio::main]
pub async fn main() {
env_logger::builder()
.filter_level(log::LevelFilter::Debug)
.init();
let args = Args::parse();
let custom_resp: Option<&str> = None;
let command = None;
let username = args.username;
let mut session = match args.sso {
true => SFAccount::login(
args.sso_username
.expect("SSO_USERNAME or --sso-username is required for SSO"),
args.password,
)
.await
.unwrap()
.characters()
.await
.unwrap()
.into_iter()
.flatten()
.find(|a| a.username() == username)
.unwrap(),
false => Session::new(
&username,
&args.password,
ServerConnection::new(
&args
.server
.expect("SERVER or --server is required for non-SSO"),
)
.unwrap(),
),
};
_ = std::fs::create_dir("cache");
let cache_name = format!("cache/{username}.login");
let login_data = match (args.cache, std::fs::read_to_string(&cache_name)) {
(_, Ok(s)) if args.diff => {
let old: Response = serde_json::from_str(&s).unwrap();
let new = session.login().await.unwrap();
for (&key, new_val) in new.values() {
if key.ends_with("id")
|| key == "timestamp"
|| key == "expeditionevent"
|| key == "idle"
{
continue;
}
let Some(old_val) = old.values().get(key) else {
println!("New key: {key}");
continue;
};
let old_val: Vec<_> = old_val.as_str().split("/").collect();
let new_val: Vec<_> = new_val.as_str().split("/").collect();
for (idx, (new, old)) in
new_val.into_iter().zip(old_val).enumerate()
{
if new.starts_with("17") && new.len() == "1774765933".len()
{
continue;
}
if key == "ownplayersave" && idx == 478 {
continue;
}
if new != old {
println!("{key}[{idx}] {old} => {new}");
}
}
}
return;
}
(true, Ok(s)) => serde_json::from_str(&s).unwrap(),
_ => {
let login_data = session.login().await.unwrap();
let ld = serde_json::to_string_pretty(&login_data).unwrap();
std::fs::write(&cache_name, ld).unwrap();
login_data
}
};
if let Some(re) = args.search {
for (&key, value) in login_data.values() {
if key == "ownplayersave" {
continue;
}
if let Some(key_re) = &args.search_key
&& !key_re.is_match(key)
{
continue;
}
let values: Vec<_> = value.as_str().split('/').collect();
for (pos, num) in values.into_iter().enumerate() {
if re.is_match(num) {
println!("{key}[{pos}] = {num}")
}
}
}
}
let mut gs = GameState::new(login_data).unwrap();
if let Some(resp) = custom_resp {
let resp = Response::parse(
resp.to_string(),
chrono::Local::now().naive_local(),
)
.unwrap();
gs.update(resp).unwrap();
}
let Some(command) = command else {
let js = serde_json::to_string_pretty(&gs).unwrap();
std::fs::write("character.json", js).unwrap();
return;
};
let cache_name = format!(
"cache/{username}-{}.response",
serde_json::to_string(&command).unwrap()
);
let resp = match (args.cache, std::fs::read_to_string(&cache_name)) {
(true, Ok(s)) => serde_json::from_str(&s).unwrap(),
_ => {
let resp = session.send_command_raw(&command).await.unwrap();
let ld = serde_json::to_string_pretty(&resp).unwrap();
std::fs::write(cache_name, ld).unwrap();
resp
}
};
gs.update(&resp).unwrap();
let js = serde_json::to_string_pretty(&gs).unwrap();
std::fs::write("character.json", js).unwrap();
}
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
struct Args {
#[arg(short, long)]
sso: bool,
#[arg(short, long)]
cache: bool,
#[arg(short, long)]
diff: bool,
#[arg(short, long, env = "USERNAME")]
username: String,
#[arg(short, long, env = "PASSWORD")]
password: String,
#[arg(long, env = "SERVER")]
server: Option<String>,
#[arg(long, env = "SSO_USERNAME")]
sso_username: Option<String>,
#[arg(long)]
search: Option<Regex>,
#[arg(long)]
search_key: Option<Regex>,
}