Implement FEP-e232 and allow to add quotes to posts

This commit is contained in:
silverpill 2022-10-01 11:08:56 +00:00
parent 5b7979b9d4
commit a685829472
6 changed files with 83 additions and 11 deletions

View file

@ -28,6 +28,10 @@ And these additional standards:
Activities are implemented in way that is compatible with Pleroma, Mastodon and other popular ActivityPub servers. Activities are implemented in way that is compatible with Pleroma, Mastodon and other popular ActivityPub servers.
## Supported FEPs
- [FEP-e232](https://codeberg.org/fediverse/fep/src/branch/main/feps/fep-e232.md)
## Profile extensions ## Profile extensions
### Cryptocurrency addresses ### Cryptocurrency addresses

View file

@ -35,6 +35,9 @@ pub struct Tag {
pub tag_type: String, pub tag_type: String,
pub href: Option<String>, pub href: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub media_type: Option<String>,
} }
#[derive(Default, Deserialize, Serialize)] #[derive(Default, Deserialize, Serialize)]

View file

@ -5,7 +5,7 @@ use tokio_postgres::GenericClient;
use crate::activitypub::{ use crate::activitypub::{
activity::{create_activity, Activity, Attachment, Tag}, activity::{create_activity, Activity, Attachment, Tag},
actors::types::Actor, actors::types::Actor,
constants::{AP_CONTEXT, AP_PUBLIC}, constants::{AP_MEDIA_TYPE, AP_CONTEXT, AP_PUBLIC},
deliverer::OutgoingActivity, deliverer::OutgoingActivity,
identifiers::{ identifiers::{
local_actor_id, local_actor_id,
@ -13,7 +13,7 @@ use crate::activitypub::{
local_actor_subscribers, local_actor_subscribers,
local_object_id, local_object_id,
}, },
vocabulary::{CREATE, DOCUMENT, HASHTAG, MENTION, NOTE}, vocabulary::{CREATE, DOCUMENT, HASHTAG, LINK, MENTION, NOTE},
}; };
use crate::config::Instance; use crate::config::Instance;
use crate::errors::DatabaseError; use crate::errors::DatabaseError;
@ -103,6 +103,7 @@ pub fn build_note(
name: Some(tag_name), name: Some(tag_name),
tag_type: MENTION.to_string(), tag_type: MENTION.to_string(),
href: Some(actor_id), href: Some(actor_id),
media_type: None,
}; };
tags.push(tag); tags.push(tag);
}; };
@ -112,6 +113,19 @@ pub fn build_note(
name: Some(format!("#{}", tag_name)), name: Some(format!("#{}", tag_name)),
tag_type: HASHTAG.to_string(), tag_type: HASHTAG.to_string(),
href: Some(tag_page_url), href: Some(tag_page_url),
media_type: None,
};
tags.push(tag);
};
assert_eq!(post.links.len(), post.linked.len());
for linked in &post.linked {
// Build FEP-e232 object link
let link_href = linked.get_object_id(instance_url);
let tag = Tag {
name: Some(format!("RE: {}", link_href)),
tag_type: LINK.to_string(),
href: Some(link_href),
media_type: Some(AP_MEDIA_TYPE.to_string()),
}; };
tags.push(tag); tags.push(tag);
}; };

View file

