Merge remote-tracking branch 'origin/main' into combined_tables_2

This commit is contained in:
Dessalines 2024-11-28 17:44:32 -05:00
commit d68c0a6691
26 changed files with 141 additions and 329 deletions

1
Cargo.lock generated
View file

@ -2582,7 +2582,6 @@ dependencies = [
"moka", "moka",
"pretty_assertions", "pretty_assertions",
"reqwest 0.12.8", "reqwest 0.12.8",
"semver",
"serde", "serde",
"serde_json", "serde_json",
"serde_with", "serde_with",

View file

@ -45,7 +45,6 @@ html2md = "0.2.14"
html2text = "0.12.6" html2text = "0.12.6"
stringreader = "0.1.1" stringreader = "0.1.1"
enum_delegate = "0.2.0" enum_delegate = "0.2.0"
semver = "1.0.23"
[dev-dependencies] [dev-dependencies]
serial_test = { workspace = true } serial_test = { workspace = true }

View file

@ -3,7 +3,7 @@
"actor": "http://enterprise.lemmy.ml/u/lemmy_beta", "actor": "http://enterprise.lemmy.ml/u/lemmy_beta",
"to": ["http://ds9.lemmy.ml/u/lemmy_alpha"], "to": ["http://ds9.lemmy.ml/u/lemmy_alpha"],
"object": { "object": {
"type": "Note", "type": "ChatMessage",
"id": "http://enterprise.lemmy.ml/private_message/1", "id": "http://enterprise.lemmy.ml/private_message/1",
"attributedTo": "http://enterprise.lemmy.ml/u/lemmy_beta", "attributedTo": "http://enterprise.lemmy.ml/u/lemmy_beta",
"to": ["http://ds9.lemmy.ml/u/lemmy_alpha"], "to": ["http://ds9.lemmy.ml/u/lemmy_alpha"],

View file

@ -1,6 +1,6 @@
{ {
"id": "https://enterprise.lemmy.ml/private_message/1621", "id": "https://enterprise.lemmy.ml/private_message/1621",
"type": "Note", "type": "ChatMessage",
"attributedTo": "https://enterprise.lemmy.ml/u/picard", "attributedTo": "https://enterprise.lemmy.ml/u/picard",
"to": ["https://queer.hacktivis.me/users/lanodan"], "to": ["https://queer.hacktivis.me/users/lanodan"],
"content": "<p>Hello hello, testing</p>\n", "content": "<p>Hello hello, testing</p>\n",

View file

@ -1,49 +0,0 @@
{
"@context": [
"https://www.w3.org/ns/activitystreams",
{
"ostatus": "http://ostatus.org#",
"atomUri": "ostatus:atomUri",
"inReplyToAtomUri": "ostatus:inReplyToAtomUri",
"conversation": "ostatus:conversation",
"sensitive": "as:sensitive",
"toot": "http://joinmastodon.org/ns#",
"votersCount": "toot:votersCount"
}
],
"id": "https://mastodon.world/users/nutomic/statuses/110854468010322301",
"type": "Note",
"summary": null,
"inReplyTo": "https://mastodon.world/users/nutomic/statuses/110854464248188528",
"published": "2023-08-08T14:29:04Z",
"url": "https://mastodon.world/@nutomic/110854468010322301",
"attributedTo": "https://mastodon.world/users/nutomic",
"to": ["https://ds9.lemmy.ml/u/nutomic"],
"cc": [],
"sensitive": false,
"atomUri": "https://mastodon.world/users/nutomic/statuses/110854468010322301",
"inReplyToAtomUri": "https://mastodon.world/users/nutomic/statuses/110854464248188528",
"conversation": "tag:mastodon.world,2023-08-08:objectId=121377096:objectType=Conversation",
"content": "<p><span class=\"h-card\" translate=\"no\"><a href=\"https://ds9.lemmy.ml/u/nutomic\" class=\"u-url mention\">@<span>nutomic@ds9.lemmy.ml</span></a></span> 444</p>",
"contentMap": {
"es": "<p><span class=\"h-card\" translate=\"no\"><a href=\"https://ds9.lemmy.ml/u/nutomic\" class=\"u-url mention\">@<span>nutomic@ds9.lemmy.ml</span></a></span> 444</p>"
},
"attachment": [],
"tag": [
{
"type": "Mention",
"href": "https://ds9.lemmy.ml/u/nutomic",
"name": "@nutomic@ds9.lemmy.ml"
}
],
"replies": {
"id": "https://mastodon.world/users/nutomic/statuses/110854468010322301/replies",
"type": "Collection",
"first": {
"type": "CollectionPage",
"next": "https://mastodon.world/users/nutomic/statuses/110854468010322301/replies?only_other_accounts=true&page=true",
"partOf": "https://mastodon.world/users/nutomic/statuses/110854468010322301/replies",
"items": []
}
}
}

View file

@ -0,0 +1,15 @@
{
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://queer.hacktivis.me/schemas/litepub-0.1.jsonld",
{
"@language": "und"
}
],
"attributedTo": "https://queer.hacktivis.me/users/lanodan",
"content": "Hi!",
"id": "https://queer.hacktivis.me/objects/2",
"published": "2020-02-12T14:08:20Z",
"to": ["https://enterprise.lemmy.ml/u/picard"],
"type": "ChatMessage"
}

View file

@ -64,17 +64,16 @@ impl ActivityHandler for RawAnnouncableActivities {
// verify and receive activity // verify and receive activity
activity.verify(context).await?; activity.verify(context).await?;
let actor_id = activity.actor().clone().into(); activity.clone().receive(context).await?;
activity.receive(context).await?;
// if community is local, send activity to followers // if community is local, send activity to followers
if let Some(community) = community { if let Some(community) = community {
if community.local { if community.local {
let actor_id = activity.actor().clone().into();
verify_person_in_community(&actor_id, &community, context).await?; verify_person_in_community(&actor_id, &community, context).await?;
AnnounceActivity::send(self, &community, context).await?; AnnounceActivity::send(self, &community, context).await?;
} }
} }
Ok(()) Ok(())
} }
} }

View file

@ -43,7 +43,6 @@ use lemmy_utils::{
error::{LemmyError, LemmyResult}, error::{LemmyError, LemmyResult},
utils::mention::scrape_text_for_mentions, utils::mention::scrape_text_for_mentions,
}; };
use serde_json::{from_value, to_value};
use url::Url; use url::Url;
impl CreateOrUpdateNote { impl CreateOrUpdateNote {
@ -99,8 +98,7 @@ impl CreateOrUpdateNote {
inboxes.add_inbox(person.shared_inbox_or_inbox()); inboxes.add_inbox(person.shared_inbox_or_inbox());
} }
let activity = let activity = AnnouncableActivities::CreateOrUpdateComment(create_or_update);
AnnouncableActivities::CreateOrUpdateNoteWrapper(from_value(to_value(create_or_update)?)?);
send_activity_in_community(activity, &person, &community, inboxes, false, &context).await send_activity_in_community(activity, &person, &community, inboxes, false, &context).await
} }
} }

