]> Untitled Git - lemmy.git/blob - crates/apub/src/objects/person.rs
Rewrite fetcher (#1792)
[lemmy.git] / crates / apub / src / objects / person.rs
1 use crate::{
2   check_is_apub_id_valid,
3   extensions::{context::lemmy_context, signatures::PublicKey},
4   objects::{FromApub, ImageObject, Source, ToApub},
5   ActorType,
6 };
7 use activitystreams::{
8   actor::Endpoints,
9   base::AnyBase,
10   chrono::{DateTime, FixedOffset},
11   object::{kind::ImageType, Tombstone},
12   primitives::OneOrMany,
13   unparsed::Unparsed,
14 };
15 use lemmy_api_common::blocking;
16 use lemmy_apub_lib::{
17   values::{MediaTypeHtml, MediaTypeMarkdown},
18   verify_domains_match,
19 };
20 use lemmy_db_queries::{source::person::Person_, DbPool};
21 use lemmy_db_schema::{
22   naive_now,
23   source::person::{Person as DbPerson, PersonForm},
24 };
25 use lemmy_utils::{
26   utils::{check_slurs, check_slurs_opt, convert_datetime, markdown_to_html},
27   LemmyError,
28 };
29 use lemmy_websocket::LemmyContext;
30 use serde::{Deserialize, Serialize};
31 use serde_with::skip_serializing_none;
32 use url::Url;
33
34 #[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq)]
35 pub enum UserTypes {
36   Person,
37   Service,
38 }
39
40 #[skip_serializing_none]
41 #[derive(Clone, Debug, Deserialize, Serialize)]
42 #[serde(rename_all = "camelCase")]
43 pub struct Person {
44   #[serde(rename = "@context")]
45   context: OneOrMany<AnyBase>,
46   #[serde(rename = "type")]
47   kind: UserTypes,
48   id: Url,
49   /// username, set at account creation and can never be changed
50   preferred_username: String,
51   /// displayname (can be changed at any time)
52   name: Option<String>,
53   content: Option<String>,
54   media_type: Option<MediaTypeHtml>,
55   source: Option<Source>,
56   /// user avatar
57   icon: Option<ImageObject>,
58   /// user banner
59   image: Option<ImageObject>,
60   matrix_user_id: Option<String>,
61   inbox: Url,
62   /// mandatory field in activitypub, currently empty in lemmy
63   outbox: Url,
64   endpoints: Endpoints<Url>,
65   public_key: PublicKey,
66   published: DateTime<FixedOffset>,
67   updated: Option<DateTime<FixedOffset>>,
68   #[serde(flatten)]
69   unparsed: Unparsed,
70 }
71
72 // TODO: can generate this with a derive macro
73 impl Person {
74   pub(crate) fn id(&self, expected_domain: &Url) -> Result<&Url, LemmyError> {
75     verify_domains_match(&self.id, expected_domain)?;
76     Ok(&self.id)
77   }
78 }
79
80 #[async_trait::async_trait(?Send)]
81 impl ToApub for DbPerson {
82   type ApubType = Person;
83
84   async fn to_apub(&self, _pool: &DbPool) -> Result<Person, LemmyError> {
85     let kind = if self.bot_account {
86       UserTypes::Service
87     } else {
88       UserTypes::Person
89     };
90     let source = self.bio.clone().map(|bio| Source {
91       content: bio,
92       media_type: MediaTypeMarkdown::Markdown,
93     });
94     let icon = self.avatar.clone().map(|url| ImageObject {
95       kind: ImageType::Image,
96       url: url.into(),
97     });
98     let image = self.banner.clone().map(|url| ImageObject {
99       kind: ImageType::Image,
100       url: url.into(),
101     });
102
103     let person = Person {
104       context: lemmy_context(),
105       kind,
106       id: self.actor_id.to_owned().into_inner(),
107       preferred_username: self.name.clone(),
108       name: self.display_name.clone(),
109       content: self.bio.as_ref().map(|b| markdown_to_html(b)),
110       media_type: self.bio.as_ref().map(|_| MediaTypeHtml::Html),
111       source,
112       icon,
113       image,
114       matrix_user_id: self.matrix_user_id.clone(),
115       published: convert_datetime(self.published),
116       outbox: self.get_outbox_url()?,
117       endpoints: Endpoints {
118         shared_inbox: self.shared_inbox_url.clone().map(|s| s.into()),
119         ..Default::default()
120       },
121       public_key: self.get_public_key()?,
122       updated: self.updated.map(convert_datetime),
123       unparsed: Default::default(),
124       inbox: self.inbox_url.clone().into(),
125     };
126     Ok(person)
127   }
128   fn to_tombstone(&self) -> Result<Tombstone, LemmyError> {
129     unimplemented!()
130   }
131 }
132
133 #[async_trait::async_trait(?Send)]
134 impl FromApub for DbPerson {
135   type ApubType = Person;
136
137   async fn from_apub(
138     person: &Person,
139     context: &LemmyContext,
140     expected_domain: &Url,
141     _request_counter: &mut i32,
142   ) -> Result<DbPerson, LemmyError> {
143     let actor_id = Some(person.id(expected_domain)?.clone().into());
144     let name = person.preferred_username.clone();
145     let display_name: Option<String> = person.name.clone();
146     let bio = person.source.clone().map(|s| s.content);
147     let shared_inbox = person.endpoints.shared_inbox.clone().map(|s| s.into());
148     let bot_account = match person.kind {
149       UserTypes::Person => false,
150       UserTypes::Service => true,
151     };
152
153     check_slurs(&name)?;
154     check_slurs_opt(&display_name)?;
155     check_slurs_opt(&bio)?;
156     check_is_apub_id_valid(&person.id, false)?;
157
158     let person_form = PersonForm {
159       name,
160       display_name: Some(display_name),
161       banned: None,
162       deleted: None,
163       avatar: Some(person.icon.clone().map(|i| i.url.into())),
164       banner: Some(person.image.clone().map(|i| i.url.into())),
165       published: Some(person.published.naive_local()),
166       updated: person.updated.map(|u| u.clone().naive_local()),
167       actor_id,
168       bio: Some(bio),
169       local: Some(false),
170       admin: Some(false),
171       bot_account: Some(bot_account),
172       private_key: None,
173       public_key: Some(Some(person.public_key.public_key_pem.clone())),
174       last_refreshed_at: Some(naive_now()),
175       inbox_url: Some(person.inbox.to_owned().into()),
176       shared_inbox_url: Some(shared_inbox),
177       matrix_user_id: Some(person.matrix_user_id.clone()),
178     };
179     let person = blocking(context.pool(), move |conn| {
180       DbPerson::upsert(conn, &person_form)
181     })
182     .await??;
183     Ok(person)
184   }
185 }