use anyhow::Result; use serde::Deserialize; use std::collections::HashMap; #[derive(Debug, Deserialize)] #[allow(dead_code)] struct Network { plc: String, bsky: String, web: Option, #[serde(rename = "handleDomains")] handle_domains: Option>, } #[derive(Debug, Deserialize)] struct HealthResponse { version: Option, } #[derive(Debug, Deserialize)] struct PackageJson { dependencies: Option>, } const PDS_PACKAGE_URL: &str = "https://raw.githubusercontent.com/bluesky-social/pds/main/service/package.json"; const DEFAULT_NETWORKS: &str = r#"{ "bsky.social": { "plc": "https://plc.directory", "bsky": "https://public.api.bsky.app", "web": "https://bsky.app", "handleDomains": ["bsky.social"] }, "syu.is": { "plc": "https://plc.syu.is", "bsky": "https://bsky.syu.is", "web": "https://syu.is", "handleDomains": ["syu.is", "syui.ai"] } }"#; async fn get_latest_version(client: &reqwest::Client) -> String { if let Ok(res) = client.get(PDS_PACKAGE_URL).send().await { if res.status().is_success() { if let Ok(pkg) = res.json::().await { if let Some(deps) = pkg.dependencies { if let Some(v) = deps.get("@atproto/pds") { return v.clone(); } } } } } "N/A".to_string() } fn load_networks(networks_path: &str) -> Result> { // 1. Try specified path if let Ok(content) = std::fs::read_to_string(networks_path) { return Ok(serde_json::from_str(&content)?); } // 2. Try ~/.config/ai.syui.log/networks.json if let Some(config_dir) = dirs::config_dir() { let config_path = config_dir.join("ai.syui.log").join("networks.json"); if let Ok(content) = std::fs::read_to_string(&config_path) { return Ok(serde_json::from_str(&content)?); } } // 3. Fall back to built-in default Ok(serde_json::from_str(DEFAULT_NETWORKS)?) } /// Show current session info (PDS, DID, handle) pub fn show_session(is_bot: bool) -> Result<()> { use super::oauth; use super::token; let has_oauth = oauth::has_oauth_session(is_bot); let session = if is_bot { token::load_bot_session()? } else { token::load_session()? }; let mut info = serde_json::json!({ "did": session.did, "handle": session.handle, "pds": session.pds.as_deref().unwrap_or("bsky.social"), "auth": if has_oauth { "oauth" } else { "legacy" }, }); if has_oauth { if let Ok(oauth_session) = oauth::load_oauth_session(is_bot) { info["issuer"] = serde_json::json!(oauth_session.issuer); } } println!("{}", serde_json::to_string_pretty(&info)?); Ok(()) } pub async fn check_versions(networks_path: &str) -> Result<()> { let networks = load_networks(networks_path)?; let client = reqwest::Client::new(); // Get latest version from GitHub let latest = get_latest_version(&client).await; println!("latest: {}", latest); println!(); for name in networks.keys() { // Check PDS using network name as domain let url = format!("https://{}/xrpc/_health", name); let version = match client.get(&url).send().await { Ok(res) => { if res.status().is_success() { match res.json::().await { Ok(health) => health.version.unwrap_or_else(|| "-".to_string()), Err(_) => "-".to_string(), } } else { "-".to_string() } } Err(_) => "-".to_string(), }; println!("{}: {}", name, version); } Ok(()) }