2 fetcher::webfinger::webfinger_resolve_actor,
4 objects::{comment::ApubComment, community::ApubCommunity, person::ApubPerson, post::ApubPost},
5 protocol::objects::{group::Group, note::Note, page::Page, person::Person},
7 use activitypub_federation::{core::object_id::ObjectId, traits::ApubObject};
8 use chrono::NaiveDateTime;
9 use lemmy_api_common::context::LemmyContext;
10 use lemmy_utils::error::LemmyError;
11 use serde::Deserialize;
14 /// Converts search query to object id. The query can either be an URL, which will be treated as
15 /// ObjectId directly, or a webfinger identifier (@user@example.com or !community@example.com)
16 /// which gets resolved to an URL.
17 #[tracing::instrument(skip_all)]
18 pub(crate) async fn search_query_to_object_id(
21 context: &LemmyContext,
22 ) -> Result<SearchableObjects, LemmyError> {
23 let request_counter = &mut 0;
24 let object_id = match Url::parse(query) {
25 // its already an url, just go with it
26 Ok(url) => ObjectId::new(url),
28 // not an url, try to resolve via webfinger
29 let mut chars = query.chars();
30 let kind = chars.next();
31 let identifier = chars.as_str();
34 webfinger_resolve_actor::<ApubPerson>(identifier, local_only, context, request_counter)
38 webfinger_resolve_actor::<ApubCommunity>(identifier, local_only, context, request_counter)
41 _ => return Err(LemmyError::from_message("invalid query")),
47 object_id.dereference_local(context).await
50 .dereference(context, local_instance(context).await, request_counter)
55 /// The types of ActivityPub objects that can be fetched directly by searching for their ID.
57 pub(crate) enum SearchableObjects {
59 Community(ApubCommunity),
64 #[derive(Deserialize)]
66 pub(crate) enum SearchableApubTypes {
73 #[async_trait::async_trait(?Send)]
74 impl ApubObject for SearchableObjects {
75 type DataType = LemmyContext;
76 type ApubType = SearchableApubTypes;
78 type Error = LemmyError;
80 fn last_refreshed_at(&self) -> Option<NaiveDateTime> {
82 SearchableObjects::Person(p) => p.last_refreshed_at(),
83 SearchableObjects::Community(c) => c.last_refreshed_at(),
84 SearchableObjects::Post(p) => p.last_refreshed_at(),
85 SearchableObjects::Comment(c) => c.last_refreshed_at(),
89 // TODO: this is inefficient, because if the object is not in local db, it will run 4 db queries
90 // before finally returning an error. it would be nice if we could check all 4 tables in
92 // we could skip this and always return an error, but then it would always fetch objects
93 // over http, and not be able to mark objects as deleted that were deleted by remote server.
94 #[tracing::instrument(skip_all)]
95 async fn read_from_apub_id(
97 context: &LemmyContext,
98 ) -> Result<Option<Self>, LemmyError> {
99 let c = ApubCommunity::read_from_apub_id(object_id.clone(), context).await?;
101 return Ok(Some(SearchableObjects::Community(c)));
103 let p = ApubPerson::read_from_apub_id(object_id.clone(), context).await?;
105 return Ok(Some(SearchableObjects::Person(p)));
107 let p = ApubPost::read_from_apub_id(object_id.clone(), context).await?;
109 return Ok(Some(SearchableObjects::Post(p)));
111 let c = ApubComment::read_from_apub_id(object_id, context).await?;
113 return Ok(Some(SearchableObjects::Comment(c)));
118 #[tracing::instrument(skip_all)]
119 async fn delete(self, data: &Self::DataType) -> Result<(), LemmyError> {
121 SearchableObjects::Person(p) => p.delete(data).await,
122 SearchableObjects::Community(c) => c.delete(data).await,
123 SearchableObjects::Post(p) => p.delete(data).await,
124 SearchableObjects::Comment(c) => c.delete(data).await,
128 async fn into_apub(self, _data: &Self::DataType) -> Result<Self::ApubType, LemmyError> {
132 #[tracing::instrument(skip_all)]
134 apub: &Self::ApubType,
135 expected_domain: &Url,
136 data: &Self::DataType,
137 request_counter: &mut i32,
138 ) -> Result<(), LemmyError> {
140 SearchableApubTypes::Group(a) => {
141 ApubCommunity::verify(a, expected_domain, data, request_counter).await
143 SearchableApubTypes::Person(a) => {
144 ApubPerson::verify(a, expected_domain, data, request_counter).await
146 SearchableApubTypes::Page(a) => {
147 ApubPost::verify(a, expected_domain, data, request_counter).await
149 SearchableApubTypes::Note(a) => {
150 ApubComment::verify(a, expected_domain, data, request_counter).await
155 #[tracing::instrument(skip_all)]
157 apub: Self::ApubType,
158 context: &LemmyContext,
160 ) -> Result<Self, LemmyError> {
161 use SearchableApubTypes as SAT;
162 use SearchableObjects as SO;
164 SAT::Group(g) => SO::Community(ApubCommunity::from_apub(g, context, rc).await?),
165 SAT::Person(p) => SO::Person(ApubPerson::from_apub(p, context, rc).await?),
166 SAT::Page(p) => SO::Post(ApubPost::from_apub(p, context, rc).await?),
167 SAT::Note(n) => SO::Comment(ApubComment::from_apub(n, context, rc).await?),