mirror of
https://github.com/LemmyNet/lemmy
synced 2024-11-27 06:50:20 +00:00
Refactor apub code, split up large files
This commit is contained in:
parent
cc9bb56d2d
commit
0dda2577e1
40 changed files with 2172 additions and 2044 deletions
2
lemmy_apub/src/activities/mod.rs
Normal file
2
lemmy_apub/src/activities/mod.rs
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
pub mod receive;
|
||||||
|
pub mod send;
|
|
@ -1,14 +1,15 @@
|
||||||
use crate::inbox::{
|
use crate::{
|
||||||
activities::{
|
activities::receive::{
|
||||||
create::receive_create,
|
create::receive_create,
|
||||||
delete::receive_delete,
|
delete::receive_delete,
|
||||||
dislike::receive_dislike,
|
dislike::receive_dislike,
|
||||||
like::receive_like,
|
like::receive_like,
|
||||||
|
receive_unhandled_activity,
|
||||||
remove::receive_remove,
|
remove::receive_remove,
|
||||||
undo::receive_undo,
|
undo::receive_undo,
|
||||||
update::receive_update,
|
update::receive_update,
|
||||||
},
|
},
|
||||||
shared_inbox::{get_community_id_from_activity, receive_unhandled_activity},
|
inbox::shared_inbox::get_community_id_from_activity,
|
||||||
};
|
};
|
||||||
use activitystreams::{
|
use activitystreams::{
|
||||||
activity::*,
|
activity::*,
|
|
@ -1,9 +1,6 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
inbox::shared_inbox::{
|
activities::receive::{announce_if_community_is_local, receive_unhandled_activity},
|
||||||
announce_if_community_is_local,
|
inbox::shared_inbox::get_user_from_activity,
|
||||||
get_user_from_activity,
|
|
||||||
receive_unhandled_activity,
|
|
||||||
},
|
|
||||||
ActorType,
|
ActorType,
|
||||||
FromApub,
|
FromApub,
|
||||||
PageExt,
|
PageExt,
|
|
@ -1,10 +1,7 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
|
activities::receive::{announce_if_community_is_local, receive_unhandled_activity},
|
||||||
fetcher::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post},
|
fetcher::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post},
|
||||||
inbox::shared_inbox::{
|
inbox::shared_inbox::get_user_from_activity,
|
||||||
announce_if_community_is_local,
|
|
||||||
get_user_from_activity,
|
|
||||||
receive_unhandled_activity,
|
|
||||||
},
|
|
||||||
ActorType,
|
ActorType,
|
||||||
FromApub,
|
FromApub,
|
||||||
GroupExt,
|
GroupExt,
|
|
@ -1,10 +1,7 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
|
activities::receive::{announce_if_community_is_local, receive_unhandled_activity},
|
||||||
fetcher::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post},
|
fetcher::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post},
|
||||||
inbox::shared_inbox::{
|
inbox::shared_inbox::get_user_from_activity,
|
||||||
announce_if_community_is_local,
|
|
||||||
get_user_from_activity,
|
|
||||||
receive_unhandled_activity,
|
|
||||||
},
|
|
||||||
FromApub,
|
FromApub,
|
||||||
PageExt,
|
PageExt,
|
||||||
};
|
};
|
|
@ -1,10 +1,7 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
|
activities::receive::{announce_if_community_is_local, receive_unhandled_activity},
|
||||||
fetcher::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post},
|
fetcher::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post},
|
||||||
inbox::shared_inbox::{
|
inbox::shared_inbox::get_user_from_activity,
|
||||||
announce_if_community_is_local,
|
|
||||||
get_user_from_activity,
|
|
||||||
receive_unhandled_activity,
|
|
||||||
},
|
|
||||||
FromApub,
|
FromApub,
|
||||||
PageExt,
|
PageExt,
|
||||||
};
|
};
|
65
lemmy_apub/src/activities/receive/mod.rs
Normal file
65
lemmy_apub/src/activities/receive/mod.rs
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
use crate::{fetcher::get_or_fetch_and_upsert_community, ActorType};
|
||||||
|
use activitystreams::{
|
||||||
|
base::{Extends, ExtendsExt},
|
||||||
|
object::{AsObject, ObjectExt},
|
||||||
|
};
|
||||||
|
use actix_web::HttpResponse;
|
||||||
|
use anyhow::Context;
|
||||||
|
use lemmy_db::user::User_;
|
||||||
|
use lemmy_utils::{location_info, LemmyError};
|
||||||
|
use lemmy_websocket::LemmyContext;
|
||||||
|
use log::debug;
|
||||||
|
use serde::Serialize;
|
||||||
|
use std::fmt::Debug;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
pub mod announce;
|
||||||
|
pub mod create;
|
||||||
|
pub mod delete;
|
||||||
|
pub mod dislike;
|
||||||
|
pub mod like;
|
||||||
|
pub mod remove;
|
||||||
|
pub mod undo;
|
||||||
|
mod undo_comment;
|
||||||
|
mod undo_post;
|
||||||
|
pub mod update;
|
||||||
|
|
||||||
|
fn receive_unhandled_activity<A>(activity: A) -> Result<HttpResponse, LemmyError>
|
||||||
|
where
|
||||||
|
A: Debug,
|
||||||
|
{
|
||||||
|
debug!("received unhandled activity type: {:?}", activity);
|
||||||
|
Ok(HttpResponse::NotImplemented().finish())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn announce_if_community_is_local<T, Kind>(
|
||||||
|
activity: T,
|
||||||
|
user: &User_,
|
||||||
|
context: &LemmyContext,
|
||||||
|
) -> Result<(), LemmyError>
|
||||||
|
where
|
||||||
|
T: AsObject<Kind>,
|
||||||
|
T: Extends<Kind>,
|
||||||
|
Kind: Serialize,
|
||||||
|
<T as Extends<Kind>>::Error: From<serde_json::Error> + Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
let cc = activity.cc().context(location_info!())?;
|
||||||
|
let cc = cc.as_many().context(location_info!())?;
|
||||||
|
let community_followers_uri = cc
|
||||||
|
.first()
|
||||||
|
.context(location_info!())?
|
||||||
|
.as_xsd_any_uri()
|
||||||
|
.context(location_info!())?;
|
||||||
|
// TODO: this is hacky but seems to be the only way to get the community ID
|
||||||
|
let community_uri = community_followers_uri
|
||||||
|
.to_string()
|
||||||
|
.replace("/followers", "");
|
||||||
|
let community = get_or_fetch_and_upsert_community(&Url::parse(&community_uri)?, context).await?;
|
||||||
|
|
||||||
|
if community.local {
|
||||||
|
community
|
||||||
|
.send_announce(activity.into_any_base()?, &user, context)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -1,11 +1,7 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
|
activities::receive::{announce_if_community_is_local, receive_unhandled_activity},
|
||||||
fetcher::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post},
|
fetcher::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post},
|
||||||
inbox::shared_inbox::{
|
inbox::shared_inbox::{get_community_id_from_activity, get_user_from_activity},
|
||||||
announce_if_community_is_local,
|
|
||||||
get_community_id_from_activity,
|
|
||||||
get_user_from_activity,
|
|
||||||
receive_unhandled_activity,
|
|
||||||
},
|
|
||||||
ActorType,
|
ActorType,
|
||||||
FromApub,
|
FromApub,
|
||||||
GroupExt,
|
GroupExt,
|
||||||
|
@ -45,7 +41,7 @@ pub async fn receive_remove(
|
||||||
let actor = get_user_from_activity(&remove, context).await?;
|
let actor = get_user_from_activity(&remove, context).await?;
|
||||||
let community = get_community_id_from_activity(&remove)?;
|
let community = get_community_id_from_activity(&remove)?;
|
||||||
if actor.actor_id()?.domain() != community.domain() {
|
if actor.actor_id()?.domain() != community.domain() {
|
||||||
return Err(anyhow!("Remove activities are only allowed on local objects").into());
|
return Err(anyhow!("Remove receive are only allowed on local objects").into());
|
||||||
}
|
}
|
||||||
|
|
||||||
match remove.object().as_single_kind_str() {
|
match remove.object().as_single_kind_str() {
|
269
lemmy_apub/src/activities/receive/undo.rs
Normal file
269
lemmy_apub/src/activities/receive/undo.rs
Normal file
|
@ -0,0 +1,269 @@
|
||||||
|
use crate::{
|
||||||
|
activities::receive::{
|
||||||
|
announce_if_community_is_local,
|
||||||
|
receive_unhandled_activity,
|
||||||
|
undo_comment::*,
|
||||||
|
undo_post::*,
|
||||||
|
},
|
||||||
|
inbox::shared_inbox::get_user_from_activity,
|
||||||
|
ActorType,
|
||||||
|
FromApub,
|
||||||
|
GroupExt,
|
||||||
|
};
|
||||||
|
use activitystreams::{
|
||||||
|
activity::*,
|
||||||
|
base::{AnyBase, AsBase},
|
||||||
|
prelude::*,
|
||||||
|
};
|
||||||
|
use actix_web::HttpResponse;
|
||||||
|
use anyhow::{anyhow, Context};
|
||||||
|
use lemmy_db::{
|
||||||
|
community::{Community, CommunityForm},
|
||||||
|
community_view::CommunityView,
|
||||||
|
naive_now,
|
||||||
|
Crud,
|
||||||
|
};
|
||||||
|
use lemmy_structs::{blocking, community::CommunityResponse};
|
||||||
|
use lemmy_utils::{location_info, LemmyError};
|
||||||
|
use lemmy_websocket::{messages::SendCommunityRoomMessage, LemmyContext, UserOperation};
|
||||||
|
|
||||||
|
pub async fn receive_undo(
|
||||||
|
activity: AnyBase,
|
||||||
|
context: &LemmyContext,
|
||||||
|
) -> Result<HttpResponse, LemmyError> {
|
||||||
|
let undo = Undo::from_any_base(activity)?.context(location_info!())?;
|
||||||
|
match undo.object().as_single_kind_str() {
|
||||||
|
Some("Delete") => receive_undo_delete(undo, context).await,
|
||||||
|
Some("Remove") => receive_undo_remove(undo, context).await,
|
||||||
|
Some("Like") => receive_undo_like(undo, context).await,
|
||||||
|
Some("Dislike") => receive_undo_dislike(undo, context).await,
|
||||||
|
_ => receive_unhandled_activity(undo),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_is_undo_valid<T, A>(outer_activity: &Undo, inner_activity: &T) -> Result<(), LemmyError>
|
||||||
|
where
|
||||||
|
T: AsBase<A> + ActorAndObjectRef,
|
||||||
|
{
|
||||||
|
let outer_actor = outer_activity.actor()?;
|
||||||
|
let outer_actor_uri = outer_actor
|
||||||
|
.as_single_xsd_any_uri()
|
||||||
|
.context(location_info!())?;
|
||||||
|
|
||||||
|
let inner_actor = inner_activity.actor()?;
|
||||||
|
let inner_actor_uri = inner_actor
|
||||||
|
.as_single_xsd_any_uri()
|
||||||
|
.context(location_info!())?;
|
||||||
|
|
||||||
|
if outer_actor_uri.domain() != inner_actor_uri.domain() {
|
||||||
|
Err(anyhow!("Cant undo receive from a different instance").into())
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn receive_undo_delete(
|
||||||
|
undo: Undo,
|
||||||
|
context: &LemmyContext,
|
||||||
|
) -> Result<HttpResponse, LemmyError> {
|
||||||
|
let delete = Delete::from_any_base(undo.object().to_owned().one().context(location_info!())?)?
|
||||||
|
.context(location_info!())?;
|
||||||
|
check_is_undo_valid(&undo, &delete)?;
|
||||||
|
let type_ = delete
|
||||||
|
.object()
|
||||||
|
.as_single_kind_str()
|
||||||
|
.context(location_info!())?;
|
||||||
|
match type_ {
|
||||||
|
"Note" => receive_undo_delete_comment(undo, &delete, context).await,
|
||||||
|
"Page" => receive_undo_delete_post(undo, &delete, context).await,
|
||||||
|
"Group" => receive_undo_delete_community(undo, &delete, context).await,
|
||||||
|
d => Err(anyhow!("Undo Delete type {} not supported", d).into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn receive_undo_remove(
|
||||||
|
undo: Undo,
|
||||||
|
context: &LemmyContext,
|
||||||
|
) -> Result<HttpResponse, LemmyError> {
|
||||||
|
let remove = Remove::from_any_base(undo.object().to_owned().one().context(location_info!())?)?
|
||||||
|
.context(location_info!())?;
|
||||||
|
check_is_undo_valid(&undo, &remove)?;
|
||||||
|
|
||||||
|
let type_ = remove
|
||||||
|
.object()
|
||||||
|
.as_single_kind_str()
|
||||||
|
.context(location_info!())?;
|
||||||
|
match type_ {
|
||||||
|
"Note" => receive_undo_remove_comment(undo, &remove, context).await,
|
||||||
|
"Page" => receive_undo_remove_post(undo, &remove, context).await,
|
||||||
|
"Group" => receive_undo_remove_community(undo, &remove, context).await,
|
||||||
|
d => Err(anyhow!("Undo Delete type {} not supported", d).into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn receive_undo_like(undo: Undo, context: &LemmyContext) -> Result<HttpResponse, LemmyError> {
|
||||||
|
let like = Like::from_any_base(undo.object().to_owned().one().context(location_info!())?)?
|
||||||
|
.context(location_info!())?;
|
||||||
|
check_is_undo_valid(&undo, &like)?;
|
||||||
|
|
||||||
|
let type_ = like
|
||||||
|
.object()
|
||||||
|
.as_single_kind_str()
|
||||||
|
.context(location_info!())?;
|
||||||
|
match type_ {
|
||||||
|
"Note" => receive_undo_like_comment(undo, &like, context).await,
|
||||||
|
"Page" => receive_undo_like_post(undo, &like, context).await,
|
||||||
|
d => Err(anyhow!("Undo Delete type {} not supported", d).into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn receive_undo_dislike(
|
||||||
|
undo: Undo,
|
||||||
|
context: &LemmyContext,
|
||||||
|
) -> Result<HttpResponse, LemmyError> {
|
||||||
|
let dislike = Dislike::from_any_base(undo.object().to_owned().one().context(location_info!())?)?
|
||||||
|
.context(location_info!())?;
|
||||||
|
check_is_undo_valid(&undo, &dislike)?;
|
||||||
|
|
||||||
|
let type_ = dislike
|
||||||
|
.object()
|
||||||
|
.as_single_kind_str()
|
||||||
|
.context(location_info!())?;
|
||||||
|
match type_ {
|
||||||
|
"Note" => receive_undo_dislike_comment(undo, &dislike, context).await,
|
||||||
|
"Page" => receive_undo_dislike_post(undo, &dislike, context).await,
|
||||||
|
d => Err(anyhow!("Undo Delete type {} not supported", d).into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn receive_undo_delete_community(
|
||||||
|
undo: Undo,
|
||||||
|
delete: &Delete,
|
||||||
|
context: &LemmyContext,
|
||||||
|
) -> Result<HttpResponse, LemmyError> {
|
||||||
|
let user = get_user_from_activity(delete, context).await?;
|
||||||
|
let group = GroupExt::from_any_base(delete.object().to_owned().one().context(location_info!())?)?
|
||||||
|
.context(location_info!())?;
|
||||||
|
|
||||||
|
let community_actor_id = CommunityForm::from_apub(&group, context, Some(user.actor_id()?))
|
||||||
|
.await?
|
||||||
|
.actor_id
|
||||||
|
.context(location_info!())?;
|
||||||
|
|
||||||
|
let community = blocking(context.pool(), move |conn| {
|
||||||
|
Community::read_from_actor_id(conn, &community_actor_id)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
let community_form = CommunityForm {
|
||||||
|
name: community.name.to_owned(),
|
||||||
|
title: community.title.to_owned(),
|
||||||
|
description: community.description.to_owned(),
|
||||||
|
category_id: community.category_id, // Note: need to keep this due to foreign key constraint
|
||||||
|
creator_id: community.creator_id, // Note: need to keep this due to foreign key constraint
|
||||||
|
removed: None,
|
||||||
|
published: None,
|
||||||
|
updated: Some(naive_now()),
|
||||||
|
deleted: Some(false),
|
||||||
|
nsfw: community.nsfw,
|
||||||
|
actor_id: Some(community.actor_id),
|
||||||
|
local: community.local,
|
||||||
|
private_key: community.private_key,
|
||||||
|
public_key: community.public_key,
|
||||||
|
last_refreshed_at: None,
|
||||||
|
icon: Some(community.icon.to_owned()),
|
||||||
|
banner: Some(community.banner.to_owned()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let community_id = community.id;
|
||||||
|
blocking(context.pool(), move |conn| {
|
||||||
|
Community::update(conn, community_id, &community_form)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
let community_id = community.id;
|
||||||
|
let res = CommunityResponse {
|
||||||
|
community: blocking(context.pool(), move |conn| {
|
||||||
|
CommunityView::read(conn, community_id, None)
|
||||||
|
})
|
||||||
|
.await??,
|
||||||
|
};
|
||||||
|
|
||||||
|
let community_id = res.community.id;
|
||||||
|
|
||||||
|
context.chat_server().do_send(SendCommunityRoomMessage {
|
||||||
|
op: UserOperation::EditCommunity,
|
||||||
|
response: res,
|
||||||
|
community_id,
|
||||||
|
websocket_id: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
announce_if_community_is_local(undo, &user, context).await?;
|
||||||
|
Ok(HttpResponse::Ok().finish())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn receive_undo_remove_community(
|
||||||
|
undo: Undo,
|
||||||
|
remove: &Remove,
|
||||||
|
context: &LemmyContext,
|
||||||
|
) -> Result<HttpResponse, LemmyError> {
|
||||||
|
let mod_ = get_user_from_activity(remove, context).await?;
|
||||||
|
let group = GroupExt::from_any_base(remove.object().to_owned().one().context(location_info!())?)?
|
||||||
|
.context(location_info!())?;
|
||||||
|
|
||||||
|
let community_actor_id = CommunityForm::from_apub(&group, context, Some(mod_.actor_id()?))
|
||||||
|
.await?
|
||||||
|
.actor_id
|
||||||
|
.context(location_info!())?;
|
||||||
|
|
||||||
|
let community = blocking(context.pool(), move |conn| {
|
||||||
|
Community::read_from_actor_id(conn, &community_actor_id)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
let community_form = CommunityForm {
|
||||||
|
name: community.name.to_owned(),
|
||||||
|
title: community.title.to_owned(),
|
||||||
|
description: community.description.to_owned(),
|
||||||
|
category_id: community.category_id, // Note: need to keep this due to foreign key constraint
|
||||||
|
creator_id: community.creator_id, // Note: need to keep this due to foreign key constraint
|
||||||
|
removed: Some(false),
|
||||||
|
published: None,
|
||||||
|
updated: Some(naive_now()),
|
||||||
|
deleted: None,
|
||||||
|
nsfw: community.nsfw,
|
||||||
|
actor_id: Some(community.actor_id),
|
||||||
|
local: community.local,
|
||||||
|
private_key: community.private_key,
|
||||||
|
public_key: community.public_key,
|
||||||
|
last_refreshed_at: None,
|
||||||
|
icon: Some(community.icon.to_owned()),
|
||||||
|
banner: Some(community.banner.to_owned()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let community_id = community.id;
|
||||||
|
blocking(context.pool(), move |conn| {
|
||||||
|
Community::update(conn, community_id, &community_form)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
let community_id = community.id;
|
||||||
|
let res = CommunityResponse {
|
||||||
|
community: blocking(context.pool(), move |conn| {
|
||||||
|
CommunityView::read(conn, community_id, None)
|
||||||
|
})
|
||||||
|
.await??,
|
||||||
|
};
|
||||||
|
|
||||||
|
let community_id = res.community.id;
|
||||||
|
|
||||||
|
context.chat_server().do_send(SendCommunityRoomMessage {
|
||||||
|
op: UserOperation::EditCommunity,
|
||||||
|
response: res,
|
||||||
|
community_id,
|
||||||
|
websocket_id: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
announce_if_community_is_local(undo, &mod_, context).await?;
|
||||||
|
Ok(HttpResponse::Ok().finish())
|
||||||
|
}
|
234
lemmy_apub/src/activities/receive/undo_comment.rs
Normal file
234
lemmy_apub/src/activities/receive/undo_comment.rs
Normal file
|
@ -0,0 +1,234 @@
|
||||||
|
use crate::{
|
||||||
|
activities::receive::announce_if_community_is_local,
|
||||||
|
fetcher::get_or_fetch_and_insert_comment,
|
||||||
|
inbox::shared_inbox::get_user_from_activity,
|
||||||
|
ActorType,
|
||||||
|
FromApub,
|
||||||
|
};
|
||||||
|
use activitystreams::{activity::*, object::Note, prelude::*};
|
||||||
|
use actix_web::HttpResponse;
|
||||||
|
use anyhow::Context;
|
||||||
|
use lemmy_db::{
|
||||||
|
comment::{Comment, CommentForm, CommentLike},
|
||||||
|
comment_view::CommentView,
|
||||||
|
naive_now,
|
||||||
|
Crud,
|
||||||
|
Likeable,
|
||||||
|
};
|
||||||
|
use lemmy_structs::{blocking, comment::CommentResponse};
|
||||||
|
use lemmy_utils::{location_info, LemmyError};
|
||||||
|
use lemmy_websocket::{messages::SendComment, LemmyContext, UserOperation};
|
||||||
|
|
||||||
|
pub(crate) async fn receive_undo_like_comment(
|
||||||
|
undo: Undo,
|
||||||
|
like: &Like,
|
||||||
|
context: &LemmyContext,
|
||||||
|
) -> Result<HttpResponse, LemmyError> {
|
||||||
|
let user = get_user_from_activity(like, context).await?;
|
||||||
|
let note = Note::from_any_base(like.object().to_owned().one().context(location_info!())?)?
|
||||||
|
.context(location_info!())?;
|
||||||
|
|
||||||
|
let comment = CommentForm::from_apub(¬e, context, None).await?;
|
||||||
|
|
||||||
|
let comment_id = get_or_fetch_and_insert_comment(&comment.get_ap_id()?, context)
|
||||||
|
.await?
|
||||||
|
.id;
|
||||||
|
|
||||||
|
let user_id = user.id;
|
||||||
|
blocking(context.pool(), move |conn| {
|
||||||
|
CommentLike::remove(conn, user_id, comment_id)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
// Refetch the view
|
||||||
|
let comment_view = blocking(context.pool(), move |conn| {
|
||||||
|
CommentView::read(conn, comment_id, None)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
// TODO get those recipient actor ids from somewhere
|
||||||
|
let recipient_ids = vec![];
|
||||||
|
let res = CommentResponse {
|
||||||
|
comment: comment_view,
|
||||||
|
recipient_ids,
|
||||||
|
form_id: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
context.chat_server().do_send(SendComment {
|
||||||
|
op: UserOperation::CreateCommentLike,
|
||||||
|
comment: res,
|
||||||
|
websocket_id: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
announce_if_community_is_local(undo, &user, context).await?;
|
||||||
|
Ok(HttpResponse::Ok().finish())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn receive_undo_dislike_comment(
|
||||||
|
undo: Undo,
|
||||||
|
dislike: &Dislike,
|
||||||
|
context: &LemmyContext,
|
||||||
|
) -> Result<HttpResponse, LemmyError> {
|
||||||
|
let user = get_user_from_activity(dislike, context).await?;
|
||||||
|
let note = Note::from_any_base(
|
||||||
|
dislike
|
||||||
|
.object()
|
||||||
|
.to_owned()
|
||||||
|
.one()
|
||||||
|
.context(location_info!())?,
|
||||||
|
)?
|
||||||
|
.context(location_info!())?;
|
||||||
|
|
||||||
|
let comment = CommentForm::from_apub(¬e, context, None).await?;
|
||||||
|
|
||||||
|
let comment_id = get_or_fetch_and_insert_comment(&comment.get_ap_id()?, context)
|
||||||
|
.await?
|
||||||
|
.id;
|
||||||
|
|
||||||
|
let user_id = user.id;
|
||||||
|
blocking(context.pool(), move |conn| {
|
||||||
|
CommentLike::remove(conn, user_id, comment_id)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
// Refetch the view
|
||||||
|
let comment_view = blocking(context.pool(), move |conn| {
|
||||||
|
CommentView::read(conn, comment_id, None)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
// TODO get those recipient actor ids from somewhere
|
||||||
|
let recipient_ids = vec![];
|
||||||
|
let res = CommentResponse {
|
||||||
|
comment: comment_view,
|
||||||
|
recipient_ids,
|
||||||
|
form_id: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
context.chat_server().do_send(SendComment {
|
||||||
|
op: UserOperation::CreateCommentLike,
|
||||||
|
comment: res,
|
||||||
|
websocket_id: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
announce_if_community_is_local(undo, &user, context).await?;
|
||||||
|
Ok(HttpResponse::Ok().finish())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn receive_undo_delete_comment(
|
||||||
|
undo: Undo,
|
||||||
|
delete: &Delete,
|
||||||
|
context: &LemmyContext,
|
||||||
|
) -> Result<HttpResponse, LemmyError> {
|
||||||
|
let user = get_user_from_activity(delete, context).await?;
|
||||||
|
let note = Note::from_any_base(delete.object().to_owned().one().context(location_info!())?)?
|
||||||
|
.context(location_info!())?;
|
||||||
|
|
||||||
|
let comment_ap_id = CommentForm::from_apub(¬e, context, Some(user.actor_id()?))
|
||||||
|
.await?
|
||||||
|
.get_ap_id()?;
|
||||||
|
|
||||||
|
let comment = get_or_fetch_and_insert_comment(&comment_ap_id, context).await?;
|
||||||
|
|
||||||
|
let comment_form = CommentForm {
|
||||||
|
content: comment.content.to_owned(),
|
||||||
|
parent_id: comment.parent_id,
|
||||||
|
post_id: comment.post_id,
|
||||||
|
creator_id: comment.creator_id,
|
||||||
|
removed: None,
|
||||||
|
deleted: Some(false),
|
||||||
|
read: None,
|
||||||
|
published: None,
|
||||||
|
updated: Some(naive_now()),
|
||||||
|
ap_id: Some(comment.ap_id),
|
||||||
|
local: comment.local,
|
||||||
|
};
|
||||||
|
let comment_id = comment.id;
|
||||||
|
blocking(context.pool(), move |conn| {
|
||||||
|
Comment::update(conn, comment_id, &comment_form)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
// Refetch the view
|
||||||
|
let comment_id = comment.id;
|
||||||
|
let comment_view = blocking(context.pool(), move |conn| {
|
||||||
|
CommentView::read(conn, comment_id, None)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
// TODO get those recipient actor ids from somewhere
|
||||||
|
let recipient_ids = vec![];
|
||||||
|
let res = CommentResponse {
|
||||||
|
comment: comment_view,
|
||||||
|
recipient_ids,
|
||||||
|
form_id: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
context.chat_server().do_send(SendComment {
|
||||||
|
op: UserOperation::EditComment,
|
||||||
|
comment: res,
|
||||||
|
websocket_id: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
announce_if_community_is_local(undo, &user, context).await?;
|
||||||
|
Ok(HttpResponse::Ok().finish())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn receive_undo_remove_comment(
|
||||||
|
undo: Undo,
|
||||||
|
remove: &Remove,
|
||||||
|
context: &LemmyContext,
|
||||||
|
) -> Result<HttpResponse, LemmyError> {
|
||||||
|
let mod_ = get_user_from_activity(remove, context).await?;
|
||||||
|
let note = Note::from_any_base(remove.object().to_owned().one().context(location_info!())?)?
|
||||||
|
.context(location_info!())?;
|
||||||
|
|
||||||
|
let comment_ap_id = CommentForm::from_apub(¬e, context, None)
|
||||||
|
.await?
|
||||||
|
.get_ap_id()?;
|
||||||
|
|
||||||
|
let comment = get_or_fetch_and_insert_comment(&comment_ap_id, context).await?;
|
||||||
|
|
||||||
|
let comment_form = CommentForm {
|
||||||
|
content: comment.content.to_owned(),
|
||||||
|
parent_id: comment.parent_id,
|
||||||
|
post_id: comment.post_id,
|
||||||
|
creator_id: comment.creator_id,
|
||||||
|
removed: Some(false),
|
||||||
|
deleted: None,
|
||||||
|
read: None,
|
||||||
|
published: None,
|
||||||
|
updated: Some(naive_now()),
|
||||||
|
ap_id: Some(comment.ap_id),
|
||||||
|
local: comment.local,
|
||||||
|
};
|
||||||
|
let comment_id = comment.id;
|
||||||
|
blocking(context.pool(), move |conn| {
|
||||||
|
Comment::update(conn, comment_id, &comment_form)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
// Refetch the view
|
||||||
|
let comment_id = comment.id;
|
||||||
|
let comment_view = blocking(context.pool(), move |conn| {
|
||||||
|
CommentView::read(conn, comment_id, None)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
// TODO get those recipient actor ids from somewhere
|
||||||
|
let recipient_ids = vec![];
|
||||||
|
let res = CommentResponse {
|
||||||
|
comment: comment_view,
|
||||||
|
recipient_ids,
|
||||||
|
form_id: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
context.chat_server().do_send(SendComment {
|
||||||
|
op: UserOperation::EditComment,
|
||||||
|
comment: res,
|
||||||
|
websocket_id: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
announce_if_community_is_local(undo, &mod_, context).await?;
|
||||||
|
Ok(HttpResponse::Ok().finish())
|
||||||
|
}
|
225
lemmy_apub/src/activities/receive/undo_post.rs
Normal file
225
lemmy_apub/src/activities/receive/undo_post.rs
Normal file
|
@ -0,0 +1,225 @@
|
||||||
|
use crate::{
|
||||||
|
activities::receive::announce_if_community_is_local,
|
||||||
|
fetcher::get_or_fetch_and_insert_post,
|
||||||
|
inbox::shared_inbox::get_user_from_activity,
|
||||||
|
ActorType,
|
||||||
|
FromApub,
|
||||||
|
PageExt,
|
||||||
|
};
|
||||||
|
use activitystreams::{activity::*, prelude::*};
|
||||||
|
use actix_web::HttpResponse;
|
||||||
|
use anyhow::Context;
|
||||||
|
use lemmy_db::{
|
||||||
|
naive_now,
|
||||||
|
post::{Post, PostForm, PostLike},
|
||||||
|
post_view::PostView,
|
||||||
|
Crud,
|
||||||
|
Likeable,
|
||||||
|
};
|
||||||
|
use lemmy_structs::{blocking, post::PostResponse};
|
||||||
|
use lemmy_utils::{location_info, LemmyError};
|
||||||
|
use lemmy_websocket::{messages::SendPost, LemmyContext, UserOperation};
|
||||||
|
|
||||||
|
pub(crate) async fn receive_undo_like_post(
|
||||||
|
undo: Undo,
|
||||||
|
like: &Like,
|
||||||
|
context: &LemmyContext,
|
||||||
|
) -> Result<HttpResponse, LemmyError> {
|
||||||
|
let user = get_user_from_activity(like, context).await?;
|
||||||
|
let page = PageExt::from_any_base(like.object().to_owned().one().context(location_info!())?)?
|
||||||
|
.context(location_info!())?;
|
||||||
|
|
||||||
|
let post = PostForm::from_apub(&page, context, None).await?;
|
||||||
|
|
||||||
|
let post_id = get_or_fetch_and_insert_post(&post.get_ap_id()?, context)
|
||||||
|
.await?
|
||||||
|
.id;
|
||||||
|
|
||||||
|
let user_id = user.id;
|
||||||
|
blocking(context.pool(), move |conn| {
|
||||||
|
PostLike::remove(conn, user_id, post_id)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
// Refetch the view
|
||||||
|
let post_view = blocking(context.pool(), move |conn| {
|
||||||
|
PostView::read(conn, post_id, None)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
let res = PostResponse { post: post_view };
|
||||||
|
|
||||||
|
context.chat_server().do_send(SendPost {
|
||||||
|
op: UserOperation::CreatePostLike,
|
||||||
|
post: res,
|
||||||
|
websocket_id: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
announce_if_community_is_local(undo, &user, context).await?;
|
||||||
|
Ok(HttpResponse::Ok().finish())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn receive_undo_dislike_post(
|
||||||
|
undo: Undo,
|
||||||
|
dislike: &Dislike,
|
||||||
|
context: &LemmyContext,
|
||||||
|
) -> Result<HttpResponse, LemmyError> {
|
||||||
|
let user = get_user_from_activity(dislike, context).await?;
|
||||||
|
let page = PageExt::from_any_base(
|
||||||
|
dislike
|
||||||
|
.object()
|
||||||
|
.to_owned()
|
||||||
|
.one()
|
||||||
|
.context(location_info!())?,
|
||||||
|
)?
|
||||||
|
.context(location_info!())?;
|
||||||
|
|
||||||
|
let post = PostForm::from_apub(&page, context, None).await?;
|
||||||
|
|
||||||
|
let post_id = get_or_fetch_and_insert_post(&post.get_ap_id()?, context)
|
||||||
|
.await?
|
||||||
|
.id;
|
||||||
|
|
||||||
|
let user_id = user.id;
|
||||||
|
blocking(context.pool(), move |conn| {
|
||||||
|
PostLike::remove(conn, user_id, post_id)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
// Refetch the view
|
||||||
|
let post_view = blocking(context.pool(), move |conn| {
|
||||||
|
PostView::read(conn, post_id, None)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
let res = PostResponse { post: post_view };
|
||||||
|
|
||||||
|
context.chat_server().do_send(SendPost {
|
||||||
|
op: UserOperation::CreatePostLike,
|
||||||
|
post: res,
|
||||||
|
websocket_id: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
announce_if_community_is_local(undo, &user, context).await?;
|
||||||
|
Ok(HttpResponse::Ok().finish())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn receive_undo_delete_post(
|
||||||
|
undo: Undo,
|
||||||
|
delete: &Delete,
|
||||||
|
context: &LemmyContext,
|
||||||
|
) -> Result<HttpResponse, LemmyError> {
|
||||||
|
let user = get_user_from_activity(delete, context).await?;
|
||||||
|
let page = PageExt::from_any_base(delete.object().to_owned().one().context(location_info!())?)?
|
||||||
|
.context(location_info!())?;
|
||||||
|
|
||||||
|
let post_ap_id = PostForm::from_apub(&page, context, Some(user.actor_id()?))
|
||||||
|
.await?
|
||||||
|
.get_ap_id()?;
|
||||||
|
|
||||||
|
let post = get_or_fetch_and_insert_post(&post_ap_id, context).await?;
|
||||||
|
|
||||||
|
let post_form = PostForm {
|
||||||
|
name: post.name.to_owned(),
|
||||||
|
url: post.url.to_owned(),
|
||||||
|
body: post.body.to_owned(),
|
||||||
|
creator_id: post.creator_id.to_owned(),
|
||||||
|
community_id: post.community_id,
|
||||||
|
removed: None,
|
||||||
|
deleted: Some(false),
|
||||||
|
nsfw: post.nsfw,
|
||||||
|
locked: None,
|
||||||
|
stickied: None,
|
||||||
|
updated: Some(naive_now()),
|
||||||
|
embed_title: post.embed_title,
|
||||||
|
embed_description: post.embed_description,
|
||||||
|
embed_html: post.embed_html,
|
||||||
|
thumbnail_url: post.thumbnail_url,
|
||||||
|
ap_id: Some(post.ap_id),
|
||||||
|
local: post.local,
|
||||||
|
published: None,
|
||||||
|
};
|
||||||
|
let post_id = post.id;
|
||||||
|
blocking(context.pool(), move |conn| {
|
||||||
|
Post::update(conn, post_id, &post_form)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
// Refetch the view
|
||||||
|
let post_id = post.id;
|
||||||
|
let post_view = blocking(context.pool(), move |conn| {
|
||||||
|
PostView::read(conn, post_id, None)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
let res = PostResponse { post: post_view };
|
||||||
|
|
||||||
|
context.chat_server().do_send(SendPost {
|
||||||
|
op: UserOperation::EditPost,
|
||||||
|
post: res,
|
||||||
|
websocket_id: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
announce_if_community_is_local(undo, &user, context).await?;
|
||||||
|
Ok(HttpResponse::Ok().finish())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn receive_undo_remove_post(
|
||||||
|
undo: Undo,
|
||||||
|
remove: &Remove,
|
||||||
|
context: &LemmyContext,
|
||||||
|
) -> Result<HttpResponse, LemmyError> {
|
||||||
|
let mod_ = get_user_from_activity(remove, context).await?;
|
||||||
|
let page = PageExt::from_any_base(remove.object().to_owned().one().context(location_info!())?)?
|
||||||
|
.context(location_info!())?;
|
||||||
|
|
||||||
|
let post_ap_id = PostForm::from_apub(&page, context, None)
|
||||||
|
.await?
|
||||||
|
.get_ap_id()?;
|
||||||
|
|
||||||
|
let post = get_or_fetch_and_insert_post(&post_ap_id, context).await?;
|
||||||
|
|
||||||
|
let post_form = PostForm {
|
||||||
|
name: post.name.to_owned(),
|
||||||
|
url: post.url.to_owned(),
|
||||||
|
body: post.body.to_owned(),
|
||||||
|
creator_id: post.creator_id.to_owned(),
|
||||||
|
community_id: post.community_id,
|
||||||
|
removed: Some(false),
|
||||||
|
deleted: None,
|
||||||
|
nsfw: post.nsfw,
|
||||||
|
locked: None,
|
||||||
|
stickied: None,
|
||||||
|
updated: Some(naive_now()),
|
||||||
|
embed_title: post.embed_title,
|
||||||
|
embed_description: post.embed_description,
|
||||||
|
embed_html: post.embed_html,
|
||||||
|
thumbnail_url: post.thumbnail_url,
|
||||||
|
ap_id: Some(post.ap_id),
|
||||||
|
local: post.local,
|
||||||
|
published: None,
|
||||||
|
};
|
||||||
|
let post_id = post.id;
|
||||||
|
blocking(context.pool(), move |conn| {
|
||||||
|
Post::update(conn, post_id, &post_form)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
// Refetch the view
|
||||||
|
let post_id = post.id;
|
||||||
|
let post_view = blocking(context.pool(), move |conn| {
|
||||||
|
PostView::read(conn, post_id, None)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
let res = PostResponse { post: post_view };
|
||||||
|
|
||||||
|
context.chat_server().do_send(SendPost {
|
||||||
|
op: UserOperation::EditPost,
|
||||||
|
post: res,
|
||||||
|
websocket_id: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
announce_if_community_is_local(undo, &mod_, context).await?;
|
||||||
|
Ok(HttpResponse::Ok().finish())
|
||||||
|
}
|
|
@ -1,10 +1,7 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
|
activities::receive::{announce_if_community_is_local, receive_unhandled_activity},
|
||||||
fetcher::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post},
|
fetcher::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post},
|
||||||
inbox::shared_inbox::{
|
inbox::shared_inbox::get_user_from_activity,
|
||||||
announce_if_community_is_local,
|
|
||||||
get_user_from_activity,
|
|
||||||
receive_unhandled_activity,
|
|
||||||
},
|
|
||||||
ActorType,
|
ActorType,
|
||||||
FromApub,
|
FromApub,
|
||||||
PageExt,
|
PageExt,
|
|
@ -1,20 +1,10 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
|
activities::send::generate_activity_id,
|
||||||
activity_queue::{send_comment_mentions, send_to_community},
|
activity_queue::{send_comment_mentions, send_to_community},
|
||||||
check_actor_domain,
|
fetcher::get_or_fetch_and_upsert_user,
|
||||||
create_apub_response,
|
|
||||||
create_apub_tombstone_response,
|
|
||||||
create_tombstone,
|
|
||||||
fetch_webfinger_url,
|
|
||||||
fetcher::{
|
|
||||||
get_or_fetch_and_insert_comment,
|
|
||||||
get_or_fetch_and_insert_post,
|
|
||||||
get_or_fetch_and_upsert_user,
|
|
||||||
},
|
|
||||||
generate_activity_id,
|
|
||||||
ActorType,
|
ActorType,
|
||||||
ApubLikeableType,
|
ApubLikeableType,
|
||||||
ApubObjectType,
|
ApubObjectType,
|
||||||
FromApub,
|
|
||||||
ToApub,
|
ToApub,
|
||||||
};
|
};
|
||||||
use activitystreams::{
|
use activitystreams::{
|
||||||
|
@ -30,174 +20,25 @@ use activitystreams::{
|
||||||
},
|
},
|
||||||
base::AnyBase,
|
base::AnyBase,
|
||||||
link::Mention,
|
link::Mention,
|
||||||
object::{kind::NoteType, Note, Tombstone},
|
|
||||||
prelude::*,
|
prelude::*,
|
||||||
public,
|
public,
|
||||||
};
|
};
|
||||||
use actix_web::{body::Body, web, web::Path, HttpResponse};
|
use anyhow::anyhow;
|
||||||
use anyhow::Context;
|
|
||||||
use diesel::result::Error::NotFound;
|
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use lemmy_db::{
|
use lemmy_db::{comment::Comment, community::Community, post::Post, user::User_, Crud};
|
||||||
comment::{Comment, CommentForm},
|
use lemmy_structs::{blocking, WebFingerResponse};
|
||||||
community::Community,
|
|
||||||
post::Post,
|
|
||||||
user::User_,
|
|
||||||
Crud,
|
|
||||||
DbPool,
|
|
||||||
};
|
|
||||||
use lemmy_structs::blocking;
|
|
||||||
use lemmy_utils::{
|
use lemmy_utils::{
|
||||||
location_info,
|
request::{retry, RecvError},
|
||||||
utils::{convert_datetime, remove_slurs, scrape_text_for_mentions, MentionData},
|
settings::Settings,
|
||||||
|
utils::{scrape_text_for_mentions, MentionData},
|
||||||
LemmyError,
|
LemmyError,
|
||||||
};
|
};
|
||||||
use lemmy_websocket::LemmyContext;
|
use lemmy_websocket::LemmyContext;
|
||||||
use log::debug;
|
use log::debug;
|
||||||
use serde::Deserialize;
|
use reqwest::Client;
|
||||||
use serde_json::Error;
|
use serde_json::Error;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
pub struct CommentQuery {
|
|
||||||
comment_id: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return the post json over HTTP.
|
|
||||||
pub async fn get_apub_comment(
|
|
||||||
info: Path<CommentQuery>,
|
|
||||||
context: web::Data<LemmyContext>,
|
|
||||||
) -> Result<HttpResponse<Body>, LemmyError> {
|
|
||||||
let id = info.comment_id.parse::<i32>()?;
|
|
||||||
let comment = blocking(context.pool(), move |conn| Comment::read(conn, id)).await??;
|
|
||||||
if !comment.local {
|
|
||||||
return Err(NotFound.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
if !comment.deleted {
|
|
||||||
Ok(create_apub_response(
|
|
||||||
&comment.to_apub(context.pool()).await?,
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
Ok(create_apub_tombstone_response(&comment.to_tombstone()?))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
|
||||||
impl ToApub for Comment {
|
|
||||||
type Response = Note;
|
|
||||||
|
|
||||||
async fn to_apub(&self, pool: &DbPool) -> Result<Note, LemmyError> {
|
|
||||||
let mut comment = Note::new();
|
|
||||||
|
|
||||||
let creator_id = self.creator_id;
|
|
||||||
let creator = blocking(pool, move |conn| User_::read(conn, creator_id)).await??;
|
|
||||||
|
|
||||||
let post_id = self.post_id;
|
|
||||||
let post = blocking(pool, move |conn| Post::read(conn, post_id)).await??;
|
|
||||||
|
|
||||||
let community_id = post.community_id;
|
|
||||||
let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??;
|
|
||||||
|
|
||||||
// Add a vector containing some important info to the "in_reply_to" field
|
|
||||||
// [post_ap_id, Option(parent_comment_ap_id)]
|
|
||||||
let mut in_reply_to_vec = vec![post.ap_id];
|
|
||||||
|
|
||||||
if let Some(parent_id) = self.parent_id {
|
|
||||||
let parent_comment = blocking(pool, move |conn| Comment::read(conn, parent_id)).await??;
|
|
||||||
|
|
||||||
in_reply_to_vec.push(parent_comment.ap_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
comment
|
|
||||||
// Not needed when the Post is embedded in a collection (like for community outbox)
|
|
||||||
.set_context(activitystreams::context())
|
|
||||||
.set_id(Url::parse(&self.ap_id)?)
|
|
||||||
.set_published(convert_datetime(self.published))
|
|
||||||
.set_to(community.actor_id)
|
|
||||||
.set_many_in_reply_tos(in_reply_to_vec)
|
|
||||||
.set_content(self.content.to_owned())
|
|
||||||
.set_attributed_to(creator.actor_id);
|
|
||||||
|
|
||||||
if let Some(u) = self.updated {
|
|
||||||
comment.set_updated(convert_datetime(u));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(comment)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn to_tombstone(&self) -> Result<Tombstone, LemmyError> {
|
|
||||||
create_tombstone(self.deleted, &self.ap_id, self.updated, NoteType::Note)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
|
||||||
impl FromApub for CommentForm {
|
|
||||||
type ApubType = Note;
|
|
||||||
|
|
||||||
/// Parse an ActivityPub note received from another instance into a Lemmy comment
|
|
||||||
async fn from_apub(
|
|
||||||
note: &Note,
|
|
||||||
context: &LemmyContext,
|
|
||||||
expected_domain: Option<Url>,
|
|
||||||
) -> Result<CommentForm, LemmyError> {
|
|
||||||
let creator_actor_id = ¬e
|
|
||||||
.attributed_to()
|
|
||||||
.context(location_info!())?
|
|
||||||
.as_single_xsd_any_uri()
|
|
||||||
.context(location_info!())?;
|
|
||||||
|
|
||||||
let creator = get_or_fetch_and_upsert_user(creator_actor_id, context).await?;
|
|
||||||
|
|
||||||
let mut in_reply_tos = note
|
|
||||||
.in_reply_to()
|
|
||||||
.as_ref()
|
|
||||||
.context(location_info!())?
|
|
||||||
.as_many()
|
|
||||||
.context(location_info!())?
|
|
||||||
.iter()
|
|
||||||
.map(|i| i.as_xsd_any_uri().context(""));
|
|
||||||
let post_ap_id = in_reply_tos.next().context(location_info!())??;
|
|
||||||
|
|
||||||
// This post, or the parent comment might not yet exist on this server yet, fetch them.
|
|
||||||
let post = get_or_fetch_and_insert_post(&post_ap_id, context).await?;
|
|
||||||
|
|
||||||
// The 2nd item, if it exists, is the parent comment apub_id
|
|
||||||
// For deeply nested comments, FromApub automatically gets called recursively
|
|
||||||
let parent_id: Option<i32> = match in_reply_tos.next() {
|
|
||||||
Some(parent_comment_uri) => {
|
|
||||||
let parent_comment_ap_id = &parent_comment_uri?;
|
|
||||||
let parent_comment =
|
|
||||||
get_or_fetch_and_insert_comment(&parent_comment_ap_id, context).await?;
|
|
||||||
|
|
||||||
Some(parent_comment.id)
|
|
||||||
}
|
|
||||||
None => None,
|
|
||||||
};
|
|
||||||
let content = note
|
|
||||||
.content()
|
|
||||||
.context(location_info!())?
|
|
||||||
.as_single_xsd_string()
|
|
||||||
.context(location_info!())?
|
|
||||||
.to_string();
|
|
||||||
let content_slurs_removed = remove_slurs(&content);
|
|
||||||
|
|
||||||
Ok(CommentForm {
|
|
||||||
creator_id: creator.id,
|
|
||||||
post_id: post.id,
|
|
||||||
parent_id,
|
|
||||||
content: content_slurs_removed,
|
|
||||||
removed: None,
|
|
||||||
read: None,
|
|
||||||
published: note.published().map(|u| u.to_owned().naive_local()),
|
|
||||||
updated: note.updated().map(|u| u.to_owned().naive_local()),
|
|
||||||
deleted: None,
|
|
||||||
ap_id: Some(check_actor_domain(note, expected_domain)?),
|
|
||||||
local: false,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
#[async_trait::async_trait(?Send)]
|
||||||
impl ApubObjectType for Comment {
|
impl ApubObjectType for Comment {
|
||||||
/// Send out information about a newly created comment, to the followers of the community.
|
/// Send out information about a newly created comment, to the followers of the community.
|
||||||
|
@ -518,3 +359,33 @@ async fn collect_non_local_mentions_and_addresses(
|
||||||
tags,
|
tags,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn fetch_webfinger_url(mention: &MentionData, client: &Client) -> Result<Url, LemmyError> {
|
||||||
|
let fetch_url = format!(
|
||||||
|
"{}://{}/.well-known/webfinger?resource=acct:{}@{}",
|
||||||
|
Settings::get().get_protocol_string(),
|
||||||
|
mention.domain,
|
||||||
|
mention.name,
|
||||||
|
mention.domain
|
||||||
|
);
|
||||||
|
debug!("Fetching webfinger url: {}", &fetch_url);
|
||||||
|
|
||||||
|
let response = retry(|| client.get(&fetch_url).send()).await?;
|
||||||
|
|
||||||
|
let res: WebFingerResponse = response
|
||||||
|
.json()
|
||||||
|
.await
|
||||||
|
.map_err(|e| RecvError(e.to_string()))?;
|
||||||
|
|
||||||
|
let link = res
|
||||||
|
.links
|
||||||
|
.iter()
|
||||||
|
.find(|l| l.type_.eq(&Some("application/activity+json".to_string())))
|
||||||
|
.ok_or_else(|| anyhow!("No application/activity+json link found."))?;
|
||||||
|
link
|
||||||
|
.href
|
||||||
|
.to_owned()
|
||||||
|
.map(|u| Url::parse(&u))
|
||||||
|
.transpose()?
|
||||||
|
.ok_or_else(|| anyhow!("No href found.").into())
|
||||||
|
}
|
224
lemmy_apub/src/activities/send/community.rs
Normal file
224
lemmy_apub/src/activities/send/community.rs
Normal file
|
@ -0,0 +1,224 @@
|
||||||
|
use crate::{
|
||||||
|
activities::send::generate_activity_id,
|
||||||
|
activity_queue::{send_activity_single_dest, send_to_community_followers},
|
||||||
|
check_is_apub_id_valid,
|
||||||
|
fetcher::get_or_fetch_and_upsert_actor,
|
||||||
|
ActorType,
|
||||||
|
ToApub,
|
||||||
|
};
|
||||||
|
use activitystreams::{
|
||||||
|
activity::{
|
||||||
|
kind::{AcceptType, AnnounceType, DeleteType, LikeType, RemoveType, UndoType},
|
||||||
|
Accept,
|
||||||
|
ActorAndObjectRefExt,
|
||||||
|
Announce,
|
||||||
|
Delete,
|
||||||
|
Follow,
|
||||||
|
Remove,
|
||||||
|
Undo,
|
||||||
|
},
|
||||||
|
base::{AnyBase, BaseExt, ExtendsExt},
|
||||||
|
object::ObjectExt,
|
||||||
|
public,
|
||||||
|
};
|
||||||
|
use anyhow::Context;
|
||||||
|
use itertools::Itertools;
|
||||||
|
use lemmy_db::{community::Community, community_view::CommunityFollowerView, user::User_, DbPool};
|
||||||
|
use lemmy_structs::blocking;
|
||||||
|
use lemmy_utils::{location_info, settings::Settings, LemmyError};
|
||||||
|
use lemmy_websocket::LemmyContext;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
#[async_trait::async_trait(?Send)]
|
||||||
|
impl ActorType for Community {
|
||||||
|
fn actor_id_str(&self) -> String {
|
||||||
|
self.actor_id.to_owned()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn public_key(&self) -> Option<String> {
|
||||||
|
self.public_key.to_owned()
|
||||||
|
}
|
||||||
|
fn private_key(&self) -> Option<String> {
|
||||||
|
self.private_key.to_owned()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn user_id(&self) -> i32 {
|
||||||
|
self.creator_id
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn send_follow(
|
||||||
|
&self,
|
||||||
|
_follow_actor_id: &Url,
|
||||||
|
_context: &LemmyContext,
|
||||||
|
) -> Result<(), LemmyError> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn send_unfollow(
|
||||||
|
&self,
|
||||||
|
_follow_actor_id: &Url,
|
||||||
|
_context: &LemmyContext,
|
||||||
|
) -> Result<(), LemmyError> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// As a local community, accept the follow request from a remote user.
|
||||||
|
async fn send_accept_follow(
|
||||||
|
&self,
|
||||||
|
follow: Follow,
|
||||||
|
context: &LemmyContext,
|
||||||
|
) -> Result<(), LemmyError> {
|
||||||
|
let actor_uri = follow
|
||||||
|
.actor()?
|
||||||
|
.as_single_xsd_any_uri()
|
||||||
|
.context(location_info!())?;
|
||||||
|
let actor = get_or_fetch_and_upsert_actor(actor_uri, context).await?;
|
||||||
|
|
||||||
|
let mut accept = Accept::new(self.actor_id.to_owned(), follow.into_any_base()?);
|
||||||
|
let to = actor.get_inbox_url()?;
|
||||||
|
accept
|
||||||
|
.set_context(activitystreams::context())
|
||||||
|
.set_id(generate_activity_id(AcceptType::Accept)?)
|
||||||
|
.set_to(to.clone());
|
||||||
|
|
||||||
|
send_activity_single_dest(accept, self, to, context).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn send_delete(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
|
||||||
|
let group = self.to_apub(context.pool()).await?;
|
||||||
|
|
||||||
|
let mut delete = Delete::new(creator.actor_id.to_owned(), group.into_any_base()?);
|
||||||
|
delete
|
||||||
|
.set_context(activitystreams::context())
|
||||||
|
.set_id(generate_activity_id(DeleteType::Delete)?)
|
||||||
|
.set_to(public())
|
||||||
|
.set_many_ccs(vec![self.get_followers_url()?]);
|
||||||
|
|
||||||
|
send_to_community_followers(delete, self, context, None).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn send_undo_delete(
|
||||||
|
&self,
|
||||||
|
creator: &User_,
|
||||||
|
context: &LemmyContext,
|
||||||
|
) -> Result<(), LemmyError> {
|
||||||
|
let group = self.to_apub(context.pool()).await?;
|
||||||
|
|
||||||
|
let mut delete = Delete::new(creator.actor_id.to_owned(), group.into_any_base()?);
|
||||||
|
delete
|
||||||
|
.set_context(activitystreams::context())
|
||||||
|
.set_id(generate_activity_id(DeleteType::Delete)?)
|
||||||
|
.set_to(public())
|
||||||
|
.set_many_ccs(vec![self.get_followers_url()?]);
|
||||||
|
|
||||||
|
let mut undo = Undo::new(creator.actor_id.to_owned(), delete.into_any_base()?);
|
||||||
|
undo
|
||||||
|
.set_context(activitystreams::context())
|
||||||
|
.set_id(generate_activity_id(UndoType::Undo)?)
|
||||||
|
.set_to(public())
|
||||||
|
.set_many_ccs(vec![self.get_followers_url()?]);
|
||||||
|
|
||||||
|
send_to_community_followers(undo, self, context, None).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn send_remove(&self, mod_: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
|
||||||
|
let group = self.to_apub(context.pool()).await?;
|
||||||
|
|
||||||
|
let mut remove = Remove::new(mod_.actor_id.to_owned(), group.into_any_base()?);
|
||||||
|
remove
|
||||||
|
.set_context(activitystreams::context())
|
||||||
|
.set_id(generate_activity_id(RemoveType::Remove)?)
|
||||||
|
.set_to(public())
|
||||||
|
.set_many_ccs(vec![self.get_followers_url()?]);
|
||||||
|
|
||||||
|
send_to_community_followers(remove, self, context, None).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn send_undo_remove(&self, mod_: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
|
||||||
|
let group = self.to_apub(context.pool()).await?;
|
||||||
|
|
||||||
|
let mut remove = Remove::new(mod_.actor_id.to_owned(), group.into_any_base()?);
|
||||||
|
remove
|
||||||
|
.set_context(activitystreams::context())
|
||||||
|
.set_id(generate_activity_id(RemoveType::Remove)?)
|
||||||
|
.set_to(public())
|
||||||
|
.set_many_ccs(vec![self.get_followers_url()?]);
|
||||||
|
|
||||||
|
// Undo that fake activity
|
||||||
|
let mut undo = Undo::new(mod_.actor_id.to_owned(), remove.into_any_base()?);
|
||||||
|
undo
|
||||||
|
.set_context(activitystreams::context())
|
||||||
|
.set_id(generate_activity_id(LikeType::Like)?)
|
||||||
|
.set_to(public())
|
||||||
|
.set_many_ccs(vec![self.get_followers_url()?]);
|
||||||
|
|
||||||
|
send_to_community_followers(undo, self, context, None).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn send_announce(
|
||||||
|
&self,
|
||||||
|
activity: AnyBase,
|
||||||
|
sender: &User_,
|
||||||
|
context: &LemmyContext,
|
||||||
|
) -> Result<(), LemmyError> {
|
||||||
|
let mut announce = Announce::new(self.actor_id.to_owned(), activity);
|
||||||
|
announce
|
||||||
|
.set_context(activitystreams::context())
|
||||||
|
.set_id(generate_activity_id(AnnounceType::Announce)?)
|
||||||
|
.set_to(public())
|
||||||
|
.set_many_ccs(vec![self.get_followers_url()?]);
|
||||||
|
|
||||||
|
send_to_community_followers(
|
||||||
|
announce,
|
||||||
|
self,
|
||||||
|
context,
|
||||||
|
Some(sender.get_shared_inbox_url()?),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// For a given community, returns the inboxes of all followers.
|
||||||
|
///
|
||||||
|
/// TODO: this function is very badly implemented, we should just store shared_inbox_url in
|
||||||
|
/// CommunityFollowerView
|
||||||
|
async fn get_follower_inboxes(&self, pool: &DbPool) -> Result<Vec<Url>, LemmyError> {
|
||||||
|
let id = self.id;
|
||||||
|
|
||||||
|
let inboxes = blocking(pool, move |conn| {
|
||||||
|
CommunityFollowerView::for_community(conn, id)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
let inboxes = inboxes
|
||||||
|
.into_iter()
|
||||||
|
.filter(|i| !i.user_local)
|
||||||
|
.map(|u| -> Result<Url, LemmyError> {
|
||||||
|
let url = Url::parse(&u.user_actor_id)?;
|
||||||
|
let domain = url.domain().context(location_info!())?;
|
||||||
|
let port = if let Some(port) = url.port() {
|
||||||
|
format!(":{}", port)
|
||||||
|
} else {
|
||||||
|
"".to_string()
|
||||||
|
};
|
||||||
|
Ok(Url::parse(&format!(
|
||||||
|
"{}://{}{}/inbox",
|
||||||
|
Settings::get().get_protocol_string(),
|
||||||
|
domain,
|
||||||
|
port,
|
||||||
|
))?)
|
||||||
|
})
|
||||||
|
.filter_map(Result::ok)
|
||||||
|
// Don't send to blocked instances
|
||||||
|
.filter(|inbox| check_is_apub_id_valid(inbox).is_ok())
|
||||||
|
.unique()
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(inboxes)
|
||||||
|
}
|
||||||
|
}
|
22
lemmy_apub/src/activities/send/mod.rs
Normal file
22
lemmy_apub/src/activities/send/mod.rs
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
use lemmy_utils::settings::Settings;
|
||||||
|
use url::{ParseError, Url};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
pub mod comment;
|
||||||
|
pub mod community;
|
||||||
|
pub mod post;
|
||||||
|
pub mod private_message;
|
||||||
|
pub mod user;
|
||||||
|
|
||||||
|
fn generate_activity_id<T>(kind: T) -> Result<Url, ParseError>
|
||||||
|
where
|
||||||
|
T: ToString,
|
||||||
|
{
|
||||||
|
let id = format!(
|
||||||
|
"{}/receive/{}/{}",
|
||||||
|
Settings::get().get_protocol_and_hostname(),
|
||||||
|
kind.to_string().to_lowercase(),
|
||||||
|
Uuid::new_v4()
|
||||||
|
);
|
||||||
|
Url::parse(&id)
|
||||||
|
}
|
|
@ -1,17 +1,9 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
|
activities::send::generate_activity_id,
|
||||||
activity_queue::send_to_community,
|
activity_queue::send_to_community,
|
||||||
check_actor_domain,
|
|
||||||
create_apub_response,
|
|
||||||
create_apub_tombstone_response,
|
|
||||||
create_tombstone,
|
|
||||||
extensions::page_extension::PageExtension,
|
|
||||||
fetcher::{get_or_fetch_and_upsert_community, get_or_fetch_and_upsert_user},
|
|
||||||
generate_activity_id,
|
|
||||||
ActorType,
|
ActorType,
|
||||||
ApubLikeableType,
|
ApubLikeableType,
|
||||||
ApubObjectType,
|
ApubObjectType,
|
||||||
FromApub,
|
|
||||||
PageExt,
|
|
||||||
ToApub,
|
ToApub,
|
||||||
};
|
};
|
||||||
use activitystreams::{
|
use activitystreams::{
|
||||||
|
@ -25,223 +17,13 @@ use activitystreams::{
|
||||||
Undo,
|
Undo,
|
||||||
Update,
|
Update,
|
||||||
},
|
},
|
||||||
object::{kind::PageType, Image, Page, Tombstone},
|
|
||||||
prelude::*,
|
prelude::*,
|
||||||
public,
|
public,
|
||||||
};
|
};
|
||||||
use activitystreams_ext::Ext1;
|
use lemmy_db::{community::Community, post::Post, user::User_, Crud};
|
||||||
use actix_web::{body::Body, web, HttpResponse};
|
|
||||||
use anyhow::Context;
|
|
||||||
use diesel::result::Error::NotFound;
|
|
||||||
use lemmy_db::{
|
|
||||||
community::Community,
|
|
||||||
post::{Post, PostForm},
|
|
||||||
user::User_,
|
|
||||||
Crud,
|
|
||||||
DbPool,
|
|
||||||
};
|
|
||||||
use lemmy_structs::blocking;
|
use lemmy_structs::blocking;
|
||||||
use lemmy_utils::{
|
use lemmy_utils::LemmyError;
|
||||||
location_info,
|
|
||||||
request::fetch_iframely_and_pictrs_data,
|
|
||||||
utils::{check_slurs, convert_datetime, remove_slurs},
|
|
||||||
LemmyError,
|
|
||||||
};
|
|
||||||
use lemmy_websocket::LemmyContext;
|
use lemmy_websocket::LemmyContext;
|
||||||
use serde::Deserialize;
|
|
||||||
use url::Url;
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
pub struct PostQuery {
|
|
||||||
post_id: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return the post json over HTTP.
|
|
||||||
pub async fn get_apub_post(
|
|
||||||
info: web::Path<PostQuery>,
|
|
||||||
context: web::Data<LemmyContext>,
|
|
||||||
) -> Result<HttpResponse<Body>, LemmyError> {
|
|
||||||
let id = info.post_id.parse::<i32>()?;
|
|
||||||
let post = blocking(context.pool(), move |conn| Post::read(conn, id)).await??;
|
|
||||||
if !post.local {
|
|
||||||
return Err(NotFound.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
if !post.deleted {
|
|
||||||
Ok(create_apub_response(&post.to_apub(context.pool()).await?))
|
|
||||||
} else {
|
|
||||||
Ok(create_apub_tombstone_response(&post.to_tombstone()?))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
|
||||||
impl ToApub for Post {
|
|
||||||
type Response = PageExt;
|
|
||||||
|
|
||||||
// Turn a Lemmy post into an ActivityPub page that can be sent out over the network.
|
|
||||||
async fn to_apub(&self, pool: &DbPool) -> Result<PageExt, LemmyError> {
|
|
||||||
let mut page = Page::new();
|
|
||||||
|
|
||||||
let creator_id = self.creator_id;
|
|
||||||
let creator = blocking(pool, move |conn| User_::read(conn, creator_id)).await??;
|
|
||||||
|
|
||||||
let community_id = self.community_id;
|
|
||||||
let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??;
|
|
||||||
|
|
||||||
page
|
|
||||||
// Not needed when the Post is embedded in a collection (like for community outbox)
|
|
||||||
// TODO: need to set proper context defining sensitive/commentsEnabled fields
|
|
||||||
// https://git.asonix.dog/Aardwolf/activitystreams/issues/5
|
|
||||||
.set_context(activitystreams::context())
|
|
||||||
.set_id(self.ap_id.parse::<Url>()?)
|
|
||||||
// Use summary field to be consistent with mastodon content warning.
|
|
||||||
// https://mastodon.xyz/@Louisa/103987265222901387.json
|
|
||||||
.set_summary(self.name.to_owned())
|
|
||||||
.set_published(convert_datetime(self.published))
|
|
||||||
.set_to(community.actor_id)
|
|
||||||
.set_attributed_to(creator.actor_id);
|
|
||||||
|
|
||||||
if let Some(body) = &self.body {
|
|
||||||
page.set_content(body.to_owned());
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: hacky code because we get self.url == Some("")
|
|
||||||
// https://github.com/LemmyNet/lemmy/issues/602
|
|
||||||
let url = self.url.as_ref().filter(|u| !u.is_empty());
|
|
||||||
if let Some(u) = url {
|
|
||||||
page.set_url(u.to_owned());
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(thumbnail_url) = &self.thumbnail_url {
|
|
||||||
let mut image = Image::new();
|
|
||||||
image.set_url(thumbnail_url.to_string());
|
|
||||||
page.set_image(image.into_any_base()?);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(u) = self.updated {
|
|
||||||
page.set_updated(convert_datetime(u));
|
|
||||||
}
|
|
||||||
|
|
||||||
let ext = PageExtension {
|
|
||||||
comments_enabled: !self.locked,
|
|
||||||
sensitive: self.nsfw,
|
|
||||||
stickied: self.stickied,
|
|
||||||
};
|
|
||||||
Ok(Ext1::new(page, ext))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn to_tombstone(&self) -> Result<Tombstone, LemmyError> {
|
|
||||||
create_tombstone(self.deleted, &self.ap_id, self.updated, PageType::Page)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
|
||||||
impl FromApub for PostForm {
|
|
||||||
type ApubType = PageExt;
|
|
||||||
|
|
||||||
/// Parse an ActivityPub page received from another instance into a Lemmy post.
|
|
||||||
async fn from_apub(
|
|
||||||
page: &PageExt,
|
|
||||||
context: &LemmyContext,
|
|
||||||
expected_domain: Option<Url>,
|
|
||||||
) -> Result<PostForm, LemmyError> {
|
|
||||||
let ext = &page.ext_one;
|
|
||||||
let creator_actor_id = page
|
|
||||||
.inner
|
|
||||||
.attributed_to()
|
|
||||||
.as_ref()
|
|
||||||
.context(location_info!())?
|
|
||||||
.as_single_xsd_any_uri()
|
|
||||||
.context(location_info!())?;
|
|
||||||
|
|
||||||
let creator = get_or_fetch_and_upsert_user(creator_actor_id, context).await?;
|
|
||||||
|
|
||||||
let community_actor_id = page
|
|
||||||
.inner
|
|
||||||
.to()
|
|
||||||
.as_ref()
|
|
||||||
.context(location_info!())?
|
|
||||||
.as_single_xsd_any_uri()
|
|
||||||
.context(location_info!())?;
|
|
||||||
|
|
||||||
let community = get_or_fetch_and_upsert_community(community_actor_id, context).await?;
|
|
||||||
|
|
||||||
let thumbnail_url = match &page.inner.image() {
|
|
||||||
Some(any_image) => Image::from_any_base(
|
|
||||||
any_image
|
|
||||||
.to_owned()
|
|
||||||
.as_one()
|
|
||||||
.context(location_info!())?
|
|
||||||
.to_owned(),
|
|
||||||
)?
|
|
||||||
.context(location_info!())?
|
|
||||||
.url()
|
|
||||||
.context(location_info!())?
|
|
||||||
.as_single_xsd_any_uri()
|
|
||||||
.map(|u| u.to_string()),
|
|
||||||
None => None,
|
|
||||||
};
|
|
||||||
let url = page
|
|
||||||
.inner
|
|
||||||
.url()
|
|
||||||
.map(|u| u.as_single_xsd_any_uri())
|
|
||||||
.flatten()
|
|
||||||
.map(|s| s.to_string());
|
|
||||||
|
|
||||||
let (iframely_title, iframely_description, iframely_html, pictrs_thumbnail) =
|
|
||||||
if let Some(url) = &url {
|
|
||||||
fetch_iframely_and_pictrs_data(context.client(), Some(url.to_owned())).await
|
|
||||||
} else {
|
|
||||||
(None, None, None, thumbnail_url)
|
|
||||||
};
|
|
||||||
|
|
||||||
let name = page
|
|
||||||
.inner
|
|
||||||
.summary()
|
|
||||||
.as_ref()
|
|
||||||
.context(location_info!())?
|
|
||||||
.as_single_xsd_string()
|
|
||||||
.context(location_info!())?
|
|
||||||
.to_string();
|
|
||||||
let body = page
|
|
||||||
.inner
|
|
||||||
.content()
|
|
||||||
.as_ref()
|
|
||||||
.map(|c| c.as_single_xsd_string())
|
|
||||||
.flatten()
|
|
||||||
.map(|s| s.to_string());
|
|
||||||
check_slurs(&name)?;
|
|
||||||
let body_slurs_removed = body.map(|b| remove_slurs(&b));
|
|
||||||
Ok(PostForm {
|
|
||||||
name,
|
|
||||||
url,
|
|
||||||
body: body_slurs_removed,
|
|
||||||
creator_id: creator.id,
|
|
||||||
community_id: community.id,
|
|
||||||
removed: None,
|
|
||||||
locked: Some(!ext.comments_enabled),
|
|
||||||
published: page
|
|
||||||
.inner
|
|
||||||
.published()
|
|
||||||
.as_ref()
|
|
||||||
.map(|u| u.to_owned().naive_local()),
|
|
||||||
updated: page
|
|
||||||
.inner
|
|
||||||
.updated()
|
|
||||||
.as_ref()
|
|
||||||
.map(|u| u.to_owned().naive_local()),
|
|
||||||
deleted: None,
|
|
||||||
nsfw: ext.sensitive,
|
|
||||||
stickied: Some(ext.stickied),
|
|
||||||
embed_title: iframely_title,
|
|
||||||
embed_description: iframely_description,
|
|
||||||
embed_html: iframely_html,
|
|
||||||
thumbnail_url: pictrs_thumbnail,
|
|
||||||
ap_id: Some(check_actor_domain(page, expected_domain)?),
|
|
||||||
local: false,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
#[async_trait::async_trait(?Send)]
|
||||||
impl ApubObjectType for Post {
|
impl ApubObjectType for Post {
|
|
@ -1,13 +1,8 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
|
activities::send::generate_activity_id,
|
||||||
activity_queue::send_activity_single_dest,
|
activity_queue::send_activity_single_dest,
|
||||||
check_actor_domain,
|
|
||||||
check_is_apub_id_valid,
|
|
||||||
create_tombstone,
|
|
||||||
fetcher::get_or_fetch_and_upsert_user,
|
|
||||||
generate_activity_id,
|
|
||||||
ActorType,
|
ActorType,
|
||||||
ApubObjectType,
|
ApubObjectType,
|
||||||
FromApub,
|
|
||||||
ToApub,
|
ToApub,
|
||||||
};
|
};
|
||||||
use activitystreams::{
|
use activitystreams::{
|
||||||
|
@ -18,100 +13,12 @@ use activitystreams::{
|
||||||
Undo,
|
Undo,
|
||||||
Update,
|
Update,
|
||||||
},
|
},
|
||||||
object::{kind::NoteType, Note, Tombstone},
|
|
||||||
prelude::*,
|
prelude::*,
|
||||||
};
|
};
|
||||||
use anyhow::Context;
|
use lemmy_db::{private_message::PrivateMessage, user::User_, Crud};
|
||||||
use lemmy_db::{
|
|
||||||
private_message::{PrivateMessage, PrivateMessageForm},
|
|
||||||
user::User_,
|
|
||||||
Crud,
|
|
||||||
DbPool,
|
|
||||||
};
|
|
||||||
use lemmy_structs::blocking;
|
use lemmy_structs::blocking;
|
||||||
use lemmy_utils::{location_info, utils::convert_datetime, LemmyError};
|
use lemmy_utils::LemmyError;
|
||||||
use lemmy_websocket::LemmyContext;
|
use lemmy_websocket::LemmyContext;
|
||||||
use url::Url;
|
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
|
||||||
impl ToApub for PrivateMessage {
|
|
||||||
type Response = Note;
|
|
||||||
|
|
||||||
async fn to_apub(&self, pool: &DbPool) -> Result<Note, LemmyError> {
|
|
||||||
let mut private_message = Note::new();
|
|
||||||
|
|
||||||
let creator_id = self.creator_id;
|
|
||||||
let creator = blocking(pool, move |conn| User_::read(conn, creator_id)).await??;
|
|
||||||
|
|
||||||
let recipient_id = self.recipient_id;
|
|
||||||
let recipient = blocking(pool, move |conn| User_::read(conn, recipient_id)).await??;
|
|
||||||
|
|
||||||
private_message
|
|
||||||
.set_context(activitystreams::context())
|
|
||||||
.set_id(Url::parse(&self.ap_id.to_owned())?)
|
|
||||||
.set_published(convert_datetime(self.published))
|
|
||||||
.set_content(self.content.to_owned())
|
|
||||||
.set_to(recipient.actor_id)
|
|
||||||
.set_attributed_to(creator.actor_id);
|
|
||||||
|
|
||||||
if let Some(u) = self.updated {
|
|
||||||
private_message.set_updated(convert_datetime(u));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(private_message)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn to_tombstone(&self) -> Result<Tombstone, LemmyError> {
|
|
||||||
create_tombstone(self.deleted, &self.ap_id, self.updated, NoteType::Note)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
|
||||||
impl FromApub for PrivateMessageForm {
|
|
||||||
type ApubType = Note;
|
|
||||||
|
|
||||||
/// Parse an ActivityPub note received from another instance into a Lemmy Private message
|
|
||||||
async fn from_apub(
|
|
||||||
note: &Note,
|
|
||||||
context: &LemmyContext,
|
|
||||||
expected_domain: Option<Url>,
|
|
||||||
) -> Result<PrivateMessageForm, LemmyError> {
|
|
||||||
let creator_actor_id = note
|
|
||||||
.attributed_to()
|
|
||||||
.context(location_info!())?
|
|
||||||
.clone()
|
|
||||||
.single_xsd_any_uri()
|
|
||||||
.context(location_info!())?;
|
|
||||||
|
|
||||||
let creator = get_or_fetch_and_upsert_user(&creator_actor_id, context).await?;
|
|
||||||
let recipient_actor_id = note
|
|
||||||
.to()
|
|
||||||
.context(location_info!())?
|
|
||||||
.clone()
|
|
||||||
.single_xsd_any_uri()
|
|
||||||
.context(location_info!())?;
|
|
||||||
let recipient = get_or_fetch_and_upsert_user(&recipient_actor_id, context).await?;
|
|
||||||
let ap_id = note.id_unchecked().context(location_info!())?.to_string();
|
|
||||||
check_is_apub_id_valid(&Url::parse(&ap_id)?)?;
|
|
||||||
|
|
||||||
Ok(PrivateMessageForm {
|
|
||||||
creator_id: creator.id,
|
|
||||||
recipient_id: recipient.id,
|
|
||||||
content: note
|
|
||||||
.content()
|
|
||||||
.context(location_info!())?
|
|
||||||
.as_single_xsd_string()
|
|
||||||
.context(location_info!())?
|
|
||||||
.to_string(),
|
|
||||||
published: note.published().map(|u| u.to_owned().naive_local()),
|
|
||||||
updated: note.updated().map(|u| u.to_owned().naive_local()),
|
|
||||||
deleted: None,
|
|
||||||
read: None,
|
|
||||||
ap_id: Some(check_actor_domain(note, expected_domain)?),
|
|
||||||
local: false,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
#[async_trait::async_trait(?Send)]
|
||||||
impl ApubObjectType for PrivateMessage {
|
impl ApubObjectType for PrivateMessage {
|
122
lemmy_apub/src/activities/send/user.rs
Normal file
122
lemmy_apub/src/activities/send/user.rs
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
use crate::{
|
||||||
|
activities::send::generate_activity_id,
|
||||||
|
activity_queue::send_activity_single_dest,
|
||||||
|
fetcher::get_or_fetch_and_upsert_actor,
|
||||||
|
ActorType,
|
||||||
|
};
|
||||||
|
use activitystreams::{
|
||||||
|
activity::{
|
||||||
|
kind::{FollowType, UndoType},
|
||||||
|
Follow,
|
||||||
|
Undo,
|
||||||
|
},
|
||||||
|
base::{AnyBase, BaseExt, ExtendsExt},
|
||||||
|
};
|
||||||
|
use lemmy_db::{user::User_, DbPool};
|
||||||
|
use lemmy_utils::LemmyError;
|
||||||
|
use lemmy_websocket::LemmyContext;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
#[async_trait::async_trait(?Send)]
|
||||||
|
impl ActorType for User_ {
|
||||||
|
fn actor_id_str(&self) -> String {
|
||||||
|
self.actor_id.to_owned()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn public_key(&self) -> Option<String> {
|
||||||
|
self.public_key.to_owned()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn private_key(&self) -> Option<String> {
|
||||||
|
self.private_key.to_owned()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn user_id(&self) -> i32 {
|
||||||
|
self.id
|
||||||
|
}
|
||||||
|
|
||||||
|
/// As a given local user, send out a follow request to a remote community.
|
||||||
|
async fn send_follow(
|
||||||
|
&self,
|
||||||
|
follow_actor_id: &Url,
|
||||||
|
context: &LemmyContext,
|
||||||
|
) -> Result<(), LemmyError> {
|
||||||
|
let mut follow = Follow::new(self.actor_id.to_owned(), follow_actor_id.as_str());
|
||||||
|
follow
|
||||||
|
.set_context(activitystreams::context())
|
||||||
|
.set_id(generate_activity_id(FollowType::Follow)?);
|
||||||
|
let follow_actor = get_or_fetch_and_upsert_actor(follow_actor_id, context).await?;
|
||||||
|
let to = follow_actor.get_inbox_url()?;
|
||||||
|
|
||||||
|
send_activity_single_dest(follow, self, to, context).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn send_unfollow(
|
||||||
|
&self,
|
||||||
|
follow_actor_id: &Url,
|
||||||
|
context: &LemmyContext,
|
||||||
|
) -> Result<(), LemmyError> {
|
||||||
|
let mut follow = Follow::new(self.actor_id.to_owned(), follow_actor_id.as_str());
|
||||||
|
follow
|
||||||
|
.set_context(activitystreams::context())
|
||||||
|
.set_id(generate_activity_id(FollowType::Follow)?);
|
||||||
|
let follow_actor = get_or_fetch_and_upsert_actor(follow_actor_id, context).await?;
|
||||||
|
|
||||||
|
let to = follow_actor.get_inbox_url()?;
|
||||||
|
|
||||||
|
// Undo that fake activity
|
||||||
|
let mut undo = Undo::new(Url::parse(&self.actor_id)?, follow.into_any_base()?);
|
||||||
|
undo
|
||||||
|
.set_context(activitystreams::context())
|
||||||
|
.set_id(generate_activity_id(UndoType::Undo)?);
|
||||||
|
|
||||||
|
send_activity_single_dest(undo, self, to, context).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn send_accept_follow(
|
||||||
|
&self,
|
||||||
|
_follow: Follow,
|
||||||
|
_context: &LemmyContext,
|
||||||
|
) -> Result<(), LemmyError> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn send_delete(&self, _creator: &User_, _context: &LemmyContext) -> Result<(), LemmyError> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn send_undo_delete(
|
||||||
|
&self,
|
||||||
|
_creator: &User_,
|
||||||
|
_context: &LemmyContext,
|
||||||
|
) -> Result<(), LemmyError> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn send_remove(&self, _creator: &User_, _context: &LemmyContext) -> Result<(), LemmyError> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn send_undo_remove(
|
||||||
|
&self,
|
||||||
|
_creator: &User_,
|
||||||
|
_context: &LemmyContext,
|
||||||
|
) -> Result<(), LemmyError> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn send_announce(
|
||||||
|
&self,
|
||||||
|
_activity: AnyBase,
|
||||||
|
_sender: &User_,
|
||||||
|
_context: &LemmyContext,
|
||||||
|
) -> Result<(), LemmyError> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_follower_inboxes(&self, _pool: &DbPool) -> Result<Vec<Url>, LemmyError> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,5 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
check_is_apub_id_valid,
|
check_is_apub_id_valid,
|
||||||
community::do_announce,
|
|
||||||
extensions::signatures::sign_and_send,
|
extensions::signatures::sign_and_send,
|
||||||
insert_activity,
|
insert_activity,
|
||||||
ActorType,
|
ActorType,
|
||||||
|
@ -112,7 +111,9 @@ where
|
||||||
{
|
{
|
||||||
// if this is a local community, we need to do an announce from the community instead
|
// if this is a local community, we need to do an announce from the community instead
|
||||||
if community.local {
|
if community.local {
|
||||||
do_announce(activity.into_any_base()?, &community, creator, context).await?;
|
community
|
||||||
|
.send_announce(activity.into_any_base()?, creator, context)
|
||||||
|
.await?;
|
||||||
} else {
|
} else {
|
||||||
let inbox = community.get_shared_inbox_url()?;
|
let inbox = community.get_shared_inbox_url()?;
|
||||||
check_is_apub_id_valid(&inbox)?;
|
check_is_apub_id_valid(&inbox)?;
|
||||||
|
|
|
@ -1,496 +0,0 @@
|
||||||
use crate::{
|
|
||||||
activity_queue::{send_activity_single_dest, send_to_community_followers},
|
|
||||||
check_actor_domain,
|
|
||||||
check_is_apub_id_valid,
|
|
||||||
create_apub_response,
|
|
||||||
create_apub_tombstone_response,
|
|
||||||
create_tombstone,
|
|
||||||
extensions::group_extensions::GroupExtension,
|
|
||||||
fetcher::{get_or_fetch_and_upsert_actor, get_or_fetch_and_upsert_user},
|
|
||||||
generate_activity_id,
|
|
||||||
ActorType,
|
|
||||||
FromApub,
|
|
||||||
GroupExt,
|
|
||||||
ToApub,
|
|
||||||
};
|
|
||||||
use activitystreams::{
|
|
||||||
activity::{
|
|
||||||
kind::{AcceptType, AnnounceType, DeleteType, LikeType, RemoveType, UndoType},
|
|
||||||
Accept,
|
|
||||||
Announce,
|
|
||||||
Delete,
|
|
||||||
Follow,
|
|
||||||
Remove,
|
|
||||||
Undo,
|
|
||||||
},
|
|
||||||
actor::{kind::GroupType, ApActor, Endpoints, Group},
|
|
||||||
base::{AnyBase, BaseExt},
|
|
||||||
collection::{OrderedCollection, UnorderedCollection},
|
|
||||||
object::{Image, Tombstone},
|
|
||||||
prelude::*,
|
|
||||||
public,
|
|
||||||
};
|
|
||||||
use activitystreams_ext::Ext2;
|
|
||||||
use actix_web::{body::Body, web, HttpResponse};
|
|
||||||
use anyhow::Context;
|
|
||||||
use itertools::Itertools;
|
|
||||||
use lemmy_db::{
|
|
||||||
community::{Community, CommunityForm},
|
|
||||||
community_view::{CommunityFollowerView, CommunityModeratorView},
|
|
||||||
naive_now,
|
|
||||||
post::Post,
|
|
||||||
user::User_,
|
|
||||||
DbPool,
|
|
||||||
};
|
|
||||||
use lemmy_structs::blocking;
|
|
||||||
use lemmy_utils::{
|
|
||||||
location_info,
|
|
||||||
settings::Settings,
|
|
||||||
utils::{check_slurs, check_slurs_opt, convert_datetime},
|
|
||||||
LemmyError,
|
|
||||||
};
|
|
||||||
use lemmy_websocket::LemmyContext;
|
|
||||||
use serde::Deserialize;
|
|
||||||
use url::Url;
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
pub struct CommunityQuery {
|
|
||||||
community_name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
|
||||||
impl ToApub for Community {
|
|
||||||
type Response = GroupExt;
|
|
||||||
|
|
||||||
// Turn a Lemmy Community into an ActivityPub group that can be sent out over the network.
|
|
||||||
async fn to_apub(&self, pool: &DbPool) -> Result<GroupExt, LemmyError> {
|
|
||||||
// The attributed to, is an ordered vector with the creator actor_ids first,
|
|
||||||
// then the rest of the moderators
|
|
||||||
// TODO Technically the instance admins can mod the community, but lets
|
|
||||||
// ignore that for now
|
|
||||||
let id = self.id;
|
|
||||||
let moderators = blocking(pool, move |conn| {
|
|
||||||
CommunityModeratorView::for_community(&conn, id)
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
let moderators: Vec<String> = moderators.into_iter().map(|m| m.user_actor_id).collect();
|
|
||||||
|
|
||||||
let mut group = Group::new();
|
|
||||||
group
|
|
||||||
.set_context(activitystreams::context())
|
|
||||||
.set_id(Url::parse(&self.actor_id)?)
|
|
||||||
.set_name(self.name.to_owned())
|
|
||||||
.set_published(convert_datetime(self.published))
|
|
||||||
.set_many_attributed_tos(moderators);
|
|
||||||
|
|
||||||
if let Some(u) = self.updated.to_owned() {
|
|
||||||
group.set_updated(convert_datetime(u));
|
|
||||||
}
|
|
||||||
if let Some(d) = self.description.to_owned() {
|
|
||||||
// TODO: this should be html, also add source field with raw markdown
|
|
||||||
// -> same for post.content and others
|
|
||||||
group.set_content(d);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(icon) = &self.icon {
|
|
||||||
let mut image = Image::new();
|
|
||||||
image.set_url(icon.to_owned());
|
|
||||||
group.set_icon(image.into_any_base()?);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(banner_url) = &self.banner {
|
|
||||||
let mut image = Image::new();
|
|
||||||
image.set_url(banner_url.to_owned());
|
|
||||||
group.set_image(image.into_any_base()?);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut ap_actor = ApActor::new(self.get_inbox_url()?, group);
|
|
||||||
ap_actor
|
|
||||||
.set_preferred_username(self.title.to_owned())
|
|
||||||
.set_outbox(self.get_outbox_url()?)
|
|
||||||
.set_followers(self.get_followers_url()?)
|
|
||||||
.set_endpoints(Endpoints {
|
|
||||||
shared_inbox: Some(self.get_shared_inbox_url()?),
|
|
||||||
..Default::default()
|
|
||||||
});
|
|
||||||
|
|
||||||
let nsfw = self.nsfw;
|
|
||||||
let category_id = self.category_id;
|
|
||||||
let group_extension = blocking(pool, move |conn| {
|
|
||||||
GroupExtension::new(conn, category_id, nsfw)
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
Ok(Ext2::new(
|
|
||||||
ap_actor,
|
|
||||||
group_extension,
|
|
||||||
self.get_public_key_ext()?,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn to_tombstone(&self) -> Result<Tombstone, LemmyError> {
|
|
||||||
create_tombstone(self.deleted, &self.actor_id, self.updated, GroupType::Group)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
|
||||||
impl ActorType for Community {
|
|
||||||
fn actor_id_str(&self) -> String {
|
|
||||||
self.actor_id.to_owned()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn public_key(&self) -> Option<String> {
|
|
||||||
self.public_key.to_owned()
|
|
||||||
}
|
|
||||||
fn private_key(&self) -> Option<String> {
|
|
||||||
self.private_key.to_owned()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// As a local community, accept the follow request from a remote user.
|
|
||||||
async fn send_accept_follow(
|
|
||||||
&self,
|
|
||||||
follow: Follow,
|
|
||||||
context: &LemmyContext,
|
|
||||||
) -> Result<(), LemmyError> {
|
|
||||||
let actor_uri = follow
|
|
||||||
.actor()?
|
|
||||||
.as_single_xsd_any_uri()
|
|
||||||
.context(location_info!())?;
|
|
||||||
let actor = get_or_fetch_and_upsert_actor(actor_uri, context).await?;
|
|
||||||
|
|
||||||
let mut accept = Accept::new(self.actor_id.to_owned(), follow.into_any_base()?);
|
|
||||||
let to = actor.get_inbox_url()?;
|
|
||||||
accept
|
|
||||||
.set_context(activitystreams::context())
|
|
||||||
.set_id(generate_activity_id(AcceptType::Accept)?)
|
|
||||||
.set_to(to.clone());
|
|
||||||
|
|
||||||
send_activity_single_dest(accept, self, to, context).await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn send_delete(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
|
|
||||||
let group = self.to_apub(context.pool()).await?;
|
|
||||||
|
|
||||||
let mut delete = Delete::new(creator.actor_id.to_owned(), group.into_any_base()?);
|
|
||||||
delete
|
|
||||||
.set_context(activitystreams::context())
|
|
||||||
.set_id(generate_activity_id(DeleteType::Delete)?)
|
|
||||||
.set_to(public())
|
|
||||||
.set_many_ccs(vec![self.get_followers_url()?]);
|
|
||||||
|
|
||||||
send_to_community_followers(delete, self, context, None).await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn send_undo_delete(
|
|
||||||
&self,
|
|
||||||
creator: &User_,
|
|
||||||
context: &LemmyContext,
|
|
||||||
) -> Result<(), LemmyError> {
|
|
||||||
let group = self.to_apub(context.pool()).await?;
|
|
||||||
|
|
||||||
let mut delete = Delete::new(creator.actor_id.to_owned(), group.into_any_base()?);
|
|
||||||
delete
|
|
||||||
.set_context(activitystreams::context())
|
|
||||||
.set_id(generate_activity_id(DeleteType::Delete)?)
|
|
||||||
.set_to(public())
|
|
||||||
.set_many_ccs(vec![self.get_followers_url()?]);
|
|
||||||
|
|
||||||
let mut undo = Undo::new(creator.actor_id.to_owned(), delete.into_any_base()?);
|
|
||||||
undo
|
|
||||||
.set_context(activitystreams::context())
|
|
||||||
.set_id(generate_activity_id(UndoType::Undo)?)
|
|
||||||
.set_to(public())
|
|
||||||
.set_many_ccs(vec![self.get_followers_url()?]);
|
|
||||||
|
|
||||||
send_to_community_followers(undo, self, context, None).await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn send_remove(&self, mod_: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
|
|
||||||
let group = self.to_apub(context.pool()).await?;
|
|
||||||
|
|
||||||
let mut remove = Remove::new(mod_.actor_id.to_owned(), group.into_any_base()?);
|
|
||||||
remove
|
|
||||||
.set_context(activitystreams::context())
|
|
||||||
.set_id(generate_activity_id(RemoveType::Remove)?)
|
|
||||||
.set_to(public())
|
|
||||||
.set_many_ccs(vec![self.get_followers_url()?]);
|
|
||||||
|
|
||||||
send_to_community_followers(remove, self, context, None).await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn send_undo_remove(&self, mod_: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
|
|
||||||
let group = self.to_apub(context.pool()).await?;
|
|
||||||
|
|
||||||
let mut remove = Remove::new(mod_.actor_id.to_owned(), group.into_any_base()?);
|
|
||||||
remove
|
|
||||||
.set_context(activitystreams::context())
|
|
||||||
.set_id(generate_activity_id(RemoveType::Remove)?)
|
|
||||||
.set_to(public())
|
|
||||||
.set_many_ccs(vec![self.get_followers_url()?]);
|
|
||||||
|
|
||||||
// Undo that fake activity
|
|
||||||
let mut undo = Undo::new(mod_.actor_id.to_owned(), remove.into_any_base()?);
|
|
||||||
undo
|
|
||||||
.set_context(activitystreams::context())
|
|
||||||
.set_id(generate_activity_id(LikeType::Like)?)
|
|
||||||
.set_to(public())
|
|
||||||
.set_many_ccs(vec![self.get_followers_url()?]);
|
|
||||||
|
|
||||||
send_to_community_followers(undo, self, context, None).await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// For a given community, returns the inboxes of all followers.
|
|
||||||
///
|
|
||||||
/// TODO: this function is very badly implemented, we should just store shared_inbox_url in
|
|
||||||
/// CommunityFollowerView
|
|
||||||
async fn get_follower_inboxes(&self, pool: &DbPool) -> Result<Vec<Url>, LemmyError> {
|
|
||||||
let id = self.id;
|
|
||||||
|
|
||||||
let inboxes = blocking(pool, move |conn| {
|
|
||||||
CommunityFollowerView::for_community(conn, id)
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
let inboxes = inboxes
|
|
||||||
.into_iter()
|
|
||||||
.filter(|i| !i.user_local)
|
|
||||||
.map(|u| -> Result<Url, LemmyError> {
|
|
||||||
let url = Url::parse(&u.user_actor_id)?;
|
|
||||||
let domain = url.domain().context(location_info!())?;
|
|
||||||
let port = if let Some(port) = url.port() {
|
|
||||||
format!(":{}", port)
|
|
||||||
} else {
|
|
||||||
"".to_string()
|
|
||||||
};
|
|
||||||
Ok(Url::parse(&format!(
|
|
||||||
"{}://{}{}/inbox",
|
|
||||||
Settings::get().get_protocol_string(),
|
|
||||||
domain,
|
|
||||||
port,
|
|
||||||
))?)
|
|
||||||
})
|
|
||||||
.filter_map(Result::ok)
|
|
||||||
// Don't send to blocked instances
|
|
||||||
.filter(|inbox| check_is_apub_id_valid(inbox).is_ok())
|
|
||||||
.unique()
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
Ok(inboxes)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn send_follow(
|
|
||||||
&self,
|
|
||||||
_follow_actor_id: &Url,
|
|
||||||
_context: &LemmyContext,
|
|
||||||
) -> Result<(), LemmyError> {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn send_unfollow(
|
|
||||||
&self,
|
|
||||||
_follow_actor_id: &Url,
|
|
||||||
_context: &LemmyContext,
|
|
||||||
) -> Result<(), LemmyError> {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn user_id(&self) -> i32 {
|
|
||||||
self.creator_id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
|
||||||
impl FromApub for CommunityForm {
|
|
||||||
type ApubType = GroupExt;
|
|
||||||
|
|
||||||
/// Parse an ActivityPub group received from another instance into a Lemmy community.
|
|
||||||
async fn from_apub(
|
|
||||||
group: &GroupExt,
|
|
||||||
context: &LemmyContext,
|
|
||||||
expected_domain: Option<Url>,
|
|
||||||
) -> Result<Self, LemmyError> {
|
|
||||||
let creator_and_moderator_uris = group.inner.attributed_to().context(location_info!())?;
|
|
||||||
let creator_uri = creator_and_moderator_uris
|
|
||||||
.as_many()
|
|
||||||
.context(location_info!())?
|
|
||||||
.iter()
|
|
||||||
.next()
|
|
||||||
.context(location_info!())?
|
|
||||||
.as_xsd_any_uri()
|
|
||||||
.context(location_info!())?;
|
|
||||||
|
|
||||||
let creator = get_or_fetch_and_upsert_user(creator_uri, context).await?;
|
|
||||||
let name = group
|
|
||||||
.inner
|
|
||||||
.name()
|
|
||||||
.context(location_info!())?
|
|
||||||
.as_one()
|
|
||||||
.context(location_info!())?
|
|
||||||
.as_xsd_string()
|
|
||||||
.context(location_info!())?
|
|
||||||
.to_string();
|
|
||||||
let title = group
|
|
||||||
.inner
|
|
||||||
.preferred_username()
|
|
||||||
.context(location_info!())?
|
|
||||||
.to_string();
|
|
||||||
// TODO: should be parsed as html and tags like <script> removed (or use markdown source)
|
|
||||||
// -> same for post.content etc
|
|
||||||
let description = group
|
|
||||||
.inner
|
|
||||||
.content()
|
|
||||||
.map(|s| s.as_single_xsd_string())
|
|
||||||
.flatten()
|
|
||||||
.map(|s| s.to_string());
|
|
||||||
check_slurs(&name)?;
|
|
||||||
check_slurs(&title)?;
|
|
||||||
check_slurs_opt(&description)?;
|
|
||||||
|
|
||||||
let icon = match group.icon() {
|
|
||||||
Some(any_image) => Some(
|
|
||||||
Image::from_any_base(any_image.as_one().context(location_info!())?.clone())
|
|
||||||
.context(location_info!())?
|
|
||||||
.context(location_info!())?
|
|
||||||
.url()
|
|
||||||
.context(location_info!())?
|
|
||||||
.as_single_xsd_any_uri()
|
|
||||||
.map(|u| u.to_string()),
|
|
||||||
),
|
|
||||||
None => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let banner = match group.image() {
|
|
||||||
Some(any_image) => Some(
|
|
||||||
Image::from_any_base(any_image.as_one().context(location_info!())?.clone())
|
|
||||||
.context(location_info!())?
|
|
||||||
.context(location_info!())?
|
|
||||||
.url()
|
|
||||||
.context(location_info!())?
|
|
||||||
.as_single_xsd_any_uri()
|
|
||||||
.map(|u| u.to_string()),
|
|
||||||
),
|
|
||||||
None => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(CommunityForm {
|
|
||||||
name,
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
category_id: group.ext_one.category.identifier.parse::<i32>()?,
|
|
||||||
creator_id: creator.id,
|
|
||||||
removed: None,
|
|
||||||
published: group.inner.published().map(|u| u.to_owned().naive_local()),
|
|
||||||
updated: group.inner.updated().map(|u| u.to_owned().naive_local()),
|
|
||||||
deleted: None,
|
|
||||||
nsfw: group.ext_one.sensitive,
|
|
||||||
actor_id: Some(check_actor_domain(group, expected_domain)?),
|
|
||||||
local: false,
|
|
||||||
private_key: None,
|
|
||||||
public_key: Some(group.ext_two.to_owned().public_key.public_key_pem),
|
|
||||||
last_refreshed_at: Some(naive_now()),
|
|
||||||
icon,
|
|
||||||
banner,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return the community json over HTTP.
|
|
||||||
pub async fn get_apub_community_http(
|
|
||||||
info: web::Path<CommunityQuery>,
|
|
||||||
context: web::Data<LemmyContext>,
|
|
||||||
) -> Result<HttpResponse<Body>, LemmyError> {
|
|
||||||
let community = blocking(context.pool(), move |conn| {
|
|
||||||
Community::read_from_name(conn, &info.community_name)
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
if !community.deleted {
|
|
||||||
let apub = community.to_apub(context.pool()).await?;
|
|
||||||
|
|
||||||
Ok(create_apub_response(&apub))
|
|
||||||
} else {
|
|
||||||
Ok(create_apub_tombstone_response(&community.to_tombstone()?))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns an empty followers collection, only populating the size (for privacy).
|
|
||||||
pub async fn get_apub_community_followers(
|
|
||||||
info: web::Path<CommunityQuery>,
|
|
||||||
context: web::Data<LemmyContext>,
|
|
||||||
) -> Result<HttpResponse<Body>, LemmyError> {
|
|
||||||
let community = blocking(context.pool(), move |conn| {
|
|
||||||
Community::read_from_name(&conn, &info.community_name)
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
let community_id = community.id;
|
|
||||||
let community_followers = blocking(context.pool(), move |conn| {
|
|
||||||
CommunityFollowerView::for_community(&conn, community_id)
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
let mut collection = UnorderedCollection::new();
|
|
||||||
collection
|
|
||||||
.set_context(activitystreams::context())
|
|
||||||
.set_id(community.get_followers_url()?)
|
|
||||||
.set_total_items(community_followers.len() as u64);
|
|
||||||
Ok(create_apub_response(&collection))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_apub_community_outbox(
|
|
||||||
info: web::Path<CommunityQuery>,
|
|
||||||
context: web::Data<LemmyContext>,
|
|
||||||
) -> Result<HttpResponse<Body>, LemmyError> {
|
|
||||||
let community = blocking(context.pool(), move |conn| {
|
|
||||||
Community::read_from_name(&conn, &info.community_name)
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
let community_id = community.id;
|
|
||||||
let posts = blocking(context.pool(), move |conn| {
|
|
||||||
Post::list_for_community(conn, community_id)
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
let mut pages: Vec<AnyBase> = vec![];
|
|
||||||
for p in posts {
|
|
||||||
pages.push(p.to_apub(context.pool()).await?.into_any_base()?);
|
|
||||||
}
|
|
||||||
|
|
||||||
let len = pages.len();
|
|
||||||
let mut collection = OrderedCollection::new();
|
|
||||||
collection
|
|
||||||
.set_many_items(pages)
|
|
||||||
.set_context(activitystreams::context())
|
|
||||||
.set_id(community.get_outbox_url()?)
|
|
||||||
.set_total_items(len as u64);
|
|
||||||
Ok(create_apub_response(&collection))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn do_announce(
|
|
||||||
activity: AnyBase,
|
|
||||||
community: &Community,
|
|
||||||
sender: &User_,
|
|
||||||
context: &LemmyContext,
|
|
||||||
) -> Result<(), LemmyError> {
|
|
||||||
let mut announce = Announce::new(community.actor_id.to_owned(), activity);
|
|
||||||
announce
|
|
||||||
.set_context(activitystreams::context())
|
|
||||||
.set_id(generate_activity_id(AnnounceType::Announce)?)
|
|
||||||
.set_to(public())
|
|
||||||
.set_many_ccs(vec![community.get_followers_url()?]);
|
|
||||||
|
|
||||||
send_to_community_followers(
|
|
||||||
announce,
|
|
||||||
community,
|
|
||||||
context,
|
|
||||||
Some(sender.get_shared_inbox_url()?),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
|
@ -44,10 +44,7 @@ static ACTOR_REFETCH_INTERVAL_SECONDS_DEBUG: i64 = 10;
|
||||||
|
|
||||||
/// Fetch any type of ActivityPub object, handling things like HTTP headers, deserialisation,
|
/// Fetch any type of ActivityPub object, handling things like HTTP headers, deserialisation,
|
||||||
/// timeouts etc.
|
/// timeouts etc.
|
||||||
pub async fn fetch_remote_object<Response>(
|
async fn fetch_remote_object<Response>(client: &Client, url: &Url) -> Result<Response, LemmyError>
|
||||||
client: &Client,
|
|
||||||
url: &Url,
|
|
||||||
) -> Result<Response, LemmyError>
|
|
||||||
where
|
where
|
||||||
Response: for<'de> Deserialize<'de>,
|
Response: for<'de> Deserialize<'de>,
|
||||||
{
|
{
|
||||||
|
@ -194,7 +191,7 @@ pub async fn search_by_apub_id(
|
||||||
Ok(response)
|
Ok(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_or_fetch_and_upsert_actor(
|
pub(crate) async fn get_or_fetch_and_upsert_actor(
|
||||||
apub_id: &Url,
|
apub_id: &Url,
|
||||||
context: &LemmyContext,
|
context: &LemmyContext,
|
||||||
) -> Result<Box<dyn ActorType>, LemmyError> {
|
) -> Result<Box<dyn ActorType>, LemmyError> {
|
||||||
|
@ -207,7 +204,7 @@ pub async fn get_or_fetch_and_upsert_actor(
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if a remote user exists, create if not found, if its too old update it.Fetch a user, insert/update it in the database and return the user.
|
/// Check if a remote user exists, create if not found, if its too old update it.Fetch a user, insert/update it in the database and return the user.
|
||||||
pub async fn get_or_fetch_and_upsert_user(
|
pub(crate) async fn get_or_fetch_and_upsert_user(
|
||||||
apub_id: &Url,
|
apub_id: &Url,
|
||||||
context: &LemmyContext,
|
context: &LemmyContext,
|
||||||
) -> Result<User_, LemmyError> {
|
) -> Result<User_, LemmyError> {
|
||||||
|
@ -259,7 +256,7 @@ fn should_refetch_actor(last_refreshed: NaiveDateTime) -> bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if a remote community exists, create if not found, if its too old update it.Fetch a community, insert/update it in the database and return the community.
|
/// Check if a remote community exists, create if not found, if its too old update it.Fetch a community, insert/update it in the database and return the community.
|
||||||
pub async fn get_or_fetch_and_upsert_community(
|
pub(crate) async fn get_or_fetch_and_upsert_community(
|
||||||
apub_id: &Url,
|
apub_id: &Url,
|
||||||
context: &LemmyContext,
|
context: &LemmyContext,
|
||||||
) -> Result<Community, LemmyError> {
|
) -> Result<Community, LemmyError> {
|
||||||
|
@ -361,7 +358,7 @@ async fn fetch_remote_community(
|
||||||
Ok(community)
|
Ok(community)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_or_fetch_and_insert_post(
|
pub(crate) async fn get_or_fetch_and_insert_post(
|
||||||
post_ap_id: &Url,
|
post_ap_id: &Url,
|
||||||
context: &LemmyContext,
|
context: &LemmyContext,
|
||||||
) -> Result<Post, LemmyError> {
|
) -> Result<Post, LemmyError> {
|
||||||
|
@ -386,7 +383,7 @@ pub async fn get_or_fetch_and_insert_post(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_or_fetch_and_insert_comment(
|
pub(crate) async fn get_or_fetch_and_insert_comment(
|
||||||
comment_ap_id: &Url,
|
comment_ap_id: &Url,
|
||||||
context: &LemmyContext,
|
context: &LemmyContext,
|
||||||
) -> Result<Comment, LemmyError> {
|
) -> Result<Comment, LemmyError> {
|
||||||
|
|
36
lemmy_apub/src/http/comment.rs
Normal file
36
lemmy_apub/src/http/comment.rs
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
use crate::{
|
||||||
|
http::{create_apub_response, create_apub_tombstone_response},
|
||||||
|
ToApub,
|
||||||
|
};
|
||||||
|
use actix_web::{body::Body, web, web::Path, HttpResponse};
|
||||||
|
use diesel::result::Error::NotFound;
|
||||||
|
use lemmy_db::{comment::Comment, Crud};
|
||||||
|
use lemmy_structs::blocking;
|
||||||
|
use lemmy_utils::LemmyError;
|
||||||
|
use lemmy_websocket::LemmyContext;
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct CommentQuery {
|
||||||
|
comment_id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the post json over HTTP.
|
||||||
|
pub async fn get_apub_comment(
|
||||||
|
info: Path<CommentQuery>,
|
||||||
|
context: web::Data<LemmyContext>,
|
||||||
|
) -> Result<HttpResponse<Body>, LemmyError> {
|
||||||
|
let id = info.comment_id.parse::<i32>()?;
|
||||||
|
let comment = blocking(context.pool(), move |conn| Comment::read(conn, id)).await??;
|
||||||
|
if !comment.local {
|
||||||
|
return Err(NotFound.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
if !comment.deleted {
|
||||||
|
Ok(create_apub_response(
|
||||||
|
&comment.to_apub(context.pool()).await?,
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Ok(create_apub_tombstone_response(&comment.to_tombstone()?))
|
||||||
|
}
|
||||||
|
}
|
93
lemmy_apub/src/http/community.rs
Normal file
93
lemmy_apub/src/http/community.rs
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
use crate::{
|
||||||
|
http::{create_apub_response, create_apub_tombstone_response},
|
||||||
|
ActorType,
|
||||||
|
ToApub,
|
||||||
|
};
|
||||||
|
use activitystreams::{
|
||||||
|
base::{AnyBase, BaseExt, ExtendsExt},
|
||||||
|
collection::{CollectionExt, OrderedCollection, UnorderedCollection},
|
||||||
|
};
|
||||||
|
use actix_web::{body::Body, web, HttpResponse};
|
||||||
|
use lemmy_db::{community::Community, community_view::CommunityFollowerView, post::Post};
|
||||||
|
use lemmy_structs::blocking;
|
||||||
|
use lemmy_utils::LemmyError;
|
||||||
|
use lemmy_websocket::LemmyContext;
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct CommunityQuery {
|
||||||
|
community_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the community json over HTTP.
|
||||||
|
pub async fn get_apub_community_http(
|
||||||
|
info: web::Path<CommunityQuery>,
|
||||||
|
context: web::Data<LemmyContext>,
|
||||||
|
) -> Result<HttpResponse<Body>, LemmyError> {
|
||||||
|
let community = blocking(context.pool(), move |conn| {
|
||||||
|
Community::read_from_name(conn, &info.community_name)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
if !community.deleted {
|
||||||
|
let apub = community.to_apub(context.pool()).await?;
|
||||||
|
|
||||||
|
Ok(create_apub_response(&apub))
|
||||||
|
} else {
|
||||||
|
Ok(create_apub_tombstone_response(&community.to_tombstone()?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns an empty followers collection, only populating the size (for privacy).
|
||||||
|
pub async fn get_apub_community_followers(
|
||||||
|
info: web::Path<CommunityQuery>,
|
||||||
|
context: web::Data<LemmyContext>,
|
||||||
|
) -> Result<HttpResponse<Body>, LemmyError> {
|
||||||
|
let community = blocking(context.pool(), move |conn| {
|
||||||
|
Community::read_from_name(&conn, &info.community_name)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
let community_id = community.id;
|
||||||
|
let community_followers = blocking(context.pool(), move |conn| {
|
||||||
|
CommunityFollowerView::for_community(&conn, community_id)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
let mut collection = UnorderedCollection::new();
|
||||||
|
collection
|
||||||
|
.set_context(activitystreams::context())
|
||||||
|
.set_id(community.get_followers_url()?)
|
||||||
|
.set_total_items(community_followers.len() as u64);
|
||||||
|
Ok(create_apub_response(&collection))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_apub_community_outbox(
|
||||||
|
info: web::Path<CommunityQuery>,
|
||||||
|
context: web::Data<LemmyContext>,
|
||||||
|
) -> Result<HttpResponse<Body>, LemmyError> {
|
||||||
|
let community = blocking(context.pool(), move |conn| {
|
||||||
|
Community::read_from_name(&conn, &info.community_name)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
let community_id = community.id;
|
||||||
|
let posts = blocking(context.pool(), move |conn| {
|
||||||
|
Post::list_for_community(conn, community_id)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
let mut pages: Vec<AnyBase> = vec![];
|
||||||
|
for p in posts {
|
||||||
|
pages.push(p.to_apub(context.pool()).await?.into_any_base()?);
|
||||||
|
}
|
||||||
|
|
||||||
|
let len = pages.len();
|
||||||
|
let mut collection = OrderedCollection::new();
|
||||||
|
collection
|
||||||
|
.set_many_items(pages)
|
||||||
|
.set_context(activitystreams::context())
|
||||||
|
.set_id(community.get_outbox_url()?)
|
||||||
|
.set_total_items(len as u64);
|
||||||
|
Ok(create_apub_response(&collection))
|
||||||
|
}
|
28
lemmy_apub/src/http/mod.rs
Normal file
28
lemmy_apub/src/http/mod.rs
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
use crate::APUB_JSON_CONTENT_TYPE;
|
||||||
|
use actix_web::{body::Body, HttpResponse};
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
pub mod comment;
|
||||||
|
pub mod community;
|
||||||
|
pub mod post;
|
||||||
|
pub mod user;
|
||||||
|
|
||||||
|
/// Convert the data to json and turn it into an HTTP Response with the correct ActivityPub
|
||||||
|
/// headers.
|
||||||
|
fn create_apub_response<T>(data: &T) -> HttpResponse<Body>
|
||||||
|
where
|
||||||
|
T: Serialize,
|
||||||
|
{
|
||||||
|
HttpResponse::Ok()
|
||||||
|
.content_type(APUB_JSON_CONTENT_TYPE)
|
||||||
|
.json(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_apub_tombstone_response<T>(data: &T) -> HttpResponse<Body>
|
||||||
|
where
|
||||||
|
T: Serialize,
|
||||||
|
{
|
||||||
|
HttpResponse::Gone()
|
||||||
|
.content_type(APUB_JSON_CONTENT_TYPE)
|
||||||
|
.json(data)
|
||||||
|
}
|
34
lemmy_apub/src/http/post.rs
Normal file
34
lemmy_apub/src/http/post.rs
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
use crate::{
|
||||||
|
http::{create_apub_response, create_apub_tombstone_response},
|
||||||
|
ToApub,
|
||||||
|
};
|
||||||
|
use actix_web::{body::Body, web, HttpResponse};
|
||||||
|
use diesel::result::Error::NotFound;
|
||||||
|
use lemmy_db::post::Post;
|
||||||
|
use lemmy_structs::blocking;
|
||||||
|
use lemmy_utils::LemmyError;
|
||||||
|
use lemmy_websocket::LemmyContext;
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct PostQuery {
|
||||||
|
post_id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the post json over HTTP.
|
||||||
|
pub async fn get_apub_post(
|
||||||
|
info: web::Path<PostQuery>,
|
||||||
|
context: web::Data<LemmyContext>,
|
||||||
|
) -> Result<HttpResponse<Body>, LemmyError> {
|
||||||
|
let id = info.post_id.parse::<i32>()?;
|
||||||
|
let post = blocking(context.pool(), move |conn| Post::read(conn, id)).await??;
|
||||||
|
if !post.local {
|
||||||
|
return Err(NotFound.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
if !post.deleted {
|
||||||
|
Ok(create_apub_response(&post.to_apub(context.pool()).await?))
|
||||||
|
} else {
|
||||||
|
Ok(create_apub_tombstone_response(&post.to_tombstone()?))
|
||||||
|
}
|
||||||
|
}
|
26
lemmy_apub/src/http/user.rs
Normal file
26
lemmy_apub/src/http/user.rs
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
use crate::{http::create_apub_response, ToApub};
|
||||||
|
use actix_web::{body::Body, web, HttpResponse};
|
||||||
|
use lemmy_db::user::User_;
|
||||||
|
use lemmy_structs::blocking;
|
||||||
|
use lemmy_utils::LemmyError;
|
||||||
|
use lemmy_websocket::LemmyContext;
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct UserQuery {
|
||||||
|
user_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the user json over HTTP.
|
||||||
|
pub async fn get_apub_user_http(
|
||||||
|
info: web::Path<UserQuery>,
|
||||||
|
context: web::Data<LemmyContext>,
|
||||||
|
) -> Result<HttpResponse<Body>, LemmyError> {
|
||||||
|
let user_name = info.into_inner().user_name;
|
||||||
|
let user = blocking(context.pool(), move |conn| {
|
||||||
|
User_::find_by_email_or_username(conn, &user_name)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
let u = user.to_apub(context.pool()).await?;
|
||||||
|
Ok(create_apub_response(&u))
|
||||||
|
}
|
|
@ -1,8 +0,0 @@
|
||||||
pub mod announce;
|
|
||||||
pub mod create;
|
|
||||||
pub mod delete;
|
|
||||||
pub mod dislike;
|
|
||||||
pub mod like;
|
|
||||||
pub mod remove;
|
|
||||||
pub mod undo;
|
|
||||||
pub mod update;
|
|
|
@ -1,702 +0,0 @@
|
||||||
use crate::{
|
|
||||||
fetcher::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post},
|
|
||||||
inbox::shared_inbox::{
|
|
||||||
announce_if_community_is_local,
|
|
||||||
get_user_from_activity,
|
|
||||||
receive_unhandled_activity,
|
|
||||||
},
|
|
||||||
ActorType,
|
|
||||||
FromApub,
|
|
||||||
GroupExt,
|
|
||||||
PageExt,
|
|
||||||
};
|
|
||||||
use activitystreams::{
|
|
||||||
activity::*,
|
|
||||||
base::{AnyBase, AsBase},
|
|
||||||
object::Note,
|
|
||||||
prelude::*,
|
|
||||||
};
|
|
||||||
use actix_web::HttpResponse;
|
|
||||||
use anyhow::{anyhow, Context};
|
|
||||||
use lemmy_db::{
|
|
||||||
comment::{Comment, CommentForm, CommentLike},
|
|
||||||
comment_view::CommentView,
|
|
||||||
community::{Community, CommunityForm},
|
|
||||||
community_view::CommunityView,
|
|
||||||
naive_now,
|
|
||||||
post::{Post, PostForm, PostLike},
|
|
||||||
post_view::PostView,
|
|
||||||
Crud,
|
|
||||||
Likeable,
|
|
||||||
};
|
|
||||||
use lemmy_structs::{
|
|
||||||
blocking,
|
|
||||||
comment::CommentResponse,
|
|
||||||
community::CommunityResponse,
|
|
||||||
post::PostResponse,
|
|
||||||
};
|
|
||||||
use lemmy_utils::{location_info, LemmyError};
|
|
||||||
use lemmy_websocket::{
|
|
||||||
messages::{SendComment, SendCommunityRoomMessage, SendPost},
|
|
||||||
LemmyContext,
|
|
||||||
UserOperation,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub async fn receive_undo(
|
|
||||||
activity: AnyBase,
|
|
||||||
context: &LemmyContext,
|
|
||||||
) -> Result<HttpResponse, LemmyError> {
|
|
||||||
let undo = Undo::from_any_base(activity)?.context(location_info!())?;
|
|
||||||
match undo.object().as_single_kind_str() {
|
|
||||||
Some("Delete") => receive_undo_delete(undo, context).await,
|
|
||||||
Some("Remove") => receive_undo_remove(undo, context).await,
|
|
||||||
Some("Like") => receive_undo_like(undo, context).await,
|
|
||||||
Some("Dislike") => receive_undo_dislike(undo, context).await,
|
|
||||||
_ => receive_unhandled_activity(undo),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_is_undo_valid<T, A>(outer_activity: &Undo, inner_activity: &T) -> Result<(), LemmyError>
|
|
||||||
where
|
|
||||||
T: AsBase<A> + ActorAndObjectRef,
|
|
||||||
{
|
|
||||||
let outer_actor = outer_activity.actor()?;
|
|
||||||
let outer_actor_uri = outer_actor
|
|
||||||
.as_single_xsd_any_uri()
|
|
||||||
.context(location_info!())?;
|
|
||||||
|
|
||||||
let inner_actor = inner_activity.actor()?;
|
|
||||||
let inner_actor_uri = inner_actor
|
|
||||||
.as_single_xsd_any_uri()
|
|
||||||
.context(location_info!())?;
|
|
||||||
|
|
||||||
if outer_actor_uri.domain() != inner_actor_uri.domain() {
|
|
||||||
Err(anyhow!("Cant undo activities from a different instance").into())
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn receive_undo_delete(
|
|
||||||
undo: Undo,
|
|
||||||
context: &LemmyContext,
|
|
||||||
) -> Result<HttpResponse, LemmyError> {
|
|
||||||
let delete = Delete::from_any_base(undo.object().to_owned().one().context(location_info!())?)?
|
|
||||||
.context(location_info!())?;
|
|
||||||
check_is_undo_valid(&undo, &delete)?;
|
|
||||||
let type_ = delete
|
|
||||||
.object()
|
|
||||||
.as_single_kind_str()
|
|
||||||
.context(location_info!())?;
|
|
||||||
match type_ {
|
|
||||||
"Note" => receive_undo_delete_comment(undo, &delete, context).await,
|
|
||||||
"Page" => receive_undo_delete_post(undo, &delete, context).await,
|
|
||||||
"Group" => receive_undo_delete_community(undo, &delete, context).await,
|
|
||||||
d => Err(anyhow!("Undo Delete type {} not supported", d).into()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn receive_undo_remove(
|
|
||||||
undo: Undo,
|
|
||||||
context: &LemmyContext,
|
|
||||||
) -> Result<HttpResponse, LemmyError> {
|
|
||||||
let remove = Remove::from_any_base(undo.object().to_owned().one().context(location_info!())?)?
|
|
||||||
.context(location_info!())?;
|
|
||||||
check_is_undo_valid(&undo, &remove)?;
|
|
||||||
|
|
||||||
let type_ = remove
|
|
||||||
.object()
|
|
||||||
.as_single_kind_str()
|
|
||||||
.context(location_info!())?;
|
|
||||||
match type_ {
|
|
||||||
"Note" => receive_undo_remove_comment(undo, &remove, context).await,
|
|
||||||
"Page" => receive_undo_remove_post(undo, &remove, context).await,
|
|
||||||
"Group" => receive_undo_remove_community(undo, &remove, context).await,
|
|
||||||
d => Err(anyhow!("Undo Delete type {} not supported", d).into()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn receive_undo_like(undo: Undo, context: &LemmyContext) -> Result<HttpResponse, LemmyError> {
|
|
||||||
let like = Like::from_any_base(undo.object().to_owned().one().context(location_info!())?)?
|
|
||||||
.context(location_info!())?;
|
|
||||||
check_is_undo_valid(&undo, &like)?;
|
|
||||||
|
|
||||||
let type_ = like
|
|
||||||
.object()
|
|
||||||
.as_single_kind_str()
|
|
||||||
.context(location_info!())?;
|
|
||||||
match type_ {
|
|
||||||
"Note" => receive_undo_like_comment(undo, &like, context).await,
|
|
||||||
"Page" => receive_undo_like_post(undo, &like, context).await,
|
|
||||||
d => Err(anyhow!("Undo Delete type {} not supported", d).into()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn receive_undo_dislike(
|
|
||||||
undo: Undo,
|
|
||||||
context: &LemmyContext,
|
|
||||||
) -> Result<HttpResponse, LemmyError> {
|
|
||||||
let dislike = Dislike::from_any_base(undo.object().to_owned().one().context(location_info!())?)?
|
|
||||||
.context(location_info!())?;
|
|
||||||
check_is_undo_valid(&undo, &dislike)?;
|
|
||||||
|
|
||||||
let type_ = dislike
|
|
||||||
.object()
|
|
||||||
.as_single_kind_str()
|
|
||||||
.context(location_info!())?;
|
|
||||||
match type_ {
|
|
||||||
"Note" => receive_undo_dislike_comment(undo, &dislike, context).await,
|
|
||||||
"Page" => receive_undo_dislike_post(undo, &dislike, context).await,
|
|
||||||
d => Err(anyhow!("Undo Delete type {} not supported", d).into()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn receive_undo_delete_comment(
|
|
||||||
undo: Undo,
|
|
||||||
delete: &Delete,
|
|
||||||
context: &LemmyContext,
|
|
||||||
) -> Result<HttpResponse, LemmyError> {
|
|
||||||
let user = get_user_from_activity(delete, context).await?;
|
|
||||||
let note = Note::from_any_base(delete.object().to_owned().one().context(location_info!())?)?
|
|
||||||
.context(location_info!())?;
|
|
||||||
|
|
||||||
let comment_ap_id = CommentForm::from_apub(¬e, context, Some(user.actor_id()?))
|
|
||||||
.await?
|
|
||||||
.get_ap_id()?;
|
|
||||||
|
|
||||||
let comment = get_or_fetch_and_insert_comment(&comment_ap_id, context).await?;
|
|
||||||
|
|
||||||
let comment_form = CommentForm {
|
|
||||||
content: comment.content.to_owned(),
|
|
||||||
parent_id: comment.parent_id,
|
|
||||||
post_id: comment.post_id,
|
|
||||||
creator_id: comment.creator_id,
|
|
||||||
removed: None,
|
|
||||||
deleted: Some(false),
|
|
||||||
read: None,
|
|
||||||
published: None,
|
|
||||||
updated: Some(naive_now()),
|
|
||||||
ap_id: Some(comment.ap_id),
|
|
||||||
local: comment.local,
|
|
||||||
};
|
|
||||||
let comment_id = comment.id;
|
|
||||||
blocking(context.pool(), move |conn| {
|
|
||||||
Comment::update(conn, comment_id, &comment_form)
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
// Refetch the view
|
|
||||||
let comment_id = comment.id;
|
|
||||||
let comment_view = blocking(context.pool(), move |conn| {
|
|
||||||
CommentView::read(conn, comment_id, None)
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
// TODO get those recipient actor ids from somewhere
|
|
||||||
let recipient_ids = vec![];
|
|
||||||
let res = CommentResponse {
|
|
||||||
comment: comment_view,
|
|
||||||
recipient_ids,
|
|
||||||
form_id: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
context.chat_server().do_send(SendComment {
|
|
||||||
op: UserOperation::EditComment,
|
|
||||||
comment: res,
|
|
||||||
websocket_id: None,
|
|
||||||
});
|
|
||||||
|
|
||||||
announce_if_community_is_local(undo, &user, context).await?;
|
|
||||||
Ok(HttpResponse::Ok().finish())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn receive_undo_remove_comment(
|
|
||||||
undo: Undo,
|
|
||||||
remove: &Remove,
|
|
||||||
context: &LemmyContext,
|
|
||||||
) -> Result<HttpResponse, LemmyError> {
|
|
||||||
let mod_ = get_user_from_activity(remove, context).await?;
|
|
||||||
let note = Note::from_any_base(remove.object().to_owned().one().context(location_info!())?)?
|
|
||||||
.context(location_info!())?;
|
|
||||||
|
|
||||||
let comment_ap_id = CommentForm::from_apub(¬e, context, None)
|
|
||||||
.await?
|
|
||||||
.get_ap_id()?;
|
|
||||||
|
|
||||||
let comment = get_or_fetch_and_insert_comment(&comment_ap_id, context).await?;
|
|
||||||
|
|
||||||
let comment_form = CommentForm {
|
|
||||||
content: comment.content.to_owned(),
|
|
||||||
parent_id: comment.parent_id,
|
|
||||||
post_id: comment.post_id,
|
|
||||||
creator_id: comment.creator_id,
|
|
||||||
removed: Some(false),
|
|
||||||
deleted: None,
|
|
||||||
read: None,
|
|
||||||
published: None,
|
|
||||||
updated: Some(naive_now()),
|
|
||||||
ap_id: Some(comment.ap_id),
|
|
||||||
local: comment.local,
|
|
||||||
};
|
|
||||||
let comment_id = comment.id;
|
|
||||||
blocking(context.pool(), move |conn| {
|
|
||||||
Comment::update(conn, comment_id, &comment_form)
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
// Refetch the view
|
|
||||||
let comment_id = comment.id;
|
|
||||||
let comment_view = blocking(context.pool(), move |conn| {
|
|
||||||
CommentView::read(conn, comment_id, None)
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
// TODO get those recipient actor ids from somewhere
|
|
||||||
let recipient_ids = vec![];
|
|
||||||
let res = CommentResponse {
|
|
||||||
comment: comment_view,
|
|
||||||
recipient_ids,
|
|
||||||
form_id: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
context.chat_server().do_send(SendComment {
|
|
||||||
op: UserOperation::EditComment,
|
|
||||||
comment: res,
|
|
||||||
websocket_id: None,
|
|
||||||
});
|
|
||||||
|
|
||||||
announce_if_community_is_local(undo, &mod_, context).await?;
|
|
||||||
Ok(HttpResponse::Ok().finish())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn receive_undo_delete_post(
|
|
||||||
undo: Undo,
|
|
||||||
delete: &Delete,
|
|
||||||
context: &LemmyContext,
|
|
||||||
) -> Result<HttpResponse, LemmyError> {
|
|
||||||
let user = get_user_from_activity(delete, context).await?;
|
|
||||||
let page = PageExt::from_any_base(delete.object().to_owned().one().context(location_info!())?)?
|
|
||||||
.context(location_info!())?;
|
|
||||||
|
|
||||||
let post_ap_id = PostForm::from_apub(&page, context, Some(user.actor_id()?))
|
|
||||||
.await?
|
|
||||||
.get_ap_id()?;
|
|
||||||
|
|
||||||
let post = get_or_fetch_and_insert_post(&post_ap_id, context).await?;
|
|
||||||
|
|
||||||
let post_form = PostForm {
|
|
||||||
name: post.name.to_owned(),
|
|
||||||
url: post.url.to_owned(),
|
|
||||||
body: post.body.to_owned(),
|
|
||||||
creator_id: post.creator_id.to_owned(),
|
|
||||||
community_id: post.community_id,
|
|
||||||
removed: None,
|
|
||||||
deleted: Some(false),
|
|
||||||
nsfw: post.nsfw,
|
|
||||||
locked: None,
|
|
||||||
stickied: None,
|
|
||||||
updated: Some(naive_now()),
|
|
||||||
embed_title: post.embed_title,
|
|
||||||
embed_description: post.embed_description,
|
|
||||||
embed_html: post.embed_html,
|
|
||||||
thumbnail_url: post.thumbnail_url,
|
|
||||||
ap_id: Some(post.ap_id),
|
|
||||||
local: post.local,
|
|
||||||
published: None,
|
|
||||||
};
|
|
||||||
let post_id = post.id;
|
|
||||||
blocking(context.pool(), move |conn| {
|
|
||||||
Post::update(conn, post_id, &post_form)
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
// Refetch the view
|
|
||||||
let post_id = post.id;
|
|
||||||
let post_view = blocking(context.pool(), move |conn| {
|
|
||||||
PostView::read(conn, post_id, None)
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
let res = PostResponse { post: post_view };
|
|
||||||
|
|
||||||
context.chat_server().do_send(SendPost {
|
|
||||||
op: UserOperation::EditPost,
|
|
||||||
post: res,
|
|
||||||
websocket_id: None,
|
|
||||||
});
|
|
||||||
|
|
||||||
announce_if_community_is_local(undo, &user, context).await?;
|
|
||||||
Ok(HttpResponse::Ok().finish())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn receive_undo_remove_post(
|
|
||||||
undo: Undo,
|
|
||||||
remove: &Remove,
|
|
||||||
context: &LemmyContext,
|
|
||||||
) -> Result<HttpResponse, LemmyError> {
|
|
||||||
let mod_ = get_user_from_activity(remove, context).await?;
|
|
||||||
let page = PageExt::from_any_base(remove.object().to_owned().one().context(location_info!())?)?
|
|
||||||
.context(location_info!())?;
|
|
||||||
|
|
||||||
let post_ap_id = PostForm::from_apub(&page, context, None)
|
|
||||||
.await?
|
|
||||||
.get_ap_id()?;
|
|
||||||
|
|
||||||
let post = get_or_fetch_and_insert_post(&post_ap_id, context).await?;
|
|
||||||
|
|
||||||
let post_form = PostForm {
|
|
||||||
name: post.name.to_owned(),
|
|
||||||
url: post.url.to_owned(),
|
|
||||||
body: post.body.to_owned(),
|
|
||||||
creator_id: post.creator_id.to_owned(),
|
|
||||||
community_id: post.community_id,
|
|
||||||
removed: Some(false),
|
|
||||||
deleted: None,
|
|
||||||
nsfw: post.nsfw,
|
|
||||||
locked: None,
|
|
||||||
stickied: None,
|
|
||||||
updated: Some(naive_now()),
|
|
||||||
embed_title: post.embed_title,
|
|
||||||
embed_description: post.embed_description,
|
|
||||||
embed_html: post.embed_html,
|
|
||||||
thumbnail_url: post.thumbnail_url,
|
|
||||||
ap_id: Some(post.ap_id),
|
|
||||||
local: post.local,
|
|
||||||
published: None,
|
|
||||||
};
|
|
||||||
let post_id = post.id;
|
|
||||||
blocking(context.pool(), move |conn| {
|
|
||||||
Post::update(conn, post_id, &post_form)
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
// Refetch the view
|
|
||||||
let post_id = post.id;
|
|
||||||
let post_view = blocking(context.pool(), move |conn| {
|
|
||||||
PostView::read(conn, post_id, None)
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
let res = PostResponse { post: post_view };
|
|
||||||
|
|
||||||
context.chat_server().do_send(SendPost {
|
|
||||||
op: UserOperation::EditPost,
|
|
||||||
post: res,
|
|
||||||
websocket_id: None,
|
|
||||||
});
|
|
||||||
|
|
||||||
announce_if_community_is_local(undo, &mod_, context).await?;
|
|
||||||
Ok(HttpResponse::Ok().finish())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn receive_undo_delete_community(
|
|
||||||
undo: Undo,
|
|
||||||
delete: &Delete,
|
|
||||||
context: &LemmyContext,
|
|
||||||
) -> Result<HttpResponse, LemmyError> {
|
|
||||||
let user = get_user_from_activity(delete, context).await?;
|
|
||||||
let group = GroupExt::from_any_base(delete.object().to_owned().one().context(location_info!())?)?
|
|
||||||
.context(location_info!())?;
|
|
||||||
|
|
||||||
let community_actor_id = CommunityForm::from_apub(&group, context, Some(user.actor_id()?))
|
|
||||||
.await?
|
|
||||||
.actor_id
|
|
||||||
.context(location_info!())?;
|
|
||||||
|
|
||||||
let community = blocking(context.pool(), move |conn| {
|
|
||||||
Community::read_from_actor_id(conn, &community_actor_id)
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
let community_form = CommunityForm {
|
|
||||||
name: community.name.to_owned(),
|
|
||||||
title: community.title.to_owned(),
|
|
||||||
description: community.description.to_owned(),
|
|
||||||
category_id: community.category_id, // Note: need to keep this due to foreign key constraint
|
|
||||||
creator_id: community.creator_id, // Note: need to keep this due to foreign key constraint
|
|
||||||
removed: None,
|
|
||||||
published: None,
|
|
||||||
updated: Some(naive_now()),
|
|
||||||
deleted: Some(false),
|
|
||||||
nsfw: community.nsfw,
|
|
||||||
actor_id: Some(community.actor_id),
|
|
||||||
local: community.local,
|
|
||||||
private_key: community.private_key,
|
|
||||||
public_key: community.public_key,
|
|
||||||
last_refreshed_at: None,
|
|
||||||
icon: Some(community.icon.to_owned()),
|
|
||||||
banner: Some(community.banner.to_owned()),
|
|
||||||
};
|
|
||||||
|
|
||||||
let community_id = community.id;
|
|
||||||
blocking(context.pool(), move |conn| {
|
|
||||||
Community::update(conn, community_id, &community_form)
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
let community_id = community.id;
|
|
||||||
let res = CommunityResponse {
|
|
||||||
community: blocking(context.pool(), move |conn| {
|
|
||||||
CommunityView::read(conn, community_id, None)
|
|
||||||
})
|
|
||||||
.await??,
|
|
||||||
};
|
|
||||||
|
|
||||||
let community_id = res.community.id;
|
|
||||||
|
|
||||||
context.chat_server().do_send(SendCommunityRoomMessage {
|
|
||||||
op: UserOperation::EditCommunity,
|
|
||||||
response: res,
|
|
||||||
community_id,
|
|
||||||
websocket_id: None,
|
|
||||||
});
|
|
||||||
|
|
||||||
announce_if_community_is_local(undo, &user, context).await?;
|
|
||||||
Ok(HttpResponse::Ok().finish())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn receive_undo_remove_community(
|
|
||||||
undo: Undo,
|
|
||||||
remove: &Remove,
|
|
||||||
context: &LemmyContext,
|
|
||||||
) -> Result<HttpResponse, LemmyError> {
|
|
||||||
let mod_ = get_user_from_activity(remove, context).await?;
|
|
||||||
let group = GroupExt::from_any_base(remove.object().to_owned().one().context(location_info!())?)?
|
|
||||||
.context(location_info!())?;
|
|
||||||
|
|
||||||
let community_actor_id = CommunityForm::from_apub(&group, context, Some(mod_.actor_id()?))
|
|
||||||
.await?
|
|
||||||
.actor_id
|
|
||||||
.context(location_info!())?;
|
|
||||||
|
|
||||||
let community = blocking(context.pool(), move |conn| {
|
|
||||||
Community::read_from_actor_id(conn, &community_actor_id)
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
let community_form = CommunityForm {
|
|
||||||
name: community.name.to_owned(),
|
|
||||||
title: community.title.to_owned(),
|
|
||||||
description: community.description.to_owned(),
|
|
||||||
category_id: community.category_id, // Note: need to keep this due to foreign key constraint
|
|
||||||
creator_id: community.creator_id, // Note: need to keep this due to foreign key constraint
|
|
||||||
removed: Some(false),
|
|
||||||
published: None,
|
|
||||||
updated: Some(naive_now()),
|
|
||||||
deleted: None,
|
|
||||||
nsfw: community.nsfw,
|
|
||||||
actor_id: Some(community.actor_id),
|
|
||||||
local: community.local,
|
|
||||||
private_key: community.private_key,
|
|
||||||
public_key: community.public_key,
|
|
||||||
last_refreshed_at: None,
|
|
||||||
icon: Some(community.icon.to_owned()),
|
|
||||||
banner: Some(community.banner.to_owned()),
|
|
||||||
};
|
|
||||||
|
|
||||||
let community_id = community.id;
|
|
||||||
blocking(context.pool(), move |conn| {
|
|
||||||
Community::update(conn, community_id, &community_form)
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
let community_id = community.id;
|
|
||||||
let res = CommunityResponse {
|
|
||||||
community: blocking(context.pool(), move |conn| {
|
|
||||||
CommunityView::read(conn, community_id, None)
|
|
||||||
})
|
|
||||||
.await??,
|
|
||||||
};
|
|
||||||
|
|
||||||
let community_id = res.community.id;
|
|
||||||
|
|
||||||
context.chat_server().do_send(SendCommunityRoomMessage {
|
|
||||||
op: UserOperation::EditCommunity,
|
|
||||||
response: res,
|
|
||||||
community_id,
|
|
||||||
websocket_id: None,
|
|
||||||
});
|
|
||||||
|
|
||||||
announce_if_community_is_local(undo, &mod_, context).await?;
|
|
||||||
Ok(HttpResponse::Ok().finish())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn receive_undo_like_comment(
|
|
||||||
undo: Undo,
|
|
||||||
like: &Like,
|
|
||||||
context: &LemmyContext,
|
|
||||||
) -> Result<HttpResponse, LemmyError> {
|
|
||||||
let user = get_user_from_activity(like, context).await?;
|
|
||||||
let note = Note::from_any_base(like.object().to_owned().one().context(location_info!())?)?
|
|
||||||
.context(location_info!())?;
|
|
||||||
|
|
||||||
let comment = CommentForm::from_apub(¬e, context, None).await?;
|
|
||||||
|
|
||||||
let comment_id = get_or_fetch_and_insert_comment(&comment.get_ap_id()?, context)
|
|
||||||
.await?
|
|
||||||
.id;
|
|
||||||
|
|
||||||
let user_id = user.id;
|
|
||||||
blocking(context.pool(), move |conn| {
|
|
||||||
CommentLike::remove(conn, user_id, comment_id)
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
// Refetch the view
|
|
||||||
let comment_view = blocking(context.pool(), move |conn| {
|
|
||||||
CommentView::read(conn, comment_id, None)
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
// TODO get those recipient actor ids from somewhere
|
|
||||||
let recipient_ids = vec![];
|
|
||||||
let res = CommentResponse {
|
|
||||||
comment: comment_view,
|
|
||||||
recipient_ids,
|
|
||||||
form_id: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
context.chat_server().do_send(SendComment {
|
|
||||||
op: UserOperation::CreateCommentLike,
|
|
||||||
comment: res,
|
|
||||||
websocket_id: None,
|
|
||||||
});
|
|
||||||
|
|
||||||
announce_if_community_is_local(undo, &user, context).await?;
|
|
||||||
Ok(HttpResponse::Ok().finish())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn receive_undo_like_post(
|
|
||||||
undo: Undo,
|
|
||||||
like: &Like,
|
|
||||||
context: &LemmyContext,
|
|
||||||
) -> Result<HttpResponse, LemmyError> {
|
|
||||||
let user = get_user_from_activity(like, context).await?;
|
|
||||||
let page = PageExt::from_any_base(like.object().to_owned().one().context(location_info!())?)?
|
|
||||||
.context(location_info!())?;
|
|
||||||
|
|
||||||
let post = PostForm::from_apub(&page, context, None).await?;
|
|
||||||
|
|
||||||
let post_id = get_or_fetch_and_insert_post(&post.get_ap_id()?, context)
|
|
||||||
.await?
|
|
||||||
.id;
|
|
||||||
|
|
||||||
let user_id = user.id;
|
|
||||||
blocking(context.pool(), move |conn| {
|
|
||||||
PostLike::remove(conn, user_id, post_id)
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
// Refetch the view
|
|
||||||
let post_view = blocking(context.pool(), move |conn| {
|
|
||||||
PostView::read(conn, post_id, None)
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
let res = PostResponse { post: post_view };
|
|
||||||
|
|
||||||
context.chat_server().do_send(SendPost {
|
|
||||||
op: UserOperation::CreatePostLike,
|
|
||||||
post: res,
|
|
||||||
websocket_id: None,
|
|
||||||
});
|
|
||||||
|
|
||||||
announce_if_community_is_local(undo, &user, context).await?;
|
|
||||||
Ok(HttpResponse::Ok().finish())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn receive_undo_dislike_comment(
|
|
||||||
undo: Undo,
|
|
||||||
dislike: &Dislike,
|
|
||||||
context: &LemmyContext,
|
|
||||||
) -> Result<HttpResponse, LemmyError> {
|
|
||||||
let user = get_user_from_activity(dislike, context).await?;
|
|
||||||
let note = Note::from_any_base(
|
|
||||||
dislike
|
|
||||||
.object()
|
|
||||||
.to_owned()
|
|
||||||
.one()
|
|
||||||
.context(location_info!())?,
|
|
||||||
)?
|
|
||||||
.context(location_info!())?;
|
|
||||||
|
|
||||||
let comment = CommentForm::from_apub(¬e, context, None).await?;
|
|
||||||
|
|
||||||
let comment_id = get_or_fetch_and_insert_comment(&comment.get_ap_id()?, context)
|
|
||||||
.await?
|
|
||||||
.id;
|
|
||||||
|
|
||||||
let user_id = user.id;
|
|
||||||
blocking(context.pool(), move |conn| {
|
|
||||||
CommentLike::remove(conn, user_id, comment_id)
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
// Refetch the view
|
|
||||||
let comment_view = blocking(context.pool(), move |conn| {
|
|
||||||
CommentView::read(conn, comment_id, None)
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
// TODO get those recipient actor ids from somewhere
|
|
||||||
let recipient_ids = vec![];
|
|
||||||
let res = CommentResponse {
|
|
||||||
comment: comment_view,
|
|
||||||
recipient_ids,
|
|
||||||
form_id: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
context.chat_server().do_send(SendComment {
|
|
||||||
op: UserOperation::CreateCommentLike,
|
|
||||||
comment: res,
|
|
||||||
websocket_id: None,
|
|
||||||
});
|
|
||||||
|
|
||||||
announce_if_community_is_local(undo, &user, context).await?;
|
|
||||||
Ok(HttpResponse::Ok().finish())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn receive_undo_dislike_post(
|
|
||||||
undo: Undo,
|
|
||||||
dislike: &Dislike,
|
|
||||||
context: &LemmyContext,
|
|
||||||
) -> Result<HttpResponse, LemmyError> {
|
|
||||||
let user = get_user_from_activity(dislike, context).await?;
|
|
||||||
let page = PageExt::from_any_base(
|
|
||||||
dislike
|
|
||||||
.object()
|
|
||||||
.to_owned()
|
|
||||||
.one()
|
|
||||||
.context(location_info!())?,
|
|
||||||
)?
|
|
||||||
.context(location_info!())?;
|
|
||||||
|
|
||||||
let post = PostForm::from_apub(&page, context, None).await?;
|
|
||||||
|
|
||||||
let post_id = get_or_fetch_and_insert_post(&post.get_ap_id()?, context)
|
|
||||||
.await?
|
|
||||||
.id;
|
|
||||||
|
|
||||||
let user_id = user.id;
|
|
||||||
blocking(context.pool(), move |conn| {
|
|
||||||
PostLike::remove(conn, user_id, post_id)
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
// Refetch the view
|
|
||||||
let post_view = blocking(context.pool(), move |conn| {
|
|
||||||
PostView::read(conn, post_id, None)
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
let res = PostResponse { post: post_view };
|
|
||||||
|
|
||||||
context.chat_server().do_send(SendPost {
|
|
||||||
op: UserOperation::CreatePostLike,
|
|
||||||
post: res,
|
|
||||||
websocket_id: None,
|
|
||||||
});
|
|
||||||
|
|
||||||
announce_if_community_is_local(undo, &user, context).await?;
|
|
||||||
Ok(HttpResponse::Ok().finish())
|
|
||||||
}
|
|
|
@ -33,7 +33,7 @@ pub enum ValidTypes {
|
||||||
|
|
||||||
pub type AcceptedActivities = ActorAndObject<ValidTypes>;
|
pub type AcceptedActivities = ActorAndObject<ValidTypes>;
|
||||||
|
|
||||||
/// Handler for all incoming activities to community inboxes.
|
/// Handler for all incoming receive to community inboxes.
|
||||||
pub async fn community_inbox(
|
pub async fn community_inbox(
|
||||||
request: HttpRequest,
|
request: HttpRequest,
|
||||||
input: web::Json<AcceptedActivities>,
|
input: web::Json<AcceptedActivities>,
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
pub mod activities;
|
|
||||||
pub mod community_inbox;
|
pub mod community_inbox;
|
||||||
pub mod shared_inbox;
|
pub mod shared_inbox;
|
||||||
pub mod user_inbox;
|
pub mod user_inbox;
|
||||||
|
|
|
@ -1,13 +1,5 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
check_is_apub_id_valid,
|
activities::receive::{
|
||||||
community::do_announce,
|
|
||||||
extensions::signatures::verify,
|
|
||||||
fetcher::{
|
|
||||||
get_or_fetch_and_upsert_actor,
|
|
||||||
get_or_fetch_and_upsert_community,
|
|
||||||
get_or_fetch_and_upsert_user,
|
|
||||||
},
|
|
||||||
inbox::activities::{
|
|
||||||
announce::receive_announce,
|
announce::receive_announce,
|
||||||
create::receive_create,
|
create::receive_create,
|
||||||
delete::receive_delete,
|
delete::receive_delete,
|
||||||
|
@ -17,11 +9,14 @@ use crate::{
|
||||||
undo::receive_undo,
|
undo::receive_undo,
|
||||||
update::receive_update,
|
update::receive_update,
|
||||||
},
|
},
|
||||||
|
check_is_apub_id_valid,
|
||||||
|
extensions::signatures::verify,
|
||||||
|
fetcher::{get_or_fetch_and_upsert_actor, get_or_fetch_and_upsert_user},
|
||||||
insert_activity,
|
insert_activity,
|
||||||
};
|
};
|
||||||
use activitystreams::{
|
use activitystreams::{
|
||||||
activity::{ActorAndObject, ActorAndObjectRef},
|
activity::{ActorAndObject, ActorAndObjectRef},
|
||||||
base::{AsBase, Extends},
|
base::AsBase,
|
||||||
object::AsObject,
|
object::AsObject,
|
||||||
prelude::*,
|
prelude::*,
|
||||||
};
|
};
|
||||||
|
@ -48,11 +43,11 @@ pub enum ValidTypes {
|
||||||
Announce,
|
Announce,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: this isnt entirely correct, cause some of these activities are not ActorAndObject,
|
// TODO: this isnt entirely correct, cause some of these receive are not ActorAndObject,
|
||||||
// but it might still work due to the anybase conversion
|
// but it might still work due to the anybase conversion
|
||||||
pub type AcceptedActivities = ActorAndObject<ValidTypes>;
|
pub type AcceptedActivities = ActorAndObject<ValidTypes>;
|
||||||
|
|
||||||
/// Handler for all incoming activities to user inboxes.
|
/// Handler for all incoming receive to user inboxes.
|
||||||
pub async fn shared_inbox(
|
pub async fn shared_inbox(
|
||||||
request: HttpRequest,
|
request: HttpRequest,
|
||||||
input: web::Json<AcceptedActivities>,
|
input: web::Json<AcceptedActivities>,
|
||||||
|
@ -95,17 +90,7 @@ pub async fn shared_inbox(
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(in crate::inbox) fn receive_unhandled_activity<A>(
|
pub(in crate) async fn get_user_from_activity<T, A>(
|
||||||
activity: A,
|
|
||||||
) -> Result<HttpResponse, LemmyError>
|
|
||||||
where
|
|
||||||
A: Debug,
|
|
||||||
{
|
|
||||||
debug!("received unhandled activity type: {:?}", activity);
|
|
||||||
Ok(HttpResponse::NotImplemented().finish())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(in crate::inbox) async fn get_user_from_activity<T, A>(
|
|
||||||
activity: &T,
|
activity: &T,
|
||||||
context: &LemmyContext,
|
context: &LemmyContext,
|
||||||
) -> Result<User_, LemmyError>
|
) -> Result<User_, LemmyError>
|
||||||
|
@ -117,9 +102,7 @@ where
|
||||||
get_or_fetch_and_upsert_user(&user_uri, context).await
|
get_or_fetch_and_upsert_user(&user_uri, context).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(in crate::inbox) fn get_community_id_from_activity<T, A>(
|
pub(in crate) fn get_community_id_from_activity<T, A>(activity: &T) -> Result<Url, LemmyError>
|
||||||
activity: &T,
|
|
||||||
) -> Result<Url, LemmyError>
|
|
||||||
where
|
where
|
||||||
T: AsBase<A> + ActorAndObjectRef + AsObject<A>,
|
T: AsBase<A> + ActorAndObjectRef + AsObject<A>,
|
||||||
{
|
{
|
||||||
|
@ -133,33 +116,3 @@ where
|
||||||
.to_owned(),
|
.to_owned(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(in crate::inbox) async fn announce_if_community_is_local<T, Kind>(
|
|
||||||
activity: T,
|
|
||||||
user: &User_,
|
|
||||||
context: &LemmyContext,
|
|
||||||
) -> Result<(), LemmyError>
|
|
||||||
where
|
|
||||||
T: AsObject<Kind>,
|
|
||||||
T: Extends<Kind>,
|
|
||||||
Kind: Serialize,
|
|
||||||
<T as Extends<Kind>>::Error: From<serde_json::Error> + Send + Sync + 'static,
|
|
||||||
{
|
|
||||||
let cc = activity.cc().context(location_info!())?;
|
|
||||||
let cc = cc.as_many().context(location_info!())?;
|
|
||||||
let community_followers_uri = cc
|
|
||||||
.first()
|
|
||||||
.context(location_info!())?
|
|
||||||
.as_xsd_any_uri()
|
|
||||||
.context(location_info!())?;
|
|
||||||
// TODO: this is hacky but seems to be the only way to get the community ID
|
|
||||||
let community_uri = community_followers_uri
|
|
||||||
.to_string()
|
|
||||||
.replace("/followers", "");
|
|
||||||
let community = get_or_fetch_and_upsert_community(&Url::parse(&community_uri)?, context).await?;
|
|
||||||
|
|
||||||
if community.local {
|
|
||||||
do_announce(activity.into_any_base()?, &community, &user, context).await?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
|
@ -41,7 +41,7 @@ pub enum ValidTypes {
|
||||||
|
|
||||||
pub type AcceptedActivities = ActorAndObject<ValidTypes>;
|
pub type AcceptedActivities = ActorAndObject<ValidTypes>;
|
||||||
|
|
||||||
/// Handler for all incoming activities to user inboxes.
|
/// Handler for all incoming receive to user inboxes.
|
||||||
pub async fn user_inbox(
|
pub async fn user_inbox(
|
||||||
request: HttpRequest,
|
request: HttpRequest,
|
||||||
input: web::Json<AcceptedActivities>,
|
input: web::Json<AcceptedActivities>,
|
||||||
|
|
|
@ -1,15 +1,13 @@
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate lazy_static;
|
extern crate lazy_static;
|
||||||
|
|
||||||
|
pub mod activities;
|
||||||
pub mod activity_queue;
|
pub mod activity_queue;
|
||||||
pub mod comment;
|
|
||||||
pub mod community;
|
|
||||||
pub mod extensions;
|
pub mod extensions;
|
||||||
pub mod fetcher;
|
pub mod fetcher;
|
||||||
|
pub mod http;
|
||||||
pub mod inbox;
|
pub mod inbox;
|
||||||
pub mod post;
|
pub mod objects;
|
||||||
pub mod private_message;
|
|
||||||
pub mod user;
|
|
||||||
|
|
||||||
use crate::extensions::{
|
use crate::extensions::{
|
||||||
group_extensions::GroupExtension,
|
group_extensions::GroupExtension,
|
||||||
|
@ -19,30 +17,19 @@ use crate::extensions::{
|
||||||
use activitystreams::{
|
use activitystreams::{
|
||||||
activity::Follow,
|
activity::Follow,
|
||||||
actor::{ApActor, Group, Person},
|
actor::{ApActor, Group, Person},
|
||||||
base::AsBase,
|
base::{AnyBase, AsBase},
|
||||||
markers::Base,
|
markers::Base,
|
||||||
object::{Page, Tombstone},
|
object::{Page, Tombstone},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
};
|
};
|
||||||
use activitystreams_ext::{Ext1, Ext2};
|
use activitystreams_ext::{Ext1, Ext2};
|
||||||
use actix_web::{body::Body, HttpResponse};
|
|
||||||
use anyhow::{anyhow, Context};
|
use anyhow::{anyhow, Context};
|
||||||
use chrono::NaiveDateTime;
|
|
||||||
use lemmy_db::{activity::do_insert_activity, user::User_, DbPool};
|
use lemmy_db::{activity::do_insert_activity, user::User_, DbPool};
|
||||||
use lemmy_structs::{blocking, WebFingerResponse};
|
use lemmy_structs::blocking;
|
||||||
use lemmy_utils::{
|
use lemmy_utils::{location_info, settings::Settings, LemmyError};
|
||||||
location_info,
|
|
||||||
request::{retry, RecvError},
|
|
||||||
settings::Settings,
|
|
||||||
utils::{convert_datetime, MentionData},
|
|
||||||
LemmyError,
|
|
||||||
};
|
|
||||||
use lemmy_websocket::LemmyContext;
|
use lemmy_websocket::LemmyContext;
|
||||||
use log::debug;
|
|
||||||
use reqwest::Client;
|
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use url::{ParseError, Url};
|
use url::{ParseError, Url};
|
||||||
use uuid::Uuid;
|
|
||||||
|
|
||||||
type GroupExt = Ext2<ApActor<Group>, GroupExtension, PublicKeyExtension>;
|
type GroupExt = Ext2<ApActor<Group>, GroupExtension, PublicKeyExtension>;
|
||||||
type PersonExt = Ext1<ApActor<Person>, PublicKeyExtension>;
|
type PersonExt = Ext1<ApActor<Person>, PublicKeyExtension>;
|
||||||
|
@ -50,26 +37,6 @@ type PageExt = Ext1<Page, PageExtension>;
|
||||||
|
|
||||||
pub static APUB_JSON_CONTENT_TYPE: &str = "application/activity+json";
|
pub static APUB_JSON_CONTENT_TYPE: &str = "application/activity+json";
|
||||||
|
|
||||||
/// Convert the data to json and turn it into an HTTP Response with the correct ActivityPub
|
|
||||||
/// headers.
|
|
||||||
fn create_apub_response<T>(data: &T) -> HttpResponse<Body>
|
|
||||||
where
|
|
||||||
T: Serialize,
|
|
||||||
{
|
|
||||||
HttpResponse::Ok()
|
|
||||||
.content_type(APUB_JSON_CONTENT_TYPE)
|
|
||||||
.json(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_apub_tombstone_response<T>(data: &T) -> HttpResponse<Body>
|
|
||||||
where
|
|
||||||
T: Serialize,
|
|
||||||
{
|
|
||||||
HttpResponse::Gone()
|
|
||||||
.content_type(APUB_JSON_CONTENT_TYPE)
|
|
||||||
.json(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Checks if the ID has a valid format, correct scheme, and is in the allowed instance list.
|
// Checks if the ID has a valid format, correct scheme, and is in the allowed instance list.
|
||||||
fn check_is_apub_id_valid(apub_id: &Url) -> Result<(), LemmyError> {
|
fn check_is_apub_id_valid(apub_id: &Url) -> Result<(), LemmyError> {
|
||||||
let settings = Settings::get();
|
let settings = Settings::get();
|
||||||
|
@ -105,7 +72,7 @@ fn check_is_apub_id_valid(apub_id: &Url) -> Result<(), LemmyError> {
|
||||||
if allowed_instances.is_empty() && blocked_instances.is_empty() {
|
if allowed_instances.is_empty() && blocked_instances.is_empty() {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else if !allowed_instances.is_empty() {
|
} else if !allowed_instances.is_empty() {
|
||||||
// need to allow this explicitly because apub activities might contain objects from our local
|
// need to allow this explicitly because apub receive might contain objects from our local
|
||||||
// instance. split is needed to remove the port in our federation test setup.
|
// instance. split is needed to remove the port in our federation test setup.
|
||||||
allowed_instances.push(local_instance);
|
allowed_instances.push(local_instance);
|
||||||
|
|
||||||
|
@ -132,31 +99,6 @@ pub trait ToApub {
|
||||||
fn to_tombstone(&self) -> Result<Tombstone, LemmyError>;
|
fn to_tombstone(&self) -> Result<Tombstone, LemmyError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Updated is actually the deletion time
|
|
||||||
fn create_tombstone<T>(
|
|
||||||
deleted: bool,
|
|
||||||
object_id: &str,
|
|
||||||
updated: Option<NaiveDateTime>,
|
|
||||||
former_type: T,
|
|
||||||
) -> Result<Tombstone, LemmyError>
|
|
||||||
where
|
|
||||||
T: ToString,
|
|
||||||
{
|
|
||||||
if deleted {
|
|
||||||
if let Some(updated) = updated {
|
|
||||||
let mut tombstone = Tombstone::new();
|
|
||||||
tombstone.set_id(object_id.parse()?);
|
|
||||||
tombstone.set_former_type(former_type.to_string());
|
|
||||||
tombstone.set_deleted(convert_datetime(updated));
|
|
||||||
Ok(tombstone)
|
|
||||||
} else {
|
|
||||||
Err(anyhow!("Cant convert to tombstone because updated time was None.").into())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Err(anyhow!("Cant convert object to tombstone if it wasnt deleted").into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
#[async_trait::async_trait(?Send)]
|
||||||
pub trait FromApub {
|
pub trait FromApub {
|
||||||
type ApubType;
|
type ApubType;
|
||||||
|
@ -258,6 +200,13 @@ pub trait ActorType {
|
||||||
async fn send_remove(&self, mod_: &User_, context: &LemmyContext) -> Result<(), LemmyError>;
|
async fn send_remove(&self, mod_: &User_, context: &LemmyContext) -> Result<(), LemmyError>;
|
||||||
async fn send_undo_remove(&self, mod_: &User_, context: &LemmyContext) -> Result<(), LemmyError>;
|
async fn send_undo_remove(&self, mod_: &User_, context: &LemmyContext) -> Result<(), LemmyError>;
|
||||||
|
|
||||||
|
async fn send_announce(
|
||||||
|
&self,
|
||||||
|
activity: AnyBase,
|
||||||
|
sender: &User_,
|
||||||
|
context: &LemmyContext,
|
||||||
|
) -> Result<(), LemmyError>;
|
||||||
|
|
||||||
/// For a given community, returns the inboxes of all followers.
|
/// For a given community, returns the inboxes of all followers.
|
||||||
async fn get_follower_inboxes(&self, pool: &DbPool) -> Result<Vec<Url>, LemmyError>;
|
async fn get_follower_inboxes(&self, pool: &DbPool) -> Result<Vec<Url>, LemmyError>;
|
||||||
|
|
||||||
|
@ -305,39 +254,6 @@ pub trait ActorType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn fetch_webfinger_url(
|
|
||||||
mention: &MentionData,
|
|
||||||
client: &Client,
|
|
||||||
) -> Result<Url, LemmyError> {
|
|
||||||
let fetch_url = format!(
|
|
||||||
"{}://{}/.well-known/webfinger?resource=acct:{}@{}",
|
|
||||||
Settings::get().get_protocol_string(),
|
|
||||||
mention.domain,
|
|
||||||
mention.name,
|
|
||||||
mention.domain
|
|
||||||
);
|
|
||||||
debug!("Fetching webfinger url: {}", &fetch_url);
|
|
||||||
|
|
||||||
let response = retry(|| client.get(&fetch_url).send()).await?;
|
|
||||||
|
|
||||||
let res: WebFingerResponse = response
|
|
||||||
.json()
|
|
||||||
.await
|
|
||||||
.map_err(|e| RecvError(e.to_string()))?;
|
|
||||||
|
|
||||||
let link = res
|
|
||||||
.links
|
|
||||||
.iter()
|
|
||||||
.find(|l| l.type_.eq(&Some("application/activity+json".to_string())))
|
|
||||||
.ok_or_else(|| anyhow!("No application/activity+json link found."))?;
|
|
||||||
link
|
|
||||||
.href
|
|
||||||
.to_owned()
|
|
||||||
.map(|u| Url::parse(&u))
|
|
||||||
.transpose()?
|
|
||||||
.ok_or_else(|| anyhow!("No href found.").into())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn insert_activity<T>(
|
pub async fn insert_activity<T>(
|
||||||
user_id: i32,
|
user_id: i32,
|
||||||
data: T,
|
data: T,
|
||||||
|
@ -353,16 +269,3 @@ where
|
||||||
.await??;
|
.await??;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(in crate) fn generate_activity_id<T>(kind: T) -> Result<Url, ParseError>
|
|
||||||
where
|
|
||||||
T: ToString,
|
|
||||||
{
|
|
||||||
let id = format!(
|
|
||||||
"{}/activities/{}/{}",
|
|
||||||
Settings::get().get_protocol_and_hostname(),
|
|
||||||
kind.to_string().to_lowercase(),
|
|
||||||
Uuid::new_v4()
|
|
||||||
);
|
|
||||||
Url::parse(&id)
|
|
||||||
}
|
|
||||||
|
|
147
lemmy_apub/src/objects/comment.rs
Normal file
147
lemmy_apub/src/objects/comment.rs
Normal file
|
@ -0,0 +1,147 @@
|
||||||
|
use crate::{
|
||||||
|
check_actor_domain,
|
||||||
|
fetcher::{
|
||||||
|
get_or_fetch_and_insert_comment,
|
||||||
|
get_or_fetch_and_insert_post,
|
||||||
|
get_or_fetch_and_upsert_user,
|
||||||
|
},
|
||||||
|
objects::create_tombstone,
|
||||||
|
FromApub,
|
||||||
|
ToApub,
|
||||||
|
};
|
||||||
|
use activitystreams::{
|
||||||
|
object::{kind::NoteType, Note, Tombstone},
|
||||||
|
prelude::*,
|
||||||
|
};
|
||||||
|
use anyhow::Context;
|
||||||
|
use lemmy_db::{
|
||||||
|
comment::{Comment, CommentForm},
|
||||||
|
community::Community,
|
||||||
|
post::Post,
|
||||||
|
user::User_,
|
||||||
|
Crud,
|
||||||
|
DbPool,
|
||||||
|
};
|
||||||
|
use lemmy_structs::blocking;
|
||||||
|
use lemmy_utils::{
|
||||||
|
location_info,
|
||||||
|
utils::{convert_datetime, remove_slurs},
|
||||||
|
LemmyError,
|
||||||
|
};
|
||||||
|
use lemmy_websocket::LemmyContext;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
#[async_trait::async_trait(?Send)]
|
||||||
|
impl ToApub for Comment {
|
||||||
|
type Response = Note;
|
||||||
|
|
||||||
|
async fn to_apub(&self, pool: &DbPool) -> Result<Note, LemmyError> {
|
||||||
|
let mut comment = Note::new();
|
||||||
|
|
||||||
|
let creator_id = self.creator_id;
|
||||||
|
let creator = blocking(pool, move |conn| User_::read(conn, creator_id)).await??;
|
||||||
|
|
||||||
|
let post_id = self.post_id;
|
||||||
|
let post = blocking(pool, move |conn| Post::read(conn, post_id)).await??;
|
||||||
|
|
||||||
|
let community_id = post.community_id;
|
||||||
|
let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??;
|
||||||
|
|
||||||
|
// Add a vector containing some important info to the "in_reply_to" field
|
||||||
|
// [post_ap_id, Option(parent_comment_ap_id)]
|
||||||
|
let mut in_reply_to_vec = vec![post.ap_id];
|
||||||
|
|
||||||
|
if let Some(parent_id) = self.parent_id {
|
||||||
|
let parent_comment = blocking(pool, move |conn| Comment::read(conn, parent_id)).await??;
|
||||||
|
|
||||||
|
in_reply_to_vec.push(parent_comment.ap_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
comment
|
||||||
|
// Not needed when the Post is embedded in a collection (like for community outbox)
|
||||||
|
.set_context(activitystreams::context())
|
||||||
|
.set_id(Url::parse(&self.ap_id)?)
|
||||||
|
.set_published(convert_datetime(self.published))
|
||||||
|
.set_to(community.actor_id)
|
||||||
|
.set_many_in_reply_tos(in_reply_to_vec)
|
||||||
|
.set_content(self.content.to_owned())
|
||||||
|
.set_attributed_to(creator.actor_id);
|
||||||
|
|
||||||
|
if let Some(u) = self.updated {
|
||||||
|
comment.set_updated(convert_datetime(u));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(comment)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_tombstone(&self) -> Result<Tombstone, LemmyError> {
|
||||||
|
create_tombstone(self.deleted, &self.ap_id, self.updated, NoteType::Note)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait(?Send)]
|
||||||
|
impl FromApub for CommentForm {
|
||||||
|
type ApubType = Note;
|
||||||
|
|
||||||
|
/// Parse an ActivityPub note received from another instance into a Lemmy comment
|
||||||
|
async fn from_apub(
|
||||||
|
note: &Note,
|
||||||
|
context: &LemmyContext,
|
||||||
|
expected_domain: Option<Url>,
|
||||||
|
) -> Result<CommentForm, LemmyError> {
|
||||||
|
let creator_actor_id = ¬e
|
||||||
|
.attributed_to()
|
||||||
|
.context(location_info!())?
|
||||||
|
.as_single_xsd_any_uri()
|
||||||
|
.context(location_info!())?;
|
||||||
|
|
||||||
|
let creator = get_or_fetch_and_upsert_user(creator_actor_id, context).await?;
|
||||||
|
|
||||||
|
let mut in_reply_tos = note
|
||||||
|
.in_reply_to()
|
||||||
|
.as_ref()
|
||||||
|
.context(location_info!())?
|
||||||
|
.as_many()
|
||||||
|
.context(location_info!())?
|
||||||
|
.iter()
|
||||||
|
.map(|i| i.as_xsd_any_uri().context(""));
|
||||||
|
let post_ap_id = in_reply_tos.next().context(location_info!())??;
|
||||||
|
|
||||||
|
// This post, or the parent comment might not yet exist on this server yet, fetch them.
|
||||||
|
let post = get_or_fetch_and_insert_post(&post_ap_id, context).await?;
|
||||||
|
|
||||||
|
// The 2nd item, if it exists, is the parent comment apub_id
|
||||||
|
// For deeply nested comments, FromApub automatically gets called recursively
|
||||||
|
let parent_id: Option<i32> = match in_reply_tos.next() {
|
||||||
|
Some(parent_comment_uri) => {
|
||||||
|
let parent_comment_ap_id = &parent_comment_uri?;
|
||||||
|
let parent_comment =
|
||||||
|
get_or_fetch_and_insert_comment(&parent_comment_ap_id, context).await?;
|
||||||
|
|
||||||
|
Some(parent_comment.id)
|
||||||
|
}
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
let content = note
|
||||||
|
.content()
|
||||||
|
.context(location_info!())?
|
||||||
|
.as_single_xsd_string()
|
||||||
|
.context(location_info!())?
|
||||||
|
.to_string();
|
||||||
|
let content_slurs_removed = remove_slurs(&content);
|
||||||
|
|
||||||
|
Ok(CommentForm {
|
||||||
|
creator_id: creator.id,
|
||||||
|
post_id: post.id,
|
||||||
|
parent_id,
|
||||||
|
content: content_slurs_removed,
|
||||||
|
removed: None,
|
||||||
|
read: None,
|
||||||
|
published: note.published().map(|u| u.to_owned().naive_local()),
|
||||||
|
updated: note.updated().map(|u| u.to_owned().naive_local()),
|
||||||
|
deleted: None,
|
||||||
|
ap_id: Some(check_actor_domain(note, expected_domain)?),
|
||||||
|
local: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
201
lemmy_apub/src/objects/community.rs
Normal file
201
lemmy_apub/src/objects/community.rs
Normal file
|
@ -0,0 +1,201 @@
|
||||||
|
use crate::{
|
||||||
|
check_actor_domain,
|
||||||
|
extensions::group_extensions::GroupExtension,
|
||||||
|
fetcher::get_or_fetch_and_upsert_user,
|
||||||
|
objects::create_tombstone,
|
||||||
|
ActorType,
|
||||||
|
FromApub,
|
||||||
|
GroupExt,
|
||||||
|
ToApub,
|
||||||
|
};
|
||||||
|
use activitystreams::{
|
||||||
|
actor::{kind::GroupType, ApActor, Endpoints, Group},
|
||||||
|
base::BaseExt,
|
||||||
|
object::{Image, Tombstone},
|
||||||
|
prelude::*,
|
||||||
|
};
|
||||||
|
use activitystreams_ext::Ext2;
|
||||||
|
use anyhow::Context;
|
||||||
|
use lemmy_db::{
|
||||||
|
community::{Community, CommunityForm},
|
||||||
|
community_view::CommunityModeratorView,
|
||||||
|
naive_now,
|
||||||
|
DbPool,
|
||||||
|
};
|
||||||
|
use lemmy_structs::blocking;
|
||||||
|
use lemmy_utils::{
|
||||||
|
location_info,
|
||||||
|
utils::{check_slurs, check_slurs_opt, convert_datetime},
|
||||||
|
LemmyError,
|
||||||
|
};
|
||||||
|
use lemmy_websocket::LemmyContext;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
#[async_trait::async_trait(?Send)]
|
||||||
|
impl ToApub for Community {
|
||||||
|
type Response = GroupExt;
|
||||||
|
|
||||||
|
// Turn a Lemmy Community into an ActivityPub group that can be sent out over the network.
|
||||||
|
async fn to_apub(&self, pool: &DbPool) -> Result<GroupExt, LemmyError> {
|
||||||
|
// The attributed to, is an ordered vector with the creator actor_ids first,
|
||||||
|
// then the rest of the moderators
|
||||||
|
// TODO Technically the instance admins can mod the community, but lets
|
||||||
|
// ignore that for now
|
||||||
|
let id = self.id;
|
||||||
|
let moderators = blocking(pool, move |conn| {
|
||||||
|
CommunityModeratorView::for_community(&conn, id)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
let moderators: Vec<String> = moderators.into_iter().map(|m| m.user_actor_id).collect();
|
||||||
|
|
||||||
|
let mut group = Group::new();
|
||||||
|
group
|
||||||
|
.set_context(activitystreams::context())
|
||||||
|
.set_id(Url::parse(&self.actor_id)?)
|
||||||
|
.set_name(self.name.to_owned())
|
||||||
|
.set_published(convert_datetime(self.published))
|
||||||
|
.set_many_attributed_tos(moderators);
|
||||||
|
|
||||||
|
if let Some(u) = self.updated.to_owned() {
|
||||||
|
group.set_updated(convert_datetime(u));
|
||||||
|
}
|
||||||
|
if let Some(d) = self.description.to_owned() {
|
||||||
|
// TODO: this should be html, also add source field with raw markdown
|
||||||
|
// -> same for post.content and others
|
||||||
|
group.set_content(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(icon) = &self.icon {
|
||||||
|
let mut image = Image::new();
|
||||||
|
image.set_url(icon.to_owned());
|
||||||
|
group.set_icon(image.into_any_base()?);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(banner_url) = &self.banner {
|
||||||
|
let mut image = Image::new();
|
||||||
|
image.set_url(banner_url.to_owned());
|
||||||
|
group.set_image(image.into_any_base()?);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut ap_actor = ApActor::new(self.get_inbox_url()?, group);
|
||||||
|
ap_actor
|
||||||
|
.set_preferred_username(self.title.to_owned())
|
||||||
|
.set_outbox(self.get_outbox_url()?)
|
||||||
|
.set_followers(self.get_followers_url()?)
|
||||||
|
.set_endpoints(Endpoints {
|
||||||
|
shared_inbox: Some(self.get_shared_inbox_url()?),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
let nsfw = self.nsfw;
|
||||||
|
let category_id = self.category_id;
|
||||||
|
let group_extension = blocking(pool, move |conn| {
|
||||||
|
GroupExtension::new(conn, category_id, nsfw)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
Ok(Ext2::new(
|
||||||
|
ap_actor,
|
||||||
|
group_extension,
|
||||||
|
self.get_public_key_ext()?,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_tombstone(&self) -> Result<Tombstone, LemmyError> {
|
||||||
|
create_tombstone(self.deleted, &self.actor_id, self.updated, GroupType::Group)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[async_trait::async_trait(?Send)]
|
||||||
|
impl FromApub for CommunityForm {
|
||||||
|
type ApubType = GroupExt;
|
||||||
|
|
||||||
|
/// Parse an ActivityPub group received from another instance into a Lemmy community.
|
||||||
|
async fn from_apub(
|
||||||
|
group: &GroupExt,
|
||||||
|
context: &LemmyContext,
|
||||||
|
expected_domain: Option<Url>,
|
||||||
|
) -> Result<Self, LemmyError> {
|
||||||
|
let creator_and_moderator_uris = group.inner.attributed_to().context(location_info!())?;
|
||||||
|
let creator_uri = creator_and_moderator_uris
|
||||||
|
.as_many()
|
||||||
|
.context(location_info!())?
|
||||||
|
.iter()
|
||||||
|
.next()
|
||||||
|
.context(location_info!())?
|
||||||
|
.as_xsd_any_uri()
|
||||||
|
.context(location_info!())?;
|
||||||
|
|
||||||
|
let creator = get_or_fetch_and_upsert_user(creator_uri, context).await?;
|
||||||
|
let name = group
|
||||||
|
.inner
|
||||||
|
.name()
|
||||||
|
.context(location_info!())?
|
||||||
|
.as_one()
|
||||||
|
.context(location_info!())?
|
||||||
|
.as_xsd_string()
|
||||||
|
.context(location_info!())?
|
||||||
|
.to_string();
|
||||||
|
let title = group
|
||||||
|
.inner
|
||||||
|
.preferred_username()
|
||||||
|
.context(location_info!())?
|
||||||
|
.to_string();
|
||||||
|
// TODO: should be parsed as html and tags like <script> removed (or use markdown source)
|
||||||
|
// -> same for post.content etc
|
||||||
|
let description = group
|
||||||
|
.inner
|
||||||
|
.content()
|
||||||
|
.map(|s| s.as_single_xsd_string())
|
||||||
|
.flatten()
|
||||||
|
.map(|s| s.to_string());
|
||||||
|
check_slurs(&name)?;
|
||||||
|
check_slurs(&title)?;
|
||||||
|
check_slurs_opt(&description)?;
|
||||||
|
|
||||||
|
let icon = match group.icon() {
|
||||||
|
Some(any_image) => Some(
|
||||||
|
Image::from_any_base(any_image.as_one().context(location_info!())?.clone())
|
||||||
|
.context(location_info!())?
|
||||||
|
.context(location_info!())?
|
||||||
|
.url()
|
||||||
|
.context(location_info!())?
|
||||||
|
.as_single_xsd_any_uri()
|
||||||
|
.map(|u| u.to_string()),
|
||||||
|
),
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let banner = match group.image() {
|
||||||
|
Some(any_image) => Some(
|
||||||
|
Image::from_any_base(any_image.as_one().context(location_info!())?.clone())
|
||||||
|
.context(location_info!())?
|
||||||
|
.context(location_info!())?
|
||||||
|
.url()
|
||||||
|
.context(location_info!())?
|
||||||
|
.as_single_xsd_any_uri()
|
||||||
|
.map(|u| u.to_string()),
|
||||||
|
),
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(CommunityForm {
|
||||||
|
name,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
category_id: group.ext_one.category.identifier.parse::<i32>()?,
|
||||||
|
creator_id: creator.id,
|
||||||
|
removed: None,
|
||||||
|
published: group.inner.published().map(|u| u.to_owned().naive_local()),
|
||||||
|
updated: group.inner.updated().map(|u| u.to_owned().naive_local()),
|
||||||
|
deleted: None,
|
||||||
|
nsfw: group.ext_one.sensitive,
|
||||||
|
actor_id: Some(check_actor_domain(group, expected_domain)?),
|
||||||
|
local: false,
|
||||||
|
private_key: None,
|
||||||
|
public_key: Some(group.ext_two.to_owned().public_key.public_key_pem),
|
||||||
|
last_refreshed_at: Some(naive_now()),
|
||||||
|
icon,
|
||||||
|
banner,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
38
lemmy_apub/src/objects/mod.rs
Normal file
38
lemmy_apub/src/objects/mod.rs
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
use activitystreams::{
|
||||||
|
base::BaseExt,
|
||||||
|
object::{Tombstone, TombstoneExt},
|
||||||
|
};
|
||||||
|
use anyhow::anyhow;
|
||||||
|
use chrono::NaiveDateTime;
|
||||||
|
use lemmy_utils::{utils::convert_datetime, LemmyError};
|
||||||
|
|
||||||
|
pub mod comment;
|
||||||
|
pub mod community;
|
||||||
|
pub mod post;
|
||||||
|
pub mod private_message;
|
||||||
|
pub mod user;
|
||||||
|
|
||||||
|
/// Updated is actually the deletion time
|
||||||
|
fn create_tombstone<T>(
|
||||||
|
deleted: bool,
|
||||||
|
object_id: &str,
|
||||||
|
updated: Option<NaiveDateTime>,
|
||||||
|
former_type: T,
|
||||||
|
) -> Result<Tombstone, LemmyError>
|
||||||
|
where
|
||||||
|
T: ToString,
|
||||||
|
{
|
||||||
|
if deleted {
|
||||||
|
if let Some(updated) = updated {
|
||||||
|
let mut tombstone = Tombstone::new();
|
||||||
|
tombstone.set_id(object_id.parse()?);
|
||||||
|
tombstone.set_former_type(former_type.to_string());
|
||||||
|
tombstone.set_deleted(convert_datetime(updated));
|
||||||
|
Ok(tombstone)
|
||||||
|
} else {
|
||||||
|
Err(anyhow!("Cant convert to tombstone because updated time was None.").into())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(anyhow!("Cant convert object to tombstone if it wasnt deleted").into())
|
||||||
|
}
|
||||||
|
}
|
200
lemmy_apub/src/objects/post.rs
Normal file
200
lemmy_apub/src/objects/post.rs
Normal file
|
@ -0,0 +1,200 @@
|
||||||
|
use crate::{
|
||||||
|
check_actor_domain,
|
||||||
|
extensions::page_extension::PageExtension,
|
||||||
|
fetcher::{get_or_fetch_and_upsert_community, get_or_fetch_and_upsert_user},
|
||||||
|
objects::create_tombstone,
|
||||||
|
FromApub,
|
||||||
|
PageExt,
|
||||||
|
ToApub,
|
||||||
|
};
|
||||||
|
use activitystreams::{
|
||||||
|
object::{kind::PageType, Image, Page, Tombstone},
|
||||||
|
prelude::*,
|
||||||
|
};
|
||||||
|
use activitystreams_ext::Ext1;
|
||||||
|
use anyhow::Context;
|
||||||
|
use lemmy_db::{
|
||||||
|
community::Community,
|
||||||
|
post::{Post, PostForm},
|
||||||
|
user::User_,
|
||||||
|
Crud,
|
||||||
|
DbPool,
|
||||||
|
};
|
||||||
|
use lemmy_structs::blocking;
|
||||||
|
use lemmy_utils::{
|
||||||
|
location_info,
|
||||||
|
request::fetch_iframely_and_pictrs_data,
|
||||||
|
utils::{check_slurs, convert_datetime, remove_slurs},
|
||||||
|
LemmyError,
|
||||||
|
};
|
||||||
|
use lemmy_websocket::LemmyContext;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
#[async_trait::async_trait(?Send)]
|
||||||
|
impl ToApub for Post {
|
||||||
|
type Response = PageExt;
|
||||||
|
|
||||||
|
// Turn a Lemmy post into an ActivityPub page that can be sent out over the network.
|
||||||
|
async fn to_apub(&self, pool: &DbPool) -> Result<PageExt, LemmyError> {
|
||||||
|
let mut page = Page::new();
|
||||||
|
|
||||||
|
let creator_id = self.creator_id;
|
||||||
|
let creator = blocking(pool, move |conn| User_::read(conn, creator_id)).await??;
|
||||||
|
|
||||||
|
let community_id = self.community_id;
|
||||||
|
let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??;
|
||||||
|
|
||||||
|
page
|
||||||
|
// Not needed when the Post is embedded in a collection (like for community outbox)
|
||||||
|
// TODO: need to set proper context defining sensitive/commentsEnabled fields
|
||||||
|
// https://git.asonix.dog/Aardwolf/activitystreams/issues/5
|
||||||
|
.set_context(activitystreams::context())
|
||||||
|
.set_id(self.ap_id.parse::<Url>()?)
|
||||||
|
// Use summary field to be consistent with mastodon content warning.
|
||||||
|
// https://mastodon.xyz/@Louisa/103987265222901387.json
|
||||||
|
.set_summary(self.name.to_owned())
|
||||||
|
.set_published(convert_datetime(self.published))
|
||||||
|
.set_to(community.actor_id)
|
||||||
|
.set_attributed_to(creator.actor_id);
|
||||||
|
|
||||||
|
if let Some(body) = &self.body {
|
||||||
|
page.set_content(body.to_owned());
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: hacky code because we get self.url == Some("")
|
||||||
|
// https://github.com/LemmyNet/lemmy/issues/602
|
||||||
|
let url = self.url.as_ref().filter(|u| !u.is_empty());
|
||||||
|
if let Some(u) = url {
|
||||||
|
page.set_url(u.to_owned());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(thumbnail_url) = &self.thumbnail_url {
|
||||||
|
let mut image = Image::new();
|
||||||
|
image.set_url(thumbnail_url.to_string());
|
||||||
|
page.set_image(image.into_any_base()?);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(u) = self.updated {
|
||||||
|
page.set_updated(convert_datetime(u));
|
||||||
|
}
|
||||||
|
|
||||||
|
let ext = PageExtension {
|
||||||
|
comments_enabled: !self.locked,
|
||||||
|
sensitive: self.nsfw,
|
||||||
|
stickied: self.stickied,
|
||||||
|
};
|
||||||
|
Ok(Ext1::new(page, ext))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_tombstone(&self) -> Result<Tombstone, LemmyError> {
|
||||||
|
create_tombstone(self.deleted, &self.ap_id, self.updated, PageType::Page)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait(?Send)]
|
||||||
|
impl FromApub for PostForm {
|
||||||
|
type ApubType = PageExt;
|
||||||
|
|
||||||
|
/// Parse an ActivityPub page received from another instance into a Lemmy post.
|
||||||
|
async fn from_apub(
|
||||||
|
page: &PageExt,
|
||||||
|
context: &LemmyContext,
|
||||||
|
expected_domain: Option<Url>,
|
||||||
|
) -> Result<PostForm, LemmyError> {
|
||||||
|
let ext = &page.ext_one;
|
||||||
|
let creator_actor_id = page
|
||||||
|
.inner
|
||||||
|
.attributed_to()
|
||||||
|
.as_ref()
|
||||||
|
.context(location_info!())?
|
||||||
|
.as_single_xsd_any_uri()
|
||||||
|
.context(location_info!())?;
|
||||||
|
|
||||||
|
let creator = get_or_fetch_and_upsert_user(creator_actor_id, context).await?;
|
||||||
|
|
||||||
|
let community_actor_id = page
|
||||||
|
.inner
|
||||||
|
.to()
|
||||||
|
.as_ref()
|
||||||
|
.context(location_info!())?
|
||||||
|
.as_single_xsd_any_uri()
|
||||||
|
.context(location_info!())?;
|
||||||
|
|
||||||
|
let community = get_or_fetch_and_upsert_community(community_actor_id, context).await?;
|
||||||
|
|
||||||
|
let thumbnail_url = match &page.inner.image() {
|
||||||
|
Some(any_image) => Image::from_any_base(
|
||||||
|
any_image
|
||||||
|
.to_owned()
|
||||||
|
.as_one()
|
||||||
|
.context(location_info!())?
|
||||||
|
.to_owned(),
|
||||||
|
)?
|
||||||
|
.context(location_info!())?
|
||||||
|
.url()
|
||||||
|
.context(location_info!())?
|
||||||
|
.as_single_xsd_any_uri()
|
||||||
|
.map(|u| u.to_string()),
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
let url = page
|
||||||
|
.inner
|
||||||
|
.url()
|
||||||
|
.map(|u| u.as_single_xsd_any_uri())
|
||||||
|
.flatten()
|
||||||
|
.map(|s| s.to_string());
|
||||||
|
|
||||||
|
let (iframely_title, iframely_description, iframely_html, pictrs_thumbnail) =
|
||||||
|
if let Some(url) = &url {
|
||||||
|
fetch_iframely_and_pictrs_data(context.client(), Some(url.to_owned())).await
|
||||||
|
} else {
|
||||||
|
(None, None, None, thumbnail_url)
|
||||||
|
};
|
||||||
|
|
||||||
|
let name = page
|
||||||
|
.inner
|
||||||
|
.summary()
|
||||||
|
.as_ref()
|
||||||
|
.context(location_info!())?
|
||||||
|
.as_single_xsd_string()
|
||||||
|
.context(location_info!())?
|
||||||
|
.to_string();
|
||||||
|
let body = page
|
||||||
|
.inner
|
||||||
|
.content()
|
||||||
|
.as_ref()
|
||||||
|
.map(|c| c.as_single_xsd_string())
|
||||||
|
.flatten()
|
||||||
|
.map(|s| s.to_string());
|
||||||
|
check_slurs(&name)?;
|
||||||
|
let body_slurs_removed = body.map(|b| remove_slurs(&b));
|
||||||
|
Ok(PostForm {
|
||||||
|
name,
|
||||||
|
url,
|
||||||
|
body: body_slurs_removed,
|
||||||
|
creator_id: creator.id,
|
||||||
|
community_id: community.id,
|
||||||
|
removed: None,
|
||||||
|
locked: Some(!ext.comments_enabled),
|
||||||
|
published: page
|
||||||
|
.inner
|
||||||
|
.published()
|
||||||
|
.as_ref()
|
||||||
|
.map(|u| u.to_owned().naive_local()),
|
||||||
|
updated: page
|
||||||
|
.inner
|
||||||
|
.updated()
|
||||||
|
.as_ref()
|
||||||
|
.map(|u| u.to_owned().naive_local()),
|
||||||
|
deleted: None,
|
||||||
|
nsfw: ext.sensitive,
|
||||||
|
stickied: Some(ext.stickied),
|
||||||
|
embed_title: iframely_title,
|
||||||
|
embed_description: iframely_description,
|
||||||
|
embed_html: iframely_html,
|
||||||
|
thumbnail_url: pictrs_thumbnail,
|
||||||
|
ap_id: Some(check_actor_domain(page, expected_domain)?),
|
||||||
|
local: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
103
lemmy_apub/src/objects/private_message.rs
Normal file
103
lemmy_apub/src/objects/private_message.rs
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
use crate::{
|
||||||
|
check_actor_domain,
|
||||||
|
check_is_apub_id_valid,
|
||||||
|
fetcher::get_or_fetch_and_upsert_user,
|
||||||
|
objects::create_tombstone,
|
||||||
|
FromApub,
|
||||||
|
ToApub,
|
||||||
|
};
|
||||||
|
use activitystreams::{
|
||||||
|
object::{kind::NoteType, Note, Tombstone},
|
||||||
|
prelude::*,
|
||||||
|
};
|
||||||
|
use anyhow::Context;
|
||||||
|
use lemmy_db::{
|
||||||
|
private_message::{PrivateMessage, PrivateMessageForm},
|
||||||
|
user::User_,
|
||||||
|
Crud,
|
||||||
|
DbPool,
|
||||||
|
};
|
||||||
|
use lemmy_structs::blocking;
|
||||||
|
use lemmy_utils::{location_info, utils::convert_datetime, LemmyError};
|
||||||
|
use lemmy_websocket::LemmyContext;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
#[async_trait::async_trait(?Send)]
|
||||||
|
impl ToApub for PrivateMessage {
|
||||||
|
type Response = Note;
|
||||||
|
|
||||||
|
async fn to_apub(&self, pool: &DbPool) -> Result<Note, LemmyError> {
|
||||||
|
let mut private_message = Note::new();
|
||||||
|
|
||||||
|
let creator_id = self.creator_id;
|
||||||
|
let creator = blocking(pool, move |conn| User_::read(conn, creator_id)).await??;
|
||||||
|
|
||||||
|
let recipient_id = self.recipient_id;
|
||||||
|
let recipient = blocking(pool, move |conn| User_::read(conn, recipient_id)).await??;
|
||||||
|
|
||||||
|
private_message
|
||||||
|
.set_context(activitystreams::context())
|
||||||
|
.set_id(Url::parse(&self.ap_id.to_owned())?)
|
||||||
|
.set_published(convert_datetime(self.published))
|
||||||
|
.set_content(self.content.to_owned())
|
||||||
|
.set_to(recipient.actor_id)
|
||||||
|
.set_attributed_to(creator.actor_id);
|
||||||
|
|
||||||
|
if let Some(u) = self.updated {
|
||||||
|
private_message.set_updated(convert_datetime(u));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(private_message)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_tombstone(&self) -> Result<Tombstone, LemmyError> {
|
||||||
|
create_tombstone(self.deleted, &self.ap_id, self.updated, NoteType::Note)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait(?Send)]
|
||||||
|
impl FromApub for PrivateMessageForm {
|
||||||
|
type ApubType = Note;
|
||||||
|
|
||||||
|
/// Parse an ActivityPub note received from another instance into a Lemmy Private message
|
||||||
|
async fn from_apub(
|
||||||
|
note: &Note,
|
||||||
|
context: &LemmyContext,
|
||||||
|
expected_domain: Option<Url>,
|
||||||
|
) -> Result<PrivateMessageForm, LemmyError> {
|
||||||
|
let creator_actor_id = note
|
||||||
|
.attributed_to()
|
||||||
|
.context(location_info!())?
|
||||||
|
.clone()
|
||||||
|
.single_xsd_any_uri()
|
||||||
|
.context(location_info!())?;
|
||||||
|
|
||||||
|
let creator = get_or_fetch_and_upsert_user(&creator_actor_id, context).await?;
|
||||||
|
let recipient_actor_id = note
|
||||||
|
.to()
|
||||||
|
.context(location_info!())?
|
||||||
|
.clone()
|
||||||
|
.single_xsd_any_uri()
|
||||||
|
.context(location_info!())?;
|
||||||
|
let recipient = get_or_fetch_and_upsert_user(&recipient_actor_id, context).await?;
|
||||||
|
let ap_id = note.id_unchecked().context(location_info!())?.to_string();
|
||||||
|
check_is_apub_id_valid(&Url::parse(&ap_id)?)?;
|
||||||
|
|
||||||
|
Ok(PrivateMessageForm {
|
||||||
|
creator_id: creator.id,
|
||||||
|
recipient_id: recipient.id,
|
||||||
|
content: note
|
||||||
|
.content()
|
||||||
|
.context(location_info!())?
|
||||||
|
.as_single_xsd_string()
|
||||||
|
.context(location_info!())?
|
||||||
|
.to_string(),
|
||||||
|
published: note.published().map(|u| u.to_owned().naive_local()),
|
||||||
|
updated: note.updated().map(|u| u.to_owned().naive_local()),
|
||||||
|
deleted: None,
|
||||||
|
read: None,
|
||||||
|
ap_id: Some(check_actor_domain(note, expected_domain)?),
|
||||||
|
local: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,47 +1,24 @@
|
||||||
use crate::{
|
use crate::{check_actor_domain, ActorType, FromApub, PersonExt, ToApub};
|
||||||
activity_queue::send_activity_single_dest,
|
|
||||||
check_actor_domain,
|
|
||||||
create_apub_response,
|
|
||||||
fetcher::get_or_fetch_and_upsert_actor,
|
|
||||||
generate_activity_id,
|
|
||||||
ActorType,
|
|
||||||
FromApub,
|
|
||||||
PersonExt,
|
|
||||||
ToApub,
|
|
||||||
};
|
|
||||||
use activitystreams::{
|
use activitystreams::{
|
||||||
activity::{
|
|
||||||
kind::{FollowType, UndoType},
|
|
||||||
Follow,
|
|
||||||
Undo,
|
|
||||||
},
|
|
||||||
actor::{ApActor, Endpoints, Person},
|
actor::{ApActor, Endpoints, Person},
|
||||||
object::{Image, Tombstone},
|
object::{Image, Tombstone},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
};
|
};
|
||||||
use activitystreams_ext::Ext1;
|
use activitystreams_ext::Ext1;
|
||||||
use actix_web::{body::Body, web, HttpResponse};
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use lemmy_db::{
|
use lemmy_db::{
|
||||||
naive_now,
|
naive_now,
|
||||||
user::{UserForm, User_},
|
user::{UserForm, User_},
|
||||||
DbPool,
|
DbPool,
|
||||||
};
|
};
|
||||||
use lemmy_structs::blocking;
|
|
||||||
use lemmy_utils::{
|
use lemmy_utils::{
|
||||||
location_info,
|
location_info,
|
||||||
utils::{check_slurs, check_slurs_opt, convert_datetime},
|
utils::{check_slurs, check_slurs_opt, convert_datetime},
|
||||||
LemmyError,
|
LemmyError,
|
||||||
};
|
};
|
||||||
use lemmy_websocket::LemmyContext;
|
use lemmy_websocket::LemmyContext;
|
||||||
use serde::Deserialize;
|
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
pub struct UserQuery {
|
|
||||||
user_name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
#[async_trait::async_trait(?Send)]
|
||||||
impl ToApub for User_ {
|
impl ToApub for User_ {
|
||||||
type Response = PersonExt;
|
type Response = PersonExt;
|
||||||
|
@ -93,101 +70,6 @@ impl ToApub for User_ {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
|
||||||
impl ActorType for User_ {
|
|
||||||
fn actor_id_str(&self) -> String {
|
|
||||||
self.actor_id.to_owned()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn public_key(&self) -> Option<String> {
|
|
||||||
self.public_key.to_owned()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn private_key(&self) -> Option<String> {
|
|
||||||
self.private_key.to_owned()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// As a given local user, send out a follow request to a remote community.
|
|
||||||
async fn send_follow(
|
|
||||||
&self,
|
|
||||||
follow_actor_id: &Url,
|
|
||||||
context: &LemmyContext,
|
|
||||||
) -> Result<(), LemmyError> {
|
|
||||||
let mut follow = Follow::new(self.actor_id.to_owned(), follow_actor_id.as_str());
|
|
||||||
follow
|
|
||||||
.set_context(activitystreams::context())
|
|
||||||
.set_id(generate_activity_id(FollowType::Follow)?);
|
|
||||||
let follow_actor = get_or_fetch_and_upsert_actor(follow_actor_id, context).await?;
|
|
||||||
let to = follow_actor.get_inbox_url()?;
|
|
||||||
|
|
||||||
send_activity_single_dest(follow, self, to, context).await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn send_unfollow(
|
|
||||||
&self,
|
|
||||||
follow_actor_id: &Url,
|
|
||||||
context: &LemmyContext,
|
|
||||||
) -> Result<(), LemmyError> {
|
|
||||||
let mut follow = Follow::new(self.actor_id.to_owned(), follow_actor_id.as_str());
|
|
||||||
follow
|
|
||||||
.set_context(activitystreams::context())
|
|
||||||
.set_id(generate_activity_id(FollowType::Follow)?);
|
|
||||||
let follow_actor = get_or_fetch_and_upsert_actor(follow_actor_id, context).await?;
|
|
||||||
|
|
||||||
let to = follow_actor.get_inbox_url()?;
|
|
||||||
|
|
||||||
// Undo that fake activity
|
|
||||||
let mut undo = Undo::new(Url::parse(&self.actor_id)?, follow.into_any_base()?);
|
|
||||||
undo
|
|
||||||
.set_context(activitystreams::context())
|
|
||||||
.set_id(generate_activity_id(UndoType::Undo)?);
|
|
||||||
|
|
||||||
send_activity_single_dest(undo, self, to, context).await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn send_delete(&self, _creator: &User_, _context: &LemmyContext) -> Result<(), LemmyError> {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn send_undo_delete(
|
|
||||||
&self,
|
|
||||||
_creator: &User_,
|
|
||||||
_context: &LemmyContext,
|
|
||||||
) -> Result<(), LemmyError> {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn send_remove(&self, _creator: &User_, _context: &LemmyContext) -> Result<(), LemmyError> {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn send_undo_remove(
|
|
||||||
&self,
|
|
||||||
_creator: &User_,
|
|
||||||
_context: &LemmyContext,
|
|
||||||
) -> Result<(), LemmyError> {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn send_accept_follow(
|
|
||||||
&self,
|
|
||||||
_follow: Follow,
|
|
||||||
_context: &LemmyContext,
|
|
||||||
) -> Result<(), LemmyError> {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_follower_inboxes(&self, _pool: &DbPool) -> Result<Vec<Url>, LemmyError> {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn user_id(&self) -> i32 {
|
|
||||||
self.id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
#[async_trait::async_trait(?Send)]
|
||||||
impl FromApub for UserForm {
|
impl FromApub for UserForm {
|
||||||
type ApubType = PersonExt;
|
type ApubType = PersonExt;
|
||||||
|
@ -272,17 +154,3 @@ impl FromApub for UserForm {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the user json over HTTP.
|
|
||||||
pub async fn get_apub_user_http(
|
|
||||||
info: web::Path<UserQuery>,
|
|
||||||
context: web::Data<LemmyContext>,
|
|
||||||
) -> Result<HttpResponse<Body>, LemmyError> {
|
|
||||||
let user_name = info.into_inner().user_name;
|
|
||||||
let user = blocking(context.pool(), move |conn| {
|
|
||||||
User_::find_by_email_or_username(conn, &user_name)
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
let u = user.to_apub(context.pool()).await?;
|
|
||||||
Ok(create_apub_response(&u))
|
|
||||||
}
|
|
|
@ -1,11 +1,13 @@
|
||||||
use actix_web::*;
|
use actix_web::*;
|
||||||
use http_signature_normalization_actix::digest::middleware::VerifyDigest;
|
use http_signature_normalization_actix::digest::middleware::VerifyDigest;
|
||||||
use lemmy_apub::{
|
use lemmy_apub::{
|
||||||
|
http::{
|
||||||
comment::get_apub_comment,
|
comment::get_apub_comment,
|
||||||
community::*,
|
community::{get_apub_community_followers, get_apub_community_http, get_apub_community_outbox},
|
||||||
inbox::{community_inbox::community_inbox, shared_inbox::shared_inbox, user_inbox::user_inbox},
|
|
||||||
post::get_apub_post,
|
post::get_apub_post,
|
||||||
user::*,
|
user::get_apub_user_http,
|
||||||
|
},
|
||||||
|
inbox::{community_inbox::community_inbox, shared_inbox::shared_inbox, user_inbox::user_inbox},
|
||||||
APUB_JSON_CONTENT_TYPE,
|
APUB_JSON_CONTENT_TYPE,
|
||||||
};
|
};
|
||||||
use lemmy_utils::settings::Settings;
|
use lemmy_utils::settings::Settings;
|
||||||
|
|
Loading…
Reference in a new issue