Implement FEP-e232 and allow to add quotes to posts
This commit is contained in:
parent
5b7979b9d4
commit
a685829472
6 changed files with 83 additions and 11 deletions
|
@ -28,6 +28,10 @@ And these additional standards:
|
|||
|
||||
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
|
||||
|
||||
### Cryptocurrency addresses
|
||||
|
|
|
@ -35,6 +35,9 @@ pub struct Tag {
|
|||
pub tag_type: String,
|
||||
|
||||
pub href: Option<String>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub media_type: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Default, Deserialize, Serialize)]
|
||||
|
|
|
@ -5,7 +5,7 @@ use tokio_postgres::GenericClient;
|
|||
use crate::activitypub::{
|
||||
activity::{create_activity, Activity, Attachment, Tag},
|
||||
actors::types::Actor,
|
||||
constants::{AP_CONTEXT, AP_PUBLIC},
|
||||
constants::{AP_MEDIA_TYPE, AP_CONTEXT, AP_PUBLIC},
|
||||
deliverer::OutgoingActivity,
|
||||
identifiers::{
|
||||
local_actor_id,
|
||||
|
@ -13,7 +13,7 @@ use crate::activitypub::{
|
|||
local_actor_subscribers,
|
||||
local_object_id,
|
||||
},
|
||||
vocabulary::{CREATE, DOCUMENT, HASHTAG, MENTION, NOTE},
|
||||
vocabulary::{CREATE, DOCUMENT, HASHTAG, LINK, MENTION, NOTE},
|
||||
};
|
||||
use crate::config::Instance;
|
||||
use crate::errors::DatabaseError;
|
||||
|
@ -103,6 +103,7 @@ pub fn build_note(
|
|||
name: Some(tag_name),
|
||||
tag_type: MENTION.to_string(),
|
||||
href: Some(actor_id),
|
||||
media_type: None,
|
||||
};
|
||||
tags.push(tag);
|
||||
};
|
||||
|
@ -112,6 +113,19 @@ pub fn build_note(
|
|||
name: Some(format!("#{}", tag_name)),
|
||||
tag_type: HASHTAG.to_string(),
|
||||
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);
|
||||
};
|
||||
|
|
|
@ -8,7 +8,7 @@ use uuid::Uuid;
|
|||
|
||||
use crate::activitypub::{
|
||||
activity::{Attachment, Link, Object, Tag},
|
||||
constants::AP_PUBLIC,
|
||||
constants::{AP_MEDIA_TYPE, AP_PUBLIC, AS_MEDIA_TYPE},
|
||||
fetcher::fetchers::fetch_file,
|
||||
fetcher::helpers::{
|
||||
get_or_import_profile_by_actor_id,
|
||||
|
@ -17,7 +17,7 @@ use crate::activitypub::{
|
|||
},
|
||||
identifiers::{parse_local_actor_id, parse_local_object_id},
|
||||
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::errors::{ConversionError, DatabaseError, ValidationError};
|
||||
|
@ -305,18 +305,33 @@ pub async fn handle_note(
|
|||
} else {
|
||||
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 {
|
||||
log::warn!("link to object found: {}", object_id);
|
||||
let quoted_id = get_internal_post_id(
|
||||
let linked_id = get_internal_post_id(
|
||||
db_client,
|
||||
&instance.url(),
|
||||
object_id,
|
||||
redirects,
|
||||
).await?;
|
||||
links.push(quoted_id);
|
||||
links.push(linked_id);
|
||||
};
|
||||
|
||||
let in_reply_to_id = match object.in_reply_to {
|
||||
|
|
|
@ -146,6 +146,7 @@ pub struct StatusData {
|
|||
|
||||
// Not supported by Mastodon
|
||||
pub mentions: Option<Vec<Uuid>>,
|
||||
pub links: Option<Vec<Uuid>>,
|
||||
}
|
||||
|
||||
impl TryFrom<StatusData> for PostCreateData {
|
||||
|
@ -169,7 +170,7 @@ impl TryFrom<StatusData> for PostCreateData {
|
|||
attachments: value.media_ids.unwrap_or(vec![]),
|
||||
mentions: value.mentions.unwrap_or(vec![]),
|
||||
tags: vec![],
|
||||
links: vec![],
|
||||
links: value.links.unwrap_or(vec![]),
|
||||
object_id: None,
|
||||
created_at: Utc::now(),
|
||||
};
|
||||
|
|
|
@ -81,8 +81,6 @@ async fn create_status(
|
|||
.into_iter().map(|profile| profile.id);
|
||||
post_data.mentions.extend(subscribers);
|
||||
};
|
||||
post_data.mentions.sort();
|
||||
post_data.mentions.dedup();
|
||||
// Hashtags
|
||||
post_data.tags = find_hashtags(&post_data.content);
|
||||
post_data.content = replace_hashtags(
|
||||
|
@ -90,6 +88,39 @@ async fn create_status(
|
|||
&post_data.content,
|
||||
&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
|
||||
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 {
|
||||
|
@ -118,14 +149,18 @@ async fn create_status(
|
|||
} else {
|
||||
None
|
||||
};
|
||||
// Remove duplicate mentions
|
||||
post_data.mentions.sort();
|
||||
post_data.mentions.dedup();
|
||||
// Create post
|
||||
let mut post = create_post(db_client, ¤t_user.id, post_data).await?;
|
||||
post.in_reply_to = maybe_in_reply_to.map(|mut in_reply_to| {
|
||||
in_reply_to.reply_count += 1;
|
||||
Box::new(in_reply_to)
|
||||
});
|
||||
post.linked = linked;
|
||||
// Federate
|
||||
prepare_create_note(db_client, config.instance(), ¤t_user, &post).await?
|
||||
prepare_create_note(db_client, instance.clone(), ¤t_user, &post).await?
|
||||
.spawn_deliver();
|
||||
|
||||
let status = Status::from_post(post, &instance.url());
|
||||
|
|
Loading…
Reference in a new issue