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
56impl 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}