}
impl AnnounceActivity {
- pub async fn send(
+ fn new(
object: AnnouncableActivities,
community: &ApubCommunity,
- additional_inboxes: Vec<Url>,
context: &LemmyContext,
- ) -> Result<(), LemmyError> {
- let announce = AnnounceActivity {
+ ) -> Result<AnnounceActivity, LemmyError> {
+ Ok(AnnounceActivity {
actor: ObjectId::new(community.actor_id()),
to: vec![public()],
object,
&context.settings().get_protocol_and_hostname(),
)?,
unparsed: Default::default(),
- };
+ })
+ }
+
+ pub async fn send(
+ object: AnnouncableActivities,
+ community: &ApubCommunity,
+ additional_inboxes: Vec<Url>,
+ context: &LemmyContext,
+ ) -> Result<(), LemmyError> {
+ let announce = AnnounceActivity::new(object.clone(), community, context)?;
let inboxes = community
- .get_follower_inboxes(additional_inboxes, context)
+ .get_follower_inboxes(additional_inboxes.clone(), context)
.await?;
- send_lemmy_activity(context, &announce, &announce.id, community, inboxes, false).await
+ send_lemmy_activity(
+ context,
+ &announce,
+ &announce.id,
+ community,
+ inboxes.clone(),
+ false,
+ )
+ .await?;
+
+ // Pleroma (and likely Mastodon) can't handle activities like Announce/Create/Page, so for
+ // compatibility, we also send Announce/Page and Announce/Note (for new and updated
+ // posts/comments).
+ use AnnouncableActivities::*;
+ let object = match object {
+ CreateOrUpdatePost(c) => Page(c.object),
+ CreateOrUpdateComment(c) => Note(c.object),
+ _ => return Ok(()),
+ };
+ let announce_compat = AnnounceActivity::new(object, community, context)?;
+ send_lemmy_activity(
+ context,
+ &announce_compat,
+ &announce_compat.id,
+ community,
+ inboxes,
+ false,
+ )
+ .await?;
+ Ok(())
}
}
if is_activity_already_known(context.pool(), &object_data.id).await? {
return Ok(());
}
- insert_activity(
- &object_data.id,
- self.object.clone(),
- false,
- true,
- context.pool(),
- )
- .await?;
+ insert_activity(&object_data.id, &self.object, false, true, context.pool()).await?;
self.object.receive(context, request_counter).await
}
}
) -> Result<(), LemmyError> {
// if this is a local community, we need to do an announce from the community instead
if community.local {
- insert_activity(activity_id, activity.clone(), true, false, context.pool()).await?;
+ insert_activity(activity_id, &activity, true, false, context.pool()).await?;
AnnounceActivity::send(activity, community, additional_inboxes, context).await?;
} else {
let mut inboxes = additional_inboxes;
insert_activity(
activity_id,
- serialised_activity.clone(),
+ &serialised_activity,
true,
sensitive,
context.pool(),
})
.await??
.into();
+
let create_or_update = CreateOrUpdatePost::new(post, actor, &community, kind, context).await?;
let id = create_or_update.id.clone();
let activity = AnnouncableActivities::CreateOrUpdatePost(create_or_update);
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,
+ 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},
},
- 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},
+ objects::{note::Note, page::Page},
},
};
use lemmy_apub_lib::traits::ActivityHandler;
UndoBlockUserFromCommunity(UndoBlockUserFromCommunity),
AddMod(AddMod),
RemoveMod(RemoveMod),
+ // For compatibility with Pleroma/Mastodon (send only)
+ Page(Page),
+ // For compatibility with Pleroma/Mastodon (send only)
+ Note(Note),
}
#[async_trait::async_trait(?Send)]
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?,
+ Page(_) => unimplemented!(),
+ Note(_) => unimplemented!(),
};
Ok(community)
}
context: &LemmyContext,
) -> Result<HttpResponse, LemmyError> {
let actor_id = ObjectId::new(activity_data.actor.clone());
- let res = receive_activity(request, activity.clone(), activity_data, context).await;
+ let res = receive_activity(request, activity.clone(), activity_data, context).await?;
if let GroupInboxActivities::AnnouncableActivities(announcable) = activity {
let community = announcable.get_community(context, &mut 0).await?;
}
}
- res
+ Ok(res)
}
/// Returns an empty followers collection, only populating the size (for privacy).
// Log the activity, so we avoid receiving and parsing it twice. Note that this could still happen
// if we receive the same activity twice in very quick succession.
- insert_activity(
- &activity_data.id,
- activity.clone(),
- false,
- true,
- context.pool(),
- )
- .await?;
+ insert_activity(&activity_data.id, &activity, false, true, context.pool()).await?;
info!("Receiving activity {}", activity_data.id.to_string());
activity
/// persistent.
async fn insert_activity<T>(
ap_id: &Url,
- activity: T,
+ activity: &T,
local: bool,
sensitive: bool,
pool: &DbPool,
where
T: Serialize + std::fmt::Debug + Send + 'static,
{
+ let data = serde_json::to_value(activity)?;
let ap_id = ap_id.to_owned().into();
blocking(pool, move |conn| {
- Activity::insert(conn, ap_id, &activity, local, sensitive)
+ Activity::insert(conn, ap_id, data, local, sensitive)
})
.await??;
Ok(())
protocol::Source,
};
use activitystreams::{object::kind::NoteType, unparsed::Unparsed};
+use anyhow::anyhow;
use chrono::{DateTime, FixedOffset};
use lemmy_api_common::blocking;
-use lemmy_apub_lib::{object_id::ObjectId, values::MediaTypeHtml};
+use lemmy_apub_lib::{
+ data::Data,
+ object_id::ObjectId,
+ traits::ActivityHandler,
+ values::MediaTypeHtml,
+};
use lemmy_db_schema::{newtypes::CommentId, source::post::Post, traits::Crud};
use lemmy_utils::LemmyError;
use lemmy_websocket::LemmyContext;
}
}
}
+
+// For Pleroma/Mastodon compat. Unimplemented because its only used for sending.
+#[async_trait::async_trait(?Send)]
+impl ActivityHandler for Note {
+ type DataType = LemmyContext;
+ async fn verify(&self, _: &Data<Self::DataType>, _: &mut i32) -> Result<(), LemmyError> {
+ Err(anyhow!("Announce/Page can only be sent, not received").into())
+ }
+ async fn receive(self, _: &Data<Self::DataType>, _: &mut i32) -> Result<(), LemmyError> {
+ unimplemented!()
+ }
+}
use activitystreams::{object::kind::PageType, unparsed::Unparsed};
use anyhow::anyhow;
use chrono::{DateTime, FixedOffset};
-use lemmy_apub_lib::{object_id::ObjectId, values::MediaTypeHtml};
+use lemmy_apub_lib::{
+ data::Data,
+ object_id::ObjectId,
+ traits::ActivityHandler,
+ values::MediaTypeHtml,
+};
use lemmy_utils::LemmyError;
use lemmy_websocket::LemmyContext;
use serde::{Deserialize, Serialize};
}
}
}
+
+// For Pleroma/Mastodon compat. Unimplemented because its only used for sending.
+#[async_trait::async_trait(?Send)]
+impl ActivityHandler for Page {
+ type DataType = LemmyContext;
+ async fn verify(&self, _: &Data<Self::DataType>, _: &mut i32) -> Result<(), LemmyError> {
+ Err(anyhow!("Announce/Page can only be sent, not received").into())
+ }
+ async fn receive(self, _: &Data<Self::DataType>, _: &mut i32) -> Result<(), LemmyError> {
+ unimplemented!()
+ }
+}
-use crate::{signatures::sign_and_send, traits::ActorType, APUB_JSON_CONTENT_TYPE};
+use crate::{signatures::sign_and_send, traits::ActorType};
use anyhow::{anyhow, Context, Error};
use background_jobs::{
create_server,
use log::warn;
use reqwest::Client;
use serde::{Deserialize, Serialize};
-use std::{collections::BTreeMap, env, fmt::Debug, future::Future, pin::Pin};
+use std::{env, fmt::Debug, future::Future, pin::Pin};
use url::Url;
pub async fn send_activity(
}
async fn do_send(task: SendActivityTask, client: &Client) -> Result<(), Error> {
- let mut headers = BTreeMap::<String, String>::new();
- headers.insert("Content-Type".into(), APUB_JSON_CONTENT_TYPE.to_string());
let result = sign_and_send(
client,
- headers,
&task.inbox,
task.activity.clone(),
&task.actor_id,
+use crate::APUB_JSON_CONTENT_TYPE;
+use activitystreams::chrono::Utc;
use actix_web::HttpRequest;
use anyhow::anyhow;
use http::{header::HeaderName, HeaderMap, HeaderValue};
use reqwest::{Client, Response};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
-use std::{collections::BTreeMap, str::FromStr};
+use std::str::FromStr;
use url::Url;
lazy_static! {
static ref CONFIG2: ConfigActix = ConfigActix::new();
- static ref HTTP_SIG_CONFIG: Config = Config::new();
+ static ref HTTP_SIG_CONFIG: Config = Config::new().mastodon_compat();
}
/// Creates an HTTP post request to `inbox_url`, with the given `client` and `headers`, and
/// `activity` as request body. The request is signed with `private_key` and then sent.
pub async fn sign_and_send(
client: &Client,
- headers: BTreeMap<String, String>,
inbox_url: &Url,
activity: String,
actor_id: &Url,
) -> Result<Response, LemmyError> {
let signing_key_id = format!("{}#main-key", actor_id);
- let mut header_map = HeaderMap::new();
- for h in headers {
- header_map.insert(
- HeaderName::from_str(h.0.as_str())?,
- HeaderValue::from_str(h.1.as_str())?,
- );
+ let mut headers = HeaderMap::new();
+ let mut host = inbox_url.domain().expect("read inbox domain").to_string();
+ if let Some(port) = inbox_url.port() {
+ host = format!("{}:{}", host, port);
}
+ headers.insert(
+ HeaderName::from_str("Content-Type")?,
+ HeaderValue::from_str(APUB_JSON_CONTENT_TYPE)?,
+ );
+ headers.insert(HeaderName::from_str("Host")?, HeaderValue::from_str(&host)?);
+ headers.insert(
+ HeaderName::from_str("Date")?,
+ HeaderValue::from_str(&Utc::now().to_rfc2822())?,
+ );
+
let response = client
.post(&inbox_url.to_string())
- .headers(header_map)
+ .headers(headers)
.signature_with_digest(
HTTP_SIG_CONFIG.clone(),
signing_key_id,
use crate::{newtypes::DbUrl, source::activity::*, traits::Crud};
use diesel::{dsl::*, result::Error, *};
-use serde::Serialize;
-use std::{
- fmt::Debug,
- io::{Error as IoError, ErrorKind},
-};
+use serde_json::Value;
+use std::io::{Error as IoError, ErrorKind};
impl Crud for Activity {
type Form = ActivityForm;
}
impl Activity {
- pub fn insert<T>(
+ pub fn insert(
conn: &PgConnection,
ap_id: DbUrl,
- data: &T,
+ data: Value,
local: bool,
sensitive: bool,
- ) -> Result<Activity, IoError>
- where
- T: Serialize + Debug,
- {
+ ) -> Result<Activity, IoError> {
let activity_form = ActivityForm {
ap_id,
- data: serde_json::to_value(&data)?,
+ data,
local: Some(local),
sensitive,
updated: None,