-pub(crate) mod community;
-mod fetch;
-pub(crate) mod objects;
-pub mod search;
-pub(crate) mod user;
-
-use crate::{
- fetcher::{
- community::get_or_fetch_and_upsert_community,
- fetch::FetchError,
- user::get_or_fetch_and_upsert_user,
- },
- ActorType,
+use activitypub_federation::{
+ config::Data,
+ fetch::webfinger::webfinger_resolve_actor,
+ traits::{Actor, Object},
};
-use chrono::NaiveDateTime;
-use http::StatusCode;
-use lemmy_db_schema::naive_now;
-use lemmy_utils::LemmyError;
-use lemmy_websocket::LemmyContext;
-use serde::Deserialize;
-use url::Url;
+use diesel::NotFound;
+use itertools::Itertools;
+use lemmy_api_common::context::LemmyContext;
+use lemmy_db_schema::traits::ApubActor;
+use lemmy_db_views::structs::LocalUserView;
+use lemmy_utils::error::LemmyError;
-static ACTOR_REFETCH_INTERVAL_SECONDS: i64 = 24 * 60 * 60;
-static ACTOR_REFETCH_INTERVAL_SECONDS_DEBUG: i64 = 10;
+pub mod post_or_comment;
+pub mod search;
+pub mod user_or_community;
-fn is_deleted<Response>(fetch_response: &Result<Response, FetchError>) -> bool
+/// Resolve actor identifier like `!news@example.com` to user or community object.
+///
+/// In case the requesting user is logged in and the object was not found locally, it is attempted
+/// to fetch via webfinger from the original instance.
+#[tracing::instrument(skip_all)]
+pub async fn resolve_actor_identifier<ActorType, DbActor>(
+ identifier: &str,
+ context: &Data<LemmyContext>,
+ local_user_view: &Option<LocalUserView>,
+ include_deleted: bool,
+) -> Result<ActorType, LemmyError>
where
- Response: for<'de> Deserialize<'de>,
+ ActorType: Object<DataType = LemmyContext, Error = LemmyError>
+ + Object
+ + Actor
+ + From<DbActor>
+ + Send
+ + 'static,
+ for<'de2> <ActorType as Object>::Kind: serde::Deserialize<'de2>,
+ DbActor: ApubActor + Send + 'static,
{
- if let Err(e) = fetch_response {
- if let Some(status) = e.status_code {
- if status == StatusCode::GONE {
- return true;
- }
+ // remote actor
+ if identifier.contains('@') {
+ let (name, domain) = identifier
+ .splitn(2, '@')
+ .collect_tuple()
+ .expect("invalid query");
+ let actor = DbActor::read_from_name_and_domain(context.pool(), name, domain).await;
+ if actor.is_ok() {
+ Ok(actor?.into())
+ } else if local_user_view.is_some() {
+ // Fetch the actor from its home instance using webfinger
+ let actor: ActorType = webfinger_resolve_actor(identifier, context).await?;
+ Ok(actor)
+ } else {
+ Err(NotFound.into())
}
}
- false
-}
-
-/// Get a remote actor from its apub ID (either a user or a community). Thin wrapper around
-/// `get_or_fetch_and_upsert_user()` and `get_or_fetch_and_upsert_community()`.
-///
-/// If it exists locally and `!should_refetch_actor()`, it is returned directly from the database.
-/// Otherwise it is fetched from the remote instance, stored and returned.
-pub(crate) async fn get_or_fetch_and_upsert_actor(
- apub_id: &Url,
- context: &LemmyContext,
- recursion_counter: &mut i32,
-) -> Result<Box<dyn ActorType>, LemmyError> {
- let community = get_or_fetch_and_upsert_community(apub_id, context, recursion_counter).await;
- let actor: Box<dyn ActorType> = match community {
- Ok(c) => Box::new(c),
- Err(_) => Box::new(get_or_fetch_and_upsert_user(apub_id, context, recursion_counter).await?),
- };
- Ok(actor)
-}
-
-/// Determines when a remote actor should be refetched from its instance. In release builds, this is
-/// `ACTOR_REFETCH_INTERVAL_SECONDS` after the last refetch, in debug builds
-/// `ACTOR_REFETCH_INTERVAL_SECONDS_DEBUG`.
-///
-/// TODO it won't pick up new avatars, summaries etc until a day after.
-/// Actors need an "update" activity pushed to other servers to fix this.
-fn should_refetch_actor(last_refreshed: NaiveDateTime) -> bool {
- let update_interval = if cfg!(debug_assertions) {
- // avoid infinite loop when fetching community outbox
- chrono::Duration::seconds(ACTOR_REFETCH_INTERVAL_SECONDS_DEBUG)
- } else {
- chrono::Duration::seconds(ACTOR_REFETCH_INTERVAL_SECONDS)
- };
- last_refreshed.lt(&(naive_now() - update_interval))
+ // local actor
+ else {
+ let identifier = identifier.to_string();
+ Ok(
+ DbActor::read_from_name(context.pool(), &identifier, include_deleted)
+ .await?
+ .into(),
+ )
+ }
}