enigmatick/
webfinger.rs

1use crate::{models::actors::Actor, WEBFINGER_ACCT_RE};
2use anyhow::Result;
3use reqwest::Client;
4use serde::{Deserialize, Serialize};
5
6#[derive(Serialize, Deserialize, Clone, Default, Debug)]
7pub struct WebFingerLink {
8    pub rel: String,
9    #[serde(rename = "type")]
10    #[serde(skip_serializing_if = "Option::is_none")]
11    pub kind: Option<String>,
12    #[serde(skip_serializing_if = "Option::is_none")]
13    pub href: Option<String>,
14    #[serde(skip_serializing_if = "Option::is_none")]
15    pub template: Option<String>,
16}
17
18#[derive(Serialize, Deserialize, Clone, Default, Debug)]
19pub struct WebFinger {
20    pub subject: String,
21    pub aliases: Option<Vec<String>>,
22    pub links: Vec<WebFingerLink>,
23}
24
25impl WebFinger {
26    pub fn get_address(&self) -> Option<String> {
27        let captures = WEBFINGER_ACCT_RE.captures_iter(&self.subject).next()?;
28
29        let username = captures.get(1)?.as_str();
30        let domain = captures.get(2)?.as_str();
31
32        Some(format!("@{username}@{domain}"))
33    }
34
35    pub fn get_id(&self) -> Option<String> {
36        self.links
37            .iter()
38            .filter_map(|x| {
39                if x.kind == Some("application/activity+json".to_string())
40                || x.kind
41                    == Some(
42                        "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
43                            .to_string(),
44                    )
45            {
46                x.href.clone()
47            } else {
48                None
49            }
50            })
51            .take(1)
52            .next()
53    }
54}
55
56// This function is used in routes/webfinger.rs to create a struct for internal users.
57// It's not useful to get a WebFinger for a remote Actor.
58impl From<Actor> for WebFinger {
59    fn from(profile: Actor) -> Self {
60        let server_url = format!("https://{}", *crate::SERVER_NAME);
61        let server_name = crate::SERVER_NAME.as_str();
62
63        WebFinger {
64            subject: format!(
65                "acct:{}@{}",
66                profile.ek_username.as_ref().unwrap(),
67                server_name
68            ),
69            aliases: Some(vec![
70                format!("{}/@{}", server_url, profile.ek_username.as_ref().unwrap()),
71                format!(
72                    "{}/user/{}",
73                    server_url,
74                    profile.ek_username.as_ref().unwrap()
75                ),
76            ]),
77            links: vec![
78                WebFingerLink {
79                    rel: "http://webfinger.net/rel/profile-page".to_string(),
80                    kind: Some("text/html".to_string()),
81                    href: Some(format!(
82                        "{}/@{}",
83                        server_url,
84                        profile.ek_username.as_ref().unwrap()
85                    )),
86                    ..Default::default()
87                },
88                WebFingerLink {
89                    rel: "self".to_string(),
90                    kind: Some("application/activity+json".to_string()),
91                    href: Some(format!(
92                        "{}/user/{}",
93                        server_url,
94                        profile.ek_username.unwrap()
95                    )),
96                    ..Default::default()
97                },
98            ],
99        }
100    }
101}
102
103pub async fn retrieve_webfinger(domain: String, username: String) -> Result<WebFinger> {
104    let url = format!("https://{domain}/.well-known/webfinger?resource=acct:{username}@{domain}");
105    let accept = "application/jrd+json";
106    let agent = "Enigmatick/0.1";
107
108    let response = Client::builder()
109        .user_agent(agent)
110        .build()
111        .unwrap()
112        .get(url)
113        .header("Accept", accept)
114        .send()
115        .await
116        .map_err(anyhow::Error::msg)?;
117
118    let json = response.json().await?;
119
120    serde_json::from_value(json).map_err(anyhow::Error::msg)
121}