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, } #[derive(Deserialize)] struct InstanceManifest { name: Option, short_name: Option, icons: Option>, } #[derive(Deserialize)] struct InstanceIcon { src: String, sizes: String, } impl InstanceIcon { fn get_size(&self) -> Option { 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, } #[derive(Deserialize)] struct NodeInfoLink { href: String, } #[derive(Deserialize)] struct NodeInfo { software: NodeInfoSoftware, metadata: Option, } #[derive(Deserialize)] struct NodeInfoSoftware { name: String, version: String, } #[derive(Deserialize)] struct NodeInfoMetadata { #[serde(rename = "nodeName")] name: Option, } async fn get_info_from_manifest(url: Url) -> Option<[Option; 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//")] pub async fn instance_info(secure: bool, host: &str) -> Option> { 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, })) }