]> Untitled Git - lemmy.git/blob - server/src/apub/user.rs
Add bio federation. (#1052)
[lemmy.git] / server / src / apub / user.rs
1 use crate::{
2   apub::{
3     activities::{generate_activity_id, send_activity},
4     create_apub_response,
5     insert_activity,
6     ActorType,
7     FromApub,
8     PersonExt,
9     ToApub,
10   },
11   blocking,
12   routes::DbPoolParam,
13   DbPool,
14   LemmyError,
15 };
16 use activitystreams::{
17   activity::{
18     kind::{FollowType, UndoType},
19     Follow,
20     Undo,
21   },
22   actor::{ApActor, Endpoints, Person},
23   context,
24   object::{Image, Tombstone},
25   prelude::*,
26 };
27 use activitystreams_ext::Ext1;
28 use actix_web::{body::Body, client::Client, web, HttpResponse};
29 use lemmy_db::{
30   naive_now,
31   user::{UserForm, User_},
32 };
33 use lemmy_utils::convert_datetime;
34 use serde::Deserialize;
35 use url::Url;
36
37 #[derive(Deserialize)]
38 pub struct UserQuery {
39   user_name: String,
40 }
41
42 #[async_trait::async_trait(?Send)]
43 impl ToApub for User_ {
44   type Response = PersonExt;
45
46   // Turn a Lemmy Community into an ActivityPub group that can be sent out over the network.
47   async fn to_apub(&self, _pool: &DbPool) -> Result<PersonExt, LemmyError> {
48     // TODO go through all these to_string and to_owned()
49     let mut person = Person::new();
50     person
51       .set_context(context())
52       .set_id(Url::parse(&self.actor_id)?)
53       .set_name(self.name.to_owned())
54       .set_published(convert_datetime(self.published));
55
56     if let Some(u) = self.updated {
57       person.set_updated(convert_datetime(u));
58     }
59
60     if let Some(avatar_url) = &self.avatar {
61       let mut image = Image::new();
62       image.set_url(avatar_url.to_owned());
63       person.set_icon(image.into_any_base()?);
64     }
65
66     if let Some(bio) = &self.bio {
67       person.set_summary(bio.to_owned());
68     }
69
70     let mut ap_actor = ApActor::new(self.get_inbox_url()?, person);
71     ap_actor
72       .set_outbox(self.get_outbox_url()?)
73       .set_followers(self.get_followers_url().parse()?)
74       .set_following(self.get_following_url().parse()?)
75       .set_liked(self.get_liked_url().parse()?)
76       .set_endpoints(Endpoints {
77         shared_inbox: Some(self.get_shared_inbox_url().parse()?),
78         ..Default::default()
79       });
80
81     if let Some(i) = &self.preferred_username {
82       ap_actor.set_preferred_username(i.to_owned());
83     }
84
85     Ok(Ext1::new(ap_actor, self.get_public_key_ext()))
86   }
87   fn to_tombstone(&self) -> Result<Tombstone, LemmyError> {
88     unimplemented!()
89   }
90 }
91
92 #[async_trait::async_trait(?Send)]
93 impl ActorType for User_ {
94   fn actor_id_str(&self) -> String {
95     self.actor_id.to_owned()
96   }
97
98   fn public_key(&self) -> String {
99     self.public_key.to_owned().unwrap()
100   }
101
102   fn private_key(&self) -> String {
103     self.private_key.to_owned().unwrap()
104   }
105
106   /// As a given local user, send out a follow request to a remote community.
107   async fn send_follow(
108     &self,
109     follow_actor_id: &str,
110     client: &Client,
111     pool: &DbPool,
112   ) -> Result<(), LemmyError> {
113     let mut follow = Follow::new(self.actor_id.to_owned(), follow_actor_id);
114     follow
115       .set_context(context())
116       .set_id(generate_activity_id(FollowType::Follow)?);
117     let to = format!("{}/inbox", follow_actor_id);
118
119     insert_activity(self.id, follow.clone(), true, pool).await?;
120
121     send_activity(client, &follow.into_any_base()?, self, vec![to]).await?;
122     Ok(())
123   }
124
125   async fn send_unfollow(
126     &self,
127     follow_actor_id: &str,
128     client: &Client,
129     pool: &DbPool,
130   ) -> Result<(), LemmyError> {
131     let mut follow = Follow::new(self.actor_id.to_owned(), follow_actor_id);
132     follow
133       .set_context(context())
134       .set_id(generate_activity_id(FollowType::Follow)?);
135
136     let to = format!("{}/inbox", follow_actor_id);
137
138     // Undo that fake activity
139     let mut undo = Undo::new(Url::parse(&self.actor_id)?, follow.into_any_base()?);
140     undo
141       .set_context(context())
142       .set_id(generate_activity_id(UndoType::Undo)?);
143
144     insert_activity(self.id, undo.clone(), true, pool).await?;
145
146     send_activity(client, &undo.into_any_base()?, self, vec![to]).await?;
147     Ok(())
148   }
149
150   async fn send_delete(
151     &self,
152     _creator: &User_,
153     _client: &Client,
154     _pool: &DbPool,
155   ) -> Result<(), LemmyError> {
156     unimplemented!()
157   }
158
159   async fn send_undo_delete(
160     &self,
161     _creator: &User_,
162     _client: &Client,
163     _pool: &DbPool,
164   ) -> Result<(), LemmyError> {
165     unimplemented!()
166   }
167
168   async fn send_remove(
169     &self,
170     _creator: &User_,
171     _client: &Client,
172     _pool: &DbPool,
173   ) -> Result<(), LemmyError> {
174     unimplemented!()
175   }
176
177   async fn send_undo_remove(
178     &self,
179     _creator: &User_,
180     _client: &Client,
181     _pool: &DbPool,
182   ) -> Result<(), LemmyError> {
183     unimplemented!()
184   }
185
186   async fn send_accept_follow(
187     &self,
188     _follow: Follow,
189     _client: &Client,
190     _pool: &DbPool,
191   ) -> Result<(), LemmyError> {
192     unimplemented!()
193   }
194
195   async fn get_follower_inboxes(&self, _pool: &DbPool) -> Result<Vec<String>, LemmyError> {
196     unimplemented!()
197   }
198
199   fn user_id(&self) -> i32 {
200     self.id
201   }
202 }
203
204 #[async_trait::async_trait(?Send)]
205 impl FromApub for UserForm {
206   type ApubType = PersonExt;
207   /// Parse an ActivityPub person received from another instance into a Lemmy user.
208   async fn from_apub(person: &PersonExt, _: &Client, _: &DbPool) -> Result<Self, LemmyError> {
209     let avatar = match person.icon() {
210       Some(any_image) => Image::from_any_base(any_image.as_one().unwrap().clone())
211         .unwrap()
212         .unwrap()
213         .url()
214         .unwrap()
215         .as_single_xsd_any_uri()
216         .map(|u| u.to_string()),
217       None => None,
218     };
219
220     Ok(UserForm {
221       name: person
222         .name()
223         .unwrap()
224         .one()
225         .unwrap()
226         .as_xsd_string()
227         .unwrap()
228         .to_string(),
229       preferred_username: person.inner.preferred_username().map(|u| u.to_string()),
230       password_encrypted: "".to_string(),
231       admin: false,
232       banned: false,
233       email: None,
234       avatar,
235       updated: person.updated().map(|u| u.to_owned().naive_local()),
236       show_nsfw: false,
237       theme: "".to_string(),
238       default_sort_type: 0,
239       default_listing_type: 0,
240       lang: "".to_string(),
241       show_avatars: false,
242       send_notifications_to_email: false,
243       matrix_user_id: None,
244       actor_id: person.id_unchecked().unwrap().to_string(),
245       bio: person
246         .inner
247         .summary()
248         .map(|s| s.as_single_xsd_string().unwrap().into()),
249       local: false,
250       private_key: None,
251       public_key: Some(person.ext_one.public_key.to_owned().public_key_pem),
252       last_refreshed_at: Some(naive_now()),
253     })
254   }
255 }
256
257 /// Return the user json over HTTP.
258 pub async fn get_apub_user_http(
259   info: web::Path<UserQuery>,
260   db: DbPoolParam,
261 ) -> Result<HttpResponse<Body>, LemmyError> {
262   let user_name = info.into_inner().user_name;
263   let user = blocking(&db, move |conn| {
264     User_::find_by_email_or_username(conn, &user_name)
265   })
266   .await??;
267   let u = user.to_apub(&db).await?;
268   Ok(create_apub_response(&u))
269 }