2 fetcher::{deletable_apub_object::DeletableApubObject, should_refetch_actor},
4 APUB_JSON_CONTENT_TYPE,
8 use lemmy_api_common::blocking;
9 use lemmy_db_queries::{ApubObject, DbPool};
10 use lemmy_db_schema::DbUrl;
11 use lemmy_utils::{request::retry, settings::structs::Settings, LemmyError};
12 use lemmy_websocket::LemmyContext;
13 use reqwest::StatusCode;
14 use serde::{Deserialize, Serialize};
16 fmt::{Debug, Display, Formatter},
22 /// Maximum number of HTTP requests allowed to handle a single incoming activity (or a single object
23 /// fetch through the search). This should be configurable.
24 static REQUEST_LIMIT: i32 = 25;
26 #[derive(Clone, PartialEq, Serialize, Deserialize, Debug)]
27 pub struct ObjectId<Kind>(Url, #[serde(skip)] PhantomData<Kind>)
29 Kind: FromApub + ApubObject + DeletableApubObject + Send + 'static,
30 for<'de2> <Kind as FromApub>::ApubType: serde::Deserialize<'de2>;
32 impl<Kind> ObjectId<Kind>
34 Kind: FromApub + ApubObject + DeletableApubObject + Send + 'static,
35 for<'de> <Kind as FromApub>::ApubType: serde::Deserialize<'de>,
37 pub fn new<T>(url: T) -> Self
41 ObjectId(url.into(), PhantomData::<Kind>)
44 pub fn inner(&self) -> &Url {
48 /// Fetches an activitypub object, either from local database (if possible), or over http.
49 pub(crate) async fn dereference(
51 context: &LemmyContext,
52 request_counter: &mut i32,
53 ) -> Result<Kind, LemmyError> {
54 let db_object = self.dereference_locally(context.pool()).await?;
56 // if its a local object, only fetch it from the database and not over http
57 if self.0.domain() == Some(&Settings::get().get_hostname_without_port()?) {
58 return match db_object {
59 None => Err(NotFound {}.into()),
64 if let Some(object) = db_object {
65 if let Some(last_refreshed_at) = object.last_refreshed_at() {
66 // TODO: rename to should_refetch_object()
67 if should_refetch_actor(last_refreshed_at) {
69 .dereference_remotely(context, request_counter, Some(object))
76 .dereference_remotely(context, request_counter, None)
81 /// returning none means the object was not found in local db
82 async fn dereference_locally(&self, pool: &DbPool) -> Result<Option<Kind>, LemmyError> {
83 let id: DbUrl = self.0.clone().into();
84 let object = blocking(pool, move |conn| ApubObject::read_from_apub_id(conn, &id)).await?;
87 Err(NotFound {}) => Ok(None),
88 Err(e) => Err(e.into()),
92 async fn dereference_remotely(
94 context: &LemmyContext,
95 request_counter: &mut i32,
96 db_object: Option<Kind>,
97 ) -> Result<Kind, LemmyError> {
98 // dont fetch local objects this way
99 debug_assert!(self.0.domain() != Some(&Settings::get().hostname));
101 *request_counter += 1;
102 if *request_counter > REQUEST_LIMIT {
103 return Err(LemmyError::from(anyhow!("Request limit reached")));
109 .get(self.0.as_str())
110 .header("Accept", APUB_JSON_CONTENT_TYPE)
111 .timeout(Duration::from_secs(60))
116 if res.status() == StatusCode::GONE {
117 if let Some(db_object) = db_object {
118 db_object.delete(context).await?;
120 return Err(anyhow!("Fetched remote object {} which was deleted", self).into());
123 let res2: Kind::ApubType = res.json().await?;
125 Ok(Kind::from_apub(&res2, context, self.inner(), request_counter).await?)
129 impl<Kind> Display for ObjectId<Kind>
131 Kind: FromApub + ApubObject + DeletableApubObject + Send + 'static,
132 for<'de> <Kind as FromApub>::ApubType: serde::Deserialize<'de>,
134 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
135 write!(f, "{}", self.0.to_string())
139 impl<Kind> From<ObjectId<Kind>> for Url
141 Kind: FromApub + ApubObject + DeletableApubObject + Send + 'static,
142 for<'de> <Kind as FromApub>::ApubType: serde::Deserialize<'de>,
144 fn from(id: ObjectId<Kind>) -> Self {
149 impl<Kind> From<ObjectId<Kind>> for DbUrl
151 Kind: FromApub + ApubObject + DeletableApubObject + Send + 'static,
152 for<'de> <Kind as FromApub>::ApubType: serde::Deserialize<'de>,
154 fn from(id: ObjectId<Kind>) -> Self {