2 apub::{activities::send_activity, create_apub_response, ActorType, FromApub, PersonExt, ToApub},
6 activity::insert_activity,
7 user::{UserForm, User_},
14 use activitystreams_ext::Ext1;
15 use activitystreams_new::{
16 activity::{Follow, Undo},
17 actor::{ApActor, Endpoints, Person},
19 object::{Image, Tombstone},
21 primitives::{XsdAnyUri, XsdDateTime},
23 use actix_web::{body::Body, client::Client, web, HttpResponse};
24 use failure::_core::str::FromStr;
25 use serde::Deserialize;
27 #[derive(Deserialize)]
28 pub struct UserQuery {
32 #[async_trait::async_trait(?Send)]
33 impl ToApub for User_ {
34 type Response = PersonExt;
36 // Turn a Lemmy Community into an ActivityPub group that can be sent out over the network.
37 async fn to_apub(&self, _pool: &DbPool) -> Result<PersonExt, LemmyError> {
38 // TODO go through all these to_string and to_owned()
39 let mut person = Person::new();
41 .set_context(context())
42 .set_id(XsdAnyUri::from_str(&self.actor_id)?)
43 .set_name(self.name.to_owned())
44 .set_published(XsdDateTime::from(convert_datetime(self.published)));
46 if let Some(u) = self.updated {
47 person.set_updated(XsdDateTime::from(convert_datetime(u)));
50 if let Some(avatar_url) = &self.avatar {
51 let mut image = Image::new();
52 image.set_url(avatar_url.to_owned());
53 person.set_icon(image.into_any_base()?);
56 let mut ap_actor = ApActor::new(self.get_inbox_url().parse()?, person);
58 .set_outbox(self.get_outbox_url().parse()?)
59 .set_followers(self.get_followers_url().parse()?)
60 .set_following(self.get_following_url().parse()?)
61 .set_liked(self.get_liked_url().parse()?)
62 .set_endpoints(Endpoints {
63 shared_inbox: Some(self.get_shared_inbox_url().parse()?),
67 if let Some(i) = &self.preferred_username {
68 ap_actor.set_preferred_username(i.to_owned());
71 Ok(Ext1::new(ap_actor, self.get_public_key_ext()))
73 fn to_tombstone(&self) -> Result<Tombstone, LemmyError> {
78 #[async_trait::async_trait(?Send)]
79 impl ActorType for User_ {
80 fn actor_id(&self) -> String {
81 self.actor_id.to_owned()
84 fn public_key(&self) -> String {
85 self.public_key.to_owned().unwrap()
88 fn private_key(&self) -> String {
89 self.private_key.to_owned().unwrap()
92 /// As a given local user, send out a follow request to a remote community.
95 follow_actor_id: &str,
98 ) -> Result<(), LemmyError> {
99 let id = format!("{}/follow/{}", self.actor_id, uuid::Uuid::new_v4());
100 let mut follow = Follow::new(self.actor_id.to_owned(), follow_actor_id);
101 follow.set_context(context()).set_id(id.parse()?);
102 let to = format!("{}/inbox", follow_actor_id);
104 insert_activity(self.id, follow.clone(), true, pool).await?;
106 send_activity(client, &follow, self, vec![to]).await?;
110 async fn send_unfollow(
112 follow_actor_id: &str,
115 ) -> Result<(), LemmyError> {
116 let id = format!("{}/follow/{}", self.actor_id, uuid::Uuid::new_v4());
117 let mut follow = Follow::new(self.actor_id.to_owned(), follow_actor_id);
118 follow.set_context(context()).set_id(id.parse()?);
120 let to = format!("{}/inbox", follow_actor_id);
123 // Undo that fake activity
124 let undo_id = format!("{}/undo/follow/{}", self.actor_id, uuid::Uuid::new_v4());
125 let mut undo = Undo::new(self.actor_id.parse::<XsdAnyUri>()?, follow.into_any_base()?);
126 undo.set_context(context()).set_id(undo_id.parse()?);
128 insert_activity(self.id, undo.clone(), true, pool).await?;
130 send_activity(client, &undo, self, vec![to]).await?;
134 async fn send_delete(
139 ) -> Result<(), LemmyError> {
143 async fn send_undo_delete(
148 ) -> Result<(), LemmyError> {
152 async fn send_remove(
157 ) -> Result<(), LemmyError> {
161 async fn send_undo_remove(
166 ) -> Result<(), LemmyError> {
170 async fn send_accept_follow(
175 ) -> Result<(), LemmyError> {
179 async fn get_follower_inboxes(&self, _pool: &DbPool) -> Result<Vec<String>, LemmyError> {
184 #[async_trait::async_trait(?Send)]
185 impl FromApub for UserForm {
186 type ApubType = PersonExt;
187 /// Parse an ActivityPub person received from another instance into a Lemmy user.
188 async fn from_apub(person: &mut PersonExt, _: &Client, _: &DbPool) -> Result<Self, LemmyError> {
189 let avatar = match person.take_icon() {
190 Some(any_image) => Image::from_any_base(any_image.as_one().unwrap().clone())
195 .as_single_xsd_any_uri()
196 .map(|u| u.to_string()),
204 .as_single_xsd_string()
207 preferred_username: person.inner.take_preferred_username(),
208 password_encrypted: "".to_string(),
215 .map(|u| u.as_ref().to_owned().naive_local()),
217 theme: "".to_string(),
218 default_sort_type: 0,
219 default_listing_type: 0,
220 lang: "".to_string(),
222 send_notifications_to_email: false,
223 matrix_user_id: None,
224 actor_id: person.id().unwrap().to_string(),
227 .map(|s| s.as_single_xsd_string().unwrap().into()),
230 public_key: Some(person.ext_one.public_key.to_owned().public_key_pem),
231 last_refreshed_at: Some(naive_now()),
236 /// Return the user json over HTTP.
237 pub async fn get_apub_user_http(
238 info: web::Path<UserQuery>,
240 ) -> Result<HttpResponse<Body>, LemmyError> {
241 let user_name = info.into_inner().user_name;
242 let user = blocking(&db, move |conn| {
243 User_::find_by_email_or_username(conn, &user_name)
246 let u = user.to_apub(&db).await?;
247 Ok(create_apub_response(&u))