2
0
Files
log/src/commands/pds.rs
2026-03-30 14:48:04 +09:00

136 lines
3.8 KiB
Rust

use anyhow::Result;
use serde::Deserialize;
use std::collections::HashMap;
#[derive(Debug, Deserialize)]
#[allow(dead_code)]
struct Network {
plc: String,
bsky: String,
web: Option<String>,
#[serde(rename = "handleDomains")]
handle_domains: Option<Vec<String>>,
}
#[derive(Debug, Deserialize)]
struct HealthResponse {
version: Option<String>,
}
#[derive(Debug, Deserialize)]
struct PackageJson {
dependencies: Option<HashMap<String, String>>,
}
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::<PackageJson>().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<HashMap<String, Network>> {
// 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::<HealthResponse>().await {
Ok(health) => health.version.unwrap_or_else(|| "-".to_string()),
Err(_) => "-".to_string(),
}
} else {
"-".to_string()
}
}
Err(_) => "-".to_string(),
};
println!("{}: {}", name, version);
}
Ok(())
}