diff --git a/src/main.rs b/src/main.rs index f75ac65..cc2ca1f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,6 +14,7 @@ mod db_actor; mod error; mod inbox; mod label; +mod nodeinfo; mod notify; mod requests; mod state; @@ -126,7 +127,12 @@ async fn main() -> Result<(), anyhow::Error> { .route(web::post().to(inbox::inbox)), ) .service(web::resource("/actor").route(web::get().to(actor_route))) - .service(actix_webfinger::resource::<_, RelayResolver>()) + .service(web::resource("/nodeinfo/2.0").route(web::get().to(nodeinfo::route))) + .service( + web::scope("/.well-known") + .service(actix_webfinger::scoped::<_, RelayResolver>()) + .service(web::resource("/nodeinfo").route(web::get().to(nodeinfo::well_known))), + ) }) .bind("0.0.0.0:8080")? .run() diff --git a/src/nodeinfo.rs b/src/nodeinfo.rs new file mode 100644 index 0000000..0adc1bd --- /dev/null +++ b/src/nodeinfo.rs @@ -0,0 +1,81 @@ +use crate::state::{State, UrlKind}; +use actix_web::{web, Responder}; +use actix_webfinger::Link; +use std::collections::HashMap; + +pub async fn well_known(state: web::Data) -> impl Responder { + web::Json(Link { + rel: "http://nodeinfo.diaspora.software/ns/schema/2.0".to_owned(), + href: Some(state.generate_url(UrlKind::NodeInfo)), + template: None, + kind: None, + }) + .with_header("Content-Type", "application/jrd+json") +} + +pub async fn route(state: web::Data) -> web::Json { + web::Json(NodeInfo { + version: NodeInfoVersion, + software: Software { + name: state.software_name(), + version: state.software_version(), + }, + protocols: vec![Protocol::ActivityPub], + services: vec![], + open_registrations: false, + usage: Usage { + local_posts: 0, + local_comments: 0, + }, + metadata: Metadata::default(), + }) +} + +#[derive(Clone, Debug, serde::Serialize)] +#[serde(rename_all = "camelCase")] +pub struct NodeInfo { + version: NodeInfoVersion, + software: Software, + protocols: Vec, + services: Vec, + open_registrations: bool, + usage: Usage, + metadata: Metadata, +} + +#[derive(Clone, Debug, Default)] +pub struct NodeInfoVersion; + +#[derive(Clone, Debug, Default, serde::Serialize)] +pub struct Software { + name: String, + version: String, +} + +#[derive(Clone, Debug, serde::Serialize)] +pub enum Protocol { + ActivityPub, +} + +#[derive(Clone, Debug, serde::Serialize)] +pub enum Service {} + +#[derive(Clone, Debug, Default, serde::Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Usage { + local_posts: u64, + local_comments: u64, +} + +#[derive(Clone, Debug, Default, serde::Serialize)] +#[serde(transparent)] +pub struct Metadata(pub HashMap); + +impl serde::ser::Serialize for NodeInfoVersion { + fn serialize(&self, serializer: S) -> Result + where + S: serde::ser::Serializer, + { + serializer.serialize_str("2.0") + } +} diff --git a/src/requests.rs b/src/requests.rs index bbbe2fe..b67a144 100644 --- a/src/requests.rs +++ b/src/requests.rs @@ -15,16 +15,23 @@ pub struct Requests { private_key: RSAPrivateKey, actor_cache: ActorCache, config: Config, + user_agent: String, } impl Requests { - pub fn new(key_id: String, private_key: RSAPrivateKey, actor_cache: ActorCache) -> Self { + pub fn new( + key_id: String, + private_key: RSAPrivateKey, + actor_cache: ActorCache, + user_agent: String, + ) -> Self { Requests { client: Client::default(), key_id, private_key, actor_cache, config: Config::default().dont_use_created_field(), + user_agent, } } @@ -48,6 +55,7 @@ impl Requests { .client .get(url) .header("Accept", "application/activity+json") + .header("User-Agent", self.user_agent.as_str()) .signature(&self.config, &self.key_id, |signing_string| { self.sign(signing_string) })? @@ -105,7 +113,7 @@ impl Requests { .post(inbox.as_str()) .header("Accept", "application/activity+json") .header("Content-Type", "application/activity+json") - .header("User-Agent", "Aode Relay v0.1.0") + .header("User-Agent", self.user_agent.as_str()) .signature_with_digest( &self.config, &self.key_id, diff --git a/src/state.rs b/src/state.rs index 019f999..5053785 100644 --- a/src/state.rs +++ b/src/state.rs @@ -40,6 +40,7 @@ pub enum UrlKind { Following, Inbox, MainKey, + NodeInfo, Outbox, } @@ -92,6 +93,7 @@ impl Settings { UrlKind::Following => format!("{}://{}/following", scheme, self.hostname), UrlKind::Inbox => format!("{}://{}/inbox", scheme, self.hostname), UrlKind::MainKey => format!("{}://{}/actor#main-key", scheme, self.hostname), + UrlKind::NodeInfo => format!("{}://{}/nodeinfo/2.0", scheme, self.hostname), UrlKind::Outbox => format!("{}://{}/outbox", scheme, self.hostname), } } @@ -99,14 +101,31 @@ impl Settings { fn generate_resource(&self) -> String { format!("relay@{}", self.hostname) } + + fn software_name(&self) -> String { + "AodeRelay".to_owned() + } + + fn software_version(&self) -> String { + "v0.1.0-master".to_owned() + } } impl State { + pub fn software_name(&self) -> String { + self.settings.software_name() + } + + pub fn software_version(&self) -> String { + self.settings.software_version() + } + pub fn requests(&self) -> Requests { Requests::new( self.generate_url(UrlKind::MainKey), self.settings.private_key.clone(), self.actor_cache.clone(), + format!("{} {}", self.software_name(), self.software_version()), ) }