use super::*; #[derive(Deserialize)] pub struct CommunityQuery { community_name: String, } impl ToApub for Community { type Response = GroupExt; // Turn a Lemmy Community into an ActivityPub group that can be sent out over the network. fn to_apub(&self, conn: &PgConnection) -> Result { let mut group = Group::default(); let oprops: &mut ObjectProperties = group.as_mut(); // The attributed to, is an ordered vector with the creator actor_ids first, // then the rest of the moderators // TODO Technically the instance admins can mod the community, but lets // ignore that for now let moderators = CommunityModeratorView::for_community(&conn, self.id)? .into_iter() .map(|m| m.user_actor_id) .collect(); oprops .set_context_xsd_any_uri(context())? .set_id(self.actor_id.to_owned())? .set_name_xsd_string(self.name.to_owned())? .set_published(convert_datetime(self.published))? .set_many_attributed_to_xsd_any_uris(moderators)?; if let Some(u) = self.updated.to_owned() { oprops.set_updated(convert_datetime(u))?; } if let Some(d) = self.description.to_owned() { // TODO: this should be html, also add source field with raw markdown // -> same for post.content and others oprops.set_summary_xsd_string(d)?; } let mut endpoint_props = EndpointProperties::default(); endpoint_props.set_shared_inbox(self.get_shared_inbox_url())?; let mut actor_props = ApActorProperties::default(); actor_props .set_preferred_username(self.title.to_owned())? .set_inbox(self.get_inbox_url())? .set_outbox(self.get_outbox_url())? .set_endpoints(endpoint_props)? .set_followers(self.get_followers_url())?; let group_extension = GroupExtension::new(conn, self.category_id, self.nsfw)?; Ok( group .extend(group_extension) .extend(actor_props) .extend(self.get_public_key_ext()), ) } fn to_tombstone(&self) -> Result { create_tombstone( self.deleted, &self.actor_id, self.updated, GroupType.to_string(), ) } } impl ActorType for Community { fn actor_id(&self) -> String { self.actor_id.to_owned() } fn public_key(&self) -> String { self.public_key.to_owned().unwrap() } /// As a local community, accept the follow request from a remote user. fn send_accept_follow(&self, follow: &Follow, conn: &PgConnection) -> Result<(), Error> { let actor_uri = follow .follow_props .get_actor_xsd_any_uri() .unwrap() .to_string(); let id = format!("{}/accept/{}", self.actor_id, uuid::Uuid::new_v4()); let mut accept = Accept::new(); accept .object_props .set_context_xsd_any_uri(context())? .set_id(id)?; accept .accept_props .set_actor_xsd_any_uri(self.actor_id.to_owned())? .set_object_base_box(BaseBox::from_concrete(follow.clone())?)?; let to = format!("{}/inbox", actor_uri); // Insert the sent activity into the activity table let activity_form = activity::ActivityForm { user_id: self.creator_id, data: serde_json::to_value(&accept)?, local: true, updated: None, }; activity::Activity::create(&conn, &activity_form)?; send_activity( &accept, &self.private_key.to_owned().unwrap(), &self.actor_id, vec![to], )?; Ok(()) } fn send_delete(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> { let group = self.to_apub(conn)?; let id = format!("{}/delete/{}", self.actor_id, uuid::Uuid::new_v4()); let mut delete = Delete::default(); populate_object_props(&mut delete.object_props, &self.get_followers_url(), &id)?; delete .delete_props .set_actor_xsd_any_uri(creator.actor_id.to_owned())? .set_object_base_box(group)?; // Insert the sent activity into the activity table let activity_form = activity::ActivityForm { user_id: self.creator_id, data: serde_json::to_value(&delete)?, local: true, updated: None, }; activity::Activity::create(&conn, &activity_form)?; // Note: For an accept, since it was automatic, no one pushed a button, // the community was the actor. // But for delete, the creator is the actor, and does the signing send_activity( &delete, &creator.private_key.as_ref().unwrap(), &creator.actor_id, self.get_follower_inboxes(&conn)?, )?; Ok(()) } fn send_undo_delete(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> { let group = self.to_apub(conn)?; let id = format!("{}/delete/{}", self.actor_id, uuid::Uuid::new_v4()); let mut delete = Delete::default(); populate_object_props(&mut delete.object_props, &self.get_followers_url(), &id)?; delete .delete_props .set_actor_xsd_any_uri(creator.actor_id.to_owned())? .set_object_base_box(group)?; // TODO // Undo that fake activity let undo_id = format!("{}/undo/delete/{}", self.actor_id, uuid::Uuid::new_v4()); let mut undo = Undo::default(); populate_object_props(&mut undo.object_props, &self.get_followers_url(), &undo_id)?; undo .undo_props .set_actor_xsd_any_uri(creator.actor_id.to_owned())? .set_object_base_box(delete)?; // Insert the sent activity into the activity table let activity_form = activity::ActivityForm { user_id: self.creator_id, data: serde_json::to_value(&undo)?, local: true, updated: None, }; activity::Activity::create(&conn, &activity_form)?; // Note: For an accept, since it was automatic, no one pushed a button, // the community was the actor. // But for delete, the creator is the actor, and does the signing send_activity( &undo, &creator.private_key.as_ref().unwrap(), &creator.actor_id, self.get_follower_inboxes(&conn)?, )?; Ok(()) } fn send_remove(&self, mod_: &User_, conn: &PgConnection) -> Result<(), Error> { let group = self.to_apub(conn)?; let id = format!("{}/remove/{}", self.actor_id, uuid::Uuid::new_v4()); let mut remove = Remove::default(); populate_object_props(&mut remove.object_props, &self.get_followers_url(), &id)?; remove .remove_props .set_actor_xsd_any_uri(mod_.actor_id.to_owned())? .set_object_base_box(group)?; // Insert the sent activity into the activity table let activity_form = activity::ActivityForm { user_id: mod_.id, data: serde_json::to_value(&remove)?, local: true, updated: None, }; activity::Activity::create(&conn, &activity_form)?; // Note: For an accept, since it was automatic, no one pushed a button, // the community was the actor. // But for delete, the creator is the actor, and does the signing send_activity( &remove, &mod_.private_key.as_ref().unwrap(), &mod_.actor_id, self.get_follower_inboxes(&conn)?, )?; Ok(()) } fn send_undo_remove(&self, mod_: &User_, conn: &PgConnection) -> Result<(), Error> { let group = self.to_apub(conn)?; let id = format!("{}/remove/{}", self.actor_id, uuid::Uuid::new_v4()); let mut remove = Remove::default(); populate_object_props(&mut remove.object_props, &self.get_followers_url(), &id)?; remove .remove_props .set_actor_xsd_any_uri(mod_.actor_id.to_owned())? .set_object_base_box(group)?; // Undo that fake activity let undo_id = format!("{}/undo/remove/{}", self.actor_id, uuid::Uuid::new_v4()); let mut undo = Undo::default(); populate_object_props(&mut undo.object_props, &self.get_followers_url(), &undo_id)?; undo .undo_props .set_actor_xsd_any_uri(mod_.actor_id.to_owned())? .set_object_base_box(remove)?; // Insert the sent activity into the activity table let activity_form = activity::ActivityForm { user_id: mod_.id, data: serde_json::to_value(&undo)?, local: true, updated: None, }; activity::Activity::create(&conn, &activity_form)?; // Note: For an accept, since it was automatic, no one pushed a button, // the community was the actor. // But for remove , the creator is the actor, and does the signing send_activity( &undo, &mod_.private_key.as_ref().unwrap(), &mod_.actor_id, self.get_follower_inboxes(&conn)?, )?; Ok(()) } /// For a given community, returns the inboxes of all followers. fn get_follower_inboxes(&self, conn: &PgConnection) -> Result, Error> { Ok( CommunityFollowerView::for_community(conn, self.id)? .into_iter() // TODO eventually this will have to use the inbox or shared_inbox column, meaning that view // will have to change .map(|c| { // If the user is local, but the community isn't, get the community shared inbox // and vice versa if c.user_local && !c.community_local { get_shared_inbox(&c.community_actor_id) } else if !c.user_local && c.community_local { get_shared_inbox(&c.user_actor_id) } else { "".to_string() } }) .filter(|s| !s.is_empty()) .unique() .collect(), ) } fn send_follow(&self, _follow_actor_id: &str, _conn: &PgConnection) -> Result<(), Error> { unimplemented!() } fn send_unfollow(&self, _follow_actor_id: &str, _conn: &PgConnection) -> Result<(), Error> { unimplemented!() } } impl FromApub for CommunityForm { type ApubType = GroupExt; /// Parse an ActivityPub group received from another instance into a Lemmy community. fn from_apub(group: &GroupExt, conn: &PgConnection) -> Result { let group_extensions: &GroupExtension = &group.base.base.extension; let oprops = &group.base.base.base.object_props; let aprops = &group.base.extension; let public_key: &PublicKey = &group.extension.public_key; let _followers_uri = Url::parse(&aprops.get_followers().unwrap().to_string())?; let _outbox_uri = Url::parse(&aprops.get_outbox().to_string())?; // TODO don't do extra fetching here // let _outbox = fetch_remote_object::(&outbox_uri)?; // let _followers = fetch_remote_object::(&followers_uri)?; let mut creator_and_moderator_uris = oprops.get_many_attributed_to_xsd_any_uris().unwrap(); let creator = creator_and_moderator_uris .next() .map(|c| get_or_fetch_and_upsert_remote_user(&c.to_string(), &conn).unwrap()) .unwrap(); Ok(CommunityForm { name: oprops.get_name_xsd_string().unwrap().to_string(), title: aprops.get_preferred_username().unwrap().to_string(), // TODO: should be parsed as html and tags like