mirror of
https://git.joinplu.me/Plume/Plume.git
synced 2024-11-19 10:11:00 +00:00
Introduce some traits for handling incoming activities
This commit is contained in:
parent
7d17751f50
commit
36c11182f4
7 changed files with 168 additions and 110 deletions
|
@ -1,27 +1,21 @@
|
|||
use activitypub::{
|
||||
Actor,
|
||||
activity::{Accept, Announce, Create, Follow, Like, Undo},
|
||||
object::{Article, Note}
|
||||
Activity, Object,
|
||||
activity::{Create, Like, Undo}
|
||||
};
|
||||
use diesel::PgConnection;
|
||||
use failure::Error;
|
||||
use serde_json;
|
||||
|
||||
use activity_pub::{
|
||||
broadcast, Id, IntoId,
|
||||
actor::Actor as APActor,
|
||||
sign::*
|
||||
Id
|
||||
};
|
||||
use models::{
|
||||
blogs::Blog,
|
||||
comments::*,
|
||||
follows,
|
||||
follows::Follow,
|
||||
likes,
|
||||
posts::*,
|
||||
reshares::*,
|
||||
users::User
|
||||
reshares::*
|
||||
};
|
||||
use safe_string::SafeString;
|
||||
|
||||
#[derive(Fail, Debug)]
|
||||
enum InboxError {
|
||||
|
@ -33,98 +27,69 @@ enum InboxError {
|
|||
CantUndo
|
||||
}
|
||||
|
||||
pub trait FromActivity<T: Object>: Sized {
|
||||
fn from_activity(conn: &PgConnection, obj: T, actor: Id) -> Self;
|
||||
|
||||
fn try_from_activity(conn: &PgConnection, act: Create) -> bool {
|
||||
if let Ok(obj) = act.create_props.object_object() {
|
||||
Self::from_activity(conn, obj, act.create_props.actor_link::<Id>().unwrap());
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Notify<T: Activity> {
|
||||
fn notify(conn: &PgConnection, act: T, actor: Id);
|
||||
}
|
||||
|
||||
pub trait Deletable {
|
||||
/// true if success
|
||||
fn delete_activity(conn: &PgConnection, id: Id) -> bool;
|
||||
}
|
||||
|
||||
pub trait Inbox {
|
||||
fn received(&self, conn: &PgConnection, act: serde_json::Value);
|
||||
|
||||
fn new_article(&self, conn: &PgConnection, article: Article) -> Result<(), Error> {
|
||||
Post::insert(conn, NewPost {
|
||||
blog_id: 0, // TODO
|
||||
slug: String::from(""), // TODO
|
||||
title: article.object_props.name_string().unwrap(),
|
||||
content: SafeString::new(&article.object_props.content_string().unwrap()),
|
||||
published: true,
|
||||
license: String::from("CC-0"),
|
||||
ap_url: article.object_props.url_string()?
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn new_comment(&self, conn: &PgConnection, note: Note, actor_id: String) -> Result<(), Error> {
|
||||
let previous_url = note.object_props.in_reply_to.clone().unwrap().as_str().unwrap().to_string();
|
||||
let previous_comment = Comment::find_by_ap_url(conn, previous_url.clone());
|
||||
Comment::insert(conn, NewComment {
|
||||
content: SafeString::new(¬e.object_props.content_string().unwrap()),
|
||||
spoiler_text: note.object_props.summary_string().unwrap_or(String::from("")),
|
||||
ap_url: note.object_props.id_string().ok(),
|
||||
in_response_to_id: previous_comment.clone().map(|c| c.id),
|
||||
post_id: previous_comment
|
||||
.map(|c| c.post_id)
|
||||
.unwrap_or_else(|| Post::find_by_ap_url(conn, previous_url).unwrap().id),
|
||||
author_id: User::from_url(conn, actor_id).unwrap().id,
|
||||
sensitive: false // "sensitive" is not a standard property, we need to think about how to support it with the activitystreams crate
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn follow(&self, conn: &PgConnection, follow: Follow) -> Result<(), Error> {
|
||||
let from = User::from_url(conn, follow.follow_props.actor.as_str().unwrap().to_string()).unwrap();
|
||||
match User::from_url(conn, follow.follow_props.object.as_str().unwrap().to_string()) {
|
||||
Some(u) => self.accept_follow(conn, &from, &u, follow, from.id, u.id),
|
||||
None => {
|
||||
let blog = Blog::from_url(conn, follow.follow_props.object.as_str().unwrap().to_string()).unwrap();
|
||||
self.accept_follow(conn, &from, &blog, follow, from.id, blog.id)
|
||||
}
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn like(&self, conn: &PgConnection, like: Like) -> Result<(), Error> {
|
||||
let liker = User::from_url(conn, like.like_props.actor.as_str().unwrap().to_string());
|
||||
let post = Post::find_by_ap_url(conn, like.like_props.object.as_str().unwrap().to_string());
|
||||
likes::Like::insert(conn, likes::NewLike {
|
||||
post_id: post.unwrap().id,
|
||||
user_id: liker.unwrap().id,
|
||||
ap_url: like.object_props.id_string()?
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unlike(&self, conn: &PgConnection, undo: Undo) -> Result<(), Error> {
|
||||
let like = likes::Like::find_by_ap_url(conn, undo.undo_props.object_object::<Like>()?.object_props.id_string()?).unwrap();
|
||||
like.delete(conn);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn announce(&self, conn: &PgConnection, announce: Announce) -> Result<(), Error> {
|
||||
let user = User::from_url(conn, announce.announce_props.actor.as_str().unwrap().to_string());
|
||||
let post = Post::find_by_ap_url(conn, announce.announce_props.object.as_str().unwrap().to_string());
|
||||
Reshare::insert(conn, NewReshare {
|
||||
post_id: post.unwrap().id,
|
||||
user_id: user.unwrap().id,
|
||||
ap_url: announce.object_props.id_string()?
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn save(&self, conn: &PgConnection, act: serde_json::Value) -> Result<(), Error> {
|
||||
let actor_id = Id::new(act["actor"].as_str().unwrap());
|
||||
match act["type"].as_str() {
|
||||
Some(t) => {
|
||||
match t {
|
||||
"Announce" => self.announce(conn, serde_json::from_value(act.clone())?),
|
||||
"Announce" => {
|
||||
Reshare::from_activity(conn, serde_json::from_value(act.clone())?, actor_id);
|
||||
Ok(())
|
||||
},
|
||||
"Create" => {
|
||||
let act: Create = serde_json::from_value(act.clone())?;
|
||||
match act.create_props.object["type"].as_str().unwrap() {
|
||||
"Article" => self.new_article(conn, act.create_props.object_object()?),
|
||||
"Note" => self.new_comment(conn, act.create_props.object_object()?, act.create_props.actor_link::<Id>()?.0),
|
||||
_ => Err(InboxError::InvalidType)?
|
||||
if Post::try_from_activity(conn, act.clone()) || Comment::try_from_activity(conn, act) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(InboxError::InvalidType)?
|
||||
}
|
||||
},
|
||||
"Follow" => self.follow(conn, serde_json::from_value(act.clone())?),
|
||||
"Like" => self.like(conn, serde_json::from_value(act.clone())?),
|
||||
"Follow" => {
|
||||
Follow::from_activity(conn, serde_json::from_value(act.clone())?, actor_id);
|
||||
Ok(())
|
||||
},
|
||||
"Like" => {
|
||||
likes::Like::from_activity(conn, serde_json::from_value(act.clone())?, actor_id);
|
||||
Ok(())
|
||||
},
|
||||
"Undo" => {
|
||||
let act: Undo = serde_json::from_value(act.clone())?;
|
||||
match act.undo_props.object["type"].as_str().unwrap() {
|
||||
"Like" => self.unlike(conn, act),
|
||||
"Like" => {
|
||||
likes::Like::delete_activity(conn, Id::new(act.undo_props.object_object::<Like>()?.object_props.id_string()?));
|
||||
Ok(())
|
||||
},
|
||||
_ => Err(InboxError::CantUndo)?
|
||||
}
|
||||
}
|
||||
|
@ -134,26 +99,6 @@ pub trait Inbox {
|
|||
None => Err(InboxError::NoType)?
|
||||
}
|
||||
}
|
||||
|
||||
fn accept_follow<A: Signer + IntoId + Clone, B: Clone + WithInbox + Actor>(
|
||||
&self,
|
||||
conn: &PgConnection,
|
||||
from: &A,
|
||||
target: &B,
|
||||
follow: Follow,
|
||||
from_id: i32,
|
||||
target_id: i32
|
||||
) {
|
||||
follows::Follow::insert(conn, follows::NewFollow {
|
||||
follower_id: from_id,
|
||||
following_id: target_id
|
||||
});
|
||||
|
||||
let mut accept = Accept::default();
|
||||
accept.accept_props.set_actor_link::<Id>(from.clone().into_id()).unwrap();
|
||||
accept.accept_props.set_object_object(follow).unwrap();
|
||||
broadcast(conn, &*from, accept, vec![target.clone()]);
|
||||
}
|
||||
}
|
||||
|
||||
pub trait WithInbox {
|
||||
|
|
|
@ -111,6 +111,12 @@ impl Id {
|
|||
}
|
||||
}
|
||||
|
||||
impl Into<String> for Id {
|
||||
fn into(self) -> String {
|
||||
self.0.clone()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait IntoId {
|
||||
fn into_id(self) -> Id;
|
||||
}
|
||||
|
|
|
@ -7,8 +7,9 @@ use diesel::{self, PgConnection, RunQueryDsl, QueryDsl, ExpressionMethods, dsl::
|
|||
use serde_json;
|
||||
|
||||
use activity_pub::{
|
||||
ap_url, IntoId, PUBLIC_VISIBILTY,
|
||||
ap_url, Id, IntoId, PUBLIC_VISIBILTY,
|
||||
actor::Actor,
|
||||
inbox::FromActivity,
|
||||
object::Object
|
||||
};
|
||||
use models::{
|
||||
|
@ -123,6 +124,24 @@ impl Comment {
|
|||
}
|
||||
}
|
||||
|
||||
impl FromActivity<Note> for Comment {
|
||||
fn from_activity(conn: &PgConnection, note: Note, actor: Id) -> Comment {
|
||||
let previous_url = note.object_props.in_reply_to.clone().unwrap().as_str().unwrap().to_string();
|
||||
let previous_comment = Comment::find_by_ap_url(conn, previous_url.clone());
|
||||
Comment::insert(conn, NewComment {
|
||||
content: SafeString::new(¬e.object_props.content_string().unwrap()),
|
||||
spoiler_text: note.object_props.summary_string().unwrap_or(String::from("")),
|
||||
ap_url: note.object_props.id_string().ok(),
|
||||
in_response_to_id: previous_comment.clone().map(|c| c.id),
|
||||
post_id: previous_comment
|
||||
.map(|c| c.post_id)
|
||||
.unwrap_or_else(|| Post::find_by_ap_url(conn, previous_url).unwrap().id),
|
||||
author_id: User::from_url(conn, actor.into()).unwrap().id,
|
||||
sensitive: false // "sensitive" is not a standard property, we need to think about how to support it with the activitypub crate
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Object for Comment {
|
||||
fn serialize(&self, conn: &PgConnection) -> serde_json::Value {
|
||||
let mut to = self.get_author(conn).get_followers(conn).into_iter().map(|f| f.ap_url).collect::<Vec<String>>();
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
use activitypub::{Actor, activity::{Accept, Follow as FollowAct}};
|
||||
use diesel::{self, PgConnection, ExpressionMethods, QueryDsl, RunQueryDsl};
|
||||
|
||||
use activity_pub::{broadcast, Id, IntoId, actor::Actor as ApActor, inbox::{FromActivity, WithInbox}, sign::Signer};
|
||||
use models::blogs::Blog;
|
||||
use models::users::User;
|
||||
use schema::follows;
|
||||
|
||||
|
@ -33,4 +36,37 @@ impl Follow {
|
|||
.expect("Unable to load follow by id")
|
||||
.into_iter().nth(0)
|
||||
}
|
||||
|
||||
pub fn accept_follow<A: Signer + IntoId + Clone, B: Clone + WithInbox + Actor>(
|
||||
conn: &PgConnection,
|
||||
from: &A,
|
||||
target: &B,
|
||||
follow: FollowAct,
|
||||
from_id: i32,
|
||||
target_id: i32
|
||||
) -> Follow {
|
||||
let res = Follow::insert(conn, NewFollow {
|
||||
follower_id: from_id,
|
||||
following_id: target_id
|
||||
});
|
||||
|
||||
let mut accept = Accept::default();
|
||||
accept.accept_props.set_actor_link::<Id>(from.clone().into_id()).unwrap();
|
||||
accept.accept_props.set_object_object(follow).unwrap();
|
||||
broadcast(conn, &*from, accept, vec![target.clone()]);
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl FromActivity<FollowAct> for Follow {
|
||||
fn from_activity(conn: &PgConnection, follow: FollowAct, _actor: Id) -> Follow {
|
||||
let from = User::from_url(conn, follow.follow_props.actor.as_str().unwrap().to_string()).unwrap();
|
||||
match User::from_url(conn, follow.follow_props.object.as_str().unwrap().to_string()) {
|
||||
Some(u) => Follow::accept_follow(conn, &from, &u, follow, from.id, u.id),
|
||||
None => {
|
||||
let blog = Blog::from_url(conn, follow.follow_props.object.as_str().unwrap().to_string()).unwrap();
|
||||
Follow::accept_follow(conn, &from, &blog, follow, from.id, blog.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,8 +4,10 @@ use diesel::{self, PgConnection, QueryDsl, RunQueryDsl, ExpressionMethods};
|
|||
use serde_json;
|
||||
|
||||
use activity_pub::{
|
||||
Id,
|
||||
IntoId,
|
||||
actor::Actor,
|
||||
inbox::{FromActivity, Deletable},
|
||||
object::Object
|
||||
};
|
||||
use models::{
|
||||
|
@ -94,6 +96,29 @@ impl Like {
|
|||
}
|
||||
}
|
||||
|
||||
impl FromActivity<activity::Like> for Like {
|
||||
fn from_activity(conn: &PgConnection, like: activity::Like, _actor: Id) -> Like {
|
||||
let liker = User::from_url(conn, like.like_props.actor.as_str().unwrap().to_string());
|
||||
let post = Post::find_by_ap_url(conn, like.like_props.object.as_str().unwrap().to_string());
|
||||
Like::insert(conn, NewLike {
|
||||
post_id: post.unwrap().id,
|
||||
user_id: liker.unwrap().id,
|
||||
ap_url: like.object_props.id_string().unwrap_or(String::from(""))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Deletable for Like {
|
||||
fn delete_activity(conn: &PgConnection, id: Id) -> bool {
|
||||
if let Some(like) = Like::find_by_ap_url(conn, id.into()) {
|
||||
like.delete(conn);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Object for Like {
|
||||
fn serialize(&self, conn: &PgConnection) -> serde_json::Value {
|
||||
json!({
|
||||
|
|
|
@ -10,6 +10,7 @@ use BASE_URL;
|
|||
use activity_pub::{
|
||||
PUBLIC_VISIBILTY, ap_url, Id, IntoId,
|
||||
actor::Actor,
|
||||
inbox::FromActivity,
|
||||
object::Object
|
||||
};
|
||||
use models::{
|
||||
|
@ -195,6 +196,20 @@ impl Post {
|
|||
}
|
||||
}
|
||||
|
||||
impl FromActivity<Article> for Post {
|
||||
fn from_activity(conn: &PgConnection, article: Article, _actor: Id) -> Post {
|
||||
Post::insert(conn, NewPost {
|
||||
blog_id: 0, // TODO
|
||||
slug: String::from(""), // TODO
|
||||
title: article.object_props.name_string().unwrap(),
|
||||
content: SafeString::new(&article.object_props.content_string().unwrap()),
|
||||
published: true,
|
||||
license: String::from("CC-0"),
|
||||
ap_url: article.object_props.url_string().unwrap_or(String::from(""))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoId for Post {
|
||||
fn into_id(self) -> Id {
|
||||
Id::new(self.ap_url.clone())
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
use activitypub::activity;
|
||||
use activitypub::activity::{Announce, Undo};
|
||||
use chrono::NaiveDateTime;
|
||||
use diesel::{self, PgConnection, QueryDsl, RunQueryDsl, ExpressionMethods};
|
||||
|
||||
use activity_pub::{IntoId, actor::Actor, object::Object};
|
||||
use activity_pub::{Id, IntoId, actor::Actor, inbox::FromActivity, object::Object};
|
||||
use models::{posts::Post, users::User};
|
||||
use schema::reshares;
|
||||
|
||||
|
@ -80,17 +80,17 @@ impl Reshare {
|
|||
Post::get(conn, self.post_id)
|
||||
}
|
||||
|
||||
pub fn delete(&self, conn: &PgConnection) -> activity::Undo {
|
||||
pub fn delete(&self, conn: &PgConnection) -> Undo {
|
||||
diesel::delete(self).execute(conn).unwrap();
|
||||
|
||||
let mut act = activity::Undo::default();
|
||||
let mut act = Undo::default();
|
||||
act.undo_props.set_actor_link(User::get(conn, self.user_id).unwrap().into_id()).unwrap();
|
||||
act.undo_props.set_object_object(self.into_activity(conn)).unwrap();
|
||||
act
|
||||
}
|
||||
|
||||
pub fn into_activity(&self, conn: &PgConnection) -> activity::Announce {
|
||||
let mut act = activity::Announce::default();
|
||||
pub fn into_activity(&self, conn: &PgConnection) -> Announce {
|
||||
let mut act = Announce::default();
|
||||
act.announce_props.set_actor_link(User::get(conn, self.user_id).unwrap().into_id()).unwrap();
|
||||
act.announce_props.set_object_link(Post::get(conn, self.post_id).unwrap().into_id()).unwrap();
|
||||
act.object_props.set_id_string(self.ap_url.clone()).unwrap();
|
||||
|
@ -98,3 +98,15 @@ impl Reshare {
|
|||
act
|
||||
}
|
||||
}
|
||||
|
||||
impl FromActivity<Announce> for Reshare {
|
||||
fn from_activity(conn: &PgConnection, announce: Announce, _actor: Id) -> Reshare {
|
||||
let user = User::from_url(conn, announce.announce_props.actor.as_str().unwrap().to_string());
|
||||
let post = Post::find_by_ap_url(conn, announce.announce_props.object.as_str().unwrap().to_string());
|
||||
Reshare::insert(conn, NewReshare {
|
||||
post_id: post.unwrap().id,
|
||||
user_id: user.unwrap().id,
|
||||
ap_url: announce.object_props.id_string().unwrap_or(String::from(""))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue