[feat]: lysand to ap for posts and users

This commit is contained in:
aprilthepink 2024-06-15 02:06:01 +02:00
parent bcab516a1f
commit 1174f92915
11 changed files with 151 additions and 39 deletions

View file

@ -6,7 +6,7 @@ use crate::{
person::DbUser,
post::{DbPost, Note},
},
utils::generate_object_id,
utils::generate_random_object_id,
};
use activitypub_federation::{
activity_sending::SendActivityTask,
@ -39,7 +39,7 @@ impl CreatePost {
to: note.to.clone(),
object: note,
kind: CreateType::Create,
id: generate_object_id(data.domain())?,
id: generate_random_object_id(data.domain())?,
};
let create_with_context = WithContext::new_default(create);
let sends = SendActivityTask::prepare(

View file

@ -14,7 +14,7 @@ use crate::{
database::StateHandle,
entities::{follow_relation, post, user},
error,
utils::{generate_follow_accept_id, generate_object_id},
utils::{generate_follow_accept_id, generate_random_object_id},
DB,
};
@ -39,7 +39,7 @@ impl Follow {
actor: local_user.clone(),
object: followee.clone(),
kind: FollowType::Follow,
id: generate_object_id(data.domain())?,
id: generate_random_object_id(data.domain())?,
};
let create_with_context = WithContext::new_default(create);
let sends = SendActivityTask::prepare(

View file

@ -15,8 +15,7 @@ pub struct Model {
pub created_at: chrono::DateTime<Utc>,
#[sea_orm(column_type = "Timestamp")]
pub updated_at: Option<chrono::DateTime<Utc>>,
#[sea_orm(column_type = "Timestamp")]
pub reblog_id: Option<chrono::DateTime<Utc>>,
pub reblog_id: Option<String>,
pub content_type: String,
pub visibility: String,
pub reply_id: Option<String>,

View file

@ -1,8 +1,8 @@
use activitypub_federation::{fetch::object_id::ObjectId, http_signatures::generate_actor_keypair};
use activitystreams_kinds::public;
use chrono::{DateTime, Utc};
use chrono::{DateTime, TimeZone, Utc};
use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, Set};
use anyhow::anyhow;
use anyhow::{anyhow, Ok};
use url::Url;
use crate::{database::State, entities::{self, post, prelude, user}, objects::post::Mention, utils::{generate_object_id, generate_user_id}, API_DOMAIN, DB, FEDERATION_CONFIG, LYSAND_DOMAIN};
@ -27,7 +27,7 @@ pub async fn db_user_from_url(url: Url) -> anyhow::Result<entities::user::Model>
if !url.domain().eq(&Some(LYSAND_DOMAIN.as_str())) {
return Err(anyhow!("not lysands domain"));
}
let user_res = prelude::User::find().filter(entities::user::Column::Url.eq(url.to_string())).one(DB.get().unwrap()).await?;
let user_res: Option<user::Model> = prelude::User::find().filter(entities::user::Column::Url.eq(url.to_string())).one(DB.get().unwrap()).await?;
if let Some(user) = user_res {
Ok(user)
@ -64,8 +64,8 @@ pub async fn fetch_note_from_url(url: Url) -> anyhow::Result<super::objects::Not
Ok(request.json::<super::objects::Note>().await?)
}
pub async fn receive_lysand_note(note: Note, db_id: String) -> anyhow::Result<()> {
let author: entities::user::Model = db_user_from_url(note.author.clone()).await?;
pub async fn receive_lysand_note(note: Note, db_id: String) -> anyhow::Result<crate::objects::post::Note> {
let lysand_author: entities::user::Model = db_user_from_url(note.author.clone()).await?;
let user_res = prelude::User::find_by_id(db_id).one(DB.get().unwrap()).await;
if user_res.is_err() {
println!("{}", user_res.as_ref().unwrap_err());
@ -75,23 +75,45 @@ pub async fn receive_lysand_note(note: Note, db_id: String) -> anyhow::Result<()
let data = FEDERATION_CONFIG.get().unwrap();
let id: ObjectId<post::Model> = generate_object_id(data.domain(), &note.id.to_string())?.into();
let user_id = generate_user_id(data.domain(), &target.id.to_string())?;
let user = fetch_user_from_url(user_id).await?;
let user = fetch_user_from_url(note.author.clone()).await?;
let mut tag: Vec<Mention> = Vec::new();
for l_tag in note.mentions.clone().unwrap_or_default() {
tag.push(Mention { href: l_tag, //todo convert to ap url
tag.push(Mention { href: l_tag, //TODO convert to ap url
kind: Default::default(), })
}
let to = match note.visibility.clone().unwrap_or(super::objects::VisibilityType::Public) {
super::objects::VisibilityType::Public => vec![public(), Url::parse(&author.followers.unwrap_or_default())?],
super::objects::VisibilityType::Followers => vec![Url::parse(&author.followers.unwrap_or_default())?],
super::objects::VisibilityType::Public => vec![public(), Url::parse(&user.followers.to_string().as_str())?],
super::objects::VisibilityType::Followers => vec![Url::parse(&user.followers.to_string().as_str())?],
super::objects::VisibilityType::Direct => note.mentions.unwrap_or_default(),
super::objects::VisibilityType::Unlisted => vec![Url::parse(&author.followers.unwrap_or_default())?],
super::objects::VisibilityType::Unlisted => vec![Url::parse(&user.followers.to_string().as_str())?],
};
let cc = match note.visibility.unwrap_or(super::objects::VisibilityType::Public) {
let cc = match note.visibility.clone().unwrap_or(super::objects::VisibilityType::Public) {
super::objects::VisibilityType::Unlisted => Some(vec![public()]),
_ => None
};
let reply: Option<ObjectId<entities::post::Model>> = if let Some(rep) = note.replies_to {
let reply: Option<ObjectId<entities::post::Model>> = if let Some(rep) = note.replies_to.clone() {
let note = fetch_note_from_url(rep).await?;
let fake_rep_url = Url::parse(&format!(
"https://{}/lysand/apnote/{}",
API_DOMAIN.to_string(),
&note.id.to_string()
))?;
Some(fake_rep_url.into())
} else {
None
};
let reply_string: Option<String> = if let Some(rep) = note.replies_to {
let note = fetch_note_from_url(rep).await?;
let fake_rep_url = Url::parse(&format!(
"https://{}/lysand/apnote/{}",
API_DOMAIN.to_string(),
&note.id.to_string()
))?;
Some(fake_rep_url.into())
} else {
None
};
let quote_string: Option<String> = if let Some(rep) = note.quotes.clone() {
let note = fetch_note_from_url(rep).await?;
let fake_rep_url = Url::parse(&format!(
"https://{}/lysand/apnote/{}",
@ -109,12 +131,38 @@ pub async fn receive_lysand_note(note: Note, db_id: String) -> anyhow::Result<()
cc,
to,
tag,
attributed_to: Url::parse(author.url.clone().as_str()).unwrap().into(),
attributed_to: Url::parse(user.uri.clone().as_str()).unwrap().into(),
content: option_content_format_text(note.content).await.unwrap_or_default(),
in_reply_to: reply
};
let visibility = match note.visibility.clone().unwrap_or(super::objects::VisibilityType::Public) {
super::objects::VisibilityType::Public => "public",
super::objects::VisibilityType::Followers => "followers",
super::objects::VisibilityType::Direct => "direct",
super::objects::VisibilityType::Unlisted => "unlisted",
};
let post = entities::post::ActiveModel {
id: Set(note.id.to_string()),
creator: Set(lysand_author.id.clone()),
content: Set(ap_note.content.clone()),
sensitive: Set(ap_note.sensitive),
created_at: Set(Utc.timestamp_micros(note.created_at.unix_timestamp()).unwrap()),
local: Set(true),
updated_at: Set(Some(Utc::now())),
content_type: Set("Note".to_string()),
visibility: Set(visibility.to_string()),
title: Set(note.subject.clone()),
url: Set(ap_note.id.to_string()),
reply_id: Set(reply_string),
quoting_id: Set(quote_string),
spoiler_text: Set(note.subject),
..Default::default()
};
post.insert(DB.get().unwrap()).await?;
Ok(ap_note)
} else {
Err(anyhow!("User not found"))
}
Ok(())
}

0
src/lysand/funcs.rs Normal file
View file

24
src/lysand/http.rs Normal file
View file

@ -0,0 +1,24 @@
use std::os::linux::raw::stat;
use activitypub_federation::{traits::Object, FEDERATION_CONTENT_TYPE};
use actix_web::{get, web, HttpResponse};
use sea_orm::{ColumnTrait, EntityTrait, QueryFilter};
use crate::{database::State, entities::{post::{self, Entity}, prelude}, error, Response, DB, FEDERATION_CONFIG};
#[get("/apbridge/object/{post}")]
async fn post_manually(
path: web::Path<(String, String)>,
state: web::Data<State>,
) -> actix_web::Result<HttpResponse, error::Error> {
let db = DB.get().unwrap();
let post = prelude::Post::find()
.filter(post::Column::Id.eq(path.0.as_str()))
.one(db)
.await?;
let post = post.unwrap();
Ok(HttpResponse::Ok().content_type(FEDERATION_CONTENT_TYPE).json(post.into_json(&FEDERATION_CONFIG.get().unwrap().to_request_data()).await?))
}

View file

@ -2,3 +2,5 @@ pub mod objects;
pub mod superx;
pub mod test;
pub mod conversion;
pub mod funcs;
pub mod http;

View file

@ -62,6 +62,7 @@ pub enum CategoryType {
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "snake_case")]
pub enum VisibilityType {
Public,
Unlisted,

View file

@ -2,6 +2,19 @@ use crate::lysand::objects::SortAlphabetically;
use super::superx::request_client;
#[actix_web::test]
async fn test_user_serial() {
let client = request_client();
let response = client
.get("https://social.lysand.org/users/018ec082-0ae1-761c-b2c5-22275a611771")
.send()
.await.unwrap();
let user = super::superx::deserialize_user(response.text().await.unwrap()).await.unwrap();
let response_outbox = client.get(user.outbox.as_str()).send().await.unwrap();
let outbox = super::superx::deserialize_outbox(response_outbox.text().await.unwrap()).await.unwrap();
assert!(outbox.items.len() > 0);
}
pub async fn main() -> anyhow::Result<()> {
let client = request_client();
@ -30,5 +43,13 @@ pub async fn main() -> anyhow::Result<()> {
println!("\n\n\nOutbox: ");
print!("{:#?}", outbox);
println!("\n\n\nas AP:");
for item in outbox.items {
let ap_item = super::conversion::receive_lysand_note(item, "https://ap.lysand.org/example".to_string()).await?;
println!("{:#?}", ap_item);
let ap_json = serde_json::to_string_pretty(&SortAlphabetically(&ap_item))?;
println!("{}", ap_json);
}
Ok(())
}

View file

@ -28,7 +28,7 @@ use tokio::signal;
use tracing::{info, instrument::WithSubscriber};
use url::Url;
use crate::utils::generate_object_id;
use crate::utils::generate_random_object_id;
use crate::{
activities::create_post::CreatePost,
database::{Config, State},
@ -84,7 +84,8 @@ async fn post_manually(
href: Url::parse(&target.id)?,
kind: Default::default(),
};
let id: ObjectId<post::Model> = generate_object_id(data.domain())?.into();
// TODO change
let id: ObjectId<post::Model> = generate_random_object_id(data.domain())?.into();
let note = Note {
kind: Default::default(),
id,
@ -153,10 +154,6 @@ static FEDERATION_CONFIG: OnceLock<FederationConfig<State>> = OnceLock::new();
async fn main() -> actix_web::Result<(), anyhow::Error> {
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
//TODO remove this
lysand::test::main().await?;
return Ok(());
let ap_id = Url::parse(&format!(
"https://{}/{}",
API_DOMAIN.to_string(),
@ -246,6 +243,10 @@ async fn main() -> actix_web::Result<(), anyhow::Error> {
.keep_alive(KeepAlive::Os)
.run();
//TODO remove this
lysand::test::main().await?;
return Ok(());
tokio::spawn(http_server);
match signal::ctrl_c().await {

View file

@ -1,10 +1,5 @@
use crate::{
activities::create_post::CreatePost,
database::StateHandle,
entities::{post, user},
error::Error,
objects::person::DbUser,
utils::generate_object_id,
activities::create_post::CreatePost, database::StateHandle, entities::{post, user}, error::Error, lysand::conversion::db_user_from_url, objects::person::DbUser, utils::generate_object_id
};
use activitypub_federation::{
config::Data,
@ -14,7 +9,7 @@ use activitypub_federation::{
traits::{Actor, Object},
};
use activitystreams_kinds::link::MentionType;
use sea_orm::{ActiveModelTrait, Set};
use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, Set};
use serde::{Deserialize, Serialize};
use tracing::info;
use url::Url;
@ -57,14 +52,35 @@ impl Object for post::Model {
type Error = Error;
async fn read_from_id(
_object_id: Url,
_data: &Data<Self::DataType>,
object_id: Url,
data: &Data<Self::DataType>,
) -> Result<Option<Self>, Self::Error> {
let post = crate::entities::prelude::Post::find()
.filter(post::Column::Id.eq(object_id.to_string()))
.one(data.app_data().database_connection.clone().as_ref()).await;
Ok(None)
}
async fn into_json(self, _data: &Data<Self::DataType>) -> Result<Self::Kind, Self::Error> {
todo!()
let creator = db_user_from_url(Url::parse(self.creator.as_str()).unwrap()).await?;
let to = match self.visibility.as_str() {
"public" => vec![public(), Url::parse(creator.followers.unwrap().as_str()).unwrap()],
"followers" => vec![Url::parse(creator.followers.unwrap().as_str()).unwrap()],
"direct" => vec![], //TODO: implement this
"unlisted" => vec![Url::parse(creator.followers.unwrap().as_str()).unwrap(), public()],
_ => vec![public()],
};
Ok(Note {
kind: Default::default(),
id: Url::parse(self.url.as_str()).unwrap().into(),
attributed_to: Url::parse(self.creator.as_str()).unwrap().into(),
to: to.clone(),
content: self.content,
in_reply_to: None,
tag: vec![],
sensitive: self.sensitive,
cc: Some(to),
})
}
async fn verify(