mirror of
https://github.com/versia-pub/activitypub.git
synced 2025-12-06 06:38:20 +01:00
feat: meow meow akkoma?
This commit is contained in:
parent
cd6ff024e4
commit
dc4afd8411
10
Cargo.lock
generated
10
Cargo.lock
generated
|
|
@ -571,6 +571,15 @@ version = "0.22.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "base64-url"
|
||||||
|
version = "3.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "38e2b6c78c06f7288d5e3c3d683bde35a79531127c83b087e5d0d77c974b4b28"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.22.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base64ct"
|
name = "base64ct"
|
||||||
version = "1.6.0"
|
version = "1.6.0"
|
||||||
|
|
@ -2061,6 +2070,7 @@ dependencies = [
|
||||||
"async-recursion",
|
"async-recursion",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"async_once",
|
"async_once",
|
||||||
|
"base64-url",
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
"dotenv",
|
"dotenv",
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ time = { version = "0.3.36", features = ["serde"] }
|
||||||
serde_derive = "1.0.201"
|
serde_derive = "1.0.201"
|
||||||
dotenv = "0.15.0"
|
dotenv = "0.15.0"
|
||||||
async-recursion = "1.1.1"
|
async-recursion = "1.1.1"
|
||||||
|
base64-url = "3.0.0"
|
||||||
|
|
||||||
[dependencies.sea-orm]
|
[dependencies.sea-orm]
|
||||||
version = "0.12.0"
|
version = "0.12.0"
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ mod m20220101_000001_post_table;
|
||||||
mod m20240417_230111_user_table;
|
mod m20240417_230111_user_table;
|
||||||
mod m20240417_233430_post_user_keys;
|
mod m20240417_233430_post_user_keys;
|
||||||
mod m20240505_002524_user_follow_relation;
|
mod m20240505_002524_user_follow_relation;
|
||||||
|
mod m20240626_030922_store_ap_json_in_posts;
|
||||||
|
|
||||||
pub struct Migrator;
|
pub struct Migrator;
|
||||||
|
|
||||||
|
|
@ -15,6 +16,7 @@ impl MigratorTrait for Migrator {
|
||||||
Box::new(m20240417_230111_user_table::Migration),
|
Box::new(m20240417_230111_user_table::Migration),
|
||||||
Box::new(m20240417_233430_post_user_keys::Migration),
|
Box::new(m20240417_233430_post_user_keys::Migration),
|
||||||
Box::new(m20240505_002524_user_follow_relation::Migration),
|
Box::new(m20240505_002524_user_follow_relation::Migration),
|
||||||
|
Box::new(m20240626_030922_store_ap_json_in_posts::Migration),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
35
migration/src/m20240626_030922_store_ap_json_in_posts.rs
Normal file
35
migration/src/m20240626_030922_store_ap_json_in_posts.rs
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
use sea_orm_migration::prelude::*;
|
||||||
|
|
||||||
|
#[derive(DeriveMigrationName)]
|
||||||
|
pub struct Migration;
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl MigrationTrait for Migration {
|
||||||
|
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||||
|
manager
|
||||||
|
.alter_table(
|
||||||
|
Table::alter()
|
||||||
|
.table(Post::Table)
|
||||||
|
.add_column_if_not_exists(ColumnDef::new(Post::ApJson).string())
|
||||||
|
.to_owned(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||||
|
manager
|
||||||
|
.alter_table(
|
||||||
|
Table::alter()
|
||||||
|
.table(Post::Table)
|
||||||
|
.drop_column(Post::ApJson)
|
||||||
|
.to_owned(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(DeriveIden)]
|
||||||
|
pub enum Post {
|
||||||
|
Table,
|
||||||
|
ApJson,
|
||||||
|
}
|
||||||
|
|
@ -6,7 +6,7 @@ use crate::{
|
||||||
person::DbUser,
|
person::DbUser,
|
||||||
post::{DbPost, Note},
|
post::{DbPost, Note},
|
||||||
},
|
},
|
||||||
utils::generate_random_object_id,
|
utils::{base_url_encode, generate_create_id, generate_random_object_id},
|
||||||
};
|
};
|
||||||
use activitypub_federation::{
|
use activitypub_federation::{
|
||||||
activity_sending::SendActivityTask,
|
activity_sending::SendActivityTask,
|
||||||
|
|
@ -32,14 +32,20 @@ pub struct CreatePost {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CreatePost {
|
impl CreatePost {
|
||||||
pub async fn send(note: Note, inbox: Url, data: &Data<StateHandle>) -> Result<(), Error> {
|
pub async fn send(
|
||||||
|
note: Note,
|
||||||
|
db_entry: post::Model,
|
||||||
|
inbox: Url,
|
||||||
|
data: &Data<StateHandle>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
print!("Sending reply to {}", ¬e.attributed_to);
|
print!("Sending reply to {}", ¬e.attributed_to);
|
||||||
|
let encoded_url = base_url_encode(¬e.id.clone().into());
|
||||||
let create = CreatePost {
|
let create = CreatePost {
|
||||||
actor: note.attributed_to.clone(),
|
actor: note.attributed_to.clone(),
|
||||||
to: note.to.clone(),
|
to: note.to.clone(),
|
||||||
object: note,
|
object: note,
|
||||||
kind: CreateType::Create,
|
kind: CreateType::Create,
|
||||||
id: generate_random_object_id(data.domain())?,
|
id: generate_create_id(data.domain(), &db_entry.id, &encoded_url)?,
|
||||||
};
|
};
|
||||||
let create_with_context = WithContext::new_default(create);
|
let create_with_context = WithContext::new_default(create);
|
||||||
let sends = SendActivityTask::prepare(
|
let sends = SendActivityTask::prepare(
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@ impl Accept {
|
||||||
actor: follow_req.object.clone(),
|
actor: follow_req.object.clone(),
|
||||||
object: follow_req,
|
object: follow_req,
|
||||||
kind: AcceptType::Accept,
|
kind: AcceptType::Accept,
|
||||||
id: generate_follow_accept_id(data.domain(), follow_relation.id)?,
|
id: generate_follow_accept_id(data.domain(), follow_relation.id.to_string().as_str())?,
|
||||||
};
|
};
|
||||||
let create_with_context = WithContext::new_default(create);
|
let create_with_context = WithContext::new_default(create);
|
||||||
let sends = SendActivityTask::prepare(
|
let sends = SendActivityTask::prepare(
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ pub struct Model {
|
||||||
pub spoiler_text: Option<String>,
|
pub spoiler_text: Option<String>,
|
||||||
pub creator: String,
|
pub creator: String,
|
||||||
pub url: String,
|
pub url: String,
|
||||||
|
pub ap_json: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
|
|
|
||||||
|
|
@ -229,6 +229,7 @@ pub async fn receive_lysand_note(
|
||||||
reply_id: Set(reply_uuid),
|
reply_id: Set(reply_uuid),
|
||||||
quoting_id: Set(quote_uuid),
|
quoting_id: Set(quote_uuid),
|
||||||
spoiler_text: Set(note.subject),
|
spoiler_text: Set(note.subject),
|
||||||
|
ap_json: Set(Some(serde_json::to_string(&ap_note).unwrap())),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let res = post.insert(DB.get().unwrap()).await?;
|
let res = post.insert(DB.get().unwrap()).await?;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,7 @@
|
||||||
use activitypub_federation::{traits::Object, FEDERATION_CONTENT_TYPE};
|
use activitypub_federation::{
|
||||||
|
protocol::context::WithContext, traits::Object, FEDERATION_CONTENT_TYPE,
|
||||||
|
};
|
||||||
|
use activitystreams_kinds::{activity::CreateType, object};
|
||||||
use actix_web::{get, web, HttpResponse};
|
use actix_web::{get, web, HttpResponse};
|
||||||
use sea_orm::{ColumnTrait, EntityTrait, QueryFilter};
|
use sea_orm::{ColumnTrait, EntityTrait, QueryFilter};
|
||||||
|
|
||||||
|
|
@ -8,7 +11,9 @@ use crate::{
|
||||||
post::{self, Entity},
|
post::{self, Entity},
|
||||||
prelude,
|
prelude,
|
||||||
},
|
},
|
||||||
error, Response, DB, FEDERATION_CONFIG,
|
error, objects,
|
||||||
|
utils::{base_url_decode, generate_create_id},
|
||||||
|
Response, DB, FEDERATION_CONFIG,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[get("/apbridge/object/{post}")]
|
#[get("/apbridge/object/{post}")]
|
||||||
|
|
@ -35,3 +40,40 @@ async fn fetch_post(
|
||||||
.await?,
|
.await?,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[get("/apbridge/create/{id}/{base64url}")]
|
||||||
|
async fn create_activity(
|
||||||
|
path: web::Path<(String, String)>,
|
||||||
|
state: web::Data<State>,
|
||||||
|
) -> actix_web::Result<HttpResponse, error::Error> {
|
||||||
|
let db = DB.get().unwrap();
|
||||||
|
|
||||||
|
let url = base_url_decode(path.1.as_str());
|
||||||
|
|
||||||
|
let post = prelude::Post::find()
|
||||||
|
.filter(post::Column::Id.eq(path.0.as_str()))
|
||||||
|
.one(db)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let post = match post {
|
||||||
|
Some(post) => post,
|
||||||
|
None => return Ok(HttpResponse::NotFound().finish()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let ap_post = crate::objects::post::Note::from_db(&post);
|
||||||
|
|
||||||
|
let data = FEDERATION_CONFIG.get().unwrap();
|
||||||
|
|
||||||
|
let create = crate::activities::create_post::CreatePost {
|
||||||
|
actor: ap_post.attributed_to.clone(),
|
||||||
|
to: ap_post.to.clone(),
|
||||||
|
object: ap_post,
|
||||||
|
kind: CreateType::Create,
|
||||||
|
id: generate_create_id(&data.to_request_data().domain(), &path.0, &path.1)?,
|
||||||
|
};
|
||||||
|
let create_with_context = WithContext::new_default(create);
|
||||||
|
|
||||||
|
Ok(HttpResponse::Ok()
|
||||||
|
.content_type(FEDERATION_CONTENT_TYPE)
|
||||||
|
.json(create_with_context))
|
||||||
|
}
|
||||||
|
|
|
||||||
23
src/main.rs
23
src/main.rs
|
|
@ -15,6 +15,7 @@ use clap::Parser;
|
||||||
use database::Database;
|
use database::Database;
|
||||||
use entities::post;
|
use entities::post;
|
||||||
use http::{http_get_user, http_post_user_inbox, webfinger};
|
use http::{http_get_user, http_post_user_inbox, webfinger};
|
||||||
|
use lysand::http::{create_activity, fetch_post};
|
||||||
use objects::person::DbUser;
|
use objects::person::DbUser;
|
||||||
use sea_orm::{ActiveModelTrait, DatabaseConnection, Set};
|
use sea_orm::{ActiveModelTrait, DatabaseConnection, Set};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
@ -89,7 +90,7 @@ async fn post_manually(
|
||||||
let id: ObjectId<post::Model> = generate_random_object_id(data.domain())?.into();
|
let id: ObjectId<post::Model> = generate_random_object_id(data.domain())?.into();
|
||||||
let note = Note {
|
let note = Note {
|
||||||
kind: Default::default(),
|
kind: Default::default(),
|
||||||
id,
|
id: id.clone(),
|
||||||
sensitive: false,
|
sensitive: false,
|
||||||
attributed_to: Url::parse(&local_user.id).unwrap().into(),
|
attributed_to: Url::parse(&local_user.id).unwrap().into(),
|
||||||
to: vec![public()],
|
to: vec![public()],
|
||||||
|
|
@ -99,8 +100,26 @@ async fn post_manually(
|
||||||
cc: vec![].into(),
|
cc: vec![].into(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let post = entities::post::ActiveModel {
|
||||||
|
id: Set(uuid::Uuid::now_v7().to_string()),
|
||||||
|
creator: Set(local_user.id.clone()),
|
||||||
|
content: Set(note.content.clone()),
|
||||||
|
sensitive: Set(false),
|
||||||
|
created_at: Set(Utc::now()),
|
||||||
|
local: Set(true),
|
||||||
|
updated_at: Set(Some(Utc::now())),
|
||||||
|
content_type: Set("Note".to_string()),
|
||||||
|
visibility: Set("public".to_string()),
|
||||||
|
url: Set(id.to_string()),
|
||||||
|
ap_json: Set(Some(serde_json::to_string(¬e).unwrap())),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let post = post.insert(DB.get().unwrap()).await?;
|
||||||
|
|
||||||
CreatePost::send(
|
CreatePost::send(
|
||||||
note,
|
note,
|
||||||
|
post,
|
||||||
target.shared_inbox_or_inbox(),
|
target.shared_inbox_or_inbox(),
|
||||||
&data.to_request_data(),
|
&data.to_request_data(),
|
||||||
)
|
)
|
||||||
|
|
@ -238,6 +257,8 @@ async fn main() -> actix_web::Result<(), anyhow::Error> {
|
||||||
.route("/{user}/inbox", web::post().to(http_post_user_inbox))
|
.route("/{user}/inbox", web::post().to(http_post_user_inbox))
|
||||||
.route("/.well-known/webfinger", web::get().to(webfinger))
|
.route("/.well-known/webfinger", web::get().to(webfinger))
|
||||||
.service(index)
|
.service(index)
|
||||||
|
.service(fetch_post)
|
||||||
|
.service(create_activity)
|
||||||
})
|
})
|
||||||
.bind(SERVER_URL.to_string())?
|
.bind(SERVER_URL.to_string())?
|
||||||
.workers(num_cpus::get())
|
.workers(num_cpus::get())
|
||||||
|
|
|
||||||
|
|
@ -176,4 +176,9 @@ impl Actor for user::Model {
|
||||||
fn inbox(&self) -> Url {
|
fn inbox(&self) -> Url {
|
||||||
Url::parse(&self.inbox).unwrap()
|
Url::parse(&self.inbox).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//TODO: Differenciate shared inbox
|
||||||
|
fn shared_inbox(&self) -> Option<Url> {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,12 @@ pub struct Note {
|
||||||
pub(crate) cc: Option<Vec<Url>>,
|
pub(crate) cc: Option<Vec<Url>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Note {
|
||||||
|
pub fn from_db(post: &post::Model) -> Self {
|
||||||
|
serde_json::from_str(&post.ap_json.as_ref().unwrap()).unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
pub struct Mention {
|
pub struct Mention {
|
||||||
pub href: Url,
|
pub href: Url,
|
||||||
|
|
|
||||||
30
src/utils.rs
30
src/utils.rs
|
|
@ -12,13 +12,35 @@ pub fn generate_user_id(domain: &str, uuid: &str) -> Result<Url, ParseError> {
|
||||||
|
|
||||||
pub fn generate_random_object_id(domain: &str) -> Result<Url, ParseError> {
|
pub fn generate_random_object_id(domain: &str) -> Result<Url, ParseError> {
|
||||||
let id: String = uuid::Uuid::new_v4().to_string();
|
let id: String = uuid::Uuid::new_v4().to_string();
|
||||||
Url::parse(&format!("https://{}/apbridge/object/{}", domain, id))
|
generate_object_id(domain, &id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate a follow accept id
|
/// Generate a follow accept id
|
||||||
pub fn generate_follow_accept_id(domain: &str, db_id: i32) -> Result<Url, ParseError> {
|
pub fn generate_follow_accept_id(domain: &str, db_id: &str) -> Result<Url, ParseError> {
|
||||||
|
Url::parse(&format!("https://{}/apbridge/follow/{}", domain, db_id))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO for later aprl: needs to be base64url!!!
|
||||||
|
pub fn generate_create_id(
|
||||||
|
domain: &str,
|
||||||
|
create_db_id: &str,
|
||||||
|
basesixfour_url: &str,
|
||||||
|
) -> Result<Url, ParseError> {
|
||||||
Url::parse(&format!(
|
Url::parse(&format!(
|
||||||
"https://{}/apbridge/activity/follow/{}",
|
"https://{}/apbridge/create/{}/{}",
|
||||||
domain, db_id
|
domain, create_db_id, basesixfour_url
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn generate_random_create_id(domain: &str, basesixfour_url: &str) -> Result<Url, ParseError> {
|
||||||
|
let id: String = uuid::Uuid::new_v4().to_string();
|
||||||
|
generate_create_id(domain, &id, basesixfour_url)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn base_url_encode(url: &Url) -> String {
|
||||||
|
base64_url::encode(&url.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn base_url_decode(encoded: &str) -> String {
|
||||||
|
String::from_utf8(base64_url::decode(encoded).unwrap()).unwrap()
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue