2 extensions::group_extensions::GroupExtension,
3 fetcher::get_or_fetch_and_upsert_user,
4 objects::{check_object_domain, create_tombstone},
10 use activitystreams::{
11 actor::{kind::GroupType, ApActor, Endpoints, Group},
13 object::{Image, Tombstone},
16 use activitystreams_ext::Ext2;
19 community::{Community, CommunityForm},
20 community_view::CommunityModeratorView,
24 use lemmy_structs::blocking;
27 utils::{check_slurs, check_slurs_opt, convert_datetime},
30 use lemmy_websocket::LemmyContext;
33 #[async_trait::async_trait(?Send)]
34 impl ToApub for Community {
35 type ApubType = GroupExt;
37 async fn to_apub(&self, pool: &DbPool) -> Result<GroupExt, LemmyError> {
38 // The attributed to, is an ordered vector with the creator actor_ids first,
39 // then the rest of the moderators
40 // TODO Technically the instance admins can mod the community, but lets
41 // ignore that for now
43 let moderators = blocking(pool, move |conn| {
44 CommunityModeratorView::for_community(&conn, id)
47 let moderators: Vec<String> = moderators.into_iter().map(|m| m.user_actor_id).collect();
49 let mut group = Group::new();
51 .set_context(activitystreams::context())
52 .set_id(Url::parse(&self.actor_id)?)
53 .set_name(self.title.to_owned())
54 .set_published(convert_datetime(self.published))
55 .set_many_attributed_tos(moderators);
57 if let Some(u) = self.updated.to_owned() {
58 group.set_updated(convert_datetime(u));
60 if let Some(d) = self.description.to_owned() {
61 // TODO: this should be html, also add source field with raw markdown
62 // -> same for post.content and others
66 if let Some(icon_url) = &self.icon {
67 let mut image = Image::new();
68 image.set_url(Url::parse(icon_url)?);
69 group.set_icon(image.into_any_base()?);
72 if let Some(banner_url) = &self.banner {
73 let mut image = Image::new();
74 image.set_url(Url::parse(banner_url)?);
75 group.set_image(image.into_any_base()?);
78 let mut ap_actor = ApActor::new(self.get_inbox_url()?, group);
80 .set_preferred_username(self.name.to_owned())
81 .set_outbox(self.get_outbox_url()?)
82 .set_followers(self.get_followers_url()?)
83 .set_endpoints(Endpoints {
84 shared_inbox: Some(self.get_shared_inbox_url()?),
89 let category_id = self.category_id;
90 let group_extension = blocking(pool, move |conn| {
91 GroupExtension::new(conn, category_id, nsfw)
98 self.get_public_key_ext()?,
102 fn to_tombstone(&self) -> Result<Tombstone, LemmyError> {
103 create_tombstone(self.deleted, &self.actor_id, self.updated, GroupType::Group)
106 #[async_trait::async_trait(?Send)]
107 impl FromApub for CommunityForm {
108 type ApubType = GroupExt;
112 context: &LemmyContext,
113 expected_domain: Option<Url>,
114 ) -> Result<Self, LemmyError> {
115 let creator_and_moderator_uris = group.inner.attributed_to().context(location_info!())?;
116 let creator_uri = creator_and_moderator_uris
118 .context(location_info!())?
121 .context(location_info!())?
123 .context(location_info!())?;
125 let creator = get_or_fetch_and_upsert_user(creator_uri, context).await?;
128 .preferred_username()
129 .context(location_info!())?
134 .context(location_info!())?
136 .context(location_info!())?
138 .context(location_info!())?
140 // TODO: should be parsed as html and tags like <script> removed (or use markdown source)
141 // -> same for post.content etc
142 let description = group
145 .map(|s| s.as_single_xsd_string())
147 .map(|s| s.to_string());
149 check_slurs(&title)?;
150 check_slurs_opt(&description)?;
152 let icon = match group.icon() {
153 Some(any_image) => Some(
154 Image::from_any_base(any_image.as_one().context(location_info!())?.clone())
155 .context(location_info!())?
156 .context(location_info!())?
158 .context(location_info!())?
159 .as_single_xsd_any_uri()
160 .map(|u| u.to_string()),
165 let banner = match group.image() {
166 Some(any_image) => Some(
167 Image::from_any_base(any_image.as_one().context(location_info!())?.clone())
168 .context(location_info!())?
169 .context(location_info!())?
171 .context(location_info!())?
172 .as_single_xsd_any_uri()
173 .map(|u| u.to_string()),
182 category_id: group.ext_one.category.identifier.parse::<i32>()?,
183 creator_id: creator.id,
185 published: group.inner.published().map(|u| u.to_owned().naive_local()),
186 updated: group.inner.updated().map(|u| u.to_owned().naive_local()),
188 nsfw: group.ext_one.sensitive,
189 actor_id: Some(check_object_domain(group, expected_domain)?),
192 public_key: Some(group.ext_two.to_owned().public_key.public_key_pem),
193 last_refreshed_at: Some(naive_now()),