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