2022-05-02 17:31:31 +00:00
|
|
|
use std::collections::HashMap;
|
2021-12-28 18:34:13 +00:00
|
|
|
|
2023-04-25 13:49:35 +00:00
|
|
|
use fedimovies_config::{Config, Instance};
|
|
|
|
use fedimovies_models::{
|
2023-03-30 20:27:17 +00:00
|
|
|
database::{DatabaseClient, DatabaseError},
|
|
|
|
posts::helpers::get_local_post_by_id,
|
|
|
|
posts::queries::get_post_by_remote_object_id,
|
|
|
|
posts::types::Post,
|
2023-04-24 15:35:32 +00:00
|
|
|
profiles::queries::{get_profile_by_acct, get_profile_by_remote_actor_id},
|
2023-03-30 20:27:17 +00:00
|
|
|
profiles::types::DbActorProfile,
|
|
|
|
};
|
2023-02-18 23:52:48 +00:00
|
|
|
|
2023-04-24 15:35:32 +00:00
|
|
|
use super::fetchers::{
|
|
|
|
fetch_actor, fetch_object, fetch_outbox, perform_webfinger_query, FetchError,
|
|
|
|
};
|
2022-10-23 22:18:01 +00:00
|
|
|
use crate::activitypub::{
|
2023-01-10 21:26:42 +00:00
|
|
|
actors::helpers::{create_remote_profile, update_remote_profile},
|
2023-02-26 22:57:41 +00:00
|
|
|
handlers::create::{get_object_links, handle_note},
|
2022-10-23 22:18:01 +00:00
|
|
|
identifiers::parse_local_object_id,
|
2023-04-10 23:04:41 +00:00
|
|
|
receiver::{handle_activity, HandlerError},
|
2023-01-14 23:31:11 +00:00
|
|
|
types::Object,
|
2022-07-08 20:32:08 +00:00
|
|
|
};
|
2022-12-03 22:09:42 +00:00
|
|
|
use crate::errors::ValidationError;
|
2023-03-25 21:50:10 +00:00
|
|
|
use crate::media::MediaStorage;
|
2023-01-10 21:26:42 +00:00
|
|
|
use crate::webfinger::types::ActorAddress;
|
2021-12-28 18:34:13 +00:00
|
|
|
|
2021-12-28 18:34:13 +00:00
|
|
|
pub async fn get_or_import_profile_by_actor_id(
|
2023-01-21 00:23:15 +00:00
|
|
|
db_client: &mut impl DatabaseClient,
|
2021-12-28 18:34:13 +00:00
|
|
|
instance: &Instance,
|
2023-03-25 21:50:10 +00:00
|
|
|
storage: &MediaStorage,
|
2021-12-28 18:34:13 +00:00
|
|
|
actor_id: &str,
|
2022-10-23 22:18:01 +00:00
|
|
|
) -> Result<DbActorProfile, HandlerError> {
|
2022-01-21 11:00:32 +00:00
|
|
|
if actor_id.starts_with(&instance.url()) {
|
2022-10-23 22:18:01 +00:00
|
|
|
return Err(HandlerError::LocalObject);
|
2022-01-21 11:00:32 +00:00
|
|
|
};
|
2023-04-24 15:35:32 +00:00
|
|
|
let profile = match get_profile_by_remote_actor_id(db_client, actor_id).await {
|
2022-07-08 20:32:08 +00:00
|
|
|
Ok(profile) => {
|
|
|
|
if profile.possibly_outdated() {
|
2022-07-22 21:03:19 +00:00
|
|
|
// Try to re-fetch actor profile
|
|
|
|
match fetch_actor(instance, actor_id).await {
|
|
|
|
Ok(actor) => {
|
|
|
|
log::info!("re-fetched profile {}", profile.acct);
|
2023-04-24 15:35:32 +00:00
|
|
|
let profile_updated =
|
|
|
|
update_remote_profile(db_client, instance, storage, profile, actor)
|
|
|
|
.await?;
|
2022-07-22 21:03:19 +00:00
|
|
|
profile_updated
|
2023-04-24 15:35:32 +00:00
|
|
|
}
|
2022-07-22 21:03:19 +00:00
|
|
|
Err(err) => {
|
|
|
|
// Ignore error and return stored profile
|
2023-04-24 15:35:32 +00:00
|
|
|
log::warn!("failed to re-fetch {} ({})", profile.acct, err,);
|
2022-07-22 21:03:19 +00:00
|
|
|
profile
|
2023-04-24 15:35:32 +00:00
|
|
|
}
|
2022-07-22 21:03:19 +00:00
|
|
|
}
|
2022-07-08 20:32:08 +00:00
|
|
|
} else {
|
|
|
|
profile
|
|
|
|
}
|
2023-04-24 15:35:32 +00:00
|
|
|
}
|
2021-12-28 18:34:13 +00:00
|
|
|
Err(DatabaseError::NotFound(_)) => {
|
2022-07-09 21:24:37 +00:00
|
|
|
let actor = fetch_actor(instance, actor_id).await?;
|
2022-10-03 21:21:20 +00:00
|
|
|
let actor_address = actor.address()?;
|
2022-11-24 01:51:43 +00:00
|
|
|
let acct = actor_address.acct(&instance.hostname());
|
2022-10-03 21:21:20 +00:00
|
|
|
match get_profile_by_acct(db_client, &acct).await {
|
2022-07-09 20:29:42 +00:00
|
|
|
Ok(profile) => {
|
|
|
|
// WARNING: Possible actor ID change
|
|
|
|
log::info!("re-fetched profile {}", profile.acct);
|
2023-04-24 15:35:32 +00:00
|
|
|
let profile_updated =
|
|
|
|
update_remote_profile(db_client, instance, storage, profile, actor).await?;
|
2022-07-22 21:03:19 +00:00
|
|
|
profile_updated
|
2023-04-24 15:35:32 +00:00
|
|
|
}
|
2022-07-09 20:29:42 +00:00
|
|
|
Err(DatabaseError::NotFound(_)) => {
|
2022-10-03 21:21:20 +00:00
|
|
|
log::info!("fetched profile {}", acct);
|
2023-04-24 15:35:32 +00:00
|
|
|
let profile =
|
|
|
|
create_remote_profile(db_client, instance, storage, actor).await?;
|
2022-07-09 20:29:42 +00:00
|
|
|
profile
|
2023-04-24 15:35:32 +00:00
|
|
|
}
|
2022-07-09 20:29:42 +00:00
|
|
|
Err(other_error) => return Err(other_error.into()),
|
|
|
|
}
|
2023-04-24 15:35:32 +00:00
|
|
|
}
|
2021-12-28 18:34:13 +00:00
|
|
|
Err(other_error) => return Err(other_error.into()),
|
|
|
|
};
|
|
|
|
Ok(profile)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Fetches actor profile and saves it into database
|
|
|
|
pub async fn import_profile_by_actor_address(
|
2023-01-21 00:23:15 +00:00
|
|
|
db_client: &mut impl DatabaseClient,
|
2021-12-28 18:34:13 +00:00
|
|
|
instance: &Instance,
|
2023-03-25 21:50:10 +00:00
|
|
|
storage: &MediaStorage,
|
2021-12-28 18:34:13 +00:00
|
|
|
actor_address: &ActorAddress,
|
2022-10-23 22:18:01 +00:00
|
|
|
) -> Result<DbActorProfile, HandlerError> {
|
2022-11-24 01:51:43 +00:00
|
|
|
if actor_address.hostname == instance.hostname() {
|
2022-10-23 22:18:01 +00:00
|
|
|
return Err(HandlerError::LocalObject);
|
2022-01-21 11:00:32 +00:00
|
|
|
};
|
2022-07-08 23:14:23 +00:00
|
|
|
let actor_id = perform_webfinger_query(instance, actor_address).await?;
|
2022-07-09 21:24:37 +00:00
|
|
|
let actor = fetch_actor(instance, &actor_id).await?;
|
2022-11-24 01:51:43 +00:00
|
|
|
let profile_acct = actor.address()?.acct(&instance.hostname());
|
|
|
|
if profile_acct != actor_address.acct(&instance.hostname()) {
|
2021-12-28 18:34:13 +00:00
|
|
|
// Redirected to different server
|
2022-07-27 13:21:12 +00:00
|
|
|
match get_profile_by_acct(db_client, &profile_acct).await {
|
2021-12-28 18:34:13 +00:00
|
|
|
Ok(profile) => return Ok(profile),
|
|
|
|
Err(DatabaseError::NotFound(_)) => (),
|
|
|
|
Err(other_error) => return Err(other_error.into()),
|
|
|
|
};
|
|
|
|
};
|
2022-07-27 13:21:12 +00:00
|
|
|
log::info!("fetched profile {}", profile_acct);
|
2023-04-24 15:35:32 +00:00
|
|
|
let profile = create_remote_profile(db_client, instance, storage, actor).await?;
|
2021-12-28 18:34:13 +00:00
|
|
|
Ok(profile)
|
|
|
|
}
|
2022-05-02 17:31:31 +00:00
|
|
|
|
2023-02-26 20:15:51 +00:00
|
|
|
// Works with local profiles
|
2023-01-11 02:16:03 +00:00
|
|
|
pub async fn get_or_import_profile_by_actor_address(
|
2023-01-21 00:23:15 +00:00
|
|
|
db_client: &mut impl DatabaseClient,
|
2023-01-11 02:16:03 +00:00
|
|
|
instance: &Instance,
|
2023-03-25 21:50:10 +00:00
|
|
|
storage: &MediaStorage,
|
2023-01-11 02:16:03 +00:00
|
|
|
actor_address: &ActorAddress,
|
|
|
|
) -> Result<DbActorProfile, HandlerError> {
|
|
|
|
let acct = actor_address.acct(&instance.hostname());
|
2023-04-24 15:35:32 +00:00
|
|
|
let profile = match get_profile_by_acct(db_client, &acct).await {
|
2023-01-11 02:16:03 +00:00
|
|
|
Ok(profile) => profile,
|
2023-01-11 19:49:12 +00:00
|
|
|
Err(db_error @ DatabaseError::NotFound(_)) => {
|
|
|
|
if actor_address.hostname == instance.hostname() {
|
|
|
|
return Err(db_error.into());
|
|
|
|
};
|
2023-04-24 15:35:32 +00:00
|
|
|
import_profile_by_actor_address(db_client, instance, storage, actor_address).await?
|
|
|
|
}
|
2023-01-11 02:16:03 +00:00
|
|
|
Err(other_error) => return Err(other_error.into()),
|
|
|
|
};
|
|
|
|
Ok(profile)
|
|
|
|
}
|
|
|
|
|
2023-03-22 22:45:43 +00:00
|
|
|
pub async fn get_post_by_object_id(
|
|
|
|
db_client: &impl DatabaseClient,
|
|
|
|
instance_url: &str,
|
|
|
|
object_id: &str,
|
|
|
|
) -> Result<Post, DatabaseError> {
|
|
|
|
match parse_local_object_id(instance_url, object_id) {
|
|
|
|
Ok(post_id) => {
|
|
|
|
// Local post
|
|
|
|
let post = get_local_post_by_id(db_client, &post_id).await?;
|
|
|
|
Ok(post)
|
2023-04-24 15:35:32 +00:00
|
|
|
}
|
2023-03-22 22:45:43 +00:00
|
|
|
Err(_) => {
|
|
|
|
// Remote post
|
|
|
|
let post = get_post_by_remote_object_id(db_client, object_id).await?;
|
|
|
|
Ok(post)
|
2023-04-24 15:35:32 +00:00
|
|
|
}
|
2023-03-22 22:45:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-26 13:52:30 +00:00
|
|
|
const RECURSION_DEPTH_MAX: usize = 50;
|
|
|
|
|
2022-05-02 17:31:31 +00:00
|
|
|
pub async fn import_post(
|
2023-01-17 23:14:18 +00:00
|
|
|
db_client: &mut impl DatabaseClient,
|
2023-03-26 00:52:09 +00:00
|
|
|
instance: &Instance,
|
|
|
|
storage: &MediaStorage,
|
2023-04-25 11:19:04 +00:00
|
|
|
tmdb_api_key: Option<String>,
|
|
|
|
default_movie_user_password: Option<String>,
|
2022-05-02 17:31:31 +00:00
|
|
|
object_id: String,
|
|
|
|
object_received: Option<Object>,
|
2022-10-23 22:18:01 +00:00
|
|
|
) -> Result<Post, HandlerError> {
|
2022-10-16 23:07:26 +00:00
|
|
|
if parse_local_object_id(&instance.url(), &object_id).is_ok() {
|
2022-10-23 22:18:01 +00:00
|
|
|
return Err(HandlerError::LocalObject);
|
2022-10-16 23:07:26 +00:00
|
|
|
};
|
|
|
|
|
2022-10-17 01:07:04 +00:00
|
|
|
let mut queue = vec![object_id]; // LIFO queue
|
2022-12-29 17:23:15 +00:00
|
|
|
let mut fetch_count = 0;
|
2022-05-02 17:31:31 +00:00
|
|
|
let mut maybe_object = object_received;
|
2022-10-17 01:07:04 +00:00
|
|
|
let mut objects: Vec<Object> = vec![];
|
2022-05-02 17:31:31 +00:00
|
|
|
let mut redirects: HashMap<String, String> = HashMap::new();
|
|
|
|
let mut posts = vec![];
|
|
|
|
|
|
|
|
// Fetch ancestors by going through inReplyTo references
|
|
|
|
// TODO: fetch replies too
|
|
|
|
#[allow(clippy::while_let_loop)]
|
|
|
|
loop {
|
2022-10-17 01:07:04 +00:00
|
|
|
let object_id = match queue.pop() {
|
2022-05-02 17:31:31 +00:00
|
|
|
Some(object_id) => {
|
2022-10-17 01:07:04 +00:00
|
|
|
if objects.iter().any(|object| object.id == object_id) {
|
2023-02-26 22:57:41 +00:00
|
|
|
// Can happen due to redirections
|
2022-10-17 01:07:04 +00:00
|
|
|
log::warn!("loop detected");
|
|
|
|
continue;
|
|
|
|
};
|
2022-10-16 23:07:26 +00:00
|
|
|
if let Ok(post_id) = parse_local_object_id(&instance.url(), &object_id) {
|
2022-05-02 17:31:31 +00:00
|
|
|
// Object is a local post
|
2022-10-16 23:07:26 +00:00
|
|
|
// Verify post exists, return error if it doesn't
|
2022-11-21 18:55:06 +00:00
|
|
|
get_local_post_by_id(db_client, &post_id).await?;
|
2022-10-17 01:07:04 +00:00
|
|
|
continue;
|
2022-08-21 22:51:40 +00:00
|
|
|
};
|
2023-04-24 15:35:32 +00:00
|
|
|
match get_post_by_remote_object_id(db_client, &object_id).await {
|
2022-05-02 17:31:31 +00:00
|
|
|
Ok(post) => {
|
|
|
|
// Object already fetched
|
|
|
|
if objects.len() == 0 {
|
|
|
|
// Return post corresponding to initial object ID
|
|
|
|
return Ok(post);
|
|
|
|
};
|
2022-10-17 01:07:04 +00:00
|
|
|
continue;
|
2023-04-24 15:35:32 +00:00
|
|
|
}
|
2022-05-02 17:31:31 +00:00
|
|
|
Err(DatabaseError::NotFound(_)) => (),
|
|
|
|
Err(other_error) => return Err(other_error.into()),
|
|
|
|
};
|
|
|
|
object_id
|
2023-04-24 15:35:32 +00:00
|
|
|
}
|
2022-05-02 17:31:31 +00:00
|
|
|
None => {
|
|
|
|
// No object to fetch
|
|
|
|
break;
|
2023-04-24 15:35:32 +00:00
|
|
|
}
|
2022-05-02 17:31:31 +00:00
|
|
|
};
|
|
|
|
let object = match maybe_object {
|
|
|
|
Some(object) => object,
|
|
|
|
None => {
|
2023-01-26 13:52:30 +00:00
|
|
|
if fetch_count >= RECURSION_DEPTH_MAX {
|
2022-12-29 17:23:15 +00:00
|
|
|
// TODO: create tombstone
|
2023-01-26 13:52:30 +00:00
|
|
|
return Err(FetchError::RecursionError.into());
|
2022-12-29 17:23:15 +00:00
|
|
|
};
|
2023-04-24 15:35:32 +00:00
|
|
|
let object = fetch_object(instance, &object_id).await.map_err(|err| {
|
|
|
|
log::warn!("{}", err);
|
2023-04-26 10:55:42 +00:00
|
|
|
ValidationError("failed to fetch object".into())
|
2023-04-24 15:35:32 +00:00
|
|
|
})?;
|
2022-05-02 17:31:31 +00:00
|
|
|
log::info!("fetched object {}", object.id);
|
2023-04-24 15:35:32 +00:00
|
|
|
fetch_count += 1;
|
2022-05-02 17:31:31 +00:00
|
|
|
object
|
2023-04-24 15:35:32 +00:00
|
|
|
}
|
2022-05-02 17:31:31 +00:00
|
|
|
};
|
|
|
|
if object.id != object_id {
|
|
|
|
// ID of fetched object doesn't match requested ID
|
|
|
|
// Add IDs to the map of redirects
|
|
|
|
redirects.insert(object_id, object.id.clone());
|
2022-10-17 01:07:04 +00:00
|
|
|
queue.push(object.id.clone());
|
2022-05-02 17:31:31 +00:00
|
|
|
// Don't re-fetch object on the next iteration
|
|
|
|
maybe_object = Some(object);
|
2022-10-17 01:07:04 +00:00
|
|
|
continue;
|
|
|
|
};
|
|
|
|
if let Some(ref object_id) = object.in_reply_to {
|
|
|
|
// Fetch parent object on next iteration
|
|
|
|
queue.push(object_id.to_owned());
|
2022-10-17 18:57:25 +00:00
|
|
|
};
|
2023-02-28 00:20:59 +00:00
|
|
|
for object_id in get_object_links(&object) {
|
2023-02-26 22:57:41 +00:00
|
|
|
// Fetch linked objects after fetching current thread
|
|
|
|
queue.insert(0, object_id);
|
2023-04-24 15:35:32 +00:00
|
|
|
}
|
2022-10-17 01:07:04 +00:00
|
|
|
maybe_object = None;
|
|
|
|
objects.push(object);
|
2023-04-24 15:35:32 +00:00
|
|
|
}
|
2022-05-02 17:31:31 +00:00
|
|
|
let initial_object_id = objects[0].id.clone();
|
|
|
|
|
|
|
|
// Objects are ordered according to their place in reply tree,
|
|
|
|
// starting with the root
|
|
|
|
objects.reverse();
|
|
|
|
for object in objects {
|
2023-04-25 11:19:04 +00:00
|
|
|
let post = handle_note(
|
|
|
|
db_client,
|
|
|
|
instance,
|
|
|
|
storage,
|
|
|
|
tmdb_api_key.clone(),
|
|
|
|
default_movie_user_password.clone(),
|
|
|
|
object,
|
|
|
|
&redirects,
|
|
|
|
)
|
|
|
|
.await?;
|
2022-05-02 17:31:31 +00:00
|
|
|
posts.push(post);
|
2023-04-24 15:35:32 +00:00
|
|
|
}
|
2022-05-02 17:31:31 +00:00
|
|
|
|
2023-04-24 15:35:32 +00:00
|
|
|
let initial_post = posts
|
|
|
|
.into_iter()
|
2022-05-02 17:31:31 +00:00
|
|
|
.find(|post| post.object_id.as_ref() == Some(&initial_object_id))
|
|
|
|
.unwrap();
|
|
|
|
Ok(initial_post)
|
|
|
|
}
|
2023-04-10 23:04:41 +00:00
|
|
|
|
|
|
|
pub async fn import_from_outbox(
|
|
|
|
config: &Config,
|
|
|
|
db_client: &mut impl DatabaseClient,
|
|
|
|
actor_id: &str,
|
2023-04-12 23:37:53 +00:00
|
|
|
limit: usize,
|
2023-04-10 23:04:41 +00:00
|
|
|
) -> Result<(), HandlerError> {
|
|
|
|
let instance = config.instance();
|
|
|
|
let actor = fetch_actor(&instance, actor_id).await?;
|
2023-04-12 23:37:53 +00:00
|
|
|
let activities = fetch_outbox(&instance, &actor.outbox, limit).await?;
|
2023-04-10 23:04:41 +00:00
|
|
|
log::info!("fetched {} activities", activities.len());
|
|
|
|
for activity in activities {
|
2023-04-26 10:55:42 +00:00
|
|
|
let activity_actor = activity["actor"].as_str().ok_or(ValidationError(
|
|
|
|
"actor property is missing from activity".to_string(),
|
|
|
|
))?;
|
2023-04-10 23:04:41 +00:00
|
|
|
if activity_actor != actor.id {
|
|
|
|
log::warn!("activity doesn't belong to outbox owner");
|
|
|
|
continue;
|
|
|
|
};
|
|
|
|
handle_activity(
|
2023-04-24 15:35:32 +00:00
|
|
|
config, db_client, &activity, true, // is authenticated
|
|
|
|
)
|
|
|
|
.await
|
|
|
|
.unwrap_or_else(|error| {
|
|
|
|
log::warn!("failed to process activity ({}): {}", error, activity,);
|
2023-04-12 23:27:34 +00:00
|
|
|
});
|
2023-04-24 15:35:32 +00:00
|
|
|
}
|
2023-04-10 23:04:41 +00:00
|
|
|
Ok(())
|
|
|
|
}
|