]> Untitled Git - lemmy.git/blob - crates/apub/src/fetcher/search.rs
Dont make webfinger request when viewing community/user profile (fixes #1896) (#2049)
[lemmy.git] / crates / apub / src / fetcher / search.rs
1 use crate::{
2   fetcher::webfinger::webfinger_resolve_actor,
3   objects::{comment::ApubComment, community::ApubCommunity, person::ApubPerson, post::ApubPost},
4   protocol::objects::{group::Group, note::Note, page::Page, person::Person},
5 };
6 use chrono::NaiveDateTime;
7 use lemmy_apub_lib::{object_id::ObjectId, traits::ApubObject};
8 use lemmy_utils::LemmyError;
9 use lemmy_websocket::LemmyContext;
10 use serde::Deserialize;
11 use url::Url;
12
13 /// Attempt to parse the query as URL, and fetch an ActivityPub object from it.
14 ///
15 /// Some working examples for use with the `docker/federation/` setup:
16 /// http://lemmy_alpha:8541/c/main, or !main@lemmy_alpha:8541
17 /// http://lemmy_beta:8551/u/lemmy_alpha, or @lemmy_beta@lemmy_beta:8551
18 /// http://lemmy_gamma:8561/post/3
19 /// http://lemmy_delta:8571/comment/2
20 #[tracing::instrument(skip_all)]
21 pub async fn search_by_apub_id(
22   query: &str,
23   context: &LemmyContext,
24 ) -> Result<SearchableObjects, LemmyError> {
25   let request_counter = &mut 0;
26   match Url::parse(query) {
27     Ok(url) => {
28       ObjectId::new(url)
29         .dereference(context, context.client(), request_counter)
30         .await
31     }
32     Err(_) => {
33       let (kind, identifier) = query.split_at(1);
34       match kind {
35         "@" => {
36           let id =
37             webfinger_resolve_actor::<ApubPerson>(identifier, context, request_counter).await?;
38           Ok(SearchableObjects::Person(
39             ObjectId::new(id)
40               .dereference(context, context.client(), request_counter)
41               .await?,
42           ))
43         }
44         "!" => {
45           let id =
46             webfinger_resolve_actor::<ApubCommunity>(identifier, context, request_counter).await?;
47           Ok(SearchableObjects::Community(
48             ObjectId::new(id)
49               .dereference(context, context.client(), request_counter)
50               .await?,
51           ))
52         }
53         _ => Err(LemmyError::from_message("invalid query")),
54       }
55     }
56   }
57 }
58
59 /// The types of ActivityPub objects that can be fetched directly by searching for their ID.
60 #[derive(Debug)]
61 pub enum SearchableObjects {
62   Person(ApubPerson),
63   Community(ApubCommunity),
64   Post(ApubPost),
65   Comment(ApubComment),
66 }
67
68 #[derive(Deserialize)]
69 #[serde(untagged)]
70 pub enum SearchableApubTypes {
71   Group(Group),
72   Person(Person),
73   Page(Page),
74   Note(Note),
75 }
76
77 #[async_trait::async_trait(?Send)]
78 impl ApubObject for SearchableObjects {
79   type DataType = LemmyContext;
80   type ApubType = SearchableApubTypes;
81   type TombstoneType = ();
82
83   fn last_refreshed_at(&self) -> Option<NaiveDateTime> {
84     match self {
85       SearchableObjects::Person(p) => p.last_refreshed_at(),
86       SearchableObjects::Community(c) => c.last_refreshed_at(),
87       SearchableObjects::Post(p) => p.last_refreshed_at(),
88       SearchableObjects::Comment(c) => c.last_refreshed_at(),
89     }
90   }
91
92   // TODO: this is inefficient, because if the object is not in local db, it will run 4 db queries
93   //       before finally returning an error. it would be nice if we could check all 4 tables in
94   //       a single query.
95   //       we could skip this and always return an error, but then it would always fetch objects
96   //       over http, and not be able to mark objects as deleted that were deleted by remote server.
97   #[tracing::instrument(skip_all)]
98   async fn read_from_apub_id(
99     object_id: Url,
100     context: &LemmyContext,
101   ) -> Result<Option<Self>, LemmyError> {
102     let c = ApubCommunity::read_from_apub_id(object_id.clone(), context).await?;
103     if let Some(c) = c {
104       return Ok(Some(SearchableObjects::Community(c)));
105     }
106     let p = ApubPerson::read_from_apub_id(object_id.clone(), context).await?;
107     if let Some(p) = p {
108       return Ok(Some(SearchableObjects::Person(p)));
109     }
110     let p = ApubPost::read_from_apub_id(object_id.clone(), context).await?;
111     if let Some(p) = p {
112       return Ok(Some(SearchableObjects::Post(p)));
113     }
114     let c = ApubComment::read_from_apub_id(object_id, context).await?;
115     if let Some(c) = c {
116       return Ok(Some(SearchableObjects::Comment(c)));
117     }
118     Ok(None)
119   }
120
121   #[tracing::instrument(skip_all)]
122   async fn delete(self, data: &Self::DataType) -> Result<(), LemmyError> {
123     match self {
124       SearchableObjects::Person(p) => p.delete(data).await,
125       SearchableObjects::Community(c) => c.delete(data).await,
126       SearchableObjects::Post(p) => p.delete(data).await,
127       SearchableObjects::Comment(c) => c.delete(data).await,
128     }
129   }
130
131   async fn into_apub(self, _data: &Self::DataType) -> Result<Self::ApubType, LemmyError> {
132     unimplemented!()
133   }
134
135   fn to_tombstone(&self) -> Result<Self::TombstoneType, LemmyError> {
136     unimplemented!()
137   }
138
139   #[tracing::instrument(skip_all)]
140   async fn verify(
141     apub: &Self::ApubType,
142     expected_domain: &Url,
143     data: &Self::DataType,
144     request_counter: &mut i32,
145   ) -> Result<(), LemmyError> {
146     match apub {
147       SearchableApubTypes::Group(a) => {
148         ApubCommunity::verify(a, expected_domain, data, request_counter).await
149       }
150       SearchableApubTypes::Person(a) => {
151         ApubPerson::verify(a, expected_domain, data, request_counter).await
152       }
153       SearchableApubTypes::Page(a) => {
154         ApubPost::verify(a, expected_domain, data, request_counter).await
155       }
156       SearchableApubTypes::Note(a) => {
157         ApubComment::verify(a, expected_domain, data, request_counter).await
158       }
159     }
160   }
161
162   #[tracing::instrument(skip_all)]
163   async fn from_apub(
164     apub: Self::ApubType,
165     context: &LemmyContext,
166     rc: &mut i32,
167   ) -> Result<Self, LemmyError> {
168     use SearchableApubTypes as SAT;
169     use SearchableObjects as SO;
170     Ok(match apub {
171       SAT::Group(g) => SO::Community(ApubCommunity::from_apub(g, context, rc).await?),
172       SAT::Person(p) => SO::Person(ApubPerson::from_apub(p, context, rc).await?),
173       SAT::Page(p) => SO::Post(ApubPost::from_apub(p, context, rc).await?),
174       SAT::Note(n) => SO::Comment(ApubComment::from_apub(n, context, rc).await?),
175     })
176   }
177 }