View file

@ -1,4 +1,3 @@
pub mod comment; pub mod comment;
pub(crate) mod note_wrapper;
pub mod post; pub mod post;
pub mod private_message; pub mod private_message;

View file

@ -1,66 +0,0 @@
use crate::{
objects::{community::ApubCommunity, note_wrapper::is_public},
protocol::{
activities::create_or_update::{
note::CreateOrUpdateNote,
note_wrapper::CreateOrUpdateNoteWrapper,
private_message::CreateOrUpdatePrivateMessage,
},
InCommunity,
},
};
use activitypub_federation::{config::Data, traits::ActivityHandler};
use lemmy_api_common::context::LemmyContext;
use lemmy_utils::error::{FederationError, LemmyError, LemmyResult};
use serde_json::{from_value, to_value};
use url::Url;
#[async_trait::async_trait]
impl ActivityHandler for CreateOrUpdateNoteWrapper {
type DataType = LemmyContext;
type Error = LemmyError;
fn id(&self) -> &Url {
&self.id
}
fn actor(&self) -> &Url {
&self.actor
}
#[tracing::instrument(skip_all)]
async fn verify(&self, context: &Data<Self::DataType>) -> LemmyResult<()> {
let val = to_value(self)?;
if is_public(&self.to, &self.cc) {
CreateOrUpdateNote::verify(&from_value(val)?, context).await?;
} else {
CreateOrUpdatePrivateMessage::verify(&from_value(val)?, context).await?;
}
Ok(())
}
#[tracing::instrument(skip_all)]
async fn receive(self, context: &Data<Self::DataType>) -> LemmyResult<()> {
let is_public = is_public(&self.to, &self.cc);
let val = to_value(self)?;
if is_public {
CreateOrUpdateNote::receive(from_value(val)?, context).await?;
} else {
CreateOrUpdatePrivateMessage::receive(from_value(val)?, context).await?;
}
Ok(())
}
}
#[async_trait::async_trait]
impl InCommunity for CreateOrUpdateNoteWrapper {
#[tracing::instrument(skip(self, context))]
async fn community(&self, context: &Data<LemmyContext>) -> LemmyResult<ApubCommunity> {
if is_public(&self.to, &self.cc) {
let comment: CreateOrUpdateNote = from_value(to_value(self)?)?;
comment.community(context).await
} else {
Err(FederationError::ObjectIsNotPublic.into())
}
}
}

View file

