Skip to content

Commit deb00a3

Browse files
authored
fix(soprano): update instances and fix actualizer JSON schema (#34)
1 parent 6943599 commit deb00a3

3 files changed

Lines changed: 199 additions & 4 deletions

File tree

fastside-actualizer/src/services/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ mod scribe;
1212
mod searx;
1313
mod searxng;
1414
mod simplytranslate;
15+
mod soprano;
1516
mod tent;
1617
mod translite;
1718

@@ -25,6 +26,7 @@ pub fn get_service_updater(name: &str) -> Option<Box<dyn ServiceUpdater>> {
2526
"searx" => Some(Box::new(searx::SearxUpdater::new())),
2627
"searxng" => Some(Box::new(searxng::SearxngUpdater::new())),
2728
"simplytranslate" => Some(Box::new(simplytranslate::SimplyTranslateUpdater::new())),
29+
"soprano" => Some(Box::new(soprano::SopranoUpdater::new())),
2830
"invidious" => Some(Box::new(invidious::InvidiousUpdater::new())),
2931
"scribe" => Some(Box::new(scribe::ScribeUpdater::new())),
3032
"libreddit" => Some(Box::new(libreddit::LibredditUpdater::new())),
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
use crate::{types::ServiceUpdater, ChangesSummary};
2+
use anyhow::anyhow;
3+
use async_trait::async_trait;
4+
use fastside_shared::serde_types::Instance;
5+
use serde::Deserialize;
6+
use url::Url;
7+
8+
pub struct SopranoUpdater {
9+
pub instances_urls: [String; 2],
10+
}
11+
12+
impl SopranoUpdater {
13+
pub fn new() -> Self {
14+
Self {
15+
instances_urls: [
16+
"https://git.vern.cc/cobra/Soprano/raw/branch/main/instances.json".to_string(),
17+
"https://codeberg.org/cobra/Soprano/raw/branch/main/instances.json".to_string(),
18+
],
19+
}
20+
}
21+
}
22+
23+
impl Default for SopranoUpdater {
24+
fn default() -> Self {
25+
Self::new()
26+
}
27+
}
28+
29+
#[derive(Debug, Deserialize)]
30+
struct SopranoInstance {
31+
#[serde(default)]
32+
clearnet: String,
33+
#[serde(default)]
34+
tor: String,
35+
#[serde(default)]
36+
i2p: String,
37+
}
38+
39+
impl SopranoInstance {
40+
fn into_urls(self) -> Vec<Url> {
41+
[self.clearnet, self.tor, self.i2p]
42+
.into_iter()
43+
.filter(|s| !s.is_empty())
44+
.filter_map(|s| Url::parse(&s).ok())
45+
.collect()
46+
}
47+
}
48+
49+
#[async_trait]
50+
impl ServiceUpdater for SopranoUpdater {
51+
async fn update(
52+
&self,
53+
client: reqwest::Client,
54+
current_instances: &[Instance],
55+
changes_summary: ChangesSummary,
56+
) -> anyhow::Result<Vec<Instance>> {
57+
let mut parsed = None;
58+
let mut last_error = None;
59+
60+
for instances_url in &self.instances_urls {
61+
let response = match client.get(instances_url).send().await {
62+
Ok(response) => response,
63+
Err(error) => {
64+
last_error = Some(anyhow!("failed to fetch {}: {}", instances_url, error));
65+
continue;
66+
}
67+
};
68+
69+
if !response.status().is_success() {
70+
last_error = Some(anyhow!(
71+
"failed to fetch {}: HTTP {}",
72+
instances_url,
73+
response.status()
74+
));
75+
continue;
76+
}
77+
78+
let response_text = match response.text().await {
79+
Ok(response_text) => response_text,
80+
Err(error) => {
81+
last_error = Some(anyhow!(
82+
"failed to read response body from {}: {}",
83+
instances_url,
84+
error
85+
));
86+
continue;
87+
}
88+
};
89+
90+
match serde_json::from_str::<Vec<SopranoInstance>>(&response_text) {
91+
Ok(instances) => {
92+
parsed = Some(instances);
93+
break;
94+
}
95+
Err(error) => {
96+
last_error = Some(anyhow!(
97+
"failed to parse JSON from {}: {}",
98+
instances_url,
99+
error
100+
));
101+
}
102+
}
103+
}
104+
105+
let parsed = parsed.ok_or_else(|| {
106+
last_error
107+
.unwrap_or_else(|| anyhow!("failed to load soprano instances from all sources"))
108+
})?;
109+
110+
let mut instances = current_instances.to_vec();
111+
let mut new_instances = Vec::new();
112+
113+
for soprano_instance in parsed {
114+
for url in soprano_instance.into_urls() {
115+
if current_instances.iter().any(|instance| instance.url == url) {
116+
continue;
117+
}
118+
new_instances.push(Instance::from(url));
119+
}
120+
}
121+
122+
changes_summary
123+
.set_new_instances_added(
124+
"soprano",
125+
new_instances
126+
.iter()
127+
.map(|instance| instance.url.clone())
128+
.collect(),
129+
)
130+
.await;
131+
132+
instances.extend(new_instances);
133+
134+
Ok(instances)
135+
}
136+
}

services.json

Lines changed: 61 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7008,9 +7008,66 @@
70087008
"search_string": null,
70097009
"regexes": [],
70107010
"aliases": [],
7011-
"source_link": null,
7012-
"deprecated_message": "No instances.",
7013-
"instances": []
7011+
"source_link": "https://codeberg.org/cobra/Soprano",
7012+
"deprecated_message": null,
7013+
"instances": [
7014+
{
7015+
"url": "https://sp.vern.cc/",
7016+
"tags": [
7017+
"clearnet",
7018+
"https"
7019+
]
7020+
},
7021+
{
7022+
"url": "http://sp.vernccvbvyi5qhfzyqengccj7lkove6bjot2xhh5kajhwvidqafczrad.onion/",
7023+
"tags": [
7024+
"http",
7025+
"tor"
7026+
]
7027+
},
7028+
{
7029+
"url": "http://vernusqvmcnvmbewuec5d3vvnh3xpejcn74pkkjhj2qzfvgpogkq.b32.i2p/",
7030+
"tags": [
7031+
"http",
7032+
"i2p"
7033+
]
7034+
},
7035+
{
7036+
"url": "https://soprano.catsarch.com/",
7037+
"tags": [
7038+
"clearnet",
7039+
"https"
7040+
]
7041+
},
7042+
{
7043+
"url": "http://soprano.catsarchywsyuss6jdxlypsw5dc7owd5u5tr6bujxb7o6xw2hipqehyd.onion/",
7044+
"tags": [
7045+
"http",
7046+
"tor"
7047+
]
7048+
},
7049+
{
7050+
"url": "http://eidq3irhlc235szbka6nn3odru3aovrxplhf4tggbz7ffam2jjiq.b32.i2p/",
7051+
"tags": [
7052+
"http",
7053+
"i2p"
7054+
]
7055+
},
7056+
{
7057+
"url": "https://tenor.fsky.io/",
7058+
"tags": [
7059+
"clearnet",
7060+
"https"
7061+
]
7062+
},
7063+
{
7064+
"url": "http://tenorxfrvzsgjde6k3f64jen7fqr5mouk7qr7c3tptab5b5ohnwpniyd.onion/",
7065+
"tags": [
7066+
"http",
7067+
"tor"
7068+
]
7069+
}
7070+
]
70147071
},
70157072
{
70167073
"type": "suds",
@@ -7596,4 +7653,4 @@
75967653
"instances": []
75977654
}
75987655
]
7599-
}
7656+
}

0 commit comments

Comments
 (0)