1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
|
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<String>,
pub relay_urls: Vec<String>,
}
pub async fn discover_repos_from_relays(
client: &nostr_sdk::Client,
npubs: &[PublicKey],
relay_urls: &[String],
) -> Result<Vec<DiscoveredRepo>> {
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<DiscoveredRepo> {
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<HashMap<String, i64>> {
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<String, GraspServer>,
) -> Vec<GraspServer> {
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
}
|