diff --git a/src/activities/follow.rs b/src/activities/follow.rs index af7dca4..b0e0f93 100644 --- a/src/activities/follow.rs +++ b/src/activities/follow.rs @@ -1,12 +1,20 @@ -use activitypub_federation::{config::Data, fetch::object_id::ObjectId, traits::ActivityHandler}; -use activitystreams_kinds::activity::FollowType; +use activitypub_federation::{ + activity_sending::SendActivityTask, + config::Data, + fetch::object_id::ObjectId, + protocol::context::WithContext, + traits::{ActivityHandler, Actor, Object}, +}; +use activitystreams_kinds::activity::{AcceptType, FollowType}; use sea_orm::{ActiveModelTrait, Set}; use serde::{Deserialize, Serialize}; use url::Url; use crate::{ database::StateHandle, - entities::{follow_relation, prelude::FollowRelation, user}, + entities::{follow_relation, post, user}, + error, + utils::{generate_follow_accept_id, generate_object_id}, DB, }; @@ -19,6 +27,73 @@ pub struct Follow { id: Url, } +impl Follow { + pub async fn send( + local_user: ObjectId, + followee: ObjectId, + inbox: Url, + data: &Data, + ) -> Result<(), error::Error> { + print!("Sending follow request to {}", &followee); + let create = Follow { + actor: local_user.clone(), + object: followee.clone(), + kind: FollowType::Follow, + id: generate_object_id(data.domain())?, + }; + let create_with_context = WithContext::new_default(create); + let sends = SendActivityTask::prepare( + &create_with_context, + &data.local_user().await?, + vec![inbox], + data, + ) + .await?; + for send in sends { + send.sign_and_send(data).await?; + } + Ok(()) + } +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct Accept { + actor: ObjectId, + object: Follow, + #[serde(rename = "type")] + kind: AcceptType, + id: Url, +} + +impl Accept { + pub async fn send( + follow_relation: follow_relation::Model, + follow_req: Follow, + inbox: Url, + data: &Data, + ) -> Result<(), error::Error> { + print!("Sending accept to {}", &follow_relation.follower_id); + let create = Accept { + actor: follow_req.object.clone(), + object: follow_req, + kind: AcceptType::Accept, + id: generate_follow_accept_id(data.domain(), follow_relation.id)?, + }; + let create_with_context = WithContext::new_default(create); + let sends = SendActivityTask::prepare( + &create_with_context, + &data.local_user().await?, + vec![inbox], + data, + ) + .await?; + for send in sends { + send.sign_and_send(data).await?; + } + Ok(()) + } +} + #[async_trait::async_trait] impl ActivityHandler for Follow { type DataType = StateHandle; @@ -37,17 +112,51 @@ impl ActivityHandler for Follow { } async fn receive(self, data: &Data) -> Result<(), Self::Error> { - let local_user = self.object.dereference(data).await?; - let follower = self.actor.dereference(data).await?; - save_follow(local_user, follower).await?; + accept_follow(self, data).await?; Ok(()) } } +#[async_trait::async_trait] +impl ActivityHandler for Accept { + type DataType = StateHandle; + type Error = crate::error::Error; + + fn id(&self) -> &Url { + &self.id + } + + fn actor(&self) -> &Url { + self.actor.inner() + } + + async fn verify(&self, data: &Data) -> Result<(), Self::Error> { + Ok(()) + } + + async fn receive(self, data: &Data) -> Result<(), Self::Error> { + let user = self.actor.dereference(data).await?; + let follower = self.object.actor.dereference(data).await?; + save_follow(user, follower).await?; + Ok(()) + } +} + +async fn accept_follow( + follow_req: Follow, + data: &Data, +) -> Result<(), crate::error::Error> { + let local_user = follow_req.actor.dereference(data).await?; + let follower = follow_req.object.dereference(data).await?; + let follow_relation = save_follow(local_user, follower.clone()).await?; + Accept::send(follow_relation, follow_req, follower.inbox().clone(), data).await?; + Ok(()) +} + async fn save_follow( local_user: user::Model, follower: user::Model, -) -> Result<(), crate::error::Error> { +) -> Result { let url = Url::parse(&follower.url)?; let follow_relation = follow_relation::ActiveModel { followee_id: Set(local_user.id.clone()), @@ -58,6 +167,6 @@ async fn save_follow( follower_inbox: Set(Some(follower.inbox.clone())), ..Default::default() }; - follow_relation.insert(DB.get().unwrap()).await?; - Ok(()) + let model = follow_relation.insert(DB.get().unwrap()).await?; + Ok(model) } diff --git a/src/main.rs b/src/main.rs index 670ebc5..1891266 100644 --- a/src/main.rs +++ b/src/main.rs @@ -28,13 +28,13 @@ use tokio::signal; use tracing::{info, instrument::WithSubscriber}; use url::Url; -use crate::entities::user; use crate::utils::generate_object_id; use crate::{ activities::create_post::CreatePost, database::{Config, State}, objects::post::{Mention, Note}, }; +use crate::{activities::follow::Follow, entities::user}; use lazy_static::lazy_static; mod activities; @@ -105,6 +105,31 @@ async fn post_manually( Ok(HttpResponse::Ok().json(Response { health: true })) } +#[get("/test/follow/{user}")] +async fn follow_manually( + path: web::Path, + state: web::Data, +) -> actix_web::Result { + let local_user = state.local_user().await?; + let data = FEDERATION_CONFIG.get().unwrap(); + let followee = + webfinger_resolve_actor::(path.as_str(), &data.to_request_data()) + .await?; + + let followee_object: ObjectId = Url::parse(&followee.id)?.into(); + let localuser_object: ObjectId = Url::parse(&local_user.id)?.into(); + + Follow::send( + localuser_object, + followee_object, + followee.shared_inbox_or_inbox(), + &data.to_request_data(), + ) + .await?; + + Ok(HttpResponse::Ok().json(Response { health: true })) +} + const DOMAIN_DEF: &str = "example.com"; const LOCAL_USER_NAME: &str = "example"; @@ -209,6 +234,7 @@ async fn main() -> actix_web::Result<(), anyhow::Error> { .wrap(prometheus.clone()) .wrap(FederationMiddleware::new(data.clone())) .service(post_manually) + .service(follow_manually) .route("/{user}", web::get().to(http_get_user)) .route("/{user}/inbox", web::post().to(http_post_user_inbox)) .route("/.well-known/webfinger", web::get().to(webfinger)) diff --git a/src/utils.rs b/src/utils.rs index 0b2b098..04f4195 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -11,3 +11,8 @@ pub fn generate_object_id(domain: &str) -> Result { .collect(); Url::parse(&format!("https://{}/objects/{}", domain, id)) } + +/// Generate a follow accept id +pub fn generate_follow_accept_id(domain: &str, db_id: i32) -> Result { + Url::parse(&format!("https://{}/activities/follow/{}", domain, db_id)) +}