diff --git a/Cargo.lock b/Cargo.lock index 1b3bf92..dbb768e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" @@ -285,6 +285,7 @@ dependencies = [ "semver", "serde", "serde_json", + "tokio", "url", ] diff --git a/Cargo.toml b/Cargo.toml index df36732..2ab9ce1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,4 +10,5 @@ rocket = { version = "0.5.1", features = ["json"] } semver = "1.0.24" serde = { version = "1.0.217", features = ["derive"] } serde_json = "1.0.135" +tokio = { version = "1.43.0", features = ["process"] } url = "2.5.4" diff --git a/src/api/instance_info.rs b/src/api/instance_info.rs index 276e947..a7f267a 100644 --- a/src/api/instance_info.rs +++ b/src/api/instance_info.rs @@ -2,10 +2,20 @@ use std::net::ToSocketAddrs; use rocket::serde::json::Json; use serde::{Deserialize, Serialize}; +use tokio::process::Command; use url::Url; use crate::known_software::KNOWN_SOFTWARE_NODEINFO_NAMES; +const MINIMUM_ICON_SIZE: usize = 16; + +fn parse_size(size: &str) -> Option { + let (x, y) = size.split_once("x")?; + let x: usize = x.parse().ok()?; + let y: usize = y.parse().ok()?; + Some(x.max(y)) +} + #[derive(Serialize)] pub struct InstanceInfo { name: String, @@ -15,24 +25,38 @@ pub struct InstanceInfo { } #[derive(Deserialize)] -struct InstanceManifest { - name: Option, - short_name: Option, - icons: Option>, +struct InstanceIcon { + href: String, + size: usize, } #[derive(Deserialize)] -struct InstanceIcon { - src: String, - sizes: String, +struct ScrapedIcon { + url: String, + size: Option, + sizes: Option>, } -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)) +impl TryInto for ScrapedIcon { + type Error = (); + + fn try_into(self) -> Result { + let size = if let Some(size) = self.size { + parse_size(&size) + } else if let Some(sizes) = self.sizes { + sizes + .into_iter() + .filter_map(|s| parse_size(&s)) + .filter(|&s| s >= MINIMUM_ICON_SIZE) + .min() + } else { + return Err(()); + } + .ok_or(())?; + Ok(InstanceIcon { + href: self.url, + size, + }) } } @@ -64,31 +88,34 @@ struct NodeInfoMetadata { 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()), - ]) +async fn find_icon(host: &str) -> Option { + let icons: Vec = serde_json::from_slice( + &Command::new("site-icons") + .args(["--json", host]) + .output() + .await + .ok()? + .stdout, + ) + .ok()?; + icons + .into_iter() + .filter_map(|i| -> Option { i.try_into().ok() }) + .filter(|i| i.size > MINIMUM_ICON_SIZE) + .min_by_key(|i| i.size) + .map(|i| i.href) } #[get("/instance_info//")] pub async fn instance_info(secure: bool, host: &str) -> Option> { let mut url = Url::parse(if secure { - "https://temp.host/manifest.json" + "https://temp.host/" } else { - "http://temp.host/manifest.json" + "http://temp.host/" }) - .ok()?; + .unwrap(); url.set_host(Some(host)).ok()?; // Using this to catch malformed hosts - let host = url.host_str()?; // Shadow the original host in case things were filtered out + let host = url.host_str()?.to_owned(); // Shadow the original host in case things were filtered out // Check if the host is globally routable. // This should help filter out a bunch of invalid or potentially malicious requests @@ -103,13 +130,8 @@ pub async fn instance_info(secure: bool, host: &str) -> Option Option