Merge pull request #1874 from LemmyNet/protocol-testing

Protocol testing
This commit is contained in:
Dessalines 2021-11-03 11:23:26 -04:00 committed by GitHub
commit 1bec551945
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
148 changed files with 3216 additions and 2015 deletions

1
Cargo.lock generated
View file

@ -1728,6 +1728,7 @@ dependencies = [
"lazy_static",
"lemmy_api_common",
"lemmy_apub",
"lemmy_apub_lib",
"lemmy_db_schema",
"lemmy_db_views",
"lemmy_db_views_actor",

View file

@ -14,6 +14,7 @@ doctest = false
[dependencies]
lemmy_apub = { version = "=0.13.5-rc.7", path = "../apub" }
lemmy_apub_lib = { version = "=0.13.5-rc.7", path = "../apub_lib" }
lemmy_utils = { version = "=0.13.5-rc.7", path = "../utils" }
lemmy_db_schema = { version = "=0.13.5-rc.7", path = "../db_schema" }
lemmy_db_views = { version = "=0.13.5-rc.7", path = "../db_views" }

View file

@ -1,5 +1,7 @@
use crate::Perform;
use std::convert::TryInto;
use actix_web::web::Data;
use lemmy_api_common::{
blocking,
check_community_ban,
@ -9,11 +11,11 @@ use lemmy_api_common::{
get_local_user_view_from_jwt,
};
use lemmy_apub::{
activities::voting::{
fetcher::post_or_comment::PostOrComment,
protocol::activities::voting::{
undo_vote::UndoVote,
vote::{Vote, VoteType},
},
fetcher::post_or_comment::PostOrComment,
};
use lemmy_db_schema::{
newtypes::LocalUserId,
@ -23,7 +25,8 @@ use lemmy_db_schema::{
use lemmy_db_views::{comment_view::CommentView, local_user_view::LocalUserView};
use lemmy_utils::{ApiError, ConnectionId, LemmyError};
use lemmy_websocket::{send::send_comment_ws_message, LemmyContext, UserOperation};
use std::convert::TryInto;
use crate::Perform;
#[async_trait::async_trait(?Send)]
impl Perform for MarkCommentAsRead {

View file

@ -1,5 +1,5 @@
use crate::Perform;
use actix_web::web::Data;
use lemmy_api_common::{
blocking,
check_community_ban,
@ -7,7 +7,7 @@ use lemmy_api_common::{
get_local_user_view_from_jwt,
is_mod_or_admin,
};
use lemmy_apub::{activities::report::Report, fetcher::object_id::ObjectId};
use lemmy_apub::{fetcher::object_id::ObjectId, protocol::activities::community::report::Report};
use lemmy_db_schema::{source::comment_report::*, traits::Reportable};
use lemmy_db_views::{
comment_report_view::{CommentReportQueryBuilder, CommentReportView},
@ -16,6 +16,8 @@ use lemmy_db_views::{
use lemmy_utils::{ApiError, ConnectionId, LemmyError};
use lemmy_websocket::{messages::SendModRoomMessage, LemmyContext, UserOperation};
use crate::Perform;
/// Creates a comment report and notifies the moderators of the community
#[async_trait::async_trait(?Send)]
impl Perform for CreateCommentReport {

View file

@ -10,16 +10,16 @@ use lemmy_api_common::{
is_mod_or_admin,
};
use lemmy_apub::{
activities::{
objects::{community::ApubCommunity, person::ApubPerson},
protocol::activities::{
community::{
add_mod::AddMod,
block_user::BlockUserFromCommunity,
remove_mod::RemoveMod,
undo_block_user::UndoBlockUserFromCommunity,
},
following::{follow::FollowCommunity as FollowCommunityApub, undo::UndoFollowCommunity},
following::{follow::FollowCommunity as FollowCommunityApub, undo_follow::UndoFollowCommunity},
},
objects::{community::ApubCommunity, person::ApubPerson},
};
use lemmy_db_schema::{
source::{

View file

@ -12,16 +12,16 @@ use lemmy_api_common::{
post::*,
};
use lemmy_apub::{
activities::{
post::create_or_update::CreateOrUpdatePost,
fetcher::post_or_comment::PostOrComment,
objects::post::ApubPost,
protocol::activities::{
create_or_update::post::CreateOrUpdatePost,
voting::{
undo_vote::UndoVote,
vote::{Vote, VoteType},
},
CreateOrUpdateType,
},
fetcher::post_or_comment::PostOrComment,
objects::post::ApubPost,
};
use lemmy_db_schema::{
source::{moderator::*, post::*},

View file

@ -1,5 +1,5 @@
use crate::Perform;
use actix_web::web::Data;
use lemmy_api_common::{
blocking,
check_community_ban,
@ -13,7 +13,7 @@ use lemmy_api_common::{
ResolvePostReport,
},
};
use lemmy_apub::{activities::report::Report, fetcher::object_id::ObjectId};
use lemmy_apub::{fetcher::object_id::ObjectId, protocol::activities::community::report::Report};
use lemmy_db_schema::{
source::post_report::{PostReport, PostReportForm},
traits::Reportable,
@ -25,6 +25,8 @@ use lemmy_db_views::{
use lemmy_utils::{ApiError, ConnectionId, LemmyError};
use lemmy_websocket::{messages::SendModRoomMessage, LemmyContext, UserOperation};
use crate::Perform;
/// Creates a post report and notifies the moderators of the community
#[async_trait::async_trait(?Send)]
impl Perform for CreatePostReport {

View file

@ -11,10 +11,10 @@ use lemmy_api_common::{
site::*,
};
use lemmy_apub::{
build_actor_id_from_shortname,
fetcher::search::{search_by_apub_id, SearchableObjects},
EndpointType,
get_actor_id_from_name,
};
use lemmy_apub_lib::webfinger::WebfingerType;
use lemmy_db_schema::{
from_opt_str_to_opt_enum,
newtypes::PersonId,
@ -174,11 +174,13 @@ impl Perform for Search {
let listing_type: Option<ListingType> = from_opt_str_to_opt_enum(&data.listing_type);
let search_type: SearchType = from_opt_str_to_opt_enum(&data.type_).unwrap_or(SearchType::All);
let community_id = data.community_id;
let community_actor_id = data
.community_name
.as_ref()
.map(|t| build_actor_id_from_shortname(EndpointType::Community, t, &context.settings()).ok())
.unwrap_or(None);
let community_actor_id = if let Some(name) = &data.community_name {
get_actor_id_from_name(WebfingerType::Group, name, context)
.await
.ok()
} else {
None
};
let creator_id = data.creator_id;
match search_type {
SearchType::Posts => {

View file

@ -1,5 +1,5 @@
use crate::PerformCrud;
use actix_web::web::Data;
use lemmy_api_common::{
blocking,
check_community_ban,
@ -11,13 +11,13 @@ use lemmy_api_common::{
get_post,
};
use lemmy_apub::{
activities::{
comment::create_or_update::CreateOrUpdateComment,
fetcher::post_or_comment::PostOrComment,
generate_local_apub_endpoint,
protocol::activities::{
create_or_update::comment::CreateOrUpdateComment,
voting::vote::{Vote, VoteType},
CreateOrUpdateType,
},
fetcher::post_or_comment::PostOrComment,
generate_apub_endpoint,
EndpointType,
};
use lemmy_db_schema::{
@ -40,6 +40,8 @@ use lemmy_websocket::{
UserOperationCrud,
};
use crate::PerformCrud;
#[async_trait::async_trait(?Send)]
impl PerformCrud for CreateComment {
type Response = CommentResponse;
@ -109,7 +111,7 @@ impl PerformCrud for CreateComment {
let updated_comment: Comment =
blocking(context.pool(), move |conn| -> Result<Comment, LemmyError> {
let apub_id = generate_apub_endpoint(
let apub_id = generate_local_apub_endpoint(
EndpointType::Comment,
&inserted_comment_id.to_string(),
&protocol_and_hostname,

View file

@ -1,7 +1,8 @@
use crate::PerformCrud;
use actix_web::web::Data;
use lemmy_api_common::{blocking, comment::*, get_local_user_view_from_jwt_opt};
use lemmy_apub::{build_actor_id_from_shortname, EndpointType};
use lemmy_apub::get_actor_id_from_name;
use lemmy_apub_lib::webfinger::WebfingerType;
use lemmy_db_schema::{
from_opt_str_to_opt_enum,
traits::DeleteableOrRemoveable,
@ -34,11 +35,13 @@ impl PerformCrud for GetComments {
let listing_type: Option<ListingType> = from_opt_str_to_opt_enum(&data.type_);
let community_id = data.community_id;
let community_actor_id = data
.community_name
.as_ref()
.map(|t| build_actor_id_from_shortname(EndpointType::Community, t, &context.settings()).ok())
.unwrap_or(None);
let community_actor_id = if let Some(name) = &data.community_name {
get_actor_id_from_name(WebfingerType::Group, name, context)
.await
.ok()
} else {
None
};
let saved_only = data.saved_only;
let page = data.page;
let limit = data.limit;

View file

@ -1,5 +1,5 @@
use crate::PerformCrud;
use actix_web::web::Data;
use lemmy_api_common::{
blocking,
check_community_ban,
@ -8,8 +8,8 @@ use lemmy_api_common::{
comment::*,
get_local_user_view_from_jwt,
};
use lemmy_apub::activities::{
comment::create_or_update::CreateOrUpdateComment,
use lemmy_apub::protocol::activities::{
create_or_update::comment::CreateOrUpdateComment,
CreateOrUpdateType,
};
use lemmy_db_schema::source::comment::Comment;
@ -26,6 +26,8 @@ use lemmy_websocket::{
UserOperationCrud,
};
use crate::PerformCrud;
#[async_trait::async_trait(?Send)]
impl PerformCrud for EditComment {
type Response = CommentResponse;

View file

@ -8,9 +8,9 @@ use lemmy_api_common::{
};
use lemmy_apub::{
fetcher::object_id::ObjectId,
generate_apub_endpoint,
generate_followers_url,
generate_inbox_url,
generate_local_apub_endpoint,
generate_shared_inbox_url,
objects::community::ApubCommunity,
EndpointType,
@ -67,7 +67,7 @@ impl PerformCrud for CreateCommunity {
}
// Double check for duplicate community actor_ids
let community_actor_id = generate_apub_endpoint(
let community_actor_id = generate_local_apub_endpoint(
EndpointType::Community,
&data.name,
&context.settings().get_protocol_and_hostname(),

View file

@ -2,11 +2,11 @@ use crate::PerformCrud;
use actix_web::web::Data;
use lemmy_api_common::{blocking, community::*, get_local_user_view_from_jwt_opt};
use lemmy_apub::{
build_actor_id_from_shortname,
fetcher::object_id::ObjectId,
get_actor_id_from_name,
objects::community::ApubCommunity,
EndpointType,
};
use lemmy_apub_lib::webfinger::WebfingerType;
use lemmy_db_schema::{
from_opt_str_to_opt_enum,
traits::DeleteableOrRemoveable,
@ -39,7 +39,7 @@ impl PerformCrud for GetCommunity {
None => {
let name = data.name.to_owned().unwrap_or_else(|| "main".to_string());
let community_actor_id =
build_actor_id_from_shortname(EndpointType::Community, &name, &context.settings())?;
get_actor_id_from_name(WebfingerType::Group, &name, context).await?;
ObjectId::<ApubCommunity>::new(community_actor_id)
.dereference(context, &mut 0)

View file

@ -5,7 +5,7 @@ use lemmy_api_common::{
community::{CommunityResponse, EditCommunity},
get_local_user_view_from_jwt,
};
use lemmy_apub::activities::community::update::UpdateCommunity;
use lemmy_apub::protocol::activities::community::update::UpdateCommunity;
use lemmy_db_schema::{
diesel_option_overwrite_to_url,
naive_now,

View file

@ -1,5 +1,7 @@
use crate::PerformCrud;
use actix_web::web::Data;
use log::warn;
use webmention::{Webmention, WebmentionError};
use lemmy_api_common::{
blocking,
check_community_ban,
@ -10,13 +12,13 @@ use lemmy_api_common::{
post::*,
};
use lemmy_apub::{
activities::{
post::create_or_update::CreateOrUpdatePost,
fetcher::post_or_comment::PostOrComment,
generate_local_apub_endpoint,
protocol::activities::{
create_or_update::post::CreateOrUpdatePost,
voting::vote::{Vote, VoteType},
CreateOrUpdateType,
},
fetcher::post_or_comment::PostOrComment,
generate_apub_endpoint,
EndpointType,
};
use lemmy_db_schema::{
@ -31,8 +33,8 @@ use lemmy_utils::{
LemmyError,
};
use lemmy_websocket::{send::send_post_ws_message, LemmyContext, UserOperationCrud};
use log::warn;
use webmention::{Webmention, WebmentionError};
use crate::PerformCrud;
#[async_trait::async_trait(?Send)]
impl PerformCrud for CreatePost {
@ -98,7 +100,7 @@ impl PerformCrud for CreatePost {
let inserted_post_id = inserted_post.id;
let protocol_and_hostname = context.settings().get_protocol_and_hostname();
let updated_post = blocking(context.pool(), move |conn| -> Result<Post, LemmyError> {
let apub_id = generate_apub_endpoint(
let apub_id = generate_local_apub_endpoint(
EndpointType::Post,
&inserted_post_id.to_string(),
&protocol_and_hostname,

View file

@ -1,7 +1,8 @@
use crate::PerformCrud;
use actix_web::web::Data;
use lemmy_api_common::{blocking, get_local_user_view_from_jwt_opt, mark_post_as_read, post::*};
use lemmy_apub::{build_actor_id_from_shortname, EndpointType};
use lemmy_apub::get_actor_id_from_name;
use lemmy_apub_lib::webfinger::WebfingerType;
use lemmy_db_schema::{
from_opt_str_to_opt_enum,
traits::DeleteableOrRemoveable,
@ -136,11 +137,13 @@ impl PerformCrud for GetPosts {
let page = data.page;
let limit = data.limit;
let community_id = data.community_id;
let community_actor_id = data
.community_name
.as_ref()
.map(|t| build_actor_id_from_shortname(EndpointType::Community, t, &context.settings()).ok())
.unwrap_or(None);
let community_actor_id = if let Some(name) = &data.community_name {
get_actor_id_from_name(WebfingerType::Group, name, context)
.await
.ok()
} else {
None
};
let saved_only = data.saved_only;
let mut posts = blocking(context.pool(), move |conn| {

View file

@ -1,5 +1,5 @@
use crate::PerformCrud;
use actix_web::web::Data;
use lemmy_api_common::{
blocking,
check_community_ban,
@ -7,7 +7,10 @@ use lemmy_api_common::{
get_local_user_view_from_jwt,
post::*,
};
use lemmy_apub::activities::{post::create_or_update::CreateOrUpdatePost, CreateOrUpdateType};
use lemmy_apub::protocol::activities::{
create_or_update::post::CreateOrUpdatePost,
CreateOrUpdateType,
};
use lemmy_db_schema::{
naive_now,
source::post::{Post, PostForm},
@ -22,6 +25,8 @@ use lemmy_utils::{
};
use lemmy_websocket::{send::send_post_ws_message, LemmyContext, UserOperationCrud};
use crate::PerformCrud;
#[async_trait::async_trait(?Send)]
impl PerformCrud for EditPost {
type Response = PostResponse;

View file

@ -7,11 +7,11 @@ use lemmy_api_common::{
person::{CreatePrivateMessage, PrivateMessageResponse},
};
use lemmy_apub::{
activities::{
generate_local_apub_endpoint,
protocol::activities::{
private_message::create_or_update::CreateOrUpdatePrivateMessage,
CreateOrUpdateType,
},
generate_apub_endpoint,
EndpointType,
};
use lemmy_db_schema::{
@ -67,7 +67,7 @@ impl PerformCrud for CreatePrivateMessage {
let updated_private_message = blocking(
context.pool(),
move |conn| -> Result<PrivateMessage, LemmyError> {
let apub_id = generate_apub_endpoint(
let apub_id = generate_local_apub_endpoint(
EndpointType::PrivateMessage,
&inserted_private_message_id.to_string(),
&protocol_and_hostname,

View file

@ -5,7 +5,7 @@ use lemmy_api_common::{
get_local_user_view_from_jwt,
person::{DeletePrivateMessage, PrivateMessageResponse},
};
use lemmy_apub::activities::private_message::{
use lemmy_apub::protocol::activities::private_message::{
delete::DeletePrivateMessage as DeletePrivateMessageApub,
undo_delete::UndoDeletePrivateMessage,
};

View file

@ -5,7 +5,7 @@ use lemmy_api_common::{
get_local_user_view_from_jwt,
person::{EditPrivateMessage, PrivateMessageResponse},
};
use lemmy_apub::activities::{
use lemmy_apub::protocol::activities::{
private_message::create_or_update::CreateOrUpdatePrivateMessage,
CreateOrUpdateType,
};

View file

@ -2,9 +2,9 @@ use crate::PerformCrud;
use actix_web::web::Data;
use lemmy_api_common::{blocking, honeypot_check, password_length_check, person::*};
use lemmy_apub::{
generate_apub_endpoint,
generate_followers_url,
generate_inbox_url,
generate_local_apub_endpoint,
generate_shared_inbox_url,
EndpointType,
};
@ -96,7 +96,7 @@ impl PerformCrud for Register {
if !is_valid_actor_name(&data.username, context.settings().actor_name_max_length) {
return Err(ApiError::err_plain("invalid_username").into());
}
let actor_id = generate_apub_endpoint(
let actor_id = generate_local_apub_endpoint(
EndpointType::Person,
&data.username,
&context.settings().get_protocol_and_hostname(),
@ -179,7 +179,7 @@ impl PerformCrud for Register {
Ok(c) => c,
Err(_e) => {
let default_community_name = "main";
let actor_id = generate_apub_endpoint(
let actor_id = generate_local_apub_endpoint(
EndpointType::Community,
default_community_name,
&protocol_and_hostname,

View file

@ -2,11 +2,11 @@ use crate::PerformCrud;
use actix_web::web::Data;
use lemmy_api_common::{blocking, get_local_user_view_from_jwt_opt, person::*};
use lemmy_apub::{
build_actor_id_from_shortname,
fetcher::object_id::ObjectId,
get_actor_id_from_name,
objects::person::ApubPerson,
EndpointType,
};
use lemmy_apub_lib::webfinger::WebfingerType;
use lemmy_db_schema::{from_opt_str_to_opt_enum, SortType};
use lemmy_db_views::{comment_view::CommentQueryBuilder, post_view::PostQueryBuilder};
use lemmy_db_views_actor::{
@ -46,8 +46,7 @@ impl PerformCrud for GetPersonDetails {
.username
.to_owned()
.unwrap_or_else(|| "admin".to_string());
let actor_id =
build_actor_id_from_shortname(EndpointType::Person, &name, &context.settings())?;
let actor_id = get_actor_id_from_name(WebfingerType::Person, &name, context).await?;
let person = ObjectId::<ApubPerson>::new(actor_id)
.dereference(context, &mut 0)

View file

@ -1,34 +0,0 @@
{
"@context": [
"https://www.w3.org/ns/activitystreams",
{
"comments_enabled": {
"id": "pt:commentsEnabled",
"type": "sc:Boolean"
},
"matrixUserId": {
"id": "as:alsoKnownAs",
"type": "sc:Text"
},
"moderators": "as:moderators",
"pt": "https://join-lemmy.org#",
"sc": "http://schema.org#",
"sensitive": "as:sensitive",
"stickied": "as:stickied"
},
"https://w3id.org/security/v1"
],
"id": "https://enterprise.lemmy.ml/comment/38741",
"type": "Note",
"attributedTo": "https://enterprise.lemmy.ml/u/picard",
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"inReplyTo": "https://enterprise.lemmy.ml/post/55143",
"content": "first comment!",
"mediaType": "text/html",
"source": {
"content": "first comment!",
"mediaType": "text/markdown"
},
"published": "2021-03-01T13:42:43.966208+00:00",
"updated": "2021-03-01T13:43:03.955787+00:00"
}

View file

@ -1,34 +0,0 @@
{
"@context": [
"https://www.w3.org/ns/activitystreams",
{
"comments_enabled": {
"id": "pt:commentsEnabled",
"type": "sc:Boolean"
},
"matrixUserId": {
"id": "as:alsoKnownAs",
"type": "sc:Text"
},
"moderators": "as:moderators",
"pt": "https://join-lemmy.org#",
"sc": "http://schema.org#",
"sensitive": "as:sensitive",
"stickied": "as:stickied"
},
"https://w3id.org/security/v1"
],
"id": "https://enterprise.lemmy.ml/private_message/1621",
"type": "ChatMessage",
"attributedTo": "https://enterprise.lemmy.ml/u/picard",
"to": [
"https://queer.hacktivis.me/users/lanodan"
],
"content": "Hello hello, testing",
"mediaType": "text/html",
"source": {
"content": "Hello hello, testing",
"mediaType": "text/markdown"
},
"published": "2021-10-21T10:13:14.597721+00:00"
}

View file

@ -0,0 +1,13 @@
{
"actor": "http://enterprise.lemmy.ml/u/lemmy_beta",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"object": "http://ds9.lemmy.ml/u/lemmy_alpha",
"target": "http://enterprise.lemmy.ml/c/main/moderators",
"cc": [
"http://enterprise.lemmy.ml/c/main"
],
"type": "Add",
"id": "http://enterprise.lemmy.ml/activities/add/ec069147-77c3-447f-88c8-0ef1df10403f"
}

View file

@ -0,0 +1,37 @@
{
"actor": "http://enterprise.lemmy.ml/c/main",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"object": {
"actor": "http://enterprise.lemmy.ml/u/lemmy_beta",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"object": {
"type": "Page",
"id": "http://enterprise.lemmy.ml/post/7",
"attributedTo": "http://enterprise.lemmy.ml/u/lemmy_beta",
"to": [
"http://enterprise.lemmy.ml/c/main",
"https://www.w3.org/ns/activitystreams#Public"
],
"name": "post 4",
"mediaType": "text/html",
"commentsEnabled": true,
"sensitive": false,
"stickied": false,
"published": "2021-11-01T12:11:22.871846+00:00"
},
"cc": [
"http://enterprise.lemmy.ml/c/main"
],
"type": "Create",
"id": "http://enterprise.lemmy.ml/activities/create/2807c9ec-3ad8-4859-a9e0-28b59b6e499f"
},
"cc": [
"http://enterprise.lemmy.ml/c/main/followers"
],
"type": "Announce",
"id": "http://enterprise.lemmy.ml/activities/announce/8030b171-803a-4108-94b1-342688f375cf"
}

View file

@ -0,0 +1,13 @@
{
"actor": "http://enterprise.lemmy.ml/u/lemmy_beta",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"object": "http://ds9.lemmy.ml/u/lemmy_alpha",
"cc": [
"http://enterprise.lemmy.ml/c/main"
],
"target": "http://enterprise.lemmy.ml/c/main",
"type": "Block",
"id": "http://enterprise.lemmy.ml/activities/block/5d42fffb-0903-4625-86d4-0b39bb344fc2"
}

View file

@ -0,0 +1,13 @@
{
"actor": "http://enterprise.lemmy.ml/u/lemmy_beta",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"object": "http://ds9.lemmy.ml/u/lemmy_alpha",
"cc": [
"http://enterprise.lemmy.ml/c/main"
],
"type": "Remove",
"target": "http://enterprise.lemmy.ml/c/main/moderators",
"id": "http://enterprise.lemmy.ml/activities/remove/aab114f8-cfbd-4935-a5b7-e1a64603650d"
}

View file

@ -0,0 +1,10 @@
{
"actor": "http://ds9.lemmy.ml/u/lemmy_alpha",
"to": [
"http://enterprise.lemmy.ml/c/main"
],
"object": "http://enterprise.lemmy.ml/post/7",
"summary": "report this post",
"type": "Flag",
"id": "http://ds9.lemmy.ml/activities/flag/98b0933f-5e45-4a95-a15f-e0dc86361ba4"
}

View file

@ -0,0 +1,24 @@
{
"actor": "http://enterprise.lemmy.ml/u/lemmy_beta",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"object": {
"actor": "http://enterprise.lemmy.ml/u/lemmy_beta",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"object": "http://ds9.lemmy.ml/u/lemmy_alpha",
"cc": [
"http://enterprise.lemmy.ml/c/main"
],
"target": "http://enterprise.lemmy.ml/c/main",
"type": "Block",
"id": "http://enterprise.lemmy.ml/activities/block/726f43ab-bd0e-4ab3-89c8-627e976f553c"
},
"cc": [
"http://enterprise.lemmy.ml/c/main"
],
"type": "Undo",
"id": "http://enterprise.lemmy.ml/activities/undo/06a20ffb-3e32-42fb-8f4c-674b36d7c557"
}

View file

@ -0,0 +1,37 @@
{
"actor": "http://ds9.lemmy.ml/u/lemmy_alpha",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"object": {
"type": "Group",
"id": "http://enterprise.lemmy.ml/c/main",
"preferredUsername": "main",
"name": "The Updated Community",
"summary": "<p>updated 2</p>\n",
"source": {
"content": "updated 2",
"mediaType": "text/markdown"
},
"sensitive": false,
"moderators": "http://enterprise.lemmy.ml/c/main/moderators",
"inbox": "http://enterprise.lemmy.ml/c/main/inbox",
"outbox": "http://enterprise.lemmy.ml/c/main/outbox",
"followers": "http://enterprise.lemmy.ml/c/main/followers",
"endpoints": {
"sharedInbox": "http://enterprise.lemmy.ml/inbox"
},
"publicKey": {
"id": "http://enterprise.lemmy.ml/c/main#main-key",
"owner": "http://enterprise.lemmy.ml/c/main",
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA16Xh06V1l2yy0WAIMUTV\nnvZIuAuKDxzDQUNT+n8gmcVuvBu7tkpbPTQ3DjGB3bQfGC2ekew/yldwOXyZ7ry1\npbJSYSrCBJrAlPLs/ao3OPTqmcl3vnSWti/hqopEV+Um2t7fwpkCjVrnzVKRSlys\nihnrth64ZiwAqq2llpaXzWc1SR2URZYSdnry/4d9UNrZVkumIeg1gk9KbCAo4j/O\njsv/aBjpZcTeLmtMZf6fcrvGre9duJdx6e2Tg/YNcnSnARosqev/UwVTzzGNVWXg\n9rItaa0a0aea4se4Bn6QXvOBbcq3+OYZMR6a34hh5BTeNG8WbpwmVahS0WFUsv9G\nswIDAQAB\n-----END PUBLIC KEY-----\n"
},
"published": "2021-10-29T15:05:51.476984+00:00",
"updated": "2021-11-01T12:23:50.151874+00:00"
},
"cc": [
"http://enterprise.lemmy.ml/c/main"
],
"type": "Update",
"id": "http://ds9.lemmy.ml/activities/update/d3717cf5-096d-473f-9530-5d52f9d51f5f"
}

View file

@ -0,0 +1,29 @@
{
"actor": "http://ds9.lemmy.ml/u/lemmy_alpha",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"object": {
"type": "Note",
"id": "http://ds9.lemmy.ml/comment/1",
"attributedTo": "http://ds9.lemmy.ml/u/lemmy_alpha",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"content": "hello",
"mediaType": "text/html",
"source": {
"content": "hello",
"mediaType": "text/markdown"
},
"inReplyTo": "http://ds9.lemmy.ml/post/1",
"published": "2021-11-01T11:45:49.794920+00:00"
},
"cc": [
"http://enterprise.lemmy.ml/c/main",
"http://ds9.lemmy.ml/u/lemmy_alpha"
],
"tag": [],
"type": "Create",
"id": "http://ds9.lemmy.ml/activities/create/1e77d67c-44ac-45ed-bf2a-460e21f60236"
}

View file

@ -0,0 +1,32 @@
{
"actor": "http://ds9.lemmy.ml/u/lemmy_alpha",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"object": {
"type": "Page",
"id": "http://ds9.lemmy.ml/post/1",
"attributedTo": "http://ds9.lemmy.ml/u/lemmy_alpha",
"to": [
"http://enterprise.lemmy.ml/c/main",
"https://www.w3.org/ns/activitystreams#Public"
],
"name": "test post",
"content": "<p>test body</p>\n",
"mediaType": "text/html",
"source": {
"content": "test body",
"mediaType": "text/markdown"
},
"url": "https://lemmy.ml/pictrs/image/xl8W7FZfk9.jpg",
"commentsEnabled": true,
"sensitive": false,
"stickied": false,
"published": "2021-10-29T15:10:51.557399+00:00"
},
"cc": [
"http://enterprise.lemmy.ml/c/main"
],
"type": "Create",
"id": "http://ds9.lemmy.ml/activities/create/eee6a57a-622f-464d-b560-73ae1fcd3ddf"
}

View file

@ -0,0 +1,33 @@
{
"actor": "http://ds9.lemmy.ml/u/lemmy_alpha",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"object": {
"type": "Page",
"id": "http://ds9.lemmy.ml/post/1",
"attributedTo": "http://ds9.lemmy.ml/u/lemmy_alpha",
"to": [
"http://enterprise.lemmy.ml/c/main",
"https://www.w3.org/ns/activitystreams#Public"
],
"name": "test post 1",
"content": "<p>test body</p>\n",
"mediaType": "text/html",
"source": {
"content": "test body",
"mediaType": "text/markdown"
},
"url": "https://lemmy.ml/pictrs/image/xl8W7FZfk9.jpg",
"commentsEnabled": true,
"sensitive": false,
"stickied": false,
"published": "2021-10-29T15:10:51.557399+00:00",
"updated": "2021-10-29T15:11:35.976374+00:00"
},
"cc": [
"http://enterprise.lemmy.ml/c/main"
],
"type": "Update",
"id": "http://ds9.lemmy.ml/activities/update/ab360117-e165-4de4-b7fc-906b62c98631"
}

View file

@ -0,0 +1,12 @@
{
"actor": "http://ds9.lemmy.ml/u/lemmy_alpha",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"object": "http://ds9.lemmy.ml/post/1",
"cc": [
"http://enterprise.lemmy.ml/c/main"
],
"type": "Delete",
"id": "http://ds9.lemmy.ml/activities/delete/f2abee48-c7bb-41d5-9e27-8775ff32db12"
}

View file

@ -0,0 +1,13 @@
{
"actor": "http://enterprise.lemmy.ml/u/lemmy_beta",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"object": "http://ds9.lemmy.ml/comment/1",
"cc": [
"http://enterprise.lemmy.ml/c/main"
],
"type": "Delete",
"summary": "bad comment",
"id": "http://enterprise.lemmy.ml/activities/delete/42ca1a79-f99e-4518-a2ca-ba2df221eb5e"
}

View file

@ -0,0 +1,23 @@
{
"actor": "http://ds9.lemmy.ml/u/lemmy_alpha",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"object": {
"actor": "http://ds9.lemmy.ml/u/lemmy_alpha",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"object": "http://ds9.lemmy.ml/post/1",
"cc": [
"http://enterprise.lemmy.ml/c/main"
],
"type": "Delete",
"id": "http://ds9.lemmy.ml/activities/delete/b13cca96-7737-41e1-9769-8fbf972b3509"
},
"cc": [
"http://enterprise.lemmy.ml/c/main"
],
"type": "Undo",
"id": "http://ds9.lemmy.ml/activities/undo/5e939cfb-b8a1-4de8-950f-9d684e9162b9"
}

View file

@ -0,0 +1,24 @@
{
"actor": "http://enterprise.lemmy.ml/u/lemmy_beta",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"object": {
"actor": "http://enterprise.lemmy.ml/u/lemmy_beta",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"object": "http://ds9.lemmy.ml/comment/1",
"cc": [
"http://enterprise.lemmy.ml/c/main"
],
"type": "Delete",
"summary": "bad comment",
"id": "http://enterprise.lemmy.ml/activities/delete/2598435c-87a3-49cd-81f3-a44b03b7af9d"
},
"cc": [
"http://enterprise.lemmy.ml/c/main"
],
"type": "Undo",
"id": "http://enterprise.lemmy.ml/activities/undo/a850cf21-3866-4b3a-b80b-56aa00997fee"
}

View file

@ -0,0 +1,17 @@
{
"actor": "http://enterprise.lemmy.ml/c/main",
"to": [
"http://ds9.lemmy.ml/u/lemmy_alpha"
],
"object": {
"actor": "http://ds9.lemmy.ml/u/lemmy_alpha",
"to": [
"http://enterprise.lemmy.ml/c/main"
],
"object": "http://enterprise.lemmy.ml/c/main",
"type": "Follow",
"id": "http://ds9.lemmy.ml/activities/follow/6abcd50b-b8ca-4952-86b0-a6dd8cc12866"
},
"type": "Accept",
"id": "http://enterprise.lemmy.ml/activities/accept/75f080cc-3d45-4654-8186-8f3bb853fa27"
}

View file

@ -0,0 +1,9 @@
{
"actor": "http://ds9.lemmy.ml/u/lemmy_alpha",
"to": [
"http://enterprise.lemmy.ml/c/main"
],
"object": "http://enterprise.lemmy.ml/c/main",
"type": "Follow",
"id": "http://ds9.lemmy.ml/activities/follow/6abcd50b-b8ca-4952-86b0-a6dd8cc12866"
}

View file

@ -0,0 +1,17 @@
{
"actor": "http://ds9.lemmy.ml/u/lemmy_alpha",
"to": [
"http://enterprise.lemmy.ml/c/main"
],
"object": {
"actor": "http://ds9.lemmy.ml/u/lemmy_alpha",
"to": [
"http://enterprise.lemmy.ml/c/main"
],
"object": "http://enterprise.lemmy.ml/c/main",
"type": "Follow",
"id": "http://ds9.lemmy.ml/activities/follow/dc2f1bc5-f3a0-4daa-a46b-428cbfbd023c"
},
"type": "Undo",
"id": "http://ds9.lemmy.ml/activities/undo/dd83c482-8ebd-4b6c-9008-c8373bd1a86a"
}

View file

@ -0,0 +1,23 @@
{
"id": "http://enterprise.lemmy.ml/activities/create/987d05fa-f637-46d7-85be-13d112bc269f",
"actor": "http://enterprise.lemmy.ml/u/lemmy_beta",
"to": [
"http://ds9.lemmy.ml/u/lemmy_alpha"
],
"object": {
"type": "ChatMessage",
"id": "http://enterprise.lemmy.ml/private_message/1",
"attributedTo": "http://enterprise.lemmy.ml/u/lemmy_beta",
"to": [
"http://ds9.lemmy.ml/u/lemmy_alpha"
],
"content": "hello",
"mediaType": "text/html",
"source": {
"content": "hello",
"mediaType": "text/markdown"
},
"published": "2021-10-29T15:31:56.058289+00:00"
},
"type": "Create"
}

View file

@ -0,0 +1,9 @@
{
"actor": "http://enterprise.lemmy.ml/u/lemmy_beta",
"to": [
"http://enterprise.lemmy.ml/u/lemmy_beta"
],
"object": "http://enterprise.lemmy.ml/private_message/1",
"type": "Delete",
"id": "http://enterprise.lemmy.ml/activities/delete/041d9858-5eef-4ad9-84ae-7455b4d87ed9"
}

View file

@ -0,0 +1,17 @@
{
"actor": "http://enterprise.lemmy.ml/u/lemmy_beta",
"to": [
"http://ds9.lemmy.ml/u/lemmy_alpha"
],
"object": {
"actor": "http://enterprise.lemmy.ml/u/lemmy_beta",
"to": [
"http://enterprise.lemmy.ml/u/lemmy_beta"
],
"object": "http://enterprise.lemmy.ml/private_message/1",
"type": "Delete",
"id": "http://enterprise.lemmy.ml/activities/delete/616c41be-04ed-4bd4-b865-30712186b122"
},
"type": "Undo",
"id": "http://enterprise.lemmy.ml/activities/undo/35e5b337-014c-4bbe-8d63-6fac96f51409"
}

View file

@ -0,0 +1,12 @@
{
"actor": "http://enterprise.lemmy.ml/u/lemmy_beta",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"object": "http://ds9.lemmy.ml/post/1",
"cc": [
"http://enterprise.lemmy.ml/c/main"
],
"type": "Dislike",
"id": "http://enterprise.lemmy.ml/activities/dislike/64d40d40-a829-43a5-8247-1fb595b3ca1c"
}

View file

@ -0,0 +1,12 @@
{
"actor": "http://ds9.lemmy.ml/u/lemmy_alpha",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"object": "http://ds9.lemmy.ml/comment/1",
"cc": [
"http://enterprise.lemmy.ml/c/main"
],
"type": "Like",
"id": "http://ds9.lemmy.ml/activities/like/fd61d070-7382-46a9-b2b7-6bb253732877"
}

View file

@ -0,0 +1,23 @@
{
"actor": "http://enterprise.lemmy.ml/u/lemmy_beta",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"object": {
"actor": "http://enterprise.lemmy.ml/u/lemmy_beta",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"object": "http://ds9.lemmy.ml/post/1",
"cc": [
"http://enterprise.lemmy.ml/c/main"
],
"type": "Like",
"id": "http://enterprise.lemmy.ml/activities/like/2227ab2c-79e2-4fca-a1d2-1d67dacf2457"
},
"cc": [
"http://enterprise.lemmy.ml/c/main"
],
"type": "Undo",
"id": "http://enterprise.lemmy.ml/activities/undo/6cc6fb71-39fe-49ea-9506-f0423b101e98"
}

View file

@ -0,0 +1,23 @@
{
"actor": "http://ds9.lemmy.ml/u/lemmy_alpha",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"object": {
"actor": "http://ds9.lemmy.ml/u/lemmy_alpha",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"object": "http://ds9.lemmy.ml/comment/1",
"cc": [
"http://enterprise.lemmy.ml/c/main"
],
"type": "Like",
"id": "http://ds9.lemmy.ml/activities/like/efcf7ae2-dfcc-4ff4-9ce4-6adf251ff004"
},
"cc": [
"http://enterprise.lemmy.ml/c/main"
],
"type": "Undo",
"id": "http://ds9.lemmy.ml/activities/undo/3518565c-24a7-4d9e-8e0a-f7a2f45ac618"
}

View file

@ -0,0 +1,6 @@
{
"id": "http://enterprise.lemmy.ml/c/main/followers",
"type": "Collection",
"totalItems": 3,
"items": []
}

View file

@ -0,0 +1,7 @@
{
"type": "OrderedCollection",
"id": "https://enterprise.lemmy.ml/c/tenforward/moderators",
"orderedItems": [
"https://enterprise.lemmy.ml/u/picard"
]
}

View file

@ -0,0 +1,209 @@
{
"type": "OrderedCollection",
"id": "https://ds9.lemmy.ml/c/main/outbox",
"totalItems": 7,
"orderedItems": [
{
"actor": "https://ds9.lemmy.ml/u/dess_ds9",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"object": {
"type": "Page",
"id": "https://ds9.lemmy.ml/post/1685",
"attributedTo": "https://ds9.lemmy.ml/u/dess_ds9",
"to": [
"https://ds9.lemmy.ml/c/main",
"https://www.w3.org/ns/activitystreams#Public"
],
"name": "Test post",
"mediaType": "text/html",
"commentsEnabled": true,
"sensitive": false,
"stickied": false,
"published": "2021-09-30T16:37:58.425718+00:00",
"updated": "2021-09-30T16:39:50.934055+00:00"
},
"cc": [
"https://ds9.lemmy.ml/c/main"
],
"type": "Create",
"id": "https://ds9.lemmy.ml/activities/create/157bc329-05cb-4dc3-ad9e-5110fde3f3aa"
},
{
"actor": "https://ds9.lemmy.ml/u/nutomic",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"object": {
"type": "Page",
"id": "https://ds9.lemmy.ml/post/1665",
"attributedTo": "https://ds9.lemmy.ml/u/nutomic",
"to": [
"https://ds9.lemmy.ml/c/main",
"https://www.w3.org/ns/activitystreams#Public"
],
"name": "another webmention test",
"mediaType": "text/html",
"url": "https://webmention.rocks/test/1",
"commentsEnabled": true,
"sensitive": false,
"stickied": false,
"published": "2021-09-17T13:22:15.026912+00:00"
},
"cc": [
"https://ds9.lemmy.ml/c/main"
],
"type": "Create",
"id": "https://ds9.lemmy.ml/activities/create/c54e4509-16ac-42bf-b3b4-0bf8516f8152"
},
{
"actor": "https://ds9.lemmy.ml/u/nutomic",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"object": {
"type": "Page",
"id": "https://ds9.lemmy.ml/post/1664",
"attributedTo": "https://ds9.lemmy.ml/u/nutomic",
"to": [
"https://ds9.lemmy.ml/c/main",
"https://www.w3.org/ns/activitystreams#Public"
],
"name": "another test",
"mediaType": "text/html",
"url": "https://webmention.rocks/test/1",
"commentsEnabled": true,
"sensitive": false,
"stickied": false,
"published": "2021-09-17T13:13:21.675891+00:00"
},
"cc": [
"https://ds9.lemmy.ml/c/main"
],
"type": "Create",
"id": "https://ds9.lemmy.ml/activities/create/25f7d2cb-11d5-4c9c-aa3c-85fbff9f9e0c"
},
{
"actor": "https://ds9.lemmy.ml/u/nutomic",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"object": {
"type": "Page",
"id": "https://ds9.lemmy.ml/post/1663",
"attributedTo": "https://ds9.lemmy.ml/u/nutomic",
"to": [
"https://ds9.lemmy.ml/c/main",
"https://www.w3.org/ns/activitystreams#Public"
],
"name": "Webmention test from Lemmy",
"mediaType": "text/html",
"url": "https://webmention.rocks/test/1",
"commentsEnabled": true,
"sensitive": false,
"stickied": false,
"published": "2021-09-17T13:00:15.392844+00:00"
},
"cc": [
"https://ds9.lemmy.ml/c/main"
],
"type": "Create",
"id": "https://ds9.lemmy.ml/activities/create/cfbd12b8-2e11-42b6-a609-b482decbaf11"
},
{
"actor": "https://ds9.lemmy.ml/u/dess_tester_3",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"object": {
"type": "Page",
"id": "https://ds9.lemmy.ml/post/1644",
"attributedTo": "https://ds9.lemmy.ml/u/dess_tester_3",
"to": [
"https://ds9.lemmy.ml/c/main",
"https://www.w3.org/ns/activitystreams#Public"
],
"name": "The best wireless earbuds you can buy right now | Engadget",
"mediaType": "text/html",
"url": "https://www.engadget.com/best-wireless-earbuds-120058222.html",
"image": {
"type": "Image",
"url": "https://ds9.lemmy.ml/pictrs/image/0WWsYOuwAE.jpg"
},
"commentsEnabled": true,
"sensitive": false,
"stickied": false,
"published": "2021-08-26T01:22:06.428368+00:00"
},
"cc": [
"https://ds9.lemmy.ml/c/main"
],
"type": "Create",
"id": "https://ds9.lemmy.ml/activities/create/76c94408-944a-4a2f-a88b-d10f12b472b0"
},
{
"actor": "https://ds9.lemmy.ml/u/dess_ds9",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"object": {
"type": "Page",
"id": "https://ds9.lemmy.ml/post/1643",
"attributedTo": "https://ds9.lemmy.ml/u/dess_ds9",
"to": [
"https://ds9.lemmy.ml/c/main",
"https://www.w3.org/ns/activitystreams#Public"
],
"name": "First Look: Cadillacs luxury EV debut seems like a winner | Engadges",
"content": "<p>test</p>\n",
"mediaType": "text/html",
"source": {
"content": "test",
"mediaType": "text/markdown"
},
"url": "https://www.engadget.com/cadillac-lyriq-luxury-ev-first-look-video-171543752.html",
"image": {
"type": "Image",
"url": "https://ds9.lemmy.ml/pictrs/image/gnmtvgXP31.jpg"
},
"commentsEnabled": true,
"sensitive": false,
"stickied": false,
"published": "2021-08-23T23:43:06.560543+00:00",
"updated": "2021-08-23T23:52:51.832606+00:00"
},
"cc": [
"https://ds9.lemmy.ml/c/main"
],
"type": "Create",
"id": "https://ds9.lemmy.ml/activities/create/b1f95918-f593-4951-91cf-2c3340cd9509"
},
{
"actor": "https://ds9.lemmy.ml/u/dess_ds9_2",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"object": {
"type": "Page",
"id": "https://ds9.lemmy.ml/post/1642",
"attributedTo": "https://ds9.lemmy.ml/u/dess_ds9_2",
"to": [
"https://ds9.lemmy.ml/c/main",
"https://www.w3.org/ns/activitystreams#Public"
],
"name": "A test post from DS9",
"mediaType": "text/html",
"commentsEnabled": true,
"sensitive": false,
"stickied": false,
"published": "2021-08-06T14:10:47.493075+00:00"
},
"cc": [
"https://ds9.lemmy.ml/c/main"
],
"type": "Create",
"id": "https://ds9.lemmy.ml/activities/create/6359b2e7-badb-4241-b5ee-b093078361bd"
}
]
}

View file

@ -0,0 +1,6 @@
{
"type": "OrderedCollection",
"id": "http://ds9.lemmy.ml/u/lemmy_alpha/outbox",
"orderedItems": [],
"totalItems": 0
}

View file

@ -0,0 +1,15 @@
{
"id": "https://enterprise.lemmy.ml/private_message/1621",
"type": "ChatMessage",
"attributedTo": "https://enterprise.lemmy.ml/u/picard",
"to": [
"https://queer.hacktivis.me/users/lanodan"
],
"content": "<p>Hello hello, testing</p>\n",
"mediaType": "text/html",
"source": {
"content": "Hello hello, testing",
"mediaType": "text/markdown"
},
"published": "2021-10-21T10:13:14.597721+00:00"
}

View file

@ -1,23 +1,4 @@
{
"@context": [
"https://www.w3.org/ns/activitystreams",
{
"comments_enabled": {
"id": "pt:commentsEnabled",
"type": "sc:Boolean"
},
"matrixUserId": {
"id": "as:alsoKnownAs",
"type": "sc:Text"
},
"moderators": "as:moderators",
"pt": "https://join-lemmy.org#",
"sc": "http://schema.org#",
"sensitive": "as:sensitive",
"stickied": "as:stickied"
},
"https://w3id.org/security/v1"
],
"id": "https://enterprise.lemmy.ml/c/tenforward",
"type": "Group",
"preferredUsername": "main",

View file

@ -0,0 +1,15 @@
{
"id": "https://enterprise.lemmy.ml/comment/38741",
"type": "Note",
"attributedTo": "https://enterprise.lemmy.ml/u/picard",
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"inReplyTo": "https://enterprise.lemmy.ml/post/55143",
"content": "<p>first comment!</p>\n",
"mediaType": "text/html",
"source": {
"content": "first comment!",
"mediaType": "text/markdown"
},
"published": "2021-03-01T13:42:43.966208+00:00",
"updated": "2021-03-01T13:43:03.955787+00:00"
}

View file

@ -1,23 +1,4 @@
{
"@context": [
"https://www.w3.org/ns/activitystreams",
{
"comments_enabled": {
"id": "pt:commentsEnabled",
"type": "sc:Boolean"
},
"matrixUserId": {
"id": "as:alsoKnownAs",
"type": "sc:Text"
},
"moderators": "as:moderators",
"pt": "https://join-lemmy.org#",
"sc": "http://schema.org#",
"sensitive": "as:sensitive",
"stickied": "as:stickied"
},
"https://w3id.org/security/v1"
],
"id": "https://enterprise.lemmy.ml/post/55143",
"type": "Page",
"attributedTo": "https://enterprise.lemmy.ml/u/picard",
@ -32,6 +13,11 @@
"content": "This is a post in the /c/tenforward community",
"mediaType": "text/markdown"
},
"url": "https://enterprise.lemmy.ml/pictrs/image/eOtYb9iEiB.png",
"image": {
"type": "Image",
"url": "https://enterprise.lemmy.ml/pictrs/image/eOtYb9iEiB.png"
},
"sensitive": false,
"commentsEnabled": true,
"stickied": true,

View file

@ -1,23 +1,4 @@
{
"@context": [
"https://www.w3.org/ns/activitystreams",
{
"comments_enabled": {
"id": "pt:commentsEnabled",
"type": "sc:Boolean"
},
"matrixUserId": {
"id": "as:alsoKnownAs",
"type": "sc:Text"
},
"moderators": "as:moderators",
"pt": "https://join-lemmy.org#",
"sc": "http://schema.org#",
"sensitive": "as:sensitive",
"stickied": "as:stickied"
},
"https://w3id.org/security/v1"
],
"id": "https://enterprise.lemmy.ml/u/picard",
"type": "Person",
"preferredUsername": "picard",
@ -35,6 +16,7 @@
"type": "Image",
"url": "https://enterprise.lemmy.ml/pictrs/image/XenaYI5hTn.png"
},
"matrix_user_id": "@picard:matrix.org",
"inbox": "https://enterprise.lemmy.ml/u/picard/inbox",
"outbox": "https://enterprise.lemmy.ml/u/picard/outbox",
"endpoints": {

View file

@ -0,0 +1,52 @@
{
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://greenish.red/schemas/litepub-0.1.jsonld",
{
"@language": "und"
}
],
"actor": "https://greenish.red/users/nutomic",
"cc": [
"https://greenish.red/users/nutomic/followers"
],
"context": "https://greenish.red/contexts/f6244742-0526-4b84-ac4f-ceadf1fb4e56",
"context_id": 6336544,
"directMessage": false,
"id": "https://greenish.red/activities/db61d52b-9c35-486a-bf27-bbd4edc6c6a1",
"object": {
"actor": "https://greenish.red/users/nutomic",
"attachment": [],
"attributedTo": "https://greenish.red/users/nutomic",
"cc": [
"https://greenish.red/users/nutomic/followers"
],
"content": "<span class=\"h-card\"><a class=\"u-url mention\" data-user=\"ACimPLEXPDd7enu3cm\" href=\"https://enterprise.lemmy.ml/u/picard\" rel=\"ugc\">@<span>lanodan</span></a></span> test",
"context": "https://greenish.red/contexts/f6244742-0526-4b84-ac4f-ceadf1fb4e56",
"conversation": "https://greenish.red/contexts/f6244742-0526-4b84-ac4f-ceadf1fb4e56",
"id": "https://greenish.red/objects/1a522f2e-d5ab-454b-93d7-e58bc0650c2a",
"inReplyTo": "https://enterprise.lemmy.ml/post/55143",
"published": "2021-10-26T10:28:35.602455Z",
"sensitive": false,
"source": "@lanodan@ds9.lemmy.ml test",
"summary": "",
"tag": [
{
"href": "https://enterprise.lemmy.ml/u/picard",
"name": "@lanodan@ds9.lemmy.ml",
"type": "Mention"
}
],
"to": [
"https://enterprise.lemmy.ml/u/picard",
"https://www.w3.org/ns/activitystreams#Public"
],
"type": "Note"
},
"published": "2021-10-26T10:28:35.595650Z",
"to": [
"https://enterprise.lemmy.ml/u/picard",
"https://www.w3.org/ns/activitystreams#Public"
],
"type": "Create"
}

View file

@ -1,28 +1,9 @@
use crate::{
activities::{
check_community_deleted_or_removed,
comment::{collect_non_local_mentions, get_notif_recipients},
community::{announce::AnnouncableActivities, send_to_community},
extract_community,
generate_activity_id,
verify_activity,
verify_person_in_community,
CreateOrUpdateType,
},
context::lemmy_context,
fetcher::object_id::ObjectId,
objects::{
comment::{ApubComment, Note},
community::ApubCommunity,
person::ApubPerson,
},
};
use activitystreams::{base::AnyBase, link::Mention, primitives::OneOrMany, unparsed::Unparsed};
use activitystreams::public;
use lemmy_api_common::{blocking, check_post_deleted_or_removed};
use lemmy_apub_lib::{
data::Data,
traits::{ActivityFields, ActivityHandler, ActorType, ApubObject},
values::PublicUrl,
traits::{ActivityHandler, ActorType, ApubObject},
verify::verify_domains_match,
};
use lemmy_db_schema::{
@ -31,25 +12,22 @@ use lemmy_db_schema::{
};
use lemmy_utils::LemmyError;
use lemmy_websocket::{send::send_comment_ws_message, LemmyContext, UserOperationCrud};
use serde::{Deserialize, Serialize};
use url::Url;
#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)]
#[serde(rename_all = "camelCase")]
pub struct CreateOrUpdateComment {
actor: ObjectId<ApubPerson>,
to: [PublicUrl; 1],
object: Note,
cc: Vec<Url>,
tag: Vec<Mention>,
#[serde(rename = "type")]
kind: CreateOrUpdateType,
id: Url,
#[serde(rename = "@context")]
context: OneOrMany<AnyBase>,
#[serde(flatten)]
unparsed: Unparsed,
}
use crate::{
activities::{
check_community_deleted_or_removed,
comment::{collect_non_local_mentions, get_notif_recipients},
community::{announce::GetCommunity, send_to_community},
generate_activity_id,
verify_activity,
verify_is_public,
verify_person_in_community,
},
activity_lists::AnnouncableActivities,
fetcher::object_id::ObjectId,
objects::{comment::ApubComment, community::ApubCommunity, person::ApubPerson},
protocol::activities::{create_or_update::comment::CreateOrUpdateComment, CreateOrUpdateType},
};
impl CreateOrUpdateComment {
pub async fn send(
@ -76,13 +54,12 @@ impl CreateOrUpdateComment {
let create_or_update = CreateOrUpdateComment {
actor: ObjectId::new(actor.actor_id()),
to: [PublicUrl::Public],
to: vec![public()],
object: comment.to_apub(context).await?,
cc: maa.ccs,
tag: maa.tags,
kind,
id: id.clone(),
context: lemmy_context(),
unparsed: Default::default(),
};
@ -100,12 +77,12 @@ impl ActivityHandler for CreateOrUpdateComment {
context: &Data<LemmyContext>,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
let community = extract_community(&self.cc, context, request_counter).await?;
let community_id = ObjectId::new(community.actor_id());
verify_is_public(&self.to)?;
let post = self.object.get_parents(context, request_counter).await?.0;
let community = self.get_community(context, request_counter).await?;
verify_activity(self, &context.settings())?;
verify_person_in_community(&self.actor, &community_id, context, request_counter).await?;
verify_person_in_community(&self.actor, &community, context, request_counter).await?;
verify_domains_match(self.actor.inner(), self.object.id_unchecked())?;
check_community_deleted_or_removed(&community)?;
check_post_deleted_or_removed(&post)?;
@ -135,3 +112,19 @@ impl ActivityHandler for CreateOrUpdateComment {
Ok(())
}
}
#[async_trait::async_trait(?Send)]
impl GetCommunity for CreateOrUpdateComment {
async fn get_community(
&self,
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<ApubCommunity, LemmyError> {
let post = self.object.get_parents(context, request_counter).await?.0;
let community = blocking(context.pool(), move |conn| {
Community::read(conn, post.community_id)
})
.await??;
Ok(community.into())
}
}

View file

@ -1,28 +1,24 @@
use crate::{
activities::{
community::{announce::AnnouncableActivities, send_to_community},
community::{announce::GetCommunity, get_community_from_moderators_url, send_to_community},
generate_activity_id,
verify_activity,
verify_add_remove_moderator_target,
verify_is_public,
verify_mod_action,
verify_person_in_community,
},
context::lemmy_context,
activity_lists::AnnouncableActivities,
fetcher::object_id::ObjectId,
generate_moderators_url,
objects::{community::ApubCommunity, person::ApubPerson},
protocol::activities::community::add_mod::AddMod,
};
use activitystreams::{
activity::kind::AddType,
base::AnyBase,
primitives::OneOrMany,
unparsed::Unparsed,
};
use activitystreams::{activity::kind::AddType, public};
use lemmy_api_common::blocking;
use lemmy_apub_lib::{
data::Data,
traits::{ActivityFields, ActivityHandler, ActorType},
values::PublicUrl,
traits::{ActivityHandler, ActorType},
};
use lemmy_db_schema::{
source::community::{CommunityModerator, CommunityModeratorForm},
@ -30,25 +26,6 @@ use lemmy_db_schema::{
};
use lemmy_utils::LemmyError;
use lemmy_websocket::LemmyContext;
use serde::{Deserialize, Serialize};
use url::Url;
#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)]
#[serde(rename_all = "camelCase")]
pub struct AddMod {
actor: ObjectId<ApubPerson>,
to: [PublicUrl; 1],
object: ObjectId<ApubPerson>,
target: Url,
cc: [ObjectId<ApubCommunity>; 1],
#[serde(rename = "type")]
kind: AddType,
id: Url,
#[serde(rename = "@context")]
context: OneOrMany<AnyBase>,
#[serde(flatten)]
unparsed: Unparsed,
}
impl AddMod {
pub async fn send(
@ -63,13 +40,12 @@ impl AddMod {
)?;
let add = AddMod {
actor: ObjectId::new(actor.actor_id()),
to: [PublicUrl::Public],
to: vec![public()],
object: ObjectId::new(added_mod.actor_id()),
target: generate_moderators_url(&community.actor_id)?.into(),
cc: [ObjectId::new(community.actor_id())],
cc: vec![community.actor_id()],
kind: AddType::Add,
id: id.clone(),
context: lemmy_context(),
unparsed: Default::default(),
};
@ -88,10 +64,12 @@ impl ActivityHandler for AddMod {
context: &Data<LemmyContext>,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
verify_is_public(&self.to)?;
verify_activity(self, &context.settings())?;
verify_person_in_community(&self.actor, &self.cc[0], context, request_counter).await?;
verify_mod_action(&self.actor, &self.cc[0], context, request_counter).await?;
verify_add_remove_moderator_target(&self.target, &self.cc[0])?;
let community = self.get_community(context, request_counter).await?;
verify_person_in_community(&self.actor, &community, context, request_counter).await?;
verify_mod_action(&self.actor, &community, context, request_counter).await?;
verify_add_remove_moderator_target(&self.target, &community)?;
Ok(())
}
@ -100,7 +78,7 @@ impl ActivityHandler for AddMod {
context: &Data<LemmyContext>,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
let community = self.cc[0].dereference(context, request_counter).await?;
let community = self.get_community(context, request_counter).await?;
let new_mod = self.object.dereference(context, request_counter).await?;
// If we had to refetch the community while parsing the activity, then the new mod has already
@ -124,3 +102,14 @@ impl ActivityHandler for AddMod {
Ok(())
}
}
#[async_trait::async_trait(?Send)]
impl GetCommunity for AddMod {
async fn get_community(
&self,
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<ApubCommunity, LemmyError> {
get_community_from_moderators_url(&self.target, context, request_counter).await
}
}

View file

@ -1,76 +1,28 @@
use crate::{
activities::{
comment::create_or_update::CreateOrUpdateComment,
community::{
add_mod::AddMod,
block_user::BlockUserFromCommunity,
list_community_follower_inboxes,
remove_mod::RemoveMod,
undo_block_user::UndoBlockUserFromCommunity,
update::UpdateCommunity,
},
deletion::{delete::Delete, undo_delete::UndoDelete},
generate_activity_id,
post::create_or_update::CreateOrUpdatePost,
verify_activity,
verify_community,
voting::{undo_vote::UndoVote, vote::Vote},
},
context::lemmy_context,
activities::{generate_activity_id, send_lemmy_activity, verify_activity, verify_is_public},
activity_lists::AnnouncableActivities,
fetcher::object_id::ObjectId,
http::is_activity_already_known,
insert_activity,
objects::community::ApubCommunity,
send_lemmy_activity,
CommunityType,
};
use activitystreams::{
activity::kind::AnnounceType,
base::AnyBase,
primitives::OneOrMany,
unparsed::Unparsed,
protocol::activities::community::announce::AnnounceActivity,
};
use activitystreams::{activity::kind::AnnounceType, public};
use lemmy_apub_lib::{
data::Data,
traits::{ActivityFields, ActivityHandler, ActorType},
values::PublicUrl,
};
use lemmy_utils::LemmyError;
use lemmy_websocket::LemmyContext;
use serde::{Deserialize, Serialize};
use url::Url;
#[derive(Clone, Debug, Deserialize, Serialize, ActivityHandler, ActivityFields)]
#[serde(untagged)]
#[activity_handler(LemmyContext)]
pub enum AnnouncableActivities {
CreateOrUpdateComment(CreateOrUpdateComment),
CreateOrUpdatePost(Box<CreateOrUpdatePost>),
Vote(Vote),
UndoVote(UndoVote),
Delete(Delete),
UndoDelete(UndoDelete),
UpdateCommunity(Box<UpdateCommunity>),
BlockUserFromCommunity(BlockUserFromCommunity),
UndoBlockUserFromCommunity(UndoBlockUserFromCommunity),
AddMod(AddMod),
RemoveMod(RemoveMod),
}
#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)]
#[serde(rename_all = "camelCase")]
pub struct AnnounceActivity {
actor: ObjectId<ApubCommunity>,
to: [PublicUrl; 1],
object: AnnouncableActivities,
cc: Vec<Url>,
#[serde(rename = "type")]
kind: AnnounceType,
id: Url,
#[serde(rename = "@context")]
context: OneOrMany<AnyBase>,
#[serde(flatten)]
unparsed: Unparsed,
#[async_trait::async_trait(?Send)]
pub(crate) trait GetCommunity {
async fn get_community(
&self,
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<ApubCommunity, LemmyError>;
}
impl AnnounceActivity {
@ -82,18 +34,19 @@ impl AnnounceActivity {
) -> Result<(), LemmyError> {
let announce = AnnounceActivity {
actor: ObjectId::new(community.actor_id()),
to: [PublicUrl::Public],
to: vec![public()],
object,
cc: vec![community.followers_url()],
cc: vec![community.followers_url.clone().into_inner()],
kind: AnnounceType::Announce,
id: generate_activity_id(
&AnnounceType::Announce,
&context.settings().get_protocol_and_hostname(),
)?,
context: lemmy_context(),
unparsed: Default::default(),
};
let inboxes = list_community_follower_inboxes(community, additional_inboxes, context).await?;
let inboxes = community
.get_follower_inboxes(additional_inboxes, context)
.await?;
send_lemmy_activity(context, &announce, &announce.id, community, inboxes, false).await
}
}
@ -106,8 +59,8 @@ impl ActivityHandler for AnnounceActivity {
context: &Data<LemmyContext>,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
verify_is_public(&self.to)?;
verify_activity(self, &context.settings())?;
verify_community(&self.actor, context, request_counter).await?;
self.object.verify(context, request_counter).await?;
Ok(())
}

View file

@ -1,26 +1,22 @@
use crate::{
activities::{
community::{announce::AnnouncableActivities, send_to_community},
community::{announce::GetCommunity, send_to_community},
generate_activity_id,
verify_activity,
verify_is_public,
verify_mod_action,
verify_person_in_community,
},
context::lemmy_context,
activity_lists::AnnouncableActivities,
fetcher::object_id::ObjectId,
objects::{community::ApubCommunity, person::ApubPerson},
protocol::activities::community::block_user::BlockUserFromCommunity,
};
use activitystreams::{
activity::kind::BlockType,
base::AnyBase,
primitives::OneOrMany,
unparsed::Unparsed,
};
use activitystreams::{activity::kind::BlockType, public};
use lemmy_api_common::blocking;
use lemmy_apub_lib::{
data::Data,
traits::{ActivityFields, ActivityHandler, ActorType},
values::PublicUrl,
traits::{ActivityHandler, ActorType},
};
use lemmy_db_schema::{
source::community::{
@ -33,24 +29,6 @@ use lemmy_db_schema::{
};
use lemmy_utils::LemmyError;
use lemmy_websocket::LemmyContext;
use serde::{Deserialize, Serialize};
use url::Url;
#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)]
#[serde(rename_all = "camelCase")]
pub struct BlockUserFromCommunity {
actor: ObjectId<ApubPerson>,
to: [PublicUrl; 1],
pub(in crate::activities::community) object: ObjectId<ApubPerson>,
cc: [ObjectId<ApubCommunity>; 1],
#[serde(rename = "type")]
kind: BlockType,
id: Url,
#[serde(rename = "@context")]
context: OneOrMany<AnyBase>,
#[serde(flatten)]
unparsed: Unparsed,
}
impl BlockUserFromCommunity {
pub(in crate::activities::community) fn new(
@ -61,15 +39,15 @@ impl BlockUserFromCommunity {
) -> Result<BlockUserFromCommunity, LemmyError> {
Ok(BlockUserFromCommunity {
actor: ObjectId::new(actor.actor_id()),
to: [PublicUrl::Public],
to: vec![public()],
object: ObjectId::new(target.actor_id()),
cc: [ObjectId::new(community.actor_id())],
cc: vec![community.actor_id()],
target: ObjectId::new(community.actor_id()),
kind: BlockType::Block,
id: generate_activity_id(
BlockType::Block,
&context.settings().get_protocol_and_hostname(),
)?,
context: lemmy_context(),
unparsed: Default::default(),
})
}
@ -97,9 +75,11 @@ impl ActivityHandler for BlockUserFromCommunity {
context: &Data<LemmyContext>,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
verify_is_public(&self.to)?;
verify_activity(self, &context.settings())?;
verify_person_in_community(&self.actor, &self.cc[0], context, request_counter).await?;
verify_mod_action(&self.actor, &self.cc[0], context, request_counter).await?;
let community = self.get_community(context, request_counter).await?;
verify_person_in_community(&self.actor, &community, context, request_counter).await?;
verify_mod_action(&self.actor, &community, context, request_counter).await?;
Ok(())
}
@ -108,7 +88,7 @@ impl ActivityHandler for BlockUserFromCommunity {
context: &Data<LemmyContext>,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
let community = self.cc[0].dereference(context, request_counter).await?;
let community = self.get_community(context, request_counter).await?;
let blocked_user = self.object.dereference(context, request_counter).await?;
let community_user_ban_form = CommunityPersonBanForm {
@ -136,3 +116,14 @@ impl ActivityHandler for BlockUserFromCommunity {
Ok(())
}
}
#[async_trait::async_trait(?Send)]
impl GetCommunity for BlockUserFromCommunity {
async fn get_community(
&self,
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<ApubCommunity, LemmyError> {
self.target.dereference(context, request_counter).await
}
}

View file

@ -1,12 +1,11 @@
use crate::{
activities::community::announce::{AnnouncableActivities, AnnounceActivity},
check_is_apub_id_valid,
activities::send_lemmy_activity,
activity_lists::AnnouncableActivities,
fetcher::object_id::ObjectId,
insert_activity,
objects::community::ApubCommunity,
send_lemmy_activity,
CommunityType,
protocol::activities::community::announce::AnnounceActivity,
};
use itertools::Itertools;
use lemmy_apub_lib::traits::ActorType;
use lemmy_utils::LemmyError;
use lemmy_websocket::LemmyContext;
@ -16,31 +15,10 @@ pub mod add_mod;
pub mod announce;
pub mod block_user;
pub mod remove_mod;
pub mod report;
pub mod undo_block_user;
pub mod update;
async fn list_community_follower_inboxes(
community: &ApubCommunity,
additional_inboxes: Vec<Url>,
context: &LemmyContext,
) -> Result<Vec<Url>, LemmyError> {
Ok(
vec![
community
.get_follower_inboxes(context.pool(), &context.settings())
.await?,
additional_inboxes,
]
.iter()
.flatten()
.unique()
.filter(|inbox| inbox.host_str() != Some(&context.settings().hostname))
.filter(|inbox| check_is_apub_id_valid(inbox, false, &context.settings()).is_ok())
.map(|inbox| inbox.to_owned())
.collect(),
)
}
pub(crate) async fn send_to_community<T: ActorType>(
activity: AnnouncableActivities,
activity_id: &Url,
@ -61,3 +39,14 @@ pub(crate) async fn send_to_community<T: ActorType>(
Ok(())
}
async fn get_community_from_moderators_url(
moderators: &Url,
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<ApubCommunity, LemmyError> {
let community_id = Url::parse(&moderators.to_string().replace("/moderators", ""))?;
ObjectId::new(community_id)
.dereference(context, request_counter)
.await
}

View file

@ -1,28 +1,24 @@
use crate::{
activities::{
community::{announce::AnnouncableActivities, send_to_community},
community::{announce::GetCommunity, get_community_from_moderators_url, send_to_community},
generate_activity_id,
verify_activity,
verify_add_remove_moderator_target,
verify_is_public,
verify_mod_action,
verify_person_in_community,
},
context::lemmy_context,
activity_lists::AnnouncableActivities,
fetcher::object_id::ObjectId,
generate_moderators_url,
objects::{community::ApubCommunity, person::ApubPerson},
protocol::activities::community::remove_mod::RemoveMod,
};
use activitystreams::{
activity::kind::RemoveType,
base::AnyBase,
primitives::OneOrMany,
unparsed::Unparsed,
};
use activitystreams::{activity::kind::RemoveType, public};
use lemmy_api_common::blocking;
use lemmy_apub_lib::{
data::Data,
traits::{ActivityFields, ActivityHandler, ActorType},
values::PublicUrl,
traits::{ActivityHandler, ActorType},
};
use lemmy_db_schema::{
source::community::{CommunityModerator, CommunityModeratorForm},
@ -30,26 +26,6 @@ use lemmy_db_schema::{
};
use lemmy_utils::LemmyError;
use lemmy_websocket::LemmyContext;
use serde::{Deserialize, Serialize};
use url::Url;
#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)]
#[serde(rename_all = "camelCase")]
pub struct RemoveMod {
actor: ObjectId<ApubPerson>,
to: [PublicUrl; 1],
pub(in crate::activities) object: ObjectId<ApubPerson>,
cc: [ObjectId<ApubCommunity>; 1],
#[serde(rename = "type")]
kind: RemoveType,
// if target is set, this is means remove mod from community
pub(in crate::activities) target: Url,
id: Url,
#[serde(rename = "@context")]
context: OneOrMany<AnyBase>,
#[serde(flatten)]
unparsed: Unparsed,
}
impl RemoveMod {
pub async fn send(
@ -64,12 +40,11 @@ impl RemoveMod {
)?;
let remove = RemoveMod {
actor: ObjectId::new(actor.actor_id()),
to: [PublicUrl::Public],
to: vec![public()],
object: ObjectId::new(removed_mod.actor_id()),
target: generate_moderators_url(&community.actor_id)?.into(),
id: id.clone(),
context: lemmy_context(),
cc: [ObjectId::new(community.actor_id())],
cc: vec![community.actor_id()],
kind: RemoveType::Remove,
unparsed: Default::default(),
};
@ -88,10 +63,12 @@ impl ActivityHandler for RemoveMod {
context: &Data<LemmyContext>,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
verify_is_public(&self.to)?;
verify_activity(self, &context.settings())?;
verify_person_in_community(&self.actor, &self.cc[0], context, request_counter).await?;
verify_mod_action(&self.actor, &self.cc[0], context, request_counter).await?;
verify_add_remove_moderator_target(&self.target, &self.cc[0])?;
let community = self.get_community(context, request_counter).await?;
verify_person_in_community(&self.actor, &community, context, request_counter).await?;
verify_mod_action(&self.actor, &community, context, request_counter).await?;
verify_add_remove_moderator_target(&self.target, &community)?;
Ok(())
}
@ -100,7 +77,7 @@ impl ActivityHandler for RemoveMod {
context: &Data<LemmyContext>,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
let community = self.cc[0].dereference(context, request_counter).await?;
let community = self.get_community(context, request_counter).await?;
let remove_mod = self.object.dereference(context, request_counter).await?;
let form = CommunityModeratorForm {
@ -115,3 +92,14 @@ impl ActivityHandler for RemoveMod {
Ok(())
}
}
#[async_trait::async_trait(?Send)]
impl GetCommunity for RemoveMod {
async fn get_community(
&self,
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<ApubCommunity, LemmyError> {
get_community_from_moderators_url(&self.target, context, request_counter).await
}
}

View file

@ -1,21 +1,9 @@
use crate::{
activities::{generate_activity_id, verify_activity, verify_person_in_community},
context::lemmy_context,
fetcher::object_id::ObjectId,
objects::{community::ApubCommunity, person::ApubPerson},
send_lemmy_activity,
PostOrComment,
};
use activitystreams::{
activity::kind::FlagType,
base::AnyBase,
primitives::OneOrMany,
unparsed::Unparsed,
};
use activitystreams::activity::kind::FlagType;
use lemmy_api_common::{blocking, comment::CommentReportResponse, post::PostReportResponse};
use lemmy_apub_lib::{
data::Data,
traits::{ActivityFields, ActivityHandler, ActorType},
traits::{ActivityHandler, ActorType},
};
use lemmy_db_schema::{
source::{
@ -27,24 +15,19 @@ use lemmy_db_schema::{
use lemmy_db_views::{comment_report_view::CommentReportView, post_report_view::PostReportView};
use lemmy_utils::LemmyError;
use lemmy_websocket::{messages::SendModRoomMessage, LemmyContext, UserOperation};
use serde::{Deserialize, Serialize};
use url::Url;
#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)]
#[serde(rename_all = "camelCase")]
pub struct Report {
actor: ObjectId<ApubPerson>,
to: [ObjectId<ApubCommunity>; 1],
object: ObjectId<PostOrComment>,
summary: String,
#[serde(rename = "type")]
kind: FlagType,
id: Url,
#[serde(rename = "@context")]
context: OneOrMany<AnyBase>,
#[serde(flatten)]
unparsed: Unparsed,
}
use crate::{
activities::{
generate_activity_id,
send_lemmy_activity,
verify_activity,
verify_person_in_community,
},
fetcher::object_id::ObjectId,
objects::{community::ApubCommunity, person::ApubPerson},
protocol::activities::community::report::Report,
PostOrComment,
};
impl Report {
pub async fn send(
@ -67,7 +50,6 @@ impl Report {
summary: reason,
kind,
id: id.clone(),
context: lemmy_context(),
unparsed: Default::default(),
};
send_lemmy_activity(
@ -91,7 +73,8 @@ impl ActivityHandler for Report {
request_counter: &mut i32,
) -> Result<(), LemmyError> {
verify_activity(self, &context.settings())?;
verify_person_in_community(&self.actor, &self.to[0], context, request_counter).await?;
let community = self.to[0].dereference(context, request_counter).await?;
verify_person_in_community(&self.actor, &community, context, request_counter).await?;
Ok(())
}

View file

@ -1,30 +1,25 @@
use crate::{
activities::{
community::{
announce::AnnouncableActivities,
block_user::BlockUserFromCommunity,
send_to_community,
},
community::{announce::GetCommunity, send_to_community},
generate_activity_id,
verify_activity,
verify_is_public,
verify_mod_action,
verify_person_in_community,
},
context::lemmy_context,
activity_lists::AnnouncableActivities,
fetcher::object_id::ObjectId,
objects::{community::ApubCommunity, person::ApubPerson},
protocol::activities::community::{
block_user::BlockUserFromCommunity,
undo_block_user::UndoBlockUserFromCommunity,
},
};
use activitystreams::{
activity::kind::UndoType,
base::AnyBase,
primitives::OneOrMany,
unparsed::Unparsed,
};
use activitystreams::{activity::kind::UndoType, public};
use lemmy_api_common::blocking;
use lemmy_apub_lib::{
data::Data,
traits::{ActivityFields, ActivityHandler, ActorType},
values::PublicUrl,
traits::{ActivityHandler, ActorType},
};
use lemmy_db_schema::{
source::community::{CommunityPersonBan, CommunityPersonBanForm},
@ -32,24 +27,6 @@ use lemmy_db_schema::{
};
use lemmy_utils::LemmyError;
use lemmy_websocket::LemmyContext;
use serde::{Deserialize, Serialize};
use url::Url;
#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)]
#[serde(rename_all = "camelCase")]
pub struct UndoBlockUserFromCommunity {
actor: ObjectId<ApubPerson>,
to: [PublicUrl; 1],
object: BlockUserFromCommunity,
cc: [ObjectId<ApubCommunity>; 1],
#[serde(rename = "type")]
kind: UndoType,
id: Url,
#[serde(rename = "@context")]
context: OneOrMany<AnyBase>,
#[serde(flatten)]
unparsed: Unparsed,
}
impl UndoBlockUserFromCommunity {
pub async fn send(
@ -66,12 +43,11 @@ impl UndoBlockUserFromCommunity {
)?;
let undo = UndoBlockUserFromCommunity {
actor: ObjectId::new(actor.actor_id()),
to: [PublicUrl::Public],
to: vec![public()],
object: block,
cc: [ObjectId::new(community.actor_id())],
cc: vec![community.actor_id()],
kind: UndoType::Undo,
id: id.clone(),
context: lemmy_context(),
unparsed: Default::default(),
};
@ -89,9 +65,11 @@ impl ActivityHandler for UndoBlockUserFromCommunity {
context: &Data<LemmyContext>,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
verify_is_public(&self.to)?;
verify_activity(self, &context.settings())?;
verify_person_in_community(&self.actor, &self.cc[0], context, request_counter).await?;
verify_mod_action(&self.actor, &self.cc[0], context, request_counter).await?;
let community = self.get_community(context, request_counter).await?;
verify_person_in_community(&self.actor, &community, context, request_counter).await?;
verify_mod_action(&self.actor, &community, context, request_counter).await?;
self.object.verify(context, request_counter).await?;
Ok(())
}
@ -101,7 +79,7 @@ impl ActivityHandler for UndoBlockUserFromCommunity {
context: &Data<LemmyContext>,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
let community = self.cc[0].dereference(context, request_counter).await?;
let community = self.get_community(context, request_counter).await?;
let blocked_user = self
.object
.object
@ -121,3 +99,14 @@ impl ActivityHandler for UndoBlockUserFromCommunity {
Ok(())
}
}
#[async_trait::async_trait(?Send)]
impl GetCommunity for UndoBlockUserFromCommunity {
async fn get_community(
&self,
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<ApubCommunity, LemmyError> {
self.object.get_community(context, request_counter).await
}
}

View file

@ -1,29 +1,22 @@
use crate::{
activities::{
community::{announce::AnnouncableActivities, send_to_community},
community::{announce::GetCommunity, send_to_community},
generate_activity_id,
verify_activity,
verify_is_public,
verify_mod_action,
verify_person_in_community,
},
context::lemmy_context,
activity_lists::AnnouncableActivities,
fetcher::object_id::ObjectId,
objects::{
community::{ApubCommunity, Group},
person::ApubPerson,
},
};
use activitystreams::{
activity::kind::UpdateType,
base::AnyBase,
primitives::OneOrMany,
unparsed::Unparsed,
objects::{community::ApubCommunity, person::ApubPerson},
protocol::{activities::community::update::UpdateCommunity, objects::group::Group},
};
use activitystreams::{activity::kind::UpdateType, public};
use lemmy_api_common::blocking;
use lemmy_apub_lib::{
data::Data,
traits::{ActivityFields, ActivityHandler, ActorType, ApubObject},
values::PublicUrl,
traits::{ActivityHandler, ActorType, ApubObject},
};
use lemmy_db_schema::{
source::community::{Community, CommunityForm},
@ -31,27 +24,6 @@ use lemmy_db_schema::{
};
use lemmy_utils::LemmyError;
use lemmy_websocket::{send::send_community_ws_message, LemmyContext, UserOperationCrud};
use serde::{Deserialize, Serialize};
use url::Url;
/// This activity is received from a remote community mod, and updates the description or other
/// fields of a local community.
#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)]
#[serde(rename_all = "camelCase")]
pub struct UpdateCommunity {
actor: ObjectId<ApubPerson>,
to: [PublicUrl; 1],
// TODO: would be nice to use a separate struct here, which only contains the fields updated here
object: Group,
cc: [ObjectId<ApubCommunity>; 1],
#[serde(rename = "type")]
kind: UpdateType,
id: Url,
#[serde(rename = "@context")]
context: OneOrMany<AnyBase>,
#[serde(flatten)]
unparsed: Unparsed,
}
impl UpdateCommunity {
pub async fn send(
@ -65,12 +37,11 @@ impl UpdateCommunity {
)?;
let update = UpdateCommunity {
actor: ObjectId::new(actor.actor_id()),
to: [PublicUrl::Public],
to: vec![public()],
object: community.to_apub(context).await?,
cc: [ObjectId::new(community.actor_id())],
cc: vec![community.actor_id()],
kind: UpdateType::Update,
id: id.clone(),
context: lemmy_context(),
unparsed: Default::default(),
};
@ -87,9 +58,11 @@ impl ActivityHandler for UpdateCommunity {
context: &Data<LemmyContext>,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
verify_is_public(&self.to)?;
verify_activity(self, &context.settings())?;
verify_person_in_community(&self.actor, &self.cc[0], context, request_counter).await?;
verify_mod_action(&self.actor, &self.cc[0], context, request_counter).await?;
let community = self.get_community(context, request_counter).await?;
verify_person_in_community(&self.actor, &community, context, request_counter).await?;
verify_mod_action(&self.actor, &community, context, request_counter).await?;
Ok(())
}
@ -98,8 +71,7 @@ impl ActivityHandler for UpdateCommunity {
context: &Data<LemmyContext>,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
let cc = self.cc[0].clone();
let community = cc.dereference(context, request_counter).await?;
let community = self.get_community(context, request_counter).await?;
let updated_community = Group::from_apub_to_form(
&self.object,
@ -133,3 +105,15 @@ impl ActivityHandler for UpdateCommunity {
Ok(())
}
}
#[async_trait::async_trait(?Send)]
impl GetCommunity for UpdateCommunity {
async fn get_community(
&self,
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<ApubCommunity, LemmyError> {
let cid = ObjectId::new(self.object.id.clone());
cid.dereference(context, request_counter).await
}
}

View file

@ -1,31 +1,11 @@
use crate::{
activities::{
community::{announce::AnnouncableActivities, send_to_community},
deletion::{
receive_delete_action,
verify_delete_activity,
DeletableObjects,
WebsocketMessages,
},
generate_activity_id,
verify_activity,
},
context::lemmy_context,
fetcher::object_id::ObjectId,
objects::{community::ApubCommunity, person::ApubPerson},
};
use activitystreams::{
activity::kind::DeleteType,
base::AnyBase,
primitives::OneOrMany,
unparsed::Unparsed,
};
use activitystreams::{activity::kind::DeleteType, public};
use anyhow::anyhow;
use url::Url;
use lemmy_api_common::blocking;
use lemmy_apub_lib::{
data::Data,
traits::{ActivityFields, ActivityHandler, ActorType},
values::PublicUrl,
traits::{ActivityHandler, ActorType},
};
use lemmy_db_schema::{
source::{
@ -49,37 +29,25 @@ use lemmy_websocket::{
LemmyContext,
UserOperationCrud,
};
use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
use url::Url;
/// This is very confusing, because there are four distinct cases to handle:
/// - user deletes their post
/// - user deletes their comment
/// - remote community mod deletes local community
/// - remote community deletes itself (triggered by a mod)
///
/// TODO: we should probably change how community deletions work to simplify this. Probably by
/// wrapping it in an announce just like other activities, instead of having the community send it.
#[skip_serializing_none]
#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)]
#[serde(rename_all = "camelCase")]
pub struct Delete {
actor: ObjectId<ApubPerson>,
to: [PublicUrl; 1],
pub(in crate::activities::deletion) object: Url,
pub(in crate::activities::deletion) cc: [ObjectId<ApubCommunity>; 1],
#[serde(rename = "type")]
kind: DeleteType,
/// If summary is present, this is a mod action (Remove in Lemmy terms). Otherwise, its a user
/// deleting their own content.
pub(in crate::activities::deletion) summary: Option<String>,
id: Url,
#[serde(rename = "@context")]
context: OneOrMany<AnyBase>,
#[serde(flatten)]
unparsed: Unparsed,
}
use crate::{
activities::{
community::{announce::GetCommunity, send_to_community},
deletion::{
receive_delete_action,
verify_delete_activity,
DeletableObjects,
WebsocketMessages,
},
generate_activity_id,
verify_activity,
verify_is_public,
},
activity_lists::AnnouncableActivities,
fetcher::object_id::ObjectId,
objects::{community::ApubCommunity, person::ApubPerson},
protocol::activities::deletion::delete::Delete,
};
#[async_trait::async_trait(?Send)]
impl ActivityHandler for Delete {
@ -89,11 +57,13 @@ impl ActivityHandler for Delete {
context: &Data<LemmyContext>,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
verify_is_public(&self.to)?;
verify_activity(self, &context.settings())?;
let community = self.get_community(context, request_counter).await?;
verify_delete_activity(
&self.object,
self,
&self.cc[0],
&community,
self.summary.is_some(),
context,
request_counter,
@ -144,16 +114,15 @@ impl Delete {
) -> Result<Delete, LemmyError> {
Ok(Delete {
actor: ObjectId::new(actor.actor_id()),
to: [PublicUrl::Public],
to: vec![public()],
object: object_id,
cc: [ObjectId::new(community.actor_id())],
cc: vec![community.actor_id()],
kind: DeleteType::Delete,
summary,
id: generate_activity_id(
DeleteType::Delete,
&context.settings().get_protocol_and_hostname(),
)?,
context: lemmy_context(),
unparsed: Default::default(),
})
}
@ -243,3 +212,26 @@ pub(in crate::activities) async fn receive_remove_action(
}
Ok(())
}
#[async_trait::async_trait(?Send)]
impl GetCommunity for Delete {
async fn get_community(
&self,
context: &LemmyContext,
_request_counter: &mut i32,
) -> Result<ApubCommunity, LemmyError> {
let community_id = match DeletableObjects::read_from_db(&self.object, context).await? {
DeletableObjects::Community(c) => c.id,
DeletableObjects::Comment(c) => {
let post = blocking(context.pool(), move |conn| Post::read(conn, c.post_id)).await??;
post.community_id
}
DeletableObjects::Post(p) => p.community_id,
};
let community = blocking(context.pool(), move |conn| {
Community::read(conn, community_id)
})
.await??;
Ok(community.into())
}
}

View file

@ -1,12 +1,5 @@
use crate::{
activities::{
deletion::{delete::Delete, undo_delete::UndoDelete},
verify_mod_action,
verify_person_in_community,
},
fetcher::object_id::ObjectId,
objects::{comment::ApubComment, community::ApubCommunity, person::ApubPerson, post::ApubPost},
};
use url::Url;
use lemmy_api_common::blocking;
use lemmy_apub_lib::{
traits::{ActivityFields, ActorType, ApubObject},
@ -19,7 +12,13 @@ use lemmy_websocket::{
LemmyContext,
UserOperationCrud,
};
use url::Url;
use crate::{
activities::{verify_mod_action, verify_person_in_community},
fetcher::object_id::ObjectId,
objects::{comment::ApubComment, community::ApubCommunity, person::ApubPerson, post::ApubPost},
protocol::activities::deletion::{delete::Delete, undo_delete::UndoDelete},
};
pub mod delete;
pub mod undo_delete;
@ -82,7 +81,7 @@ impl DeletableObjects {
pub(in crate::activities) async fn verify_delete_activity(
object: &Url,
activity: &dyn ActivityFields,
community_id: &ObjectId<ApubCommunity>,
community: &ApubCommunity,
is_mod_action: bool,
context: &LemmyContext,
request_counter: &mut i32,
@ -90,26 +89,20 @@ pub(in crate::activities) async fn verify_delete_activity(
let object = DeletableObjects::read_from_db(object, context).await?;
let actor = ObjectId::new(activity.actor().clone());
match object {
DeletableObjects::Community(c) => {
if c.local {
DeletableObjects::Community(community) => {
if community.local {
// can only do this check for local community, in remote case it would try to fetch the
// deleted community (which fails)
verify_person_in_community(&actor, community_id, context, request_counter).await?;
verify_person_in_community(&actor, &community, context, request_counter).await?;
}
// community deletion is always a mod (or admin) action
verify_mod_action(
&actor,
&ObjectId::new(c.actor_id()),
context,
request_counter,
)
.await?;
verify_mod_action(&actor, &community, context, request_counter).await?;
}
DeletableObjects::Post(p) => {
verify_delete_activity_post_or_comment(
activity,
&p.ap_id.clone().into(),
community_id,
community,
is_mod_action,
context,
request_counter,
@ -120,7 +113,7 @@ pub(in crate::activities) async fn verify_delete_activity(
verify_delete_activity_post_or_comment(
activity,
&c.ap_id.clone().into(),
community_id,
community,
is_mod_action,
context,
request_counter,
@ -134,15 +127,15 @@ pub(in crate::activities) async fn verify_delete_activity(
async fn verify_delete_activity_post_or_comment(
activity: &dyn ActivityFields,
object_id: &Url,
community_id: &ObjectId<ApubCommunity>,
community: &ApubCommunity,
is_mod_action: bool,
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
let actor = ObjectId::new(activity.actor().clone());
verify_person_in_community(&actor, community_id, context, request_counter).await?;
verify_person_in_community(&actor, community, context, request_counter).await?;
if is_mod_action {
verify_mod_action(&actor, community_id, context, request_counter).await?;
verify_mod_action(&actor, community, context, request_counter).await?;
} else {
// domain of post ap_id and post.creator ap_id are identical, so we just check the former
verify_domains_match(activity.actor(), object_id)?;

View file

@ -1,32 +1,11 @@
use crate::{
activities::{
community::{announce::AnnouncableActivities, send_to_community},
deletion::{
delete::Delete,
receive_delete_action,
verify_delete_activity,
DeletableObjects,
WebsocketMessages,
},
generate_activity_id,
verify_activity,
},
context::lemmy_context,
fetcher::object_id::ObjectId,
objects::{community::ApubCommunity, person::ApubPerson},
};
use activitystreams::{
activity::kind::UndoType,
base::AnyBase,
primitives::OneOrMany,
unparsed::Unparsed,
};
use activitystreams::{activity::kind::UndoType, public};
use anyhow::anyhow;
use url::Url;
use lemmy_api_common::blocking;
use lemmy_apub_lib::{
data::Data,
traits::{ActivityFields, ActivityHandler, ActorType},
values::PublicUrl,
traits::{ActivityHandler, ActorType},
};
use lemmy_db_schema::source::{comment::Comment, community::Community, post::Post};
use lemmy_utils::LemmyError;
@ -35,24 +14,25 @@ use lemmy_websocket::{
LemmyContext,
UserOperationCrud,
};
use serde::{Deserialize, Serialize};
use url::Url;
#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)]
#[serde(rename_all = "camelCase")]
pub struct UndoDelete {
actor: ObjectId<ApubPerson>,
to: [PublicUrl; 1],
object: Delete,
cc: [ObjectId<ApubCommunity>; 1],
#[serde(rename = "type")]
kind: UndoType,
id: Url,
#[serde(rename = "@context")]
context: OneOrMany<AnyBase>,
#[serde(flatten)]
unparsed: Unparsed,
}
use crate::{
activities::{
community::{announce::GetCommunity, send_to_community},
deletion::{
receive_delete_action,
verify_delete_activity,
DeletableObjects,
WebsocketMessages,
},
generate_activity_id,
verify_activity,
verify_is_public,
},
activity_lists::AnnouncableActivities,
fetcher::object_id::ObjectId,
objects::{community::ApubCommunity, person::ApubPerson},
protocol::activities::deletion::{delete::Delete, undo_delete::UndoDelete},
};
#[async_trait::async_trait(?Send)]
impl ActivityHandler for UndoDelete {
@ -62,12 +42,14 @@ impl ActivityHandler for UndoDelete {
context: &Data<LemmyContext>,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
verify_is_public(&self.to)?;
verify_activity(self, &context.settings())?;
self.object.verify(context, request_counter).await?;
let community = self.get_community(context, request_counter).await?;
verify_delete_activity(
&self.object.object,
self,
&self.cc[0],
&community,
self.object.summary.is_some(),
context,
request_counter,
@ -117,12 +99,11 @@ impl UndoDelete {
)?;
let undo = UndoDelete {
actor: ObjectId::new(actor.actor_id()),
to: [PublicUrl::Public],
to: vec![public()],
object,
cc: [ObjectId::new(community.actor_id())],
cc: vec![community.actor_id()],
kind: UndoType::Undo,
id: id.clone(),
context: lemmy_context(),
unparsed: Default::default(),
};
@ -164,3 +145,14 @@ impl UndoDelete {
Ok(())
}
}
#[async_trait::async_trait(?Send)]
impl GetCommunity for UndoDelete {
async fn get_community(
&self,
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<ApubCommunity, LemmyError> {
self.object.get_community(context, request_counter).await
}
}

View file

@ -1,21 +1,9 @@
use crate::{
activities::{
following::follow::FollowCommunity,
generate_activity_id,
verify_activity,
verify_community,
},
context::lemmy_context,
activities::{generate_activity_id, send_lemmy_activity, verify_activity},
fetcher::object_id::ObjectId,
objects::{community::ApubCommunity, person::ApubPerson},
send_lemmy_activity,
};
use activitystreams::{
activity::kind::AcceptType,
base::AnyBase,
primitives::OneOrMany,
unparsed::Unparsed,
protocol::activities::following::{accept::AcceptFollowCommunity, follow::FollowCommunity},
};
use activitystreams::activity::kind::AcceptType;
use lemmy_api_common::blocking;
use lemmy_apub_lib::{
data::Data,
@ -25,23 +13,6 @@ use lemmy_apub_lib::{
use lemmy_db_schema::{source::community::CommunityFollower, traits::Followable};
use lemmy_utils::LemmyError;
use lemmy_websocket::LemmyContext;
use serde::{Deserialize, Serialize};
use url::Url;
#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)]
#[serde(rename_all = "camelCase")]
pub struct AcceptFollowCommunity {
actor: ObjectId<ApubCommunity>,
to: ObjectId<ApubPerson>,
object: FollowCommunity,
#[serde(rename = "type")]
kind: AcceptType,
id: Url,
#[serde(rename = "@context")]
context: OneOrMany<AnyBase>,
#[serde(flatten)]
unparsed: Unparsed,
}
impl AcceptFollowCommunity {
pub async fn send(
@ -57,14 +28,13 @@ impl AcceptFollowCommunity {
.await?;
let accept = AcceptFollowCommunity {
actor: ObjectId::new(community.actor_id()),
to: ObjectId::new(person.actor_id()),
to: [ObjectId::new(person.actor_id())],
object: follow,
kind: AcceptType::Accept,
id: generate_activity_id(
AcceptType::Accept,
&context.settings().get_protocol_and_hostname(),
)?,
context: lemmy_context(),
unparsed: Default::default(),
};
let inbox = vec![person.inbox_url()];
@ -82,9 +52,8 @@ impl ActivityHandler for AcceptFollowCommunity {
request_counter: &mut i32,
) -> Result<(), LemmyError> {
verify_activity(self, &context.settings())?;
verify_urls_match(self.to.inner(), self.object.actor())?;
verify_urls_match(self.actor(), self.object.to.inner())?;
verify_community(&self.actor, context, request_counter).await?;
verify_urls_match(self.to[0].inner(), self.object.actor())?;
verify_urls_match(self.actor(), self.object.to[0].inner())?;
self.object.verify(context, request_counter).await?;
Ok(())
}
@ -95,7 +64,7 @@ impl ActivityHandler for AcceptFollowCommunity {
request_counter: &mut i32,
) -> Result<(), LemmyError> {
let actor = self.actor.dereference(context, request_counter).await?;
let to = self.to.dereference(context, request_counter).await?;
let to = self.to[0].dereference(context, request_counter).await?;
// This will throw an error if no follow was requested
blocking(context.pool(), move |conn| {
CommunityFollower::follow_accepted(conn, actor.id, to.id)

View file

@ -1,25 +1,20 @@
use crate::{
activities::{
following::accept::AcceptFollowCommunity,
generate_activity_id,
send_lemmy_activity,
verify_activity,
verify_person,
verify_person_in_community,
},
context::lemmy_context,
fetcher::object_id::ObjectId,
objects::{community::ApubCommunity, person::ApubPerson},
send_lemmy_activity,
};
use activitystreams::{
activity::kind::FollowType,
base::AnyBase,
primitives::OneOrMany,
unparsed::Unparsed,
protocol::activities::following::{accept::AcceptFollowCommunity, follow::FollowCommunity},
};
use activitystreams::activity::kind::FollowType;
use lemmy_api_common::blocking;
use lemmy_apub_lib::{
data::Data,
traits::{ActivityFields, ActivityHandler, ActorType},
traits::{ActivityHandler, ActorType},
verify::verify_urls_match,
};
use lemmy_db_schema::{
@ -28,24 +23,6 @@ use lemmy_db_schema::{
};
use lemmy_utils::LemmyError;
use lemmy_websocket::LemmyContext;
use serde::{Deserialize, Serialize};
use url::Url;
#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)]
#[serde(rename_all = "camelCase")]
pub struct FollowCommunity {
pub(in crate::activities::following) actor: ObjectId<ApubPerson>,
// TODO: is there any reason to put the same community id twice, in to and object?
pub(in crate::activities::following) to: ObjectId<ApubCommunity>,
pub(in crate::activities::following) object: ObjectId<ApubCommunity>,
#[serde(rename = "type")]
kind: FollowType,
id: Url,
#[serde(rename = "@context")]
context: OneOrMany<AnyBase>,
#[serde(flatten)]
unparsed: Unparsed,
}
impl FollowCommunity {
pub(in crate::activities::following) fn new(
@ -55,14 +32,13 @@ impl FollowCommunity {
) -> Result<FollowCommunity, LemmyError> {
Ok(FollowCommunity {
actor: ObjectId::new(actor.actor_id()),
to: ObjectId::new(community.actor_id()),
to: [ObjectId::new(community.actor_id())],
object: ObjectId::new(community.actor_id()),
kind: FollowType::Follow,
id: generate_activity_id(
FollowType::Follow,
&context.settings().get_protocol_and_hostname(),
)?,
context: lemmy_context(),
unparsed: Default::default(),
})
}
@ -96,8 +72,10 @@ impl ActivityHandler for FollowCommunity {
request_counter: &mut i32,
) -> Result<(), LemmyError> {
verify_activity(self, &context.settings())?;
verify_urls_match(self.to.inner(), self.object.inner())?;
verify_urls_match(self.to[0].inner(), self.object.inner())?;
verify_person(&self.actor, context, request_counter).await?;
let community = self.to[0].dereference(context, request_counter).await?;
verify_person_in_community(&self.actor, &community, context, request_counter).await?;
Ok(())
}
@ -107,7 +85,7 @@ impl ActivityHandler for FollowCommunity {
request_counter: &mut i32,
) -> Result<(), LemmyError> {
let actor = self.actor.dereference(context, request_counter).await?;
let community = self.object.dereference(context, request_counter).await?;
let community = self.to[0].dereference(context, request_counter).await?;
let community_follower_form = CommunityFollowerForm {
community_id: community.id,
person_id: actor.id,

View file

@ -1,3 +1,3 @@
pub mod accept;
pub mod follow;
pub mod undo;
pub mod undo_follow;

View file

@ -1,21 +1,10 @@
use crate::{
activities::{
following::follow::FollowCommunity,
generate_activity_id,
verify_activity,
verify_person,
},
context::lemmy_context,
activities::{generate_activity_id, send_lemmy_activity, verify_activity, verify_person},
fetcher::object_id::ObjectId,
objects::{community::ApubCommunity, person::ApubPerson},
send_lemmy_activity,
};
use activitystreams::{
activity::kind::UndoType,
base::AnyBase,
primitives::OneOrMany,
unparsed::Unparsed,
protocol::activities::following::{follow::FollowCommunity, undo_follow::UndoFollowCommunity},
};
use activitystreams::activity::kind::UndoType;
use lemmy_api_common::blocking;
use lemmy_apub_lib::{
data::Data,
@ -28,23 +17,6 @@ use lemmy_db_schema::{
};
use lemmy_utils::LemmyError;
use lemmy_websocket::LemmyContext;
use serde::{Deserialize, Serialize};
use url::Url;
#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)]
#[serde(rename_all = "camelCase")]
pub struct UndoFollowCommunity {
actor: ObjectId<ApubPerson>,
to: ObjectId<ApubCommunity>,
object: FollowCommunity,
#[serde(rename = "type")]
kind: UndoType,
id: Url,
#[serde(rename = "@context")]
context: OneOrMany<AnyBase>,
#[serde(flatten)]
unparsed: Unparsed,
}
impl UndoFollowCommunity {
pub async fn send(
@ -55,14 +27,13 @@ impl UndoFollowCommunity {
let object = FollowCommunity::new(actor, community, context)?;
let undo = UndoFollowCommunity {
actor: ObjectId::new(actor.actor_id()),
to: ObjectId::new(community.actor_id()),
to: [ObjectId::new(community.actor_id())],
object,
kind: UndoType::Undo,
id: generate_activity_id(
UndoType::Undo,
&context.settings().get_protocol_and_hostname(),
)?,
context: lemmy_context(),
unparsed: Default::default(),
};
let inbox = vec![community.shared_inbox_or_inbox_url()];
@ -79,7 +50,7 @@ impl ActivityHandler for UndoFollowCommunity {
request_counter: &mut i32,
) -> Result<(), LemmyError> {
verify_activity(self, &context.settings())?;
verify_urls_match(self.to.inner(), self.object.object.inner())?;
verify_urls_match(self.to[0].inner(), self.object.object.inner())?;
verify_urls_match(self.actor(), self.object.actor())?;
verify_person(&self.actor, context, request_counter).await?;
self.object.verify(context, request_counter).await?;
@ -92,7 +63,7 @@ impl ActivityHandler for UndoFollowCommunity {
request_counter: &mut i32,
) -> Result<(), LemmyError> {
let actor = self.actor.dereference(context, request_counter).await?;
let community = self.to.dereference(context, request_counter).await?;
let community = self.to[0].dereference(context, request_counter).await?;
let community_follower_form = CommunityFollowerForm {
community_id: community.id,

View file

@ -1,21 +1,28 @@
use crate::{
check_community_or_site_ban,
check_is_apub_id_valid,
context::WithContext,
fetcher::object_id::ObjectId,
generate_moderators_url,
insert_activity,
objects::{community::ApubCommunity, person::ApubPerson},
};
use activitystreams::public;
use anyhow::anyhow;
use lemmy_api_common::blocking;
use lemmy_apub_lib::{traits::ActivityFields, verify::verify_domains_match};
use lemmy_apub_lib::{
activity_queue::send_activity,
traits::{ActivityFields, ActorType},
verify::verify_domains_match,
};
use lemmy_db_schema::source::community::Community;
use lemmy_db_views_actor::community_view::CommunityView;
use lemmy_db_views_actor::{
community_person_ban_view::CommunityPersonBanView,
community_view::CommunityView,
};
use lemmy_utils::{settings::structs::Settings, LemmyError};
use lemmy_websocket::LemmyContext;
use serde::{Deserialize, Serialize};
use std::ops::Deref;
use strum_macros::ToString;
use log::info;
use serde::Serialize;
use url::{ParseError, Url};
use uuid::Uuid;
@ -25,15 +32,8 @@ pub mod deletion;
pub mod following;
pub mod post;
pub mod private_message;
pub mod report;
pub mod voting;
#[derive(Clone, Debug, ToString, Deserialize, Serialize)]
pub enum CreateOrUpdateType {
Create,
Update,
}
/// Checks that the specified Url actually identifies a Person (by fetching it), and that the person
/// doesn't have a site ban.
async fn verify_person(
@ -48,44 +48,26 @@ async fn verify_person(
Ok(())
}
pub(crate) async fn extract_community(
cc: &[Url],
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<ApubCommunity, LemmyError> {
let mut cc_iter = cc.iter();
loop {
if let Some(cid) = cc_iter.next() {
let cid = ObjectId::new(cid.clone());
if let Ok(c) = cid.dereference(context, request_counter).await {
break Ok(c);
}
} else {
return Err(anyhow!("No community found in cc").into());
}
}
}
/// Fetches the person and community to verify their type, then checks if person is banned from site
/// or community.
pub(crate) async fn verify_person_in_community(
person_id: &ObjectId<ApubPerson>,
community_id: &ObjectId<ApubCommunity>,
community: &ApubCommunity,
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
let community = community_id.dereference(context, request_counter).await?;
let person = person_id.dereference(context, request_counter).await?;
check_community_or_site_ban(person.deref(), community.id, context.pool()).await
}
if person.banned {
return Err(anyhow!("Person is banned from site").into());
}
let person_id = person.id;
let community_id = community.id;
let is_banned =
move |conn: &'_ _| CommunityPersonBanView::get(conn, person_id, community_id).is_ok();
if blocking(context.pool(), is_banned).await? {
return Err(anyhow!("Person is banned from community").into());
}
/// Simply check that the url actually refers to a valid group.
async fn verify_community(
community_id: &ObjectId<ApubCommunity>,
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
community_id.dereference(context, request_counter).await?;
Ok(())
}
@ -100,12 +82,10 @@ fn verify_activity(activity: &dyn ActivityFields, settings: &Settings) -> Result
/// is not federated, we cant verify their actions remotely.
pub(crate) async fn verify_mod_action(
actor_id: &ObjectId<ApubPerson>,
community_id: &ObjectId<ApubCommunity>,
community: &ApubCommunity,
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
let community = community_id.dereference_local(context).await?;
if community.local {
let actor = actor_id.dereference(context, request_counter).await?;
@ -128,9 +108,9 @@ pub(crate) async fn verify_mod_action(
/// /c/community/moderators. Any different values are unsupported.
fn verify_add_remove_moderator_target(
target: &Url,
community: &ObjectId<ApubCommunity>,
community: &ApubCommunity,
) -> Result<(), LemmyError> {
if target != &generate_moderators_url(&community.clone().into())?.into_inner() {
if target != &generate_moderators_url(&community.actor_id)?.into_inner() {
return Err(anyhow!("Unkown target url").into());
}
Ok(())
@ -165,3 +145,47 @@ where
);
Url::parse(&id)
}
async fn send_lemmy_activity<T: Serialize>(
context: &LemmyContext,
activity: &T,
activity_id: &Url,
actor: &dyn ActorType,
inboxes: Vec<Url>,
sensitive: bool,
) -> Result<(), LemmyError> {
if !context.settings().federation.enabled || inboxes.is_empty() {
return Ok(());
}
let activity = WithContext::new(activity);
info!("Sending activity {}", activity_id.to_string());
// Don't send anything to ourselves
// TODO: this should be a debug assert
let hostname = context.settings().get_hostname_without_port()?;
let inboxes: Vec<&Url> = inboxes
.iter()
.filter(|i| i.domain().expect("valid inbox url") != hostname)
.collect();
let serialised_activity = serde_json::to_string(&activity)?;
insert_activity(
activity_id,
serialised_activity.clone(),
true,
sensitive,
context.pool(),
)
.await?;
send_activity(
serialised_activity,
actor,
inboxes,
context.client(),
context.activity_queue(),
)
.await
}

View file

@ -1,51 +1,31 @@
use crate::{
activities::{
check_community_deleted_or_removed,
community::{announce::AnnouncableActivities, send_to_community},
generate_activity_id,
verify_activity,
verify_mod_action,
verify_person_in_community,
CreateOrUpdateType,
},
context::lemmy_context,
fetcher::object_id::ObjectId,
objects::{
community::ApubCommunity,
person::ApubPerson,
post::{ApubPost, Page},
},
};
use activitystreams::{base::AnyBase, primitives::OneOrMany, unparsed::Unparsed};
use activitystreams::public;
use anyhow::anyhow;
use lemmy_api_common::blocking;
use lemmy_apub_lib::{
data::Data,
traits::{ActivityFields, ActivityHandler, ActorType, ApubObject},
values::PublicUrl,
verify::{verify_domains_match, verify_urls_match},
};
use lemmy_db_schema::{source::community::Community, traits::Crud};
use lemmy_utils::LemmyError;
use lemmy_websocket::{send::send_post_ws_message, LemmyContext, UserOperationCrud};
use serde::{Deserialize, Serialize};
use url::Url;
#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)]
#[serde(rename_all = "camelCase")]
pub struct CreateOrUpdatePost {
actor: ObjectId<ApubPerson>,
to: [PublicUrl; 1],
object: Page,
cc: [ObjectId<ApubCommunity>; 1],
#[serde(rename = "type")]
kind: CreateOrUpdateType,
id: Url,
#[serde(rename = "@context")]
context: OneOrMany<AnyBase>,
#[serde(flatten)]
unparsed: Unparsed,
}
use crate::{
activities::{
check_community_deleted_or_removed,
community::{announce::GetCommunity, send_to_community},
generate_activity_id,
verify_activity,
verify_is_public,
verify_mod_action,
verify_person_in_community,
},
activity_lists::AnnouncableActivities,
fetcher::object_id::ObjectId,
objects::{community::ApubCommunity, person::ApubPerson, post::ApubPost},
protocol::activities::{create_or_update::post::CreateOrUpdatePost, CreateOrUpdateType},
};
impl CreateOrUpdatePost {
pub(crate) async fn new(
@ -61,12 +41,11 @@ impl CreateOrUpdatePost {
)?;
Ok(CreateOrUpdatePost {
actor: ObjectId::new(actor.actor_id()),
to: [PublicUrl::Public],
to: vec![public()],
object: post.to_apub(context).await?,
cc: [ObjectId::new(community.actor_id())],
cc: vec![community.actor_id()],
kind,
id: id.clone(),
context: lemmy_context(),
unparsed: Default::default(),
})
}
@ -97,9 +76,10 @@ impl ActivityHandler for CreateOrUpdatePost {
context: &Data<LemmyContext>,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
verify_is_public(&self.to)?;
verify_activity(self, &context.settings())?;
let community = self.cc[0].dereference(context, request_counter).await?;
verify_person_in_community(&self.actor, &self.cc[0], context, request_counter).await?;
let community = self.get_community(context, request_counter).await?;
verify_person_in_community(&self.actor, &community, context, request_counter).await?;
check_community_deleted_or_removed(&community)?;
match self.kind {
@ -119,7 +99,7 @@ impl ActivityHandler for CreateOrUpdatePost {
CreateOrUpdateType::Update => {
let is_mod_action = self.object.is_mod_action(context).await?;
if is_mod_action {
verify_mod_action(&self.actor, &self.cc[0], context, request_counter).await?;
verify_mod_action(&self.actor, &community, context, request_counter).await?;
} else {
verify_domains_match(self.actor.inner(), self.object.id_unchecked())?;
verify_urls_match(self.actor(), self.object.attributed_to.inner())?;
@ -147,3 +127,17 @@ impl ActivityHandler for CreateOrUpdatePost {
Ok(())
}
}
#[async_trait::async_trait(?Send)]
impl GetCommunity for CreateOrUpdatePost {
async fn get_community(
&self,
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<ApubCommunity, LemmyError> {
self
.object
.extract_community(context, request_counter)
.await
}
}

View file

@ -1,40 +1,21 @@
use crate::{
activities::{generate_activity_id, verify_activity, verify_person, CreateOrUpdateType},
context::lemmy_context,
activities::{generate_activity_id, send_lemmy_activity, verify_activity, verify_person},
fetcher::object_id::ObjectId,
objects::{
person::ApubPerson,
private_message::{ApubPrivateMessage, Note},
objects::{person::ApubPerson, private_message::ApubPrivateMessage},
protocol::activities::{
private_message::create_or_update::CreateOrUpdatePrivateMessage,
CreateOrUpdateType,
},
send_lemmy_activity,
};
use activitystreams::{base::AnyBase, primitives::OneOrMany, unparsed::Unparsed};
use lemmy_api_common::blocking;
use lemmy_apub_lib::{
data::Data,
traits::{ActivityFields, ActivityHandler, ActorType, ApubObject},
traits::{ActivityHandler, ActorType, ApubObject},
verify::verify_domains_match,
};
use lemmy_db_schema::{source::person::Person, traits::Crud};
use lemmy_utils::LemmyError;
use lemmy_websocket::{send::send_pm_ws_message, LemmyContext, UserOperationCrud};
use serde::{Deserialize, Serialize};
use url::Url;
#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)]
#[serde(rename_all = "camelCase")]
pub struct CreateOrUpdatePrivateMessage {
#[serde(rename = "@context")]
pub context: OneOrMany<AnyBase>,
id: Url,
actor: ObjectId<ApubPerson>,
to: ObjectId<ApubPerson>,
object: Note,
#[serde(rename = "type")]
kind: CreateOrUpdateType,
#[serde(flatten)]
pub unparsed: Unparsed,
}
impl CreateOrUpdatePrivateMessage {
pub async fn send(
@ -54,10 +35,9 @@ impl CreateOrUpdatePrivateMessage {
&context.settings().get_protocol_and_hostname(),
)?;
let create_or_update = CreateOrUpdatePrivateMessage {
context: lemmy_context(),
id: id.clone(),
actor: ObjectId::new(actor.actor_id()),
to: ObjectId::new(recipient.actor_id()),
to: [ObjectId::new(recipient.actor_id())],
object: private_message.to_apub(context).await?,
kind,
unparsed: Default::default(),

View file

@ -1,20 +1,14 @@
use crate::{
activities::{generate_activity_id, verify_activity, verify_person},
context::lemmy_context,
activities::{generate_activity_id, send_lemmy_activity, verify_activity, verify_person},
fetcher::object_id::ObjectId,
objects::{person::ApubPerson, private_message::ApubPrivateMessage},
send_lemmy_activity,
};
use activitystreams::{
activity::kind::DeleteType,
base::AnyBase,
primitives::OneOrMany,
unparsed::Unparsed,
protocol::activities::private_message::delete::DeletePrivateMessage,
};
use activitystreams::activity::kind::DeleteType;
use lemmy_api_common::blocking;
use lemmy_apub_lib::{
data::Data,
traits::{ActivityFields, ActivityHandler, ActorType},
traits::{ActivityHandler, ActorType},
verify::verify_domains_match,
};
use lemmy_db_schema::{
@ -23,23 +17,6 @@ use lemmy_db_schema::{
};
use lemmy_utils::LemmyError;
use lemmy_websocket::{send::send_pm_ws_message, LemmyContext, UserOperationCrud};
use serde::{Deserialize, Serialize};
use url::Url;
#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)]
#[serde(rename_all = "camelCase")]
pub struct DeletePrivateMessage {
actor: ObjectId<ApubPerson>,
to: ObjectId<ApubPerson>,
pub(in crate::activities::private_message) object: ObjectId<ApubPrivateMessage>,
#[serde(rename = "type")]
kind: DeleteType,
id: Url,
#[serde(rename = "@context")]
context: OneOrMany<AnyBase>,
#[serde(flatten)]
unparsed: Unparsed,
}
impl DeletePrivateMessage {
pub(in crate::activities::private_message) fn new(
@ -49,14 +26,13 @@ impl DeletePrivateMessage {
) -> Result<DeletePrivateMessage, LemmyError> {
Ok(DeletePrivateMessage {
actor: ObjectId::new(actor.actor_id()),
to: ObjectId::new(actor.actor_id()),
to: [ObjectId::new(actor.actor_id())],
object: ObjectId::new(pm.ap_id.clone()),
kind: DeleteType::Delete,
id: generate_activity_id(
DeleteType::Delete,
&context.settings().get_protocol_and_hostname(),
)?,
context: lemmy_context(),
unparsed: Default::default(),
})
}

View file

@ -1,21 +1,13 @@
use crate::{
activities::{
generate_activity_id,
private_message::delete::DeletePrivateMessage,
verify_activity,
verify_person,
},
context::lemmy_context,
activities::{generate_activity_id, send_lemmy_activity, verify_activity, verify_person},
fetcher::object_id::ObjectId,
objects::{person::ApubPerson, private_message::ApubPrivateMessage},
send_lemmy_activity,
};
use activitystreams::{
activity::kind::UndoType,
base::AnyBase,
primitives::OneOrMany,
unparsed::Unparsed,
protocol::activities::private_message::{
delete::DeletePrivateMessage,
undo_delete::UndoDeletePrivateMessage,
},
};
use activitystreams::activity::kind::UndoType;
use lemmy_api_common::blocking;
use lemmy_apub_lib::{
data::Data,
@ -28,23 +20,6 @@ use lemmy_db_schema::{
};
use lemmy_utils::LemmyError;
use lemmy_websocket::{send::send_pm_ws_message, LemmyContext, UserOperationCrud};
use serde::{Deserialize, Serialize};
use url::Url;
#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)]
#[serde(rename_all = "camelCase")]
pub struct UndoDeletePrivateMessage {
actor: ObjectId<ApubPerson>,
to: ObjectId<ApubPerson>,
object: DeletePrivateMessage,
#[serde(rename = "type")]
kind: UndoType,
id: Url,
#[serde(rename = "@context")]
context: OneOrMany<AnyBase>,
#[serde(flatten)]
unparsed: Unparsed,
}
impl UndoDeletePrivateMessage {
pub async fn send(
@ -65,11 +40,10 @@ impl UndoDeletePrivateMessage {
)?;
let undo = UndoDeletePrivateMessage {
actor: ObjectId::new(actor.actor_id()),
to: ObjectId::new(recipient.actor_id()),
to: [ObjectId::new(recipient.actor_id())],
object,
kind: UndoType::Undo,
id: id.clone(),
context: lemmy_context(),
unparsed: Default::default(),
};
let inbox = vec![recipient.shared_inbox_or_inbox_url()];

View file

@ -1,7 +1,3 @@
use crate::{
activities::voting::vote::VoteType,
objects::{comment::ApubComment, person::ApubPerson, post::ApubPost},
};
use lemmy_api_common::blocking;
use lemmy_db_schema::{
source::{
@ -17,6 +13,11 @@ use lemmy_websocket::{
UserOperation,
};
use crate::{
objects::{comment::ApubComment, person::ApubPerson, post::ApubPost},
protocol::activities::voting::vote::VoteType,
};
pub mod undo_vote;
pub mod vote;

View file

@ -1,55 +1,35 @@
use crate::{
activities::{
community::{announce::AnnouncableActivities, send_to_community},
generate_activity_id,
verify_activity,
verify_person_in_community,
voting::{
undo_vote_comment,
undo_vote_post,
vote::{Vote, VoteType},
},
},
context::lemmy_context,
fetcher::object_id::ObjectId,
objects::{community::ApubCommunity, person::ApubPerson},
PostOrComment,
};
use activitystreams::{
activity::kind::UndoType,
base::AnyBase,
primitives::OneOrMany,
unparsed::Unparsed,
};
use std::ops::Deref;
use activitystreams::{activity::kind::UndoType, public};
use lemmy_api_common::blocking;
use lemmy_apub_lib::{
data::Data,
traits::{ActivityFields, ActivityHandler, ActorType},
values::PublicUrl,
verify::verify_urls_match,
};
use lemmy_db_schema::{newtypes::CommunityId, source::community::Community, traits::Crud};
use lemmy_utils::LemmyError;
use lemmy_websocket::LemmyContext;
use serde::{Deserialize, Serialize};
use std::ops::Deref;
use url::Url;
#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)]
#[serde(rename_all = "camelCase")]
pub struct UndoVote {
actor: ObjectId<ApubPerson>,
to: [PublicUrl; 1],
object: Vote,
cc: [ObjectId<ApubCommunity>; 1],
#[serde(rename = "type")]
kind: UndoType,
id: Url,
#[serde(rename = "@context")]
context: OneOrMany<AnyBase>,
#[serde(flatten)]
unparsed: Unparsed,
}
use crate::{
activities::{
community::{announce::GetCommunity, send_to_community},
generate_activity_id,
verify_activity,
verify_is_public,
verify_person_in_community,
voting::{undo_vote_comment, undo_vote_post},
},
activity_lists::AnnouncableActivities,
fetcher::object_id::ObjectId,
objects::{community::ApubCommunity, person::ApubPerson},
protocol::activities::voting::{
undo_vote::UndoVote,
vote::{Vote, VoteType},
},
PostOrComment,
};
impl UndoVote {
pub async fn send(
@ -72,12 +52,11 @@ impl UndoVote {
)?;
let undo_vote = UndoVote {
actor: ObjectId::new(actor.actor_id()),
to: [PublicUrl::Public],
to: vec![public()],
object,
cc: [ObjectId::new(community.actor_id())],
cc: vec![community.actor_id()],
kind: UndoType::Undo,
id: id.clone(),
context: lemmy_context(),
unparsed: Default::default(),
};
let activity = AnnouncableActivities::UndoVote(undo_vote);
@ -93,8 +72,10 @@ impl ActivityHandler for UndoVote {
context: &Data<LemmyContext>,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
verify_is_public(&self.to)?;
verify_activity(self, &context.settings())?;
verify_person_in_community(&self.actor, &self.cc[0], context, request_counter).await?;
let community = self.get_community(context, request_counter).await?;
verify_person_in_community(&self.actor, &community, context, request_counter).await?;
verify_urls_match(self.actor(), self.object.actor())?;
self.object.verify(context, request_counter).await?;
Ok(())
@ -117,3 +98,14 @@ impl ActivityHandler for UndoVote {
}
}
}
#[async_trait::async_trait(?Send)]
impl GetCommunity for UndoVote {
async fn get_community(
&self,
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<ApubCommunity, LemmyError> {
self.object.get_community(context, request_counter).await
}
}

View file

@ -1,74 +1,35 @@
use crate::{
activities::{
community::{announce::AnnouncableActivities, send_to_community},
generate_activity_id,
verify_activity,
verify_person_in_community,
voting::{vote_comment, vote_post},
},
context::lemmy_context,
fetcher::object_id::ObjectId,
objects::{community::ApubCommunity, person::ApubPerson},
PostOrComment,
};
use activitystreams::{base::AnyBase, primitives::OneOrMany, unparsed::Unparsed};
use anyhow::anyhow;
use std::ops::Deref;
use activitystreams::public;
use lemmy_api_common::blocking;
use lemmy_apub_lib::{
data::Data,
traits::{ActivityFields, ActivityHandler, ActorType},
values::PublicUrl,
traits::{ActivityHandler, ActorType},
};
use lemmy_db_schema::{
newtypes::CommunityId,
source::{community::Community, post::Post},
traits::Crud,
};
use lemmy_db_schema::{newtypes::CommunityId, source::community::Community, traits::Crud};
use lemmy_utils::LemmyError;
use lemmy_websocket::LemmyContext;
use serde::{Deserialize, Serialize};
use std::{convert::TryFrom, ops::Deref};
use strum_macros::ToString;
use url::Url;
#[derive(Clone, Debug, ToString, Deserialize, Serialize)]
pub enum VoteType {
Like,
Dislike,
}
impl TryFrom<i16> for VoteType {
type Error = LemmyError;
fn try_from(value: i16) -> Result<Self, Self::Error> {
match value {
1 => Ok(VoteType::Like),
-1 => Ok(VoteType::Dislike),
_ => Err(anyhow!("invalid vote value").into()),
}
}
}
impl From<&VoteType> for i16 {
fn from(value: &VoteType) -> i16 {
match value {
VoteType::Like => 1,
VoteType::Dislike => -1,
}
}
}
#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)]
#[serde(rename_all = "camelCase")]
pub struct Vote {
actor: ObjectId<ApubPerson>,
to: [PublicUrl; 1],
pub(in crate::activities::voting) object: ObjectId<PostOrComment>,
cc: [ObjectId<ApubCommunity>; 1],
#[serde(rename = "type")]
pub(in crate::activities::voting) kind: VoteType,
id: Url,
#[serde(rename = "@context")]
context: OneOrMany<AnyBase>,
#[serde(flatten)]
unparsed: Unparsed,
}
use crate::{
activities::{
community::{announce::GetCommunity, send_to_community},
generate_activity_id,
verify_activity,
verify_is_public,
verify_person_in_community,
voting::{vote_comment, vote_post},
},
activity_lists::AnnouncableActivities,
fetcher::object_id::ObjectId,
objects::{community::ApubCommunity, person::ApubPerson},
protocol::activities::voting::vote::{Vote, VoteType},
PostOrComment,
};
impl Vote {
pub(in crate::activities::voting) fn new(
@ -80,12 +41,11 @@ impl Vote {
) -> Result<Vote, LemmyError> {
Ok(Vote {
actor: ObjectId::new(actor.actor_id()),
to: [PublicUrl::Public],
to: vec![public()],
object: ObjectId::new(object.ap_id()),
cc: [ObjectId::new(community.actor_id())],
cc: vec![community.actor_id()],
kind: kind.clone(),
id: generate_activity_id(kind, &context.settings().get_protocol_and_hostname())?,
context: lemmy_context(),
unparsed: Default::default(),
})
}
@ -118,8 +78,10 @@ impl ActivityHandler for Vote {
context: &Data<LemmyContext>,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
verify_is_public(&self.to)?;
verify_activity(self, &context.settings())?;
verify_person_in_community(&self.actor, &self.cc[0], context, request_counter).await?;
let community = self.get_community(context, request_counter).await?;
verify_person_in_community(&self.actor, &community, context, request_counter).await?;
Ok(())
}
@ -136,3 +98,24 @@ impl ActivityHandler for Vote {
}
}
}
#[async_trait::async_trait(?Send)]
impl GetCommunity for Vote {
async fn get_community(
&self,
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<ApubCommunity, LemmyError> {
let object = self.object.dereference(context, request_counter).await?;
let cid = match object {
PostOrComment::Post(p) => p.community_id,
PostOrComment::Comment(c) => {
blocking(context.pool(), move |conn| Post::read(conn, c.post_id))
.await??
.community_id
}
};
let community = blocking(context.pool(), move |conn| Community::read(conn, cid)).await??;
Ok(community.into())
}
}

View file

@ -0,0 +1,107 @@
use crate::{
activities::community::announce::GetCommunity,
objects::community::ApubCommunity,
protocol::activities::{
community::{
add_mod::AddMod,
announce::AnnounceActivity,
block_user::BlockUserFromCommunity,
remove_mod::RemoveMod,
report::Report,
undo_block_user::UndoBlockUserFromCommunity,
update::UpdateCommunity,
},
create_or_update::{comment::CreateOrUpdateComment, post::CreateOrUpdatePost},
deletion::{delete::Delete, undo_delete::UndoDelete},
following::{
accept::AcceptFollowCommunity,
follow::FollowCommunity,
undo_follow::UndoFollowCommunity,
},
private_message::{
create_or_update::CreateOrUpdatePrivateMessage,
delete::DeletePrivateMessage,
undo_delete::UndoDeletePrivateMessage,
},
voting::{undo_vote::UndoVote, vote::Vote},
},
};
use lemmy_apub_lib::traits::{ActivityFields, ActivityHandler};
use lemmy_utils::LemmyError;
use lemmy_websocket::LemmyContext;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Deserialize, Serialize, ActivityHandler, ActivityFields)]
#[serde(untagged)]
#[activity_handler(LemmyContext)]
pub enum SharedInboxActivities {
GroupInboxActivities(GroupInboxActivities),
// Note, pm activities need to be at the end, otherwise comments will end up here. We can probably
// avoid this problem by replacing createpm.object with our own struct, instead of NoteExt.
PersonInboxActivities(PersonInboxActivities),
}
#[derive(Clone, Debug, Deserialize, Serialize, ActivityHandler, ActivityFields)]
#[serde(untagged)]
#[activity_handler(LemmyContext)]
pub enum GroupInboxActivities {
FollowCommunity(FollowCommunity),
UndoFollowCommunity(UndoFollowCommunity),
AnnouncableActivities(AnnouncableActivities),
Report(Report),
}
#[derive(Clone, Debug, Deserialize, Serialize, ActivityHandler, ActivityFields)]
#[serde(untagged)]
#[activity_handler(LemmyContext)]
pub enum PersonInboxActivities {
AcceptFollowCommunity(AcceptFollowCommunity),
/// Some activities can also be sent from user to user, eg a comment with mentions
AnnouncableActivities(AnnouncableActivities),
CreateOrUpdatePrivateMessage(CreateOrUpdatePrivateMessage),
DeletePrivateMessage(DeletePrivateMessage),
UndoDeletePrivateMessage(UndoDeletePrivateMessage),
AnnounceActivity(Box<AnnounceActivity>),
}
#[derive(Clone, Debug, Deserialize, Serialize, ActivityHandler, ActivityFields)]
#[serde(untagged)]
#[activity_handler(LemmyContext)]
pub enum AnnouncableActivities {
CreateOrUpdateComment(CreateOrUpdateComment),
CreateOrUpdatePost(Box<CreateOrUpdatePost>),
Vote(Vote),
UndoVote(UndoVote),
Delete(Delete),
UndoDelete(UndoDelete),
UpdateCommunity(Box<UpdateCommunity>),
BlockUserFromCommunity(BlockUserFromCommunity),
UndoBlockUserFromCommunity(UndoBlockUserFromCommunity),
AddMod(AddMod),
RemoveMod(RemoveMod),
}
#[async_trait::async_trait(?Send)]
impl GetCommunity for AnnouncableActivities {
async fn get_community(
&self,
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<ApubCommunity, LemmyError> {
use AnnouncableActivities::*;
let community = match self {
CreateOrUpdateComment(a) => a.get_community(context, request_counter).await?,
CreateOrUpdatePost(a) => a.get_community(context, request_counter).await?,
Vote(a) => a.get_community(context, request_counter).await?,
UndoVote(a) => a.get_community(context, request_counter).await?,
Delete(a) => a.get_community(context, request_counter).await?,
UndoDelete(a) => a.get_community(context, request_counter).await?,
UpdateCommunity(a) => a.get_community(context, request_counter).await?,
BlockUserFromCommunity(a) => a.get_community(context, request_counter).await?,
UndoBlockUserFromCommunity(a) => a.get_community(context, request_counter).await?,
AddMod(a) => a.get_community(context, request_counter).await?,
RemoveMod(a) => a.get_community(context, request_counter).await?,
};
Ok(community)
}
}

View file

@ -1,17 +1,11 @@
use crate::{
collections::CommunityContext,
context::lemmy_context,
fetcher::object_id::ObjectId,
generate_moderators_url,
objects::person::ApubPerson,
protocol::collections::group_moderators::GroupModerators,
};
use activitystreams::{
base::AnyBase,
chrono::NaiveDateTime,
collection::kind::OrderedCollectionType,
primitives::OneOrMany,
url::Url,
};
use activitystreams::{chrono::NaiveDateTime, collection::kind::OrderedCollectionType};
use lemmy_api_common::blocking;
use lemmy_apub_lib::{traits::ApubObject, verify::verify_domains_match};
use lemmy_db_schema::{
@ -20,19 +14,7 @@ use lemmy_db_schema::{
};
use lemmy_db_views_actor::community_moderator_view::CommunityModeratorView;
use lemmy_utils::LemmyError;
use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
#[skip_serializing_none]
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct GroupModerators {
#[serde(rename = "@context")]
context: OneOrMany<AnyBase>,
r#type: OrderedCollectionType,
id: Url,
ordered_items: Vec<ObjectId<ApubPerson>>,
}
use url::Url;
#[derive(Clone, Debug)]
pub(crate) struct ApubCommunityModerators(pub(crate) Vec<CommunityModeratorView>);
@ -75,7 +57,6 @@ impl ApubObject for ApubCommunityModerators {
.map(|m| ObjectId::<ApubPerson>::new(m.moderator.actor_id.clone().into_inner()))
.collect();
Ok(GroupModerators {
context: lemmy_context(),
r#type: OrderedCollectionType::OrderedCollection,
id: generate_moderators_url(&data.0.actor_id)?.into(),
ordered_items,
@ -139,3 +120,74 @@ impl ApubObject for ApubCommunityModerators {
Ok(ApubCommunityModerators { 0: vec![] })
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::objects::{
community::tests::parse_lemmy_community,
person::tests::parse_lemmy_person,
tests::{file_to_json_object, init_context},
};
use lemmy_db_schema::{
source::{
community::Community,
person::{Person, PersonForm},
},
traits::Crud,
};
use serial_test::serial;
#[actix_rt::test]
#[serial]
async fn test_parse_lemmy_community_moderators() {
let context = init_context();
let community = parse_lemmy_community(&context).await;
let community_id = community.id;
let old_mod = PersonForm {
name: "holly".into(),
..PersonForm::default()
};
let old_mod = Person::create(&context.pool().get().unwrap(), &old_mod).unwrap();
let community_moderator_form = CommunityModeratorForm {
community_id: community.id,
person_id: old_mod.id,
};
CommunityModerator::join(&context.pool().get().unwrap(), &community_moderator_form).unwrap();
let new_mod = parse_lemmy_person(&context).await;
let json: GroupModerators =
file_to_json_object("assets/lemmy/collections/group_moderators.json");
let url = Url::parse("https://enterprise.lemmy.ml/c/tenforward").unwrap();
let mut request_counter = 0;
let community_context = CommunityContext {
0: community,
1: context,
};
ApubCommunityModerators::from_apub(&json, &community_context, &url, &mut request_counter)
.await
.unwrap();
assert_eq!(request_counter, 0);
let current_moderators = blocking(community_context.1.pool(), move |conn| {
CommunityModeratorView::for_community(conn, community_id)
})
.await
.unwrap()
.unwrap();
assert_eq!(current_moderators.len(), 1);
assert_eq!(current_moderators[0].moderator.id, new_mod.id);
Person::delete(&*community_context.1.pool().get().unwrap(), old_mod.id).unwrap();
Person::delete(&*community_context.1.pool().get().unwrap(), new_mod.id).unwrap();
Community::delete(
&*community_context.1.pool().get().unwrap(),
community_context.0.id,
)
.unwrap();
}
}

View file

@ -1,17 +1,7 @@
use crate::{
activities::{post::create_or_update::CreateOrUpdatePost, CreateOrUpdateType},
collections::CommunityContext,
context::lemmy_context,
generate_outbox_url,
objects::{person::ApubPerson, post::ApubPost},
};
use activitystreams::{
base::AnyBase,
chrono::NaiveDateTime,
collection::kind::OrderedCollectionType,
primitives::OneOrMany,
url::Url,
};
use activitystreams::collection::kind::OrderedCollectionType;
use chrono::NaiveDateTime;
use url::Url;
use lemmy_api_common::blocking;
use lemmy_apub_lib::{
data::Data,
@ -23,19 +13,16 @@ use lemmy_db_schema::{
traits::Crud,
};
use lemmy_utils::LemmyError;
use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
#[skip_serializing_none]
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct GroupOutbox {
#[serde(rename = "@context")]
context: OneOrMany<AnyBase>,
r#type: OrderedCollectionType,
id: Url,
ordered_items: Vec<CreateOrUpdatePost>,
}
use crate::{
collections::CommunityContext,
generate_outbox_url,
objects::{person::ApubPerson, post::ApubPost},
protocol::{
activities::{create_or_update::post::CreateOrUpdatePost, CreateOrUpdateType},
collections::group_outbox::GroupOutbox,
},
};
#[derive(Clone, Debug)]
pub(crate) struct ApubCommunityOutbox(Vec<ApubPost>);
@ -88,9 +75,9 @@ impl ApubObject for ApubCommunityOutbox {
}
Ok(GroupOutbox {
context: lemmy_context(),
r#type: OrderedCollectionType::OrderedCollection,
id: generate_outbox_url(&data.0.actor_id)?.into(),
total_items: ordered_items.len() as i32,
ordered_items,
})
}

View file

@ -1,5 +1,7 @@
use crate::objects::community::ApubCommunity;
use lemmy_websocket::LemmyContext;
use crate::objects::community::ApubCommunity;
pub(crate) mod community_moderators;
pub(crate) mod community_outbox;

View file

@ -1,28 +1,51 @@
use activitystreams::{base::AnyBase, context, primitives::OneOrMany};
use serde::{Deserialize, Serialize};
use serde_json::json;
use url::Url;
pub(crate) fn lemmy_context() -> OneOrMany<AnyBase> {
let context_ext = AnyBase::from_arbitrary_json(json!(
{
"sc": "http://schema.org#",
"sensitive": "as:sensitive",
"stickied": "as:stickied",
"pt": "https://join-lemmy.org#",
"comments_enabled": {
"type": "sc:Boolean",
"id": "pt:commentsEnabled"
},
"moderators": "as:moderators",
"matrixUserId": {
"type": "sc:Text",
"id": "as:alsoKnownAs"
},
}))
.expect("parse context");
OneOrMany::from(vec![
AnyBase::from(context()),
context_ext,
AnyBase::from(Url::parse("https://w3id.org/security/v1").expect("parse context")),
])
lazy_static! {
static ref CONTEXT: OneOrMany<AnyBase> = {
let context_ext = AnyBase::from_arbitrary_json(json!(
{
"sc": "http://schema.org#",
"sensitive": "as:sensitive",
"stickied": "as:stickied",
"pt": "https://join-lemmy.org#",
"comments_enabled": {
"type": "sc:Boolean",
"id": "pt:commentsEnabled"
},
"moderators": "as:moderators",
"matrixUserId": {
"type": "sc:Text",
"id": "as:alsoKnownAs"
},
}))
.expect("parse context");
OneOrMany::from(vec![
AnyBase::from(context()),
context_ext,
AnyBase::from(Url::parse("https://w3id.org/security/v1").expect("parse context")),
])
};
}
#[derive(Serialize, Deserialize)]
pub(crate) struct WithContext<T> {
#[serde(rename = "@context")]
context: OneOrMany<AnyBase>,
#[serde(flatten)]
inner: T,
}
impl<T> WithContext<T> {
pub(crate) fn new(inner: T) -> WithContext<T> {
WithContext {
context: CONTEXT.clone(),
inner,
}
}
pub(crate) fn inner(self) -> T {
self.inner
}
}

View file

@ -22,7 +22,6 @@ use url::Url;
/// fetch through the search). This should be configurable.
static REQUEST_LIMIT: i32 = 25;
// TODO: after moving this file to library, remove lazy_static dependency from apub crate
lazy_static! {
static ref CLIENT: Client = Client::builder()
.user_agent(build_user_agent(&Settings::get()))

View file

@ -1,14 +1,16 @@
use crate::objects::{
comment::{ApubComment, Note},
post::{ApubPost, Page},
};
use activitystreams::chrono::NaiveDateTime;
use chrono::NaiveDateTime;
use serde::Deserialize;
use url::Url;
use lemmy_apub_lib::traits::ApubObject;
use lemmy_db_schema::source::{comment::CommentForm, post::PostForm};
use lemmy_utils::LemmyError;
use lemmy_websocket::LemmyContext;
use serde::Deserialize;
use url::Url;
use crate::{
objects::{comment::ApubComment, post::ApubPost},
protocol::objects::{note::Note, page::Page},
};
#[derive(Clone, Debug)]
pub enum PostOrComment {

View file

@ -1,15 +1,9 @@
use crate::{
fetcher::object_id::ObjectId,
objects::{
comment::{ApubComment, Note},
community::{ApubCommunity, Group},
person::{ApubPerson, Person},
post::{ApubPost, Page},
},
};
use activitystreams::chrono::NaiveDateTime;
use anyhow::anyhow;
use chrono::NaiveDateTime;
use itertools::Itertools;
use serde::Deserialize;
use url::Url;
use lemmy_api_common::blocking;
use lemmy_apub_lib::{
traits::ApubObject,
@ -21,8 +15,12 @@ use lemmy_db_schema::{
};
use lemmy_utils::LemmyError;
use lemmy_websocket::LemmyContext;
use serde::Deserialize;
use url::Url;
use crate::{
fetcher::object_id::ObjectId,
objects::{comment::ApubComment, community::ApubCommunity, person::ApubPerson, post::ApubPost},
protocol::objects::{group::Group, note::Note, page::Page, person::Person},
};
/// Attempt to parse the query as URL, and fetch an ActivityPub object from it.
///

View file

@ -1,16 +1,12 @@
use crate::{
activities::{
community::announce::{AnnouncableActivities, AnnounceActivity},
extract_community,
following::{follow::FollowCommunity, undo::UndoFollowCommunity},
report::Report,
},
activities::{community::announce::GetCommunity, verify_person_in_community},
activity_lists::GroupInboxActivities,
collections::{
community_moderators::ApubCommunityModerators,
community_outbox::ApubCommunityOutbox,
CommunityContext,
},
context::lemmy_context,
context::WithContext,
fetcher::object_id::ObjectId,
generate_outbox_url,
http::{
@ -20,20 +16,19 @@ use crate::{
receive_activity,
},
objects::community::ApubCommunity,
};
use activitystreams::{
base::BaseExt,
collection::{CollectionExt, UnorderedCollection},
protocol::{
activities::community::announce::AnnounceActivity,
collections::group_followers::GroupFollowers,
},
};
use actix_web::{body::Body, web, web::Payload, HttpRequest, HttpResponse};
use lemmy_api_common::blocking;
use lemmy_apub_lib::traits::{ActivityFields, ActivityHandler, ApubObject};
use lemmy_apub_lib::traits::{ActivityFields, ApubObject};
use lemmy_db_schema::source::community::Community;
use lemmy_db_views_actor::community_follower_view::CommunityFollowerView;
use lemmy_utils::LemmyError;
use lemmy_websocket::LemmyContext;
use log::trace;
use serde::{Deserialize, Serialize};
use log::info;
use serde::Deserialize;
#[derive(Deserialize)]
pub(crate) struct CommunityQuery {
@ -60,16 +55,6 @@ pub(crate) async fn get_apub_community_http(
}
}
#[derive(Clone, Debug, Deserialize, Serialize, ActivityHandler, ActivityFields)]
#[serde(untagged)]
#[activity_handler(LemmyContext)]
pub enum GroupInboxActivities {
FollowCommunity(FollowCommunity),
UndoFollowCommunity(UndoFollowCommunity),
AnnouncableActivities(AnnouncableActivities),
Report(Report),
}
/// Handler for all incoming receive to community inboxes.
pub async fn community_inbox(
request: HttpRequest,
@ -78,10 +63,10 @@ pub async fn community_inbox(
context: web::Data<LemmyContext>,
) -> Result<HttpResponse, LemmyError> {
let unparsed = payload_to_string(payload).await?;
trace!("Received community inbox activity {}", unparsed);
let activity = serde_json::from_str::<GroupInboxActivities>(&unparsed)?;
info!("Received community inbox activity {}", unparsed);
let activity = serde_json::from_str::<WithContext<GroupInboxActivities>>(&unparsed)?;
receive_group_inbox(activity.clone(), request, &context).await?;
receive_group_inbox(activity.inner(), request, &context).await?;
Ok(HttpResponse::Ok().finish())
}
@ -92,12 +77,16 @@ pub(in crate::http) async fn receive_group_inbox(
context: &LemmyContext,
) -> Result<HttpResponse, LemmyError> {
let res = receive_activity(request, activity.clone(), context).await;
if let GroupInboxActivities::AnnouncableActivities(announcable) = activity.clone() {
let community = extract_community(&announcable.cc(), context, &mut 0).await?;
if let GroupInboxActivities::AnnouncableActivities(announcable) = activity {
let community = announcable.get_community(context, &mut 0).await?;
let actor_id = ObjectId::new(announcable.actor().clone());
verify_person_in_community(&actor_id, &community, context, &mut 0).await?;
if community.local {
AnnounceActivity::send(announcable, &community, vec![], context).await?;
}
}
res
}
@ -110,19 +99,8 @@ pub(crate) async fn get_apub_community_followers(
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_many_contexts(lemmy_context())
.set_id(community.followers_url.into())
.set_total_items(community_followers.len() as u64);
Ok(create_apub_response(&collection))
let followers = GroupFollowers::new(community, &context).await?;
Ok(create_apub_response(&followers))
}
/// Returns the community outbox, which is populated by a maximum of 20 posts (but no other

View file

@ -1,10 +1,9 @@
use crate::{
activity_lists::SharedInboxActivities,
check_is_apub_id_valid,
context::WithContext,
fetcher::get_or_fetch_and_upsert_actor,
http::{
community::{receive_group_inbox, GroupInboxActivities},
person::{receive_person_inbox, PersonInboxActivities},
},
http::{community::receive_group_inbox, person::receive_person_inbox},
insert_activity,
};
use actix_web::{
@ -27,7 +26,7 @@ use lemmy_apub_lib::{
use lemmy_db_schema::{source::activity::Activity, DbPool};
use lemmy_utils::{location_info, LemmyError};
use lemmy_websocket::LemmyContext;
use log::{info, trace};
use log::info;
use serde::{Deserialize, Serialize};
use std::{fmt::Debug, io::Read};
use url::Url;
@ -38,25 +37,15 @@ mod person;
mod post;
pub mod routes;
#[derive(Clone, Debug, Deserialize, Serialize, ActivityHandler, ActivityFields)]
#[serde(untagged)]
#[activity_handler(LemmyContext)]
pub enum SharedInboxActivities {
GroupInboxActivities(GroupInboxActivities),
// Note, pm activities need to be at the end, otherwise comments will end up here. We can probably
// avoid this problem by replacing createpm.object with our own struct, instead of NoteExt.
PersonInboxActivities(PersonInboxActivities),
}
pub async fn shared_inbox(
request: HttpRequest,
payload: Payload,
context: web::Data<LemmyContext>,
) -> Result<HttpResponse, LemmyError> {
let unparsed = payload_to_string(payload).await?;
trace!("Received shared inbox activity {}", unparsed);
let activity = serde_json::from_str::<SharedInboxActivities>(&unparsed)?;
match activity {
info!("Received shared inbox activity {}", unparsed);
let activity = serde_json::from_str::<WithContext<SharedInboxActivities>>(&unparsed)?;
match activity.inner() {
SharedInboxActivities::GroupInboxActivities(g) => {
receive_group_inbox(g, request, &context).await
}
@ -134,7 +123,7 @@ where
{
HttpResponse::Ok()
.content_type(APUB_JSON_CONTENT_TYPE)
.json(data)
.json(WithContext::new(data))
}
fn create_apub_tombstone_response<T>(data: &T) -> HttpResponse<Body>
@ -144,7 +133,7 @@ where
HttpResponse::Gone()
.content_type(APUB_JSON_CONTENT_TYPE)
.status(StatusCode::GONE)
.json(data)
.json(WithContext::new(data))
}
#[derive(Deserialize)]

View file

@ -1,15 +1,6 @@
use crate::{
activities::{
community::announce::{AnnouncableActivities, AnnounceActivity},
following::accept::AcceptFollowCommunity,
private_message::{
create_or_update::CreateOrUpdatePrivateMessage,
delete::DeletePrivateMessage,
undo_delete::UndoDeletePrivateMessage,
},
},
context::lemmy_context,
generate_outbox_url,
activity_lists::PersonInboxActivities,
context::WithContext,
http::{
create_apub_response,
create_apub_tombstone_response,
@ -17,20 +8,16 @@ use crate::{
receive_activity,
},
objects::person::ApubPerson,
};
use activitystreams::{
base::BaseExt,
collection::{CollectionExt, OrderedCollection},
protocol::collections::person_outbox::PersonOutbox,
};
use actix_web::{body::Body, web, web::Payload, HttpRequest, HttpResponse};
use lemmy_api_common::blocking;
use lemmy_apub_lib::traits::{ActivityFields, ActivityHandler, ApubObject};
use lemmy_apub_lib::traits::ApubObject;
use lemmy_db_schema::source::person::Person;
use lemmy_utils::LemmyError;
use lemmy_websocket::LemmyContext;
use log::trace;
use serde::{Deserialize, Serialize};
use url::Url;
use log::info;
use serde::Deserialize;
#[derive(Deserialize)]
pub struct PersonQuery {
@ -59,19 +46,6 @@ pub(crate) async fn get_apub_person_http(
}
}
#[derive(Clone, Debug, Deserialize, Serialize, ActivityHandler, ActivityFields)]
#[serde(untagged)]
#[activity_handler(LemmyContext)]
pub enum PersonInboxActivities {
AcceptFollowCommunity(AcceptFollowCommunity),
/// Some activities can also be sent from user to user, eg a comment with mentions
AnnouncableActivities(AnnouncableActivities),
CreateOrUpdatePrivateMessage(CreateOrUpdatePrivateMessage),
DeletePrivateMessage(DeletePrivateMessage),
UndoDeletePrivateMessage(UndoDeletePrivateMessage),
AnnounceActivity(Box<AnnounceActivity>),
}
pub async fn person_inbox(
request: HttpRequest,
payload: Payload,
@ -79,9 +53,9 @@ pub async fn person_inbox(
context: web::Data<LemmyContext>,
) -> Result<HttpResponse, LemmyError> {
let unparsed = payload_to_string(payload).await?;
trace!("Received person inbox activity {}", unparsed);
let activity = serde_json::from_str::<PersonInboxActivities>(&unparsed)?;
receive_person_inbox(activity, request, &context).await
info!("Received person inbox activity {}", unparsed);
let activity = serde_json::from_str::<WithContext<PersonInboxActivities>>(&unparsed)?;
receive_person_inbox(activity.inner(), request, &context).await
}
pub(in crate::http) async fn receive_person_inbox(
@ -100,12 +74,6 @@ pub(crate) async fn get_apub_person_outbox(
Person::find_by_name(conn, &info.user_name)
})
.await??;
// TODO: populate the person outbox
let mut collection = OrderedCollection::new();
collection
.set_many_items(Vec::<Url>::new())
.set_many_contexts(lemmy_context())
.set_id(generate_outbox_url(&person.actor_id)?.into())
.set_total_items(0_u64);
Ok(create_apub_response(&collection))
let outbox = PersonOutbox::new(person).await?;
Ok(create_apub_response(&outbox))
}

View file

@ -1,10 +1,12 @@
pub mod activities;
pub(crate) mod activity_lists;
pub(crate) mod collections;
mod context;
pub mod fetcher;
pub mod http;
pub mod migrations;
pub mod objects;
pub mod protocol;
#[macro_use]
extern crate lazy_static;
@ -12,16 +14,10 @@ extern crate lazy_static;
use crate::fetcher::post_or_comment::PostOrComment;
use anyhow::{anyhow, Context};
use lemmy_api_common::blocking;
use lemmy_apub_lib::{activity_queue::send_activity, traits::ActorType};
use lemmy_db_schema::{
newtypes::{CommunityId, DbUrl},
source::{activity::Activity, person::Person},
DbPool,
};
use lemmy_db_views_actor::community_person_ban_view::CommunityPersonBanView;
use lemmy_apub_lib::webfinger::{webfinger_resolve_actor, WebfingerType};
use lemmy_db_schema::{newtypes::DbUrl, source::activity::Activity, DbPool};
use lemmy_utils::{location_info, settings::structs::Settings, LemmyError};
use lemmy_websocket::LemmyContext;
use log::info;
use serde::Serialize;
use std::net::IpAddr;
use url::{ParseError, Url};
@ -34,6 +30,8 @@ use url::{ParseError, Url};
/// - URL being in the allowlist (if it is active)
/// - URL not being in the blocklist (if it is active)
///
/// `use_strict_allowlist` should be true only when parsing a remote community, or when parsing a
/// post/comment in a local community.
pub(crate) fn check_is_apub_id_valid(
apub_id: &Url,
use_strict_allowlist: bool,
@ -92,16 +90,6 @@ pub(crate) fn check_is_apub_id_valid(
Ok(())
}
#[async_trait::async_trait(?Send)]
pub trait CommunityType {
fn followers_url(&self) -> Url;
async fn get_follower_inboxes(
&self,
pool: &DbPool,
settings: &Settings,
) -> Result<Vec<Url>, LemmyError>;
}
pub enum EndpointType {
Community,
Person,
@ -111,7 +99,7 @@ pub enum EndpointType {
}
/// Generates an apub endpoint for a given domain, IE xyz.tld
fn generate_apub_endpoint_for_domain(
pub fn generate_local_apub_endpoint(
endpoint_type: EndpointType,
name: &str,
domain: &str,
@ -127,15 +115,6 @@ fn generate_apub_endpoint_for_domain(
Ok(Url::parse(&format!("{}/{}/{}", domain, point, name))?.into())
}
/// Generates the ActivityPub ID for a given object type and ID.
pub fn generate_apub_endpoint(
endpoint_type: EndpointType,
name: &str,
protocol_and_hostname: &str,
) -> Result<DbUrl, ParseError> {
generate_apub_endpoint_for_domain(endpoint_type, name, protocol_and_hostname)
}
pub fn generate_followers_url(actor_id: &DbUrl) -> Result<DbUrl, ParseError> {
Ok(Url::parse(&format!("{}/followers", actor_id))?.into())
}
@ -169,23 +148,31 @@ fn generate_moderators_url(community_id: &DbUrl) -> Result<DbUrl, LemmyError> {
/// Takes in a shortname of the type dessalines@xyz.tld or dessalines (assumed to be local), and outputs the actor id.
/// Used in the API for communities and users.
pub fn build_actor_id_from_shortname(
endpoint_type: EndpointType,
pub async fn get_actor_id_from_name(
webfinger_type: WebfingerType,
short_name: &str,
settings: &Settings,
) -> Result<DbUrl, ParseError> {
context: &LemmyContext,
) -> Result<DbUrl, LemmyError> {
let split = short_name.split('@').collect::<Vec<&str>>();
let name = split[0];
// If there's no @, its local
let domain = if split.len() == 1 {
settings.get_protocol_and_hostname()
if split.len() == 1 {
let domain = context.settings().get_protocol_and_hostname();
let endpoint_type = match webfinger_type {
WebfingerType::Person => EndpointType::Person,
WebfingerType::Group => EndpointType::Community,
};
Ok(generate_local_apub_endpoint(endpoint_type, name, &domain)?)
} else {
format!("{}://{}", settings.get_protocol_string(), split[1])
};
generate_apub_endpoint_for_domain(endpoint_type, name, &domain)
let protocol = context.settings().get_protocol_string();
Ok(
webfinger_resolve_actor(name, split[1], webfinger_type, context.client(), protocol)
.await?
.into(),
)
}
}
/// Store a sent or received activity in the database, for logging purposes. These records are not
@ -207,64 +194,3 @@ where
.await??;
Ok(())
}
async fn check_community_or_site_ban(
person: &Person,
community_id: CommunityId,
pool: &DbPool,
) -> Result<(), LemmyError> {
if person.banned {
return Err(anyhow!("Person is banned from site").into());
}
let person_id = person.id;
let is_banned =
move |conn: &'_ _| CommunityPersonBanView::get(conn, person_id, community_id).is_ok();
if blocking(pool, is_banned).await? {
return Err(anyhow!("Person is banned from community").into());
}
Ok(())
}
pub(crate) async fn send_lemmy_activity<T: Serialize>(
context: &LemmyContext,
activity: &T,
activity_id: &Url,
actor: &dyn ActorType,
inboxes: Vec<Url>,
sensitive: bool,
) -> Result<(), LemmyError> {
if !context.settings().federation.enabled || inboxes.is_empty() {
return Ok(());
}
info!("Sending activity {}", activity_id.to_string());
// Don't send anything to ourselves
// TODO: this should be a debug assert
let hostname = context.settings().get_hostname_without_port()?;
let inboxes: Vec<&Url> = inboxes
.iter()
.filter(|i| i.domain().expect("valid inbox url") != hostname)
.collect();
let serialised_activity = serde_json::to_string(&activity)?;
insert_activity(
activity_id,
serialised_activity.clone(),
true,
sensitive,
context.pool(),
)
.await?;
send_activity(
serialised_activity,
actor,
inboxes,
context.client(),
context.activity_queue(),
)
.await
}

View file

@ -1,29 +1,17 @@
use crate::{
activities::{verify_is_public, verify_person_in_community},
context::lemmy_context,
fetcher::object_id::ObjectId,
objects::{person::ApubPerson, post::ApubPost, tombstone::Tombstone, Source},
PostOrComment,
};
use activitystreams::{
base::AnyBase,
chrono::NaiveDateTime,
object::kind::NoteType,
primitives::OneOrMany,
public,
unparsed::Unparsed,
};
use std::ops::Deref;
use activitystreams::{object::kind::NoteType, public};
use anyhow::anyhow;
use chrono::{DateTime, FixedOffset};
use chrono::NaiveDateTime;
use html2md::parse_html;
use url::Url;
use lemmy_api_common::blocking;
use lemmy_apub_lib::{
traits::ApubObject,
values::{MediaTypeHtml, MediaTypeMarkdown},
verify::verify_domains_match,
};
use lemmy_db_schema::{
newtypes::CommentId,
source::{
comment::{Comment, CommentForm},
community::Community,
@ -37,107 +25,21 @@ use lemmy_utils::{
LemmyError,
};
use lemmy_websocket::LemmyContext;
use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
use std::ops::Deref;
use url::Url;
#[skip_serializing_none]
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Note {
#[serde(rename = "@context")]
context: OneOrMany<AnyBase>,
r#type: NoteType,
id: Url,
pub(crate) attributed_to: ObjectId<ApubPerson>,
/// Indicates that the object is publicly readable. Unlike [`Post.to`], this one doesn't contain
/// the community ID, as it would be incompatible with Pleroma (and we can get the community from
/// the post in [`in_reply_to`]).
to: Vec<Url>,
content: String,
media_type: Option<MediaTypeHtml>,
source: SourceCompat,
in_reply_to: ObjectId<PostOrComment>,
published: Option<DateTime<FixedOffset>>,
updated: Option<DateTime<FixedOffset>>,
#[serde(flatten)]
unparsed: Unparsed,
}
/// Pleroma puts a raw string in the source, so we have to handle it here for deserialization to work
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
#[serde(untagged)]
enum SourceCompat {
Lemmy(Source),
Pleroma(String),
}
impl Note {
pub(crate) fn id_unchecked(&self) -> &Url {
&self.id
}
pub(crate) fn id(&self, expected_domain: &Url) -> Result<&Url, LemmyError> {
verify_domains_match(&self.id, expected_domain)?;
Ok(&self.id)
}
pub(crate) async fn get_parents(
&self,
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<(ApubPost, Option<CommentId>), LemmyError> {
// Fetch parent comment chain in a box, otherwise it can cause a stack overflow.
let parent = Box::pin(
self
.in_reply_to
.dereference(context, request_counter)
.await?,
);
match parent.deref() {
PostOrComment::Post(p) => {
// Workaround because I cant figure out how to get the post out of the box (and we dont
// want to stackoverflow in a deep comment hierarchy).
let post_id = p.id;
let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
Ok((post.into(), None))
}
PostOrComment::Comment(c) => {
let post_id = c.post_id;
let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
Ok((post.into(), Some(c.id)))
}
}
}
pub(crate) async fn verify(
&self,
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
let (post, _parent_comment_id) = self.get_parents(context, request_counter).await?;
let community_id = post.community_id;
let community = blocking(context.pool(), move |conn| {
Community::read(conn, community_id)
})
.await??;
if post.locked {
return Err(anyhow!("Post is locked").into());
}
verify_domains_match(self.attributed_to.inner(), &self.id)?;
verify_person_in_community(
&self.attributed_to,
&ObjectId::new(community.actor_id),
context,
request_counter,
)
.await?;
verify_is_public(&self.to)?;
Ok(())
}
}
use crate::{
activities::verify_person_in_community,
check_is_apub_id_valid,
fetcher::object_id::ObjectId,
protocol::{
objects::{
note::{Note, SourceCompat},
tombstone::Tombstone,
},
Source,
},
PostOrComment,
};
use lemmy_utils::utils::markdown_to_html;
#[derive(Clone, Debug)]
pub struct ApubComment(Comment);
@ -202,12 +104,11 @@ impl ApubObject for ApubComment {
};
let note = Note {
context: lemmy_context(),
r#type: NoteType::Note,
id: self.ap_id.to_owned().into_inner(),
attributed_to: ObjectId::new(creator.actor_id),
to: vec![public()],
content: self.content.clone(),
content: markdown_to_html(&self.content),
media_type: Some(MediaTypeHtml::Html),
source: SourceCompat::Lemmy(Source {
content: self.content.clone(),
@ -244,6 +145,19 @@ impl ApubObject for ApubComment {
.dereference(context, request_counter)
.await?;
let (post, parent_comment_id) = note.get_parents(context, request_counter).await?;
let community_id = post.community_id;
let community = blocking(context.pool(), move |conn| {
Community::read(conn, community_id)
})
.await??;
check_is_apub_id_valid(&note.id, community.local, &context.settings())?;
verify_person_in_community(
&note.attributed_to,
&community.into(),
context,
request_counter,
)
.await?;
if post.locked {
return Err(anyhow!("Post is locked").into());
}
@ -274,10 +188,12 @@ impl ApubObject for ApubComment {
}
#[cfg(test)]
mod tests {
pub(crate) mod tests {
use super::*;
use crate::objects::{
community::ApubCommunity,
community::{tests::parse_lemmy_community, ApubCommunity},
person::{tests::parse_lemmy_person, ApubPerson},
post::ApubPost,
tests::{file_to_json_object, init_context},
};
use assert_json_diff::assert_json_include;
@ -287,15 +203,9 @@ mod tests {
url: &Url,
context: &LemmyContext,
) -> (ApubPerson, ApubCommunity, ApubPost) {
let person_json = file_to_json_object("assets/lemmy-person.json");
let person = ApubPerson::from_apub(&person_json, context, url, &mut 0)
.await
.unwrap();
let community_json = file_to_json_object("assets/lemmy-community.json");
let community = ApubCommunity::from_apub(&community_json, context, url, &mut 0)
.await
.unwrap();
let post_json = file_to_json_object("assets/lemmy-post.json");
let person = parse_lemmy_person(context).await;
let community = parse_lemmy_community(context).await;
let post_json = file_to_json_object("assets/lemmy/objects/page.json");
let post = ApubPost::from_apub(&post_json, context, url, &mut 0)
.await
.unwrap();
@ -310,14 +220,12 @@ mod tests {
#[actix_rt::test]
#[serial]
async fn test_parse_lemmy_comment() {
// TODO: changed ObjectId::dereference() so that it always fetches if
// last_refreshed_at() == None. But post doesnt store that and expects to never be refetched
pub(crate) async fn test_parse_lemmy_comment() {
let context = init_context();
let url = Url::parse("https://enterprise.lemmy.ml/comment/38741").unwrap();
let data = prepare_comment_test(&url, &context).await;
let json = file_to_json_object("assets/lemmy-comment.json");
let json = file_to_json_object("assets/lemmy/objects/note.json");
let mut request_counter = 0;
let comment = ApubComment::from_apub(&json, &context, &url, &mut request_counter)
.await
@ -345,11 +253,11 @@ mod tests {
let pleroma_url =
Url::parse("https://queer.hacktivis.me/objects/8d4973f4-53de-49cd-8c27-df160e16a9c2")
.unwrap();
let person_json = file_to_json_object("assets/pleroma-person.json");
let person_json = file_to_json_object("assets/pleroma/objects/person.json");
ApubPerson::from_apub(&person_json, &context, &pleroma_url, &mut 0)
.await
.unwrap();
let json = file_to_json_object("assets/pleroma-comment.json");
let json = file_to_json_object("assets/pleroma/objects/note.json");
let mut request_counter = 0;
let comment = ApubComment::from_apub(&json, &context, &pleroma_url, &mut request_counter)
.await

View file

@ -1,128 +1,37 @@
use crate::{
check_is_apub_id_valid,
collections::{
community_moderators::ApubCommunityModerators,
community_outbox::ApubCommunityOutbox,
CommunityContext,
},
context::lemmy_context,
fetcher::object_id::ObjectId,
generate_moderators_url,
generate_outbox_url,
objects::{get_summary_from_string_or_source, tombstone::Tombstone, ImageObject, Source},
CommunityType,
};
use activitystreams::{
actor::{kind::GroupType, Endpoints},
base::AnyBase,
chrono::NaiveDateTime,
object::kind::ImageType,
primitives::OneOrMany,
unparsed::Unparsed,
};
use chrono::{DateTime, FixedOffset};
use chrono::NaiveDateTime;
use itertools::Itertools;
use lemmy_api_common::blocking;
use lemmy_apub_lib::{
signatures::PublicKey,
traits::{ActorType, ApubObject},
values::MediaTypeMarkdown,
verify::verify_domains_match,
};
use lemmy_db_schema::{
naive_now,
source::community::{Community, CommunityForm},
DbPool,
};
use lemmy_db_views_actor::community_follower_view::CommunityFollowerView;
use lemmy_utils::{
settings::structs::Settings,
utils::{check_slurs, check_slurs_opt, convert_datetime, markdown_to_html},
LemmyError,
};
use lemmy_websocket::LemmyContext;
use log::debug;
use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
use std::ops::Deref;
use url::Url;
#[skip_serializing_none]
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Group {
#[serde(rename = "@context")]
context: OneOrMany<AnyBase>,
#[serde(rename = "type")]
kind: GroupType,
id: Url,
/// username, set at account creation and can never be changed
preferred_username: String,
/// title (can be changed at any time)
name: String,
summary: Option<String>,
source: Option<Source>,
icon: Option<ImageObject>,
/// banner
image: Option<ImageObject>,
// lemmy extension
sensitive: Option<bool>,
// lemmy extension
pub(crate) moderators: Option<ObjectId<ApubCommunityModerators>>,
inbox: Url,
pub(crate) outbox: ObjectId<ApubCommunityOutbox>,
followers: Url,
endpoints: Endpoints<Url>,
public_key: PublicKey,
published: Option<DateTime<FixedOffset>>,
updated: Option<DateTime<FixedOffset>>,
#[serde(flatten)]
unparsed: Unparsed,
}
impl Group {
pub(crate) fn id(&self, expected_domain: &Url) -> Result<&Url, LemmyError> {
verify_domains_match(&self.id, expected_domain)?;
Ok(&self.id)
}
pub(crate) async fn from_apub_to_form(
group: &Group,
expected_domain: &Url,
settings: &Settings,
) -> Result<CommunityForm, LemmyError> {
let actor_id = Some(group.id(expected_domain)?.clone().into());
let name = group.preferred_username.clone();
let title = group.name.clone();
let description = get_summary_from_string_or_source(&group.summary, &group.source);
let shared_inbox = group.endpoints.shared_inbox.clone().map(|s| s.into());
let slur_regex = &settings.slur_regex();
check_slurs(&name, slur_regex)?;
check_slurs(&title, slur_regex)?;
check_slurs_opt(&description, slur_regex)?;
Ok(CommunityForm {
name,
title,
description,
removed: None,
published: group.published.map(|u| u.naive_local()),
updated: group.updated.map(|u| u.naive_local()),
deleted: None,
nsfw: Some(group.sensitive.unwrap_or(false)),
actor_id,
local: Some(false),
private_key: None,
public_key: Some(group.public_key.public_key_pem.clone()),
last_refreshed_at: Some(naive_now()),
icon: Some(group.icon.clone().map(|i| i.url.into())),
banner: Some(group.image.clone().map(|i| i.url.into())),
followers_url: Some(group.followers.clone().into()),
inbox_url: Some(group.inbox.clone().into()),
shared_inbox_url: Some(shared_inbox),
})
}
}
use crate::{
check_is_apub_id_valid,
collections::{community_moderators::ApubCommunityModerators, CommunityContext},
fetcher::object_id::ObjectId,
generate_moderators_url,
generate_outbox_url,
protocol::{
objects::{group::Group, tombstone::Tombstone},
ImageObject,
Source,
},
};
use lemmy_api_common::blocking;
use lemmy_apub_lib::{
traits::{ActorType, ApubObject},
values::MediaTypeMarkdown,
};
use lemmy_db_schema::source::community::Community;
use lemmy_db_views_actor::community_follower_view::CommunityFollowerView;
use lemmy_utils::{
utils::{convert_datetime, markdown_to_html},
LemmyError,
};
use lemmy_websocket::LemmyContext;
#[derive(Clone, Debug)]
pub struct ApubCommunity(Community);
@ -186,7 +95,6 @@ impl ApubObject for ApubCommunity {
});
let group = Group {
context: lemmy_context(),
kind: GroupType::Group,
id: self.actor_id(),
preferred_username: self.name.clone(),
@ -283,32 +191,32 @@ impl ActorType for ApubCommunity {
}
}
#[async_trait::async_trait(?Send)]
impl CommunityType for Community {
fn followers_url(&self) -> Url {
self.followers_url.clone().into()
}
impl ApubCommunity {
/// For a given community, returns the inboxes of all followers.
async fn get_follower_inboxes(
pub(crate) async fn get_follower_inboxes(
&self,
pool: &DbPool,
settings: &Settings,
additional_inboxes: Vec<Url>,
context: &LemmyContext,
) -> Result<Vec<Url>, LemmyError> {
let id = self.id;
let follows = blocking(pool, move |conn| {
let follows = blocking(context.pool(), move |conn| {
CommunityFollowerView::for_community(conn, id)
})
.await??;
let inboxes = follows
let follower_inboxes: Vec<Url> = follows
.into_iter()
.filter(|f| !f.follower.local)
.map(|f| f.follower.shared_inbox_url.unwrap_or(f.follower.inbox_url))
.map(|i| i.into_inner())
.collect();
let inboxes = vec![follower_inboxes, additional_inboxes]
.into_iter()
.flatten()
.unique()
.filter(|inbox| inbox.host_str() != Some(&context.settings().hostname))
// Don't send to blocked instances
.filter(|inbox| check_is_apub_id_valid(inbox, false, settings).is_ok())
.filter(|inbox| check_is_apub_id_valid(inbox, false, &context.settings()).is_ok())
.collect();
Ok(inboxes)
@ -316,42 +224,39 @@ impl CommunityType for Community {
}
#[cfg(test)]
mod tests {
pub(crate) mod tests {
use super::*;
use crate::objects::tests::{file_to_json_object, init_context};
use assert_json_diff::assert_json_include;
use lemmy_db_schema::traits::Crud;
use serial_test::serial;
#[actix_rt::test]
#[serial]
async fn test_parse_lemmy_community() {
let context = init_context();
let mut json: Group = file_to_json_object("assets/lemmy-community.json");
let json_orig = json.clone();
pub(crate) async fn parse_lemmy_community(context: &LemmyContext) -> ApubCommunity {
let mut json: Group = file_to_json_object("assets/lemmy/objects/group.json");
// change these links so they dont fetch over the network
json.moderators = Some(ObjectId::new(
Url::parse("https://enterprise.lemmy.ml/c/tenforward/not_moderators").unwrap(),
));
json.moderators = None;
json.outbox =
ObjectId::new(Url::parse("https://enterprise.lemmy.ml/c/tenforward/not_outbox").unwrap());
let url = Url::parse("https://enterprise.lemmy.ml/c/tenforward").unwrap();
let mut request_counter = 0;
let community = ApubCommunity::from_apub(&json, &context, &url, &mut request_counter)
let community = ApubCommunity::from_apub(&json, context, &url, &mut request_counter)
.await
.unwrap();
// this makes two requests to the (intentionally) broken outbox/moderators collections
assert_eq!(request_counter, 1);
community
}
#[actix_rt::test]
#[serial]
async fn test_parse_lemmy_community() {
let context = init_context();
let community = parse_lemmy_community(&context).await;
assert_eq!(community.actor_id.clone().into_inner(), url);
assert_eq!(community.title, "Ten Forward");
assert!(community.public_key.is_some());
assert!(!community.local);
assert_eq!(community.description.as_ref().unwrap().len(), 132);
// this makes two requests to the (intentionally) broken outbox/moderators collections
assert_eq!(request_counter, 2);
let to_apub = community.to_apub(&context).await.unwrap();
assert_json_include!(actual: json_orig, expected: to_apub);
Community::delete(&*context.pool().get().unwrap(), community.id).unwrap();
}

View file

@ -1,32 +1,13 @@
use activitystreams::object::kind::ImageType;
use crate::protocol::Source;
use html2md::parse_html;
use lemmy_apub_lib::values::MediaTypeMarkdown;
use serde::{Deserialize, Serialize};
use url::Url;
pub mod comment;
pub mod community;
pub mod person;
pub mod post;
pub mod private_message;
pub mod tombstone;
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Source {
content: String,
media_type: MediaTypeMarkdown,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ImageObject {
#[serde(rename = "type")]
kind: ImageType,
url: Url,
}
fn get_summary_from_string_or_source(
pub(crate) fn get_summary_from_string_or_source(
raw: &Option<String>,
source: &Option<Source>,
) -> Option<String> {
@ -38,7 +19,7 @@ fn get_summary_from_string_or_source(
}
#[cfg(test)]
mod tests {
pub(crate) mod tests {
use actix::Actor;
use diesel::{
r2d2::{ConnectionManager, Pool},

Some files were not shown because too many files have changed in this diff Show more