@ -8,7 +8,7 @@ use uuid::Uuid;
use crate::activitypub::{ use crate::activitypub::{
activity::{Attachment, Link, Object, Tag}, activity::{Attachment, Link, Object, Tag},
constants::AP_PUBLIC, constants::{AP_MEDIA_TYPE, AP_PUBLIC, AS_MEDIA_TYPE},
fetcher::fetchers::fetch_file, fetcher::fetchers::fetch_file,
fetcher::helpers::{ fetcher::helpers::{
get_or_import_profile_by_actor_id, get_or_import_profile_by_actor_id,
@ -17,7 +17,7 @@ use crate::activitypub::{
}, },
identifiers::{parse_local_actor_id, parse_local_object_id}, identifiers::{parse_local_actor_id, parse_local_object_id},
receiver::{parse_array, parse_property_value}, receiver::{parse_array, parse_property_value},
vocabulary::{DOCUMENT, HASHTAG, IMAGE, MENTION, NOTE}, vocabulary::{DOCUMENT, HASHTAG, IMAGE, LINK, MENTION, NOTE},
}; };
use crate::config::Instance; use crate::config::Instance;
use crate::errors::{ConversionError, DatabaseError, ValidationError}; use crate::errors::{ConversionError, DatabaseError, ValidationError};
@ -305,18 +305,33 @@ pub async fn handle_note(
} else { } else {
log::warn!("failed to parse mention {}", tag_name); log::warn!("failed to parse mention {}", tag_name);
}; };
} else if tag.tag_type == LINK {
if tag.media_type != Some(AP_MEDIA_TYPE.to_string()) &&
tag.media_type != Some(AS_MEDIA_TYPE.to_string())
{
// Unknown media type
continue;
};
if let Some(href) = tag.href {
let linked_id = get_internal_post_id(
db_client,
&instance.url(),
&href,
redirects,
).await?;
links.push(linked_id);
};
}; };
}; };
}; };
if let Some(ref object_id) = object.quote_url { if let Some(ref object_id) = object.quote_url {
log::warn!("link to object found: {}", object_id); let linked_id = get_internal_post_id(
let quoted_id = get_internal_post_id(
db_client, db_client,
&instance.url(), &instance.url(),
object_id, object_id,
redirects, redirects,
).await?; ).await?;
links.push(quoted_id); links.push(linked_id);
}; };
let in_reply_to_id = match object.in_reply_to { let in_reply_to_id = match object.in_reply_to {

View file

@ -146,6 +146,7 @@ pub struct StatusData {
// Not supported by Mastodon // Not supported by Mastodon
pub mentions: Option<Vec<Uuid>>, pub mentions: Option<Vec<Uuid>>,
pub links: Option<Vec<Uuid>>,
} }
impl TryFrom<StatusData> for PostCreateData { impl TryFrom<StatusData> for PostCreateData {
@ -169,7 +170,7 @@ impl TryFrom<StatusData> for PostCreateData {
attachments: value.media_ids.unwrap_or(vec![]), attachments: value.media_ids.unwrap_or(vec![]),
mentions: value.mentions.unwrap_or(vec![]), mentions: value.mentions.unwrap_or(vec![]),
tags: vec![], tags: vec![],
links: vec![], links: value.links.unwrap_or(vec![]),
object_id: None, object_id: None,
created_at: Utc::now(), created_at: Utc::now(),
}; };

View file

@ -81,8 +81,6 @@ async fn create_status(
.into_iter().map(|profile| profile.id); .into_iter().map(|profile| profile.id);
post_data.mentions.extend(subscribers); post_data.mentions.extend(subscribers);
}; };
post_data.mentions.sort();
post_data.mentions.dedup();
// Hashtags // Hashtags
post_data.tags = find_hashtags(&post_data.content); post_data.tags = find_hashtags(&post_data.content);
post_data.content = replace_hashtags( post_data.content = replace_hashtags(
@ -90,6 +88,39 @@ async fn create_status(
&post_data.content, &post_data.content,
&post_data.tags, &post_data.tags,
); );
// Links
let linked = match &post_data.links[..] {
[] => vec![],
[linked_id] => {
if post_data.in_reply_to_id.is_some() {
return Err(ValidationError("can't add links to reply").into());
};
if post_data.visibility != Visibility::Public {
return Err(ValidationError("can't add links to non-public posts").into());
};
let linked = match get_post_by_id(db_client, linked_id).await {
Ok(post) => post,
Err(DatabaseError::NotFound(_)) => {
return Err(ValidationError("referenced post does't exist").into());
},
Err(other_error) => return Err(other_error.into()),
};
if linked.repost_of_id.is_some() {
return Err(ValidationError("can't reference repost").into());
};
if linked.visibility != Visibility::Public {
return Err(ValidationError("can't reference non-public post").into());
};
// Append inline quote and add author to mentions
post_data.content += &format!(
r#"<p class="inline-quote">RE: <a href="{0}">{0}</a></p>"#,
linked.get_object_id(&instance.url()),
);
post_data.mentions.push(linked.author.id);
vec![linked]
},
_ => return Err(ValidationError("too many links").into()),
};
// Reply validation // Reply validation
let maybe_in_reply_to = if let Some(in_reply_to_id) = post_data.in_reply_to_id.as_ref() { let maybe_in_reply_to = if let Some(in_reply_to_id) = post_data.in_reply_to_id.as_ref() {
let in_reply_to = match get_post_by_id(db_client, in_reply_to_id).await { let in_reply_to = match get_post_by_id(db_client, in_reply_to_id).await {
@ -118,14 +149,18 @@ async fn create_status(
} else { } else {
None None
}; };
// Remove duplicate mentions
post_data.mentions.sort();
post_data.mentions.dedup();
// Create post // Create post
let mut post = create_post(db_client, &current_user.id, post_data).await?; let mut post = create_post(db_client, &current_user.id, post_data).await?;
post.in_reply_to = maybe_in_reply_to.map(|mut in_reply_to| { post.in_reply_to = maybe_in_reply_to.map(|mut in_reply_to| {
in_reply_to.reply_count += 1; in_reply_to.reply_count += 1;
Box::new(in_reply_to) Box::new(in_reply_to)
}); });
post.linked = linked;
// Federate // Federate
prepare_create_note(db_client, config.instance(), &current_user, &post).await? prepare_create_note(db_client, instance.clone(), &current_user, &post).await?
.spawn_deliver(); .spawn_deliver();
let status = Status::from_post(post, &instance.url()); let status = Status::from_post(post, &instance.url());