use crate::db::MirrorDb; use crate::health::GraspServer; use anyhow::{Context, Result}; use nostr::Kind; use nostr_sdk::prelude::*; use std::collections::HashMap; #[derive(Debug, Clone)] pub struct DiscoveredRepo { pub pubkey: PublicKey, pub identifier: String, pub event_id: EventId, pub clone_urls: Vec, pub relay_urls: Vec, } pub async fn discover_repos_from_relays( client: &nostr_sdk::Client, npubs: &[PublicKey], relay_urls: &[String], ) -> Result> { for url in relay_urls { if let Err(e) = client.add_relay(url).await { tracing::warn!(relay = %url, error = %e, "failed to add relay"); continue; } } client.connect().await; let mut repos = Vec::new(); for pk in npubs { let filter = Filter::new() .kind(Kind::Custom(30617)) .author(*pk) .limit(50); let events = client .fetch_events(filter, std::time::Duration::from_secs(30)) .await .context("failed to fetch events from relays")?; for event in events.into_iter() { if let Some(repo) = parse_announcement(&event) { repos.push(repo); } } } tracing::info!(count = repos.len(), "discovered repos from relays"); Ok(repos) } fn parse_announcement(event: &Event) -> Option { let mut identifier = None; let mut clone_urls = Vec::new(); let mut relay_urls = Vec::new(); for tag in event.tags.iter() { let tag_vec = tag.clone().to_vec(); if tag_vec.len() < 2 { continue; } match tag_vec[0].as_str() { "d" => identifier = Some(tag_vec[1].clone()), "clone" => clone_urls.push(tag_vec[1].clone()), "relays" => relay_urls.push(tag_vec[1].clone()), _ => {} } } let identifier = identifier?; if clone_urls.is_empty() { tracing::warn!( identifier = %identifier, "repo announcement has no clone URLs" ); } Some(DiscoveredRepo { pubkey: event.pubkey, identifier, event_id: event.id, clone_urls, relay_urls, }) } pub async fn persist_discovered_repos( db: &MirrorDb, repos: &[DiscoveredRepo], ) -> Result> { let mut repo_ids = HashMap::new(); for repo in repos { let pk_hex = repo.pubkey.to_hex(); match db .upsert_repo(&pk_hex, &repo.identifier, &repo.event_id.to_hex()) .await { Ok(id) => { tracing::debug!( identifier = %repo.identifier, repo_id = id, "persisted repo" ); repo_ids.insert(format!("{}:{}", pk_hex, repo.identifier), id); } Err(e) => { tracing::error!( identifier = %repo.identifier, error = %e, "failed to persist repo" ); } } } Ok(repo_ids) } pub fn identify_missing_servers( repo: &DiscoveredRepo, servers: &HashMap, ) -> Vec { let mut missing = Vec::new(); for (_domain, server) in servers { if !server.is_grasp_server() { continue; } let has_clone = repo .clone_urls .iter() .any(|url| url.contains(&server.domain)); if !has_clone { missing.push(server.clone()); } } missing }