fedimovies/src/activitypub/handlers/announce.rs

121 lines
3.7 KiB
Rust

use serde::Deserialize;
use serde_json::Value;
use mitra_config::Config;
use crate::activitypub::{
fetcher::helpers::{get_or_import_profile_by_actor_id, import_post},
identifiers::parse_local_object_id,
receiver::deserialize_into_object_id,
vocabulary::{CREATE, DELETE, DISLIKE, LIKE, NOTE, UNDO, UPDATE},
};
use crate::database::{DatabaseClient, DatabaseError};
use crate::errors::ValidationError;
use crate::media::MediaStorage;
use crate::models::{
posts::queries::{
create_post,
get_post_by_remote_object_id,
},
posts::types::PostCreateData,
};
use super::HandlerResult;
#[derive(Deserialize)]
struct Announce {
id: String,
actor: String,
#[serde(deserialize_with = "deserialize_into_object_id")]
object: String,
}
pub async fn handle_announce(
config: &Config,
db_client: &mut impl DatabaseClient,
activity: Value,
) -> HandlerResult {
if let Some(CREATE | DELETE | DISLIKE | LIKE | UNDO | UPDATE) =
activity["object"]["type"].as_str()
{
// Ignore wrapped activities from Lemmy
// https://codeberg.org/fediverse/fep/src/branch/main/feps/fep-1b12.md
return Ok(None);
};
let activity: Announce = serde_json::from_value(activity)
.map_err(|_| ValidationError("unexpected activity structure"))?;
let repost_object_id = activity.id;
match get_post_by_remote_object_id(
db_client,
&repost_object_id,
).await {
Ok(_) => return Ok(None), // Ignore if repost already exists
Err(DatabaseError::NotFound(_)) => (),
Err(other_error) => return Err(other_error.into()),
};
let author = get_or_import_profile_by_actor_id(
db_client,
&config.instance(),
&MediaStorage::from(config),
&activity.actor,
).await?;
let post_id = match parse_local_object_id(
&config.instance_url(),
&activity.object,
) {
Ok(post_id) => post_id,
Err(_) => {
// Try to get remote post
let post = import_post(config, db_client, activity.object, None).await?;
post.id
},
};
let repost_data = PostCreateData::repost(
post_id,
Some(repost_object_id.clone()),
);
match create_post(db_client, &author.id, repost_data).await {
Ok(_) => Ok(Some(NOTE)),
Err(DatabaseError::AlreadyExists("post")) => {
// Ignore activity if repost already exists (with a different
// object ID, or due to race condition in a handler).
log::warn!("repost already exists: {}", repost_object_id);
Ok(None)
},
// May return "post not found" error if post if not public
Err(other_error) => Err(other_error.into()),
}
}
#[cfg(test)]
mod tests {
use serde_json::json;
use super::*;
#[test]
fn test_deserialize_announce() {
let activity_raw = json!({
"type": "Announce",
"id": "https://example.com/activities/321",
"actor": "https://example.com/users/1",
"object": "https://test.org/objects/999",
});
let activity: Announce = serde_json::from_value(activity_raw).unwrap();
assert_eq!(activity.object, "https://test.org/objects/999");
}
#[test]
fn test_deserialize_announce_nested() {
let activity_raw = json!({
"type": "Announce",
"id": "https://example.com/activities/321",
"actor": "https://example.com/users/1",
"object": {
"type": "Note",
"id": "https://test.org/objects/999",
},
});
let activity: Announce = serde_json::from_value(activity_raw).unwrap();
assert_eq!(activity.object, "https://test.org/objects/999");
}
}