]> Untitled Git - lemmy.git/blob - crates/apub/src/fetcher/webfinger.rs
Merge pull request #2593 from LemmyNet/refactor-notifications
[lemmy.git] / crates / apub / src / fetcher / webfinger.rs
1 use crate::{local_instance, ActorType, FEDERATION_HTTP_FETCH_LIMIT};
2 use activitypub_federation::{core::object_id::ObjectId, traits::ApubObject};
3 use anyhow::anyhow;
4 use itertools::Itertools;
5 use lemmy_api_common::context::LemmyContext;
6 use lemmy_db_schema::newtypes::DbUrl;
7 use lemmy_utils::{error::LemmyError, WebfingerResponse};
8 use tracing::debug;
9 use url::Url;
10
11 /// Turns a person id like `@name@example.com` into an apub ID, like `https://example.com/user/name`,
12 /// using webfinger.
13 pub(crate) async fn webfinger_resolve_actor<Kind>(
14   identifier: &str,
15   local_only: bool,
16   context: &LemmyContext,
17   request_counter: &mut i32,
18 ) -> Result<DbUrl, LemmyError>
19 where
20   Kind: ApubObject<DataType = LemmyContext, Error = LemmyError> + ActorType + Send + 'static,
21   for<'de2> <Kind as ApubObject>::ApubType: serde::Deserialize<'de2>,
22 {
23   let protocol = context.settings().get_protocol_string();
24   let (_, domain) = identifier
25     .splitn(2, '@')
26     .collect_tuple()
27     .ok_or_else(|| LemmyError::from_message("Invalid webfinger query, missing domain"))?;
28   let fetch_url = format!(
29     "{}://{}/.well-known/webfinger?resource=acct:{}",
30     protocol, domain, identifier
31   );
32   debug!("Fetching webfinger url: {}", &fetch_url);
33
34   *request_counter += 1;
35   if *request_counter > FEDERATION_HTTP_FETCH_LIMIT {
36     return Err(LemmyError::from_message("Request retry limit reached"));
37   }
38
39   let response = context.client().get(&fetch_url).send().await?;
40
41   let res: WebfingerResponse = response.json().await.map_err(LemmyError::from)?;
42
43   let links: Vec<Url> = res
44     .links
45     .iter()
46     .filter(|link| {
47       if let Some(type_) = &link.kind {
48         type_.starts_with("application/")
49       } else {
50         false
51       }
52     })
53     .filter_map(|l| l.href.clone())
54     .collect();
55   for l in links {
56     let object_id = ObjectId::<Kind>::new(l);
57     let object = if local_only {
58       object_id.dereference_local(context).await
59     } else {
60       object_id
61         .dereference(context, local_instance(context).await, request_counter)
62         .await
63     };
64     if object.is_ok() {
65       return object.map(|o| o.actor_id().into());
66     }
67   }
68   let err = anyhow!("Failed to resolve actor for {}", identifier);
69   Err(LemmyError::from_error_message(err, "failed_to_resolve"))
70 }