mod api;
mod app;
mod auth;
mod config;
mod debug_log;
mod emoji;
mod event_loop;
#[macro_use]
mod logging;
#[macro_use]
mod reply_debug_log;
mod session;
mod terminal;
mod text_wrapper;
mod ui;
use anyhow::Result;
use app::App;
use clap::Parser;
use std::process::Command;
const CURRENT_VERSION: &str = env!("CARGO_PKG_VERSION");
#[derive(Parser)]
#[command(name = "fido")]
#[command(about = "A terminal-based social network for developers")]
#[command(version)]
struct Cli {
#[arg(long, short, env = "FIDO_SERVER_URL")]
server: Option<String>,
#[arg(long, short)]
verbose: bool,
#[arg(long)]
update: bool,
}
fn load_env() {
let _ = dotenv::dotenv();
}
async fn check_for_updates() -> Option<String> {
#[derive(serde::Deserialize)]
struct CrateResponse {
#[serde(rename = "crate")]
krate: CrateInfo,
}
#[derive(serde::Deserialize)]
struct CrateInfo {
max_stable_version: String,
}
let client = reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(3))
.build()
.ok()?;
let resp = client
.get("https://crates.io/api/v1/crates/fido")
.header("User-Agent", format!("fido/{}", CURRENT_VERSION))
.send()
.await
.ok()?;
let data: CrateResponse = resp.json().await.ok()?;
let latest = &data.krate.max_stable_version;
if latest != CURRENT_VERSION && is_newer_version(latest, CURRENT_VERSION) {
Some(latest.clone())
} else {
None
}
}
fn is_newer_version(latest: &str, current: &str) -> bool {
let parse = |v: &str| -> Vec<u32> {
v.split('.')
.filter_map(|s| s.parse().ok())
.collect()
};
let latest_parts = parse(latest);
let current_parts = parse(current);
for (l, c) in latest_parts.iter().zip(current_parts.iter()) {
if l > c { return true; }
if l < c { return false; }
}
latest_parts.len() > current_parts.len()
}
#[tokio::main]
async fn main() -> Result<()> {
let cli = Cli::parse();
if cli.update {
println!("Updating fido to the latest version...");
let status = Command::new("cargo")
.args(["install", "fido", "--force"])
.status();
match status {
Ok(exit_status) if exit_status.success() => {
println!("✓ fido updated successfully!");
return Ok(());
}
Ok(exit_status) => {
eprintln!("✗ Update failed with exit code: {}", exit_status);
std::process::exit(1);
}
Err(e) => {
eprintln!("✗ Failed to run cargo install: {}", e);
eprintln!(" Make sure cargo is installed and in your PATH");
std::process::exit(1);
}
}
}
load_env();
let log_config = if cli.verbose {
logging::LogConfig::verbose()
} else {
logging::LogConfig::disabled()
};
logging::init_logging(&log_config)?;
let mut tui = terminal::init()?;
let is_demo_mode = std::env::var("FIDO_DEMO_MODE").is_ok();
let mut app = if is_demo_mode {
log::info!("Starting in DEMO MODE with MockBackend");
App::demo()
} else if let Some(server_url) = cli.server {
App::with_server_url(server_url)
} else {
App::new()
};
app.log_config = log_config;
if !is_demo_mode && std::env::var("FIDO_WEB_MODE").is_err() {
if let Some(latest_version) = check_for_updates().await {
app.update_available = Some(latest_version);
}
}
let is_web_mode = std::env::var("FIDO_WEB_MODE").is_ok();
if is_web_mode || is_demo_mode {
app.auth_state.show_github_option = false;
}
let mut auth_flow = auth::AuthFlow::new(app.api_client.clone())?;
if !is_web_mode && !is_demo_mode {
if let Ok(Some(user)) = auth_flow.check_existing_session().await {
log::info!("Restored session for user: {}", user.username);
app.auth_state.current_user = Some(user);
app.current_screen = app::Screen::Main;
app.api_client = auth_flow.api_client().clone();
let _ = app.load_settings().await;
app.load_filter_preference();
let _ = app.load_posts().await;
} else {
log::info!("No valid session found, showing authentication screen");
let _ = app.load_test_users().await;
}
} else {
if is_demo_mode {
log::info!("Running in demo mode, loading test users only");
} else {
log::info!("Running in web mode, loading test users only");
}
let _ = app.load_test_users().await;
}
let mut event_loop = event_loop::EventLoop::new();
event_loop.run(&mut app, &mut auth_flow, &mut tui).await?;
terminal::restore()?;
Ok(())
}