@ -3,7 +3,7 @@ use crate::{
insert_received_activity, insert_received_activity,
objects::{person::ApubPerson, private_message::ApubPrivateMessage}, objects::{person::ApubPerson, private_message::ApubPrivateMessage},
protocol::activities::{ protocol::activities::{
create_or_update::private_message::CreateOrUpdatePrivateMessage, create_or_update::chat_message::CreateOrUpdateChatMessage,
CreateOrUpdateType, CreateOrUpdateType,
}, },
}; };
@ -30,7 +30,7 @@ pub(crate) async fn send_create_or_update_pm(
kind.clone(), kind.clone(),
&context.settings().get_protocol_and_hostname(), &context.settings().get_protocol_and_hostname(),
)?; )?;
let create_or_update = CreateOrUpdatePrivateMessage { let create_or_update = CreateOrUpdateChatMessage {
id: id.clone(), id: id.clone(),
actor: actor.id().into(), actor: actor.id().into(),
to: [recipient.id().into()], to: [recipient.id().into()],
@ -44,7 +44,7 @@ pub(crate) async fn send_create_or_update_pm(
} }
#[async_trait::async_trait] #[async_trait::async_trait]
impl ActivityHandler for CreateOrUpdatePrivateMessage { impl ActivityHandler for CreateOrUpdateChatMessage {
type DataType = LemmyContext; type DataType = LemmyContext;
type Error = LemmyError; type Error = LemmyError;

View file

@ -36,7 +36,7 @@ use lemmy_db_schema::{
community::{Community, CommunityUpdateForm}, community::{Community, CommunityUpdateForm},
person::Person, person::Person,
post::{Post, PostUpdateForm}, post::{Post, PostUpdateForm},
private_message::{PrivateMessage as DbPrivateMessage, PrivateMessageUpdateForm}, private_message::{PrivateMessage, PrivateMessageUpdateForm},
}, },
traits::Crud, traits::Crud,
}; };
@ -82,7 +82,7 @@ pub(crate) async fn send_apub_delete_in_community(
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
pub(crate) async fn send_apub_delete_private_message( pub(crate) async fn send_apub_delete_private_message(
actor: &ApubPerson, actor: &ApubPerson,
pm: DbPrivateMessage, pm: PrivateMessage,
deleted: bool, deleted: bool,
context: Data<LemmyContext>, context: Data<LemmyContext>,
) -> LemmyResult<()> { ) -> LemmyResult<()> {
@ -298,7 +298,7 @@ async fn receive_delete_action(
} }
} }
DeletableObjects::PrivateMessage(pm) => { DeletableObjects::PrivateMessage(pm) => {
DbPrivateMessage::update( PrivateMessage::update(
&mut context.pool(), &mut context.pool(),
pm.id, pm.id,
&PrivateMessageUpdateForm { &PrivateMessageUpdateForm {

View file

@ -11,7 +11,11 @@ use crate::{
report::Report, report::Report,
update::UpdateCommunity, update::UpdateCommunity,
}, },
create_or_update::{note_wrapper::CreateOrUpdateNoteWrapper, page::CreateOrUpdatePage}, create_or_update::{
chat_message::CreateOrUpdateChatMessage,
note::CreateOrUpdateNote,
page::CreateOrUpdatePage,
},
deletion::{delete::Delete, undo_delete::UndoDelete}, deletion::{delete::Delete, undo_delete::UndoDelete},
following::{ following::{
accept::AcceptFollow, accept::AcceptFollow,
@ -44,17 +48,47 @@ pub enum SharedInboxActivities {
AcceptFollow(AcceptFollow), AcceptFollow(AcceptFollow),
RejectFollow(RejectFollow), RejectFollow(RejectFollow),
UndoFollow(UndoFollow), UndoFollow(UndoFollow),
CreateOrUpdatePrivateMessage(CreateOrUpdateChatMessage),
Report(Report), Report(Report),
AnnounceActivity(AnnounceActivity), AnnounceActivity(AnnounceActivity),
/// This is a catch-all and needs to be last /// This is a catch-all and needs to be last
RawAnnouncableActivities(RawAnnouncableActivities), RawAnnouncableActivities(RawAnnouncableActivities),
} }
/// List of activities which the group inbox can handle.
#[derive(Debug, Deserialize, Serialize)]
#[serde(untagged)]
#[enum_delegate::implement(ActivityHandler)]
pub enum GroupInboxActivities {
Follow(Follow),
UndoFollow(UndoFollow),
Report(Report),
/// This is a catch-all and needs to be last
AnnouncableActivities(RawAnnouncableActivities),
}
/// List of activities which the person inbox can handle.
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(untagged)]
#[enum_delegate::implement(ActivityHandler)]
pub enum PersonInboxActivities {
Follow(Follow),
AcceptFollow(AcceptFollow),
RejectFollow(RejectFollow),
UndoFollow(UndoFollow),
CreateOrUpdatePrivateMessage(CreateOrUpdateChatMessage),
Delete(Delete),
UndoDelete(UndoDelete),
AnnounceActivity(AnnounceActivity),
/// User can also receive some "announcable" activities, eg a comment mention.
AnnouncableActivities(AnnouncableActivities),
}
#[derive(Clone, Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(untagged)] #[serde(untagged)]
#[enum_delegate::implement(ActivityHandler)] #[enum_delegate::implement(ActivityHandler)]
pub enum AnnouncableActivities { pub enum AnnouncableActivities {
CreateOrUpdateNoteWrapper(CreateOrUpdateNoteWrapper), CreateOrUpdateComment(CreateOrUpdateNote),
CreateOrUpdatePost(CreateOrUpdatePage), CreateOrUpdatePost(CreateOrUpdatePage),
Vote(Vote), Vote(Vote),
UndoVote(UndoVote), UndoVote(UndoVote),
@ -77,7 +111,7 @@ impl InCommunity for AnnouncableActivities {
async fn community(&self, context: &Data<LemmyContext>) -> LemmyResult<ApubCommunity> { async fn community(&self, context: &Data<LemmyContext>) -> LemmyResult<ApubCommunity> {
use AnnouncableActivities::*; use AnnouncableActivities::*;
match self { match self {
CreateOrUpdateNoteWrapper(a) => a.community(context).await, CreateOrUpdateComment(a) => a.community(context).await,
CreateOrUpdatePost(a) => a.community(context).await, CreateOrUpdatePost(a) => a.community(context).await,
Vote(a) => a.community(context).await, Vote(a) => a.community(context).await,
UndoVote(a) => a.community(context).await, UndoVote(a) => a.community(context).await,
@ -99,32 +133,40 @@ impl InCommunity for AnnouncableActivities {
mod tests { mod tests {
use crate::{ use crate::{
activity_lists::SharedInboxActivities, activity_lists::{GroupInboxActivities, PersonInboxActivities, SharedInboxActivities},
protocol::tests::{test_json, test_parse_lemmy_item}, protocol::tests::{test_json, test_parse_lemmy_item},
}; };
use lemmy_utils::error::LemmyResult; use lemmy_utils::error::LemmyResult;
#[test]
fn test_group_inbox() -> LemmyResult<()> {
test_parse_lemmy_item::<GroupInboxActivities>("assets/lemmy/activities/following/follow.json")?;
test_parse_lemmy_item::<GroupInboxActivities>(
"assets/lemmy/activities/create_or_update/create_note.json",
)?;
Ok(())
}
#[test]
fn test_person_inbox() -> LemmyResult<()> {
test_parse_lemmy_item::<PersonInboxActivities>(
"assets/lemmy/activities/following/accept.json",
)?;
test_parse_lemmy_item::<PersonInboxActivities>(
"assets/lemmy/activities/create_or_update/create_note.json",
)?;
test_parse_lemmy_item::<PersonInboxActivities>(
"assets/lemmy/activities/create_or_update/create_private_message.json",
)?;
test_json::<PersonInboxActivities>("assets/mastodon/activities/follow.json")?;
Ok(())
}
#[test] #[test]
fn test_shared_inbox() -> LemmyResult<()> { fn test_shared_inbox() -> LemmyResult<()> {
test_parse_lemmy_item::<SharedInboxActivities>( test_parse_lemmy_item::<SharedInboxActivities>(
"assets/lemmy/activities/deletion/delete_user.json", "assets/lemmy/activities/deletion/delete_user.json",
)?; )?;
test_parse_lemmy_item::<SharedInboxActivities>(
"assets/lemmy/activities/following/accept.json",
)?;
test_parse_lemmy_item::<SharedInboxActivities>(
"assets/lemmy/activities/create_or_update/create_comment.json",
)?;
test_parse_lemmy_item::<SharedInboxActivities>(
"assets/lemmy/activities/create_or_update/create_private_message.json",
)?;
test_parse_lemmy_item::<SharedInboxActivities>(
"assets/lemmy/activities/following/follow.json",
)?;
test_parse_lemmy_item::<SharedInboxActivities>(
"assets/lemmy/activities/create_or_update/create_comment.json",
)?;
test_json::<SharedInboxActivities>("assets/mastodon/activities/follow.json")?;
Ok(()) Ok(())
} }
} }

View file

@ -266,7 +266,7 @@ pub(crate) mod tests {
let url = Url::parse("https://enterprise.lemmy.ml/comment/38741")?; let url = Url::parse("https://enterprise.lemmy.ml/comment/38741")?;
let data = prepare_comment_test(&url, &context).await?; let data = prepare_comment_test(&url, &context).await?;
let json: Note = file_to_json_object("assets/lemmy/objects/comment.json")?; let json: Note = file_to_json_object("assets/lemmy/objects/note.json")?;
ApubComment::verify(&json, &url, &context).await?; ApubComment::verify(&json, &url, &context).await?;
let comment = ApubComment::from_json(json.clone(), &context).await?; let comment = ApubComment::from_json(json.clone(), &context).await?;

View file

@ -15,7 +15,6 @@ use std::fmt::Debug;
pub mod comment; pub mod comment;
pub mod community; pub mod community;
pub mod instance; pub mod instance;
pub mod note_wrapper;
pub mod person; pub mod person;
pub mod post; pub mod post;
pub mod private_message; pub mod private_message;

View file

@ -1,85 +0,0 @@
use super::comment::ApubComment;
use crate::{
objects::private_message::ApubPrivateMessage,
protocol::objects::note_wrapper::NoteWrapper,
};
use activitypub_federation::{config::Data, kinds::public, traits::Object};
use chrono::{DateTime, Utc};
use lemmy_api_common::{context::LemmyContext, LemmyErrorType};
use lemmy_utils::error::{LemmyError, LemmyResult};
use serde_json::{from_value, to_value};
use url::Url;
/// Private messages and public comments are quite awkward in Activitypub, because the json
/// format looks identical. They only way to differentiate them is to check for the presence
/// or absence of `https://www.w3.org/ns/activitystreams#Public` in `to` or `cc` which this
/// wrapper does.
#[derive(Debug)]
pub(crate) struct ApubNote {}
#[async_trait::async_trait]
impl Object for ApubNote {
type DataType = LemmyContext;
type Kind = NoteWrapper;
type Error = LemmyError;
fn last_refreshed_at(&self) -> Option<DateTime<Utc>> {
None
}
#[tracing::instrument(skip_all)]
async fn read_from_id(
_object_id: Url,
_context: &Data<Self::DataType>,
) -> LemmyResult<Option<Self>> {
Err(LemmyErrorType::Unknown("not implemented".to_string()).into())
}
#[tracing::instrument(skip_all)]
async fn delete(self, _context: &Data<Self::DataType>) -> LemmyResult<()> {
Err(LemmyErrorType::Unknown("not implemented".to_string()).into())
}
async fn verify(
note: &NoteWrapper,
expected_domain: &Url,
context: &Data<LemmyContext>,
) -> LemmyResult<()> {
let val = to_value(note)?;
if is_public(&note.to, &note.cc) {
ApubComment::verify(&from_value(val)?, expected_domain, context).await?;
} else {
ApubPrivateMessage::verify(&from_value(val)?, expected_domain, context).await?;
}
Ok(())
}
async fn from_json(note: NoteWrapper, context: &Data<LemmyContext>) -> LemmyResult<ApubNote> {
let is_public = is_public(&note.to, &note.cc);
let val = to_value(note)?;
if is_public {
ApubComment::from_json(from_value(val)?, context).await?;
} else {
ApubPrivateMessage::from_json(from_value(val)?, context).await?;
}
Ok(ApubNote {})
}
async fn into_json(self, _context: &Data<Self::DataType>) -> LemmyResult<NoteWrapper> {
Err(LemmyErrorType::Unknown("not implemented".to_string()).into())
}
}
pub(crate) fn is_public(to: &Option<Vec<Url>>, cc: &Option<Vec<Url>>) -> bool {
if let Some(to) = to {
if to.contains(&public()) {
return true;
}
}
if let Some(cc) = cc {
if cc.contains(&public()) {
return true;
}
}
false
}

View file

@ -4,7 +4,7 @@ use crate::{
fetcher::markdown_links::markdown_rewrite_remote_links, fetcher::markdown_links::markdown_rewrite_remote_links,
objects::read_from_string_or_source, objects::read_from_string_or_source,
protocol::{ protocol::{
objects::private_message::{PrivateMessage, PrivateMessageType}, objects::chat_message::{ChatMessage, ChatMessageType},
Source, Source,
}, },
}; };
@ -25,11 +25,10 @@ use lemmy_api_common::{
}; };
use lemmy_db_schema::{ use lemmy_db_schema::{
source::{ source::{
instance::Instance,
local_site::LocalSite, local_site::LocalSite,
person::Person, person::Person,
person_block::PersonBlock, person_block::PersonBlock,
private_message::{PrivateMessage as DbPrivateMessage, PrivateMessageInsertForm}, private_message::{PrivateMessage, PrivateMessageInsertForm},
}, },
traits::Crud, traits::Crud,
}; };
@ -38,22 +37,21 @@ use lemmy_utils::{
error::{FederationError, LemmyError, LemmyErrorType, LemmyResult}, error::{FederationError, LemmyError, LemmyErrorType, LemmyResult},
utils::markdown::markdown_to_html, utils::markdown::markdown_to_html,
}; };
use semver::{Version, VersionReq};
use std::ops::Deref; use std::ops::Deref;
use url::Url; use url::Url;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct ApubPrivateMessage(pub(crate) DbPrivateMessage); pub struct ApubPrivateMessage(pub(crate) PrivateMessage);
impl Deref for ApubPrivateMessage { impl Deref for ApubPrivateMessage {
type Target = DbPrivateMessage; type Target = PrivateMessage;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
&self.0 &self.0
} }
} }
impl From<DbPrivateMessage> for ApubPrivateMessage { impl From<PrivateMessage> for ApubPrivateMessage {
fn from(pm: DbPrivateMessage) -> Self { fn from(pm: PrivateMessage) -> Self {
ApubPrivateMessage(pm) ApubPrivateMessage(pm)
} }
} }
@ -61,7 +59,7 @@ impl From<DbPrivateMessage> for ApubPrivateMessage {
#[async_trait::async_trait] #[async_trait::async_trait]
impl Object for ApubPrivateMessage { impl Object for ApubPrivateMessage {
type DataType = LemmyContext; type DataType = LemmyContext;
type Kind = PrivateMessage; type Kind = ChatMessage;
type Error = LemmyError; type Error = LemmyError;
fn last_refreshed_at(&self) -> Option<DateTime<Utc>> { fn last_refreshed_at(&self) -> Option<DateTime<Utc>> {
@ -74,7 +72,7 @@ impl Object for ApubPrivateMessage {
context: &Data<Self::DataType>, context: &Data<Self::DataType>,
) -> LemmyResult<Option<Self>> { ) -> LemmyResult<Option<Self>> {
Ok( Ok(
DbPrivateMessage::read_from_apub_id(&mut context.pool(), object_id) PrivateMessage::read_from_apub_id(&mut context.pool(), object_id)
.await? .await?
.map(Into::into), .map(Into::into),
) )
@ -86,26 +84,15 @@ impl Object for ApubPrivateMessage {
} }
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn into_json(self, context: &Data<Self::DataType>) -> LemmyResult<PrivateMessage> { async fn into_json(self, context: &Data<Self::DataType>) -> LemmyResult<ChatMessage> {
let creator_id = self.creator_id; let creator_id = self.creator_id;
let creator = Person::read(&mut context.pool(), creator_id).await?; let creator = Person::read(&mut context.pool(), creator_id).await?;
let recipient_id = self.recipient_id; let recipient_id = self.recipient_id;
let recipient = Person::read(&mut context.pool(), recipient_id).await?; let recipient = Person::read(&mut context.pool(), recipient_id).await?;
let instance = Instance::read(&mut context.pool(), recipient.instance_id).await?; let note = ChatMessage {
let mut kind = PrivateMessageType::Note; r#type: ChatMessageType::ChatMessage,
// Deprecated: For Lemmy versions before 0.20, send private messages with old type
if let (Some(software), Some(version)) = (instance.software, &instance.version) {
let req = VersionReq::parse("<0.20")?;
if software == "lemmy" && req.matches(&Version::parse(version)?) {
kind = PrivateMessageType::ChatMessage
}
}
let note = PrivateMessage {
kind,
id: self.ap_id.clone().into(), id: self.ap_id.clone().into(),
attributed_to: creator.actor_id.into(), attributed_to: creator.actor_id.into(),
to: [recipient.actor_id.into()], to: [recipient.actor_id.into()],
@ -120,7 +107,7 @@ impl Object for ApubPrivateMessage {
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn verify( async fn verify(
note: &PrivateMessage, note: &ChatMessage,
expected_domain: &Url, expected_domain: &Url,
context: &Data<Self::DataType>, context: &Data<Self::DataType>,
) -> LemmyResult<()> { ) -> LemmyResult<()> {
@ -141,7 +128,7 @@ impl Object for ApubPrivateMessage {
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn from_json( async fn from_json(
note: PrivateMessage, note: ChatMessage,
context: &Data<Self::DataType>, context: &Data<Self::DataType>,
) -> LemmyResult<ApubPrivateMessage> { ) -> LemmyResult<ApubPrivateMessage> {
let creator = note.attributed_to.dereference(context).await?; let creator = note.attributed_to.dereference(context).await?;
@ -174,7 +161,7 @@ impl Object for ApubPrivateMessage {
local: Some(false), local: Some(false),
}; };
let timestamp = note.updated.or(note.published).unwrap_or_else(Utc::now); let timestamp = note.updated.or(note.published).unwrap_or_else(Utc::now);
let pm = DbPrivateMessage::insert_apub(&mut context.pool(), timestamp, &form).await?; let pm = PrivateMessage::insert_apub(&mut context.pool(), timestamp, &form).await?;
Ok(pm.into()) Ok(pm.into())
} }
} }
@ -226,7 +213,7 @@ mod tests {
let context = LemmyContext::init_test_context().await; let context = LemmyContext::init_test_context().await;
let url = Url::parse("https://enterprise.lemmy.ml/private_message/1621")?; let url = Url::parse("https://enterprise.lemmy.ml/private_message/1621")?;
let data = prepare_comment_test(&url, &context).await?; let data = prepare_comment_test(&url, &context).await?;
let json: PrivateMessage = file_to_json_object("assets/lemmy/objects/private_message.json")?; let json: ChatMessage = file_to_json_object("assets/lemmy/objects/chat_message.json")?;
ApubPrivateMessage::verify(&json, &url, &context).await?; ApubPrivateMessage::verify(&json, &url, &context).await?;
let pm = ApubPrivateMessage::from_json(json.clone(), &context).await?; let pm = ApubPrivateMessage::from_json(json.clone(), &context).await?;
@ -238,7 +225,27 @@ mod tests {
let to_apub = pm.into_json(&context).await?; let to_apub = pm.into_json(&context).await?;
assert_json_include!(actual: json, expected: to_apub); assert_json_include!(actual: json, expected: to_apub);
DbPrivateMessage::delete(&mut context.pool(), pm_id).await?; PrivateMessage::delete(&mut context.pool(), pm_id).await?;
cleanup(data, &context).await?;
Ok(())
}
#[tokio::test]
#[serial]
async fn test_parse_pleroma_pm() -> LemmyResult<()> {
let context = LemmyContext::init_test_context().await;
let url = Url::parse("https://enterprise.lemmy.ml/private_message/1621")?;
let data = prepare_comment_test(&url, &context).await?;
let pleroma_url = Url::parse("https://queer.hacktivis.me/objects/2")?;
let json = file_to_json_object("assets/pleroma/objects/chat_message.json")?;
ApubPrivateMessage::verify(&json, &pleroma_url, &context).await?;
let pm = ApubPrivateMessage::from_json(json, &context).await?;
assert_eq!(pm.ap_id, pleroma_url.into());
assert_eq!(pm.content.len(), 3);
assert_eq!(context.request_count(), 0);
PrivateMessage::delete(&mut context.pool(), pm.id).await?;
cleanup(data, &context).await?; cleanup(data, &context).await?;
Ok(()) Ok(())
} }

View file

@ -1,6 +1,6 @@
use crate::{ use crate::{
objects::person::ApubPerson, objects::person::ApubPerson,
protocol::{activities::CreateOrUpdateType, objects::private_message::PrivateMessage}, protocol::{activities::CreateOrUpdateType, objects::chat_message::ChatMessage},
}; };
use activitypub_federation::{fetch::object_id::ObjectId, protocol::helpers::deserialize_one}; use activitypub_federation::{fetch::object_id::ObjectId, protocol::helpers::deserialize_one};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -8,12 +8,12 @@ use url::Url;
#[derive(Clone, Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct CreateOrUpdatePrivateMessage { pub struct CreateOrUpdateChatMessage {
pub(crate) id: Url, pub(crate) id: Url,
pub(crate) actor: ObjectId<ApubPerson>, pub(crate) actor: ObjectId<ApubPerson>,
#[serde(deserialize_with = "deserialize_one")] #[serde(deserialize_with = "deserialize_one")]
pub(crate) to: [ObjectId<ApubPerson>; 1], pub(crate) to: [ObjectId<ApubPerson>; 1],
pub(crate) object: PrivateMessage, pub(crate) object: ChatMessage,
#[serde(rename = "type")] #[serde(rename = "type")]
pub(crate) kind: CreateOrUpdateType, pub(crate) kind: CreateOrUpdateType,
} }

View file

@ -1,16 +1,14 @@
pub mod chat_message;
pub mod note; pub mod note;
pub(crate) mod note_wrapper;
pub mod page; pub mod page;
pub mod private_message;
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::note_wrapper::CreateOrUpdateNoteWrapper;
use crate::protocol::{ use crate::protocol::{
activities::create_or_update::{ activities::create_or_update::{
chat_message::CreateOrUpdateChatMessage,
note::CreateOrUpdateNote, note::CreateOrUpdateNote,
page::CreateOrUpdatePage, page::CreateOrUpdatePage,
private_message::CreateOrUpdatePrivateMessage,
}, },
tests::test_parse_lemmy_item, tests::test_parse_lemmy_item,
}; };
@ -25,15 +23,9 @@ mod tests {
"assets/lemmy/activities/create_or_update/update_page.json", "assets/lemmy/activities/create_or_update/update_page.json",
)?; )?;
test_parse_lemmy_item::<CreateOrUpdateNote>( test_parse_lemmy_item::<CreateOrUpdateNote>(
"assets/lemmy/activities/create_or_update/create_comment.json", "assets/lemmy/activities/create_or_update/create_note.json",
)?; )?;
test_parse_lemmy_item::<CreateOrUpdatePrivateMessage>( test_parse_lemmy_item::<CreateOrUpdateChatMessage>(
"assets/lemmy/activities/create_or_update/create_private_message.json",
)?;
test_parse_lemmy_item::<CreateOrUpdateNoteWrapper>(
"assets/lemmy/activities/create_or_update/create_comment.json",
)?;
test_parse_lemmy_item::<CreateOrUpdateNoteWrapper>(
"assets/lemmy/activities/create_or_update/create_private_message.json", "assets/lemmy/activities/create_or_update/create_private_message.json",
)?; )?;
Ok(()) Ok(())

View file

@ -1,16 +0,0 @@
use crate::protocol::objects::note_wrapper::NoteWrapper;
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};
use url::Url;
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CreateOrUpdateNoteWrapper {
object: NoteWrapper,
pub(crate) id: Url,
pub(crate) actor: Url,
pub(crate) to: Option<Vec<Url>>,
pub(crate) cc: Option<Vec<Url>>,
#[serde(flatten)]
other: Map<String, Value>,
}

View file

@ -116,7 +116,6 @@ pub(crate) mod tests {
// parse file into hashmap, which ensures that every field is included // parse file into hashmap, which ensures that every field is included
let raw = file_to_json_object::<HashMap<String, serde_json::Value>>(path)?; let raw = file_to_json_object::<HashMap<String, serde_json::Value>>(path)?;
// assert that all fields are identical, otherwise print diff // assert that all fields are identical, otherwise print diff
//dbg!(&parsed, &raw);
assert_json_include!(actual: &parsed, expected: raw); assert_json_include!(actual: &parsed, expected: raw);
Ok(parsed) Ok(parsed)
} }

View file

@ -16,9 +16,8 @@ use serde_with::skip_serializing_none;
#[skip_serializing_none] #[skip_serializing_none]
#[derive(Clone, Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct PrivateMessage { pub struct ChatMessage {
#[serde(rename = "type")] pub(crate) r#type: ChatMessageType,
pub(crate) kind: PrivateMessageType,
pub(crate) id: ObjectId<ApubPrivateMessage>, pub(crate) id: ObjectId<ApubPrivateMessage>,
pub(crate) attributed_to: ObjectId<ApubPerson>, pub(crate) attributed_to: ObjectId<ApubPerson>,
#[serde(deserialize_with = "deserialize_one")] #[serde(deserialize_with = "deserialize_one")]
@ -32,10 +31,8 @@ pub struct PrivateMessage {
pub(crate) updated: Option<DateTime<Utc>>, pub(crate) updated: Option<DateTime<Utc>>,
} }
/// https://docs.pleroma.social/backend/development/ap_extensions/#chatmessages
#[derive(Clone, Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Deserialize, Serialize)]
pub enum PrivateMessageType { pub enum ChatMessageType {
/// For compatibility with Lemmy 0.19 and earlier
/// https://docs.pleroma.social/backend/development/ap_extensions/#chatmessages
ChatMessage, ChatMessage,
Note,
} }

View file

@ -8,13 +8,12 @@ use lemmy_utils::error::LemmyResult;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use url::Url; use url::Url;
pub(crate) mod chat_message;
pub(crate) mod group; pub(crate) mod group;
pub(crate) mod instance; pub(crate) mod instance;
pub(crate) mod note; pub(crate) mod note;
pub(crate) mod note_wrapper;
pub(crate) mod page; pub(crate) mod page;
pub(crate) mod person; pub(crate) mod person;
pub(crate) mod private_message;
pub(crate) mod tombstone; pub(crate) mod tombstone;
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] #[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
@ -102,15 +101,14 @@ impl LanguageTag {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::note_wrapper::NoteWrapper;
use crate::protocol::{ use crate::protocol::{
objects::{ objects::{
chat_message::ChatMessage,
group::Group, group::Group,
instance::Instance, instance::Instance,
note::Note, note::Note,
page::Page, page::Page,
person::Person, person::Person,
private_message::PrivateMessage,
tombstone::Tombstone, tombstone::Tombstone,
}, },
tests::{test_json, test_parse_lemmy_item}, tests::{test_json, test_parse_lemmy_item},
@ -123,10 +121,8 @@ mod tests {
test_parse_lemmy_item::<Group>("assets/lemmy/objects/group.json")?; test_parse_lemmy_item::<Group>("assets/lemmy/objects/group.json")?;
test_parse_lemmy_item::<Person>("assets/lemmy/objects/person.json")?; test_parse_lemmy_item::<Person>("assets/lemmy/objects/person.json")?;
test_parse_lemmy_item::<Page>("assets/lemmy/objects/page.json")?; test_parse_lemmy_item::<Page>("assets/lemmy/objects/page.json")?;
test_parse_lemmy_item::<Note>("assets/lemmy/objects/comment.json")?; test_parse_lemmy_item::<Note>("assets/lemmy/objects/note.json")?;
test_parse_lemmy_item::<PrivateMessage>("assets/lemmy/objects/private_message.json")?; test_parse_lemmy_item::<ChatMessage>("assets/lemmy/objects/chat_message.json")?;
test_parse_lemmy_item::<NoteWrapper>("assets/lemmy/objects/comment.json")?;
test_parse_lemmy_item::<NoteWrapper>("assets/lemmy/objects/private_message.json")?;
test_parse_lemmy_item::<Tombstone>("assets/lemmy/objects/tombstone.json")?; test_parse_lemmy_item::<Tombstone>("assets/lemmy/objects/tombstone.json")?;
Ok(()) Ok(())
} }
@ -135,6 +131,7 @@ mod tests {
fn test_parse_objects_pleroma() -> LemmyResult<()> { fn test_parse_objects_pleroma() -> LemmyResult<()> {
test_json::<Person>("assets/pleroma/objects/person.json")?; test_json::<Person>("assets/pleroma/objects/person.json")?;
test_json::<Note>("assets/pleroma/objects/note.json")?; test_json::<Note>("assets/pleroma/objects/note.json")?;
test_json::<ChatMessage>("assets/pleroma/objects/chat_message.json")?;
Ok(()) Ok(())
} }

View file

@ -1,14 +0,0 @@
use activitypub_federation::kinds::object::NoteType;
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};
use url::Url;
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct NoteWrapper {
pub(crate) r#type: NoteType,
pub(crate) to: Option<Vec<Url>>,
pub(crate) cc: Option<Vec<Url>>,
#[serde(flatten)]
other: Map<String, Value>,
}