Federate mentions
This commit is contained in:
parent
4da44159ed
commit
2bfb6253f8
6 changed files with 115 additions and 8 deletions
|
@ -22,6 +22,17 @@ pub struct Attachment {
|
||||||
pub url: String,
|
pub url: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct Tag {
|
||||||
|
pub name: String,
|
||||||
|
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
pub tag_type: String,
|
||||||
|
|
||||||
|
pub href: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default, Deserialize, Serialize)]
|
#[derive(Default, Deserialize, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Object {
|
pub struct Object {
|
||||||
|
@ -54,6 +65,9 @@ pub struct Object {
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub content: Option<String>,
|
pub content: Option<String>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub tag: Option<Vec<Tag>>,
|
||||||
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub to: Option<Value>,
|
pub to: Option<Value>,
|
||||||
}
|
}
|
||||||
|
@ -99,6 +113,7 @@ fn create_activity(
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_note(
|
pub fn create_note(
|
||||||
|
instance_host: &str,
|
||||||
instance_url: &str,
|
instance_url: &str,
|
||||||
post: &Post,
|
post: &Post,
|
||||||
in_reply_to: Option<&Post>,
|
in_reply_to: Option<&Post>,
|
||||||
|
@ -122,6 +137,17 @@ pub fn create_note(
|
||||||
}
|
}
|
||||||
}).collect();
|
}).collect();
|
||||||
let mut recipients = vec![AP_PUBLIC.to_string()];
|
let mut recipients = vec![AP_PUBLIC.to_string()];
|
||||||
|
let mentions: Vec<Tag> = post.mentions.iter().map(|profile| {
|
||||||
|
let actor_id = profile.actor_id(instance_url).unwrap();
|
||||||
|
if !profile.is_local() {
|
||||||
|
recipients.push(actor_id.clone());
|
||||||
|
};
|
||||||
|
Tag {
|
||||||
|
name: profile.actor_address(instance_host),
|
||||||
|
tag_type: MENTION.to_string(),
|
||||||
|
href: actor_id,
|
||||||
|
}
|
||||||
|
}).collect();
|
||||||
let in_reply_to_object_id = match post.in_reply_to_id {
|
let in_reply_to_object_id = match post.in_reply_to_id {
|
||||||
Some(in_reply_to_id) => {
|
Some(in_reply_to_id) => {
|
||||||
let post = in_reply_to.unwrap();
|
let post = in_reply_to.unwrap();
|
||||||
|
@ -131,7 +157,9 @@ pub fn create_note(
|
||||||
} else {
|
} else {
|
||||||
// Replying to remote post
|
// Replying to remote post
|
||||||
let remote_actor_id = post.author.actor_id(instance_url).unwrap();
|
let remote_actor_id = post.author.actor_id(instance_url).unwrap();
|
||||||
|
if !recipients.contains(&remote_actor_id) {
|
||||||
recipients.push(remote_actor_id);
|
recipients.push(remote_actor_id);
|
||||||
|
};
|
||||||
post.object_id.clone()
|
post.object_id.clone()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -148,16 +176,18 @@ pub fn create_note(
|
||||||
attributed_to: Some(actor_id),
|
attributed_to: Some(actor_id),
|
||||||
in_reply_to: in_reply_to_object_id,
|
in_reply_to: in_reply_to_object_id,
|
||||||
content: Some(post.content.clone()),
|
content: Some(post.content.clone()),
|
||||||
|
tag: Some(mentions),
|
||||||
to: Some(json!(recipients)),
|
to: Some(json!(recipients)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_activity_note(
|
pub fn create_activity_note(
|
||||||
|
instance_host: &str,
|
||||||
instance_url: &str,
|
instance_url: &str,
|
||||||
post: &Post,
|
post: &Post,
|
||||||
in_reply_to: Option<&Post>,
|
in_reply_to: Option<&Post>,
|
||||||
) -> Activity {
|
) -> Activity {
|
||||||
let object = create_note(instance_url, post, in_reply_to);
|
let object = create_note(instance_host, instance_url, post, in_reply_to);
|
||||||
let activity = create_activity(
|
let activity = create_activity(
|
||||||
instance_url,
|
instance_url,
|
||||||
&post.author.username,
|
&post.author.username,
|
||||||
|
@ -292,6 +322,7 @@ impl OrderedCollection {
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
const INSTANCE_HOST: &str = "example.com";
|
||||||
const INSTANCE_URL: &str = "https://example.com";
|
const INSTANCE_URL: &str = "https://example.com";
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -301,7 +332,7 @@ mod tests {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let post = Post { author, ..Default::default() };
|
let post = Post { author, ..Default::default() };
|
||||||
let note = create_note(INSTANCE_URL, &post, None);
|
let note = create_note(INSTANCE_HOST, INSTANCE_URL, &post, None);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
note.id,
|
note.id,
|
||||||
|
@ -323,7 +354,7 @@ mod tests {
|
||||||
in_reply_to_id: Some(parent.id),
|
in_reply_to_id: Some(parent.id),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let note = create_note(INSTANCE_URL, &post, Some(&parent));
|
let note = create_note(INSTANCE_HOST, INSTANCE_URL, &post, Some(&parent));
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
note.in_reply_to.unwrap(),
|
note.in_reply_to.unwrap(),
|
||||||
|
@ -334,28 +365,35 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_create_note_with_remote_parent() {
|
fn test_create_note_with_remote_parent() {
|
||||||
|
let parent_author_acct = "test@test.net";
|
||||||
let parent_author_actor_id = "https://test.net/user/test";
|
let parent_author_actor_id = "https://test.net/user/test";
|
||||||
let parent_author = DbActorProfile {
|
let parent_author = DbActorProfile {
|
||||||
|
acct: parent_author_acct.to_string(),
|
||||||
actor_json: Some(json!({
|
actor_json: Some(json!({
|
||||||
"id": parent_author_actor_id,
|
"id": parent_author_actor_id,
|
||||||
})),
|
})),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let parent = Post {
|
let parent = Post {
|
||||||
author: parent_author,
|
author: parent_author.clone(),
|
||||||
object_id: Some("https://test.net/obj/123".to_string()),
|
object_id: Some("https://test.net/obj/123".to_string()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let post = Post {
|
let post = Post {
|
||||||
in_reply_to_id: Some(parent.id),
|
in_reply_to_id: Some(parent.id),
|
||||||
|
mentions: vec![parent_author],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let note = create_note(INSTANCE_URL, &post, Some(&parent));
|
let note = create_note(INSTANCE_HOST, INSTANCE_URL, &post, Some(&parent));
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
note.in_reply_to.unwrap(),
|
note.in_reply_to.unwrap(),
|
||||||
parent.object_id.unwrap(),
|
parent.object_id.unwrap(),
|
||||||
);
|
);
|
||||||
|
let tags = note.tag.unwrap();
|
||||||
|
assert_eq!(tags.len(), 1);
|
||||||
|
assert_eq!(tags[0].name, parent_author_acct);
|
||||||
|
assert_eq!(tags[0].href, parent_author_actor_id);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
note.to.unwrap(),
|
note.to.unwrap(),
|
||||||
json!([AP_PUBLIC, parent_author_actor_id]),
|
json!([AP_PUBLIC, parent_author_actor_id]),
|
||||||
|
|
|
@ -176,6 +176,19 @@ pub async fn process_note(
|
||||||
attachments.push(db_attachment.id);
|
attachments.push(db_attachment.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let mut mentions: Vec<Uuid> = Vec::new();
|
||||||
|
if let Some(list) = object.tag {
|
||||||
|
for tag in list {
|
||||||
|
if tag.tag_type == MENTION {
|
||||||
|
let profile = get_or_fetch_profile_by_actor_id(
|
||||||
|
db_client,
|
||||||
|
&tag.href,
|
||||||
|
&config.media_dir(),
|
||||||
|
).await?;
|
||||||
|
mentions.push(profile.id);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
let in_reply_to_id = match object.in_reply_to {
|
let in_reply_to_id = match object.in_reply_to {
|
||||||
Some(object_id) => {
|
Some(object_id) => {
|
||||||
match parse_object_id(&config.instance_url(), &object_id) {
|
match parse_object_id(&config.instance_url(), &object_id) {
|
||||||
|
@ -196,7 +209,7 @@ pub async fn process_note(
|
||||||
content,
|
content,
|
||||||
in_reply_to_id,
|
in_reply_to_id,
|
||||||
attachments: attachments,
|
attachments: attachments,
|
||||||
mentions: vec![],
|
mentions: mentions,
|
||||||
object_id: Some(object.id),
|
object_id: Some(object.id),
|
||||||
created_at: object.published,
|
created_at: object.published,
|
||||||
};
|
};
|
||||||
|
|
|
@ -139,7 +139,12 @@ pub async fn get_object(
|
||||||
},
|
},
|
||||||
None => None,
|
None => None,
|
||||||
};
|
};
|
||||||
let object = create_note(&config.instance_url(), post, in_reply_to);
|
let object = create_note(
|
||||||
|
&config.instance().host(),
|
||||||
|
&config.instance().url(),
|
||||||
|
post,
|
||||||
|
in_reply_to,
|
||||||
|
);
|
||||||
let response = HttpResponse::Ok()
|
let response = HttpResponse::Ok()
|
||||||
.content_type(ACTIVITY_CONTENT_TYPE)
|
.content_type(ACTIVITY_CONTENT_TYPE)
|
||||||
.json(object);
|
.json(object);
|
||||||
|
|
|
@ -13,6 +13,7 @@ pub const PERSON: &str = "Person";
|
||||||
// Object types
|
// Object types
|
||||||
pub const DOCUMENT: &str = "Document";
|
pub const DOCUMENT: &str = "Document";
|
||||||
pub const IMAGE: &str = "Image";
|
pub const IMAGE: &str = "Image";
|
||||||
|
pub const MENTION: &str = "Mention";
|
||||||
pub const NOTE: &str = "Note";
|
pub const NOTE: &str = "Note";
|
||||||
|
|
||||||
// Misc
|
// Misc
|
||||||
|
|
|
@ -74,6 +74,7 @@ async fn create_status(
|
||||||
None => None,
|
None => None,
|
||||||
};
|
};
|
||||||
let activity = create_activity_note(
|
let activity = create_activity_note(
|
||||||
|
&instance.host(),
|
||||||
&instance.url(),
|
&instance.url(),
|
||||||
&post,
|
&post,
|
||||||
maybe_in_reply_to.as_ref(),
|
maybe_in_reply_to.as_ref(),
|
||||||
|
@ -94,6 +95,13 @@ async fn create_status(
|
||||||
recipients.push(remote_actor);
|
recipients.push(remote_actor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for profile in post.mentions.iter() {
|
||||||
|
let maybe_remote_actor = profile.actor()
|
||||||
|
.map_err(|_| HttpError::InternalError)?;
|
||||||
|
if let Some(remote_actor) = maybe_remote_actor {
|
||||||
|
recipients.push(remote_actor);
|
||||||
|
};
|
||||||
|
};
|
||||||
deliver_activity(&config, ¤t_user, activity, recipients);
|
deliver_activity(&config, ¤t_user, activity, recipients);
|
||||||
let status = Status::from_post(post, &instance.url());
|
let status = Status::from_post(post, &instance.url());
|
||||||
Ok(HttpResponse::Created().json(status))
|
Ok(HttpResponse::Created().json(status))
|
||||||
|
|
|
@ -85,6 +85,14 @@ impl DbActorProfile {
|
||||||
};
|
};
|
||||||
Ok(actor_id)
|
Ok(actor_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn actor_address(&self, instance_host: &str) -> String {
|
||||||
|
if self.is_local() {
|
||||||
|
format!("{}@{}", self.acct, instance_host)
|
||||||
|
} else {
|
||||||
|
self.acct.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -156,3 +164,37 @@ impl ProfileUpdateData {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use serde_json::json;
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
const INSTANCE_HOST: &str = "example.com";
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_local_actor_address() {
|
||||||
|
let local_profile = DbActorProfile {
|
||||||
|
acct: "user".to_string(),
|
||||||
|
actor_json: None,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
local_profile.actor_address(INSTANCE_HOST),
|
||||||
|
"user@example.com",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_remote_actor_address() {
|
||||||
|
let remote_profile = DbActorProfile {
|
||||||
|
acct: "test@remote.com".to_string(),
|
||||||
|
actor_json: Some(json!({"id": "https://test"})),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
remote_profile.actor_address(INSTANCE_HOST),
|
||||||
|
remote_profile.acct,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue