123 lines
3.4 KiB
Rust
123 lines
3.4 KiB
Rust
use rocket::serde::json::Json;
|
|
use serde::{Deserialize, Serialize};
|
|
use url::Url;
|
|
|
|
use crate::known_software::KNOWN_SOFTWARE_NODEINFO_NAMES;
|
|
|
|
#[derive(Serialize)]
|
|
pub struct InstanceInfo {
|
|
name: String,
|
|
software: String,
|
|
#[serde(rename = "iconURL")]
|
|
icon_url: Option<String>,
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
struct InstanceManifest {
|
|
name: Option<String>,
|
|
short_name: Option<String>,
|
|
icons: Option<Vec<InstanceIcon>>,
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
struct InstanceIcon {
|
|
src: String,
|
|
sizes: String,
|
|
}
|
|
|
|
impl InstanceIcon {
|
|
fn get_size(&self) -> Option<usize> {
|
|
let (x, y) = self.sizes.split_once("x")?;
|
|
let x: usize = x.parse().ok()?;
|
|
let y: usize = y.parse().ok()?;
|
|
Some(x.max(y))
|
|
}
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
struct NodeInfoDiscovery {
|
|
links: Vec<NodeInfoLink>,
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
struct NodeInfoLink {
|
|
href: String,
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
struct NodeInfo {
|
|
software: NodeInfoSoftware,
|
|
metadata: Option<NodeInfoMetadata>,
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
struct NodeInfoSoftware {
|
|
name: String,
|
|
version: String,
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
struct NodeInfoMetadata {
|
|
#[serde(rename = "nodeName")]
|
|
name: Option<String>,
|
|
}
|
|
|
|
async fn get_info_from_manifest(url: Url) -> Option<[Option<String>; 3]> {
|
|
// FIXME: Iceshrimp.NET doesn't have a manifest...
|
|
let response = reqwest::get(url.clone()).await.ok()?.text().await.ok()?;
|
|
let manifest: InstanceManifest = serde_json::from_str(&response).ok()?;
|
|
Some([
|
|
manifest.name,
|
|
manifest.short_name,
|
|
manifest
|
|
.icons
|
|
.as_ref()
|
|
.and_then(|icons| icons.iter().min_by_key(|icon| icon.get_size()))
|
|
.map(|icon| icon.src.to_owned()),
|
|
])
|
|
}
|
|
|
|
#[get("/instance_info/<secure>/<host>")]
|
|
pub async fn instance_info(secure: bool, host: &str) -> Option<Json<InstanceInfo>> {
|
|
let mut url = Url::parse(&format!(
|
|
"http{}://{host}/manifest.json",
|
|
if secure { "s" } else { "" }
|
|
))
|
|
.ok()?;
|
|
// I'm not sure if you can sneak in a path, but better safe than sorry
|
|
// I don't really care about username/password/port, those are fine
|
|
if url.path() != "/manifest.json" {
|
|
return None;
|
|
}
|
|
let [name, short_name, icon_url] = get_info_from_manifest(url.clone())
|
|
.await
|
|
.unwrap_or_default();
|
|
let icon_url = icon_url
|
|
.and_then(|i| url.join(&i).ok())
|
|
.map(|u| u.to_string());
|
|
// FIXME: Iceshrimp.NET doesn't have a nodeinfo discovery file either.............
|
|
url.set_path("/.well-known/nodeinfo");
|
|
let response = reqwest::get(url.clone()).await.ok()?.text().await.ok()?;
|
|
let nodeinfo_discovery: NodeInfoDiscovery = serde_json::from_str(&response).ok()?;
|
|
let response = reqwest::get(&nodeinfo_discovery.links.first()?.href)
|
|
.await
|
|
.ok()?
|
|
.text()
|
|
.await
|
|
.ok()?;
|
|
let nodeinfo: NodeInfo = serde_json::from_str(&response).ok()?;
|
|
let (software_name, fork_map) = KNOWN_SOFTWARE_NODEINFO_NAMES.get(&nodeinfo.software.name)?;
|
|
let software = semver::Version::parse(&nodeinfo.software.version)
|
|
.ok()
|
|
.and_then(|v| fork_map.get(v.build.as_str()))
|
|
.unwrap_or(software_name)
|
|
.to_owned();
|
|
Some(Json(InstanceInfo {
|
|
name: name
|
|
.or(short_name)
|
|
.or(nodeinfo.metadata.and_then(|m| m.name))
|
|
.unwrap_or(url.host_str().unwrap().to_owned()),
|
|
software,
|
|
icon_url,
|
|
}))
|
|
}
|