v6/src/activitypub/articles.rs

125 lines
4.5 KiB
Rust

use super::{
actor::{self, ID},
conversation_context, db, federate, gen_ap_id, gen_converation_id,
types::{ArticleExt, Conversation},
util::{accept, rewrite_srcs},
DOMAIN,
};
use crate::generator::{HtmlContent, Post};
use activitystreams::{
activity::Create,
context, iri,
iri_string::types::IriString,
object::{Article, ObjectExt},
prelude::{BaseExt, ExtendsExt},
public,
time::OffsetDateTime,
};
use axum::{body::Body, http::Request, response::IntoResponse};
use log::{error, info};
use mime::Mime;
use once_cell::sync::Lazy;
use sqlx::SqlitePool;
use url::Url;
pub async fn insert_posts(posts: &[Post<HtmlContent>], pool: &SqlitePool) {
let existing = sqlx::query!("select id from articles")
.fetch_all(pool)
.await
.unwrap();
for post in posts.iter() {
let id = format!("https://{}{}", *DOMAIN, post.comments_permalink());
if existing.iter().any(|e| e.id == id) {
continue;
}
let mut article = ArticleExt::new(Article::new(), Conversation::new());
article.add_context(context());
article.add_context(conversation_context());
article.set_id(id.parse::<IriString>().unwrap());
article.set_attributed_to(ID.clone());
article.set_published(
OffsetDateTime::from_unix_timestamp(post.metadata.date.timestamp())
.expect("convert chrono::DateTime to time::OffsetDateTime"),
);
article.ext_one.conversation = Some(gen_converation_id());
article.add_to(public());
article.add_cc(actor::FOLLOWERS.clone());
article.set_name(post.metadata.title.clone());
let content = post.content.html();
let content_with_absolute_srcs = rewrite_srcs::rewrite(&content, &Url::parse(&id).unwrap());
article.set_content(content_with_absolute_srcs);
let conversation = article.ext_one.conversation.as_ref().unwrap();
let article_json: String = serde_json::to_string_pretty(&article).unwrap();
sqlx::query!("insert into articles (id, conversation, has_federated, article_object) values (?1, ?2, false, ?3)", id, conversation, article_json)
.execute(pool)
.await.unwrap();
}
}
static AP_JSON: Lazy<Mime> = Lazy::new(|| "application/activity+json".parse::<Mime>().unwrap());
static LD_JSON: Lazy<Mime> = Lazy::new(|| "application/ld+json".parse::<Mime>().unwrap());
pub async fn handle(request: &Request<Body>) -> Option<impl IntoResponse> {
let mimes = [&*AP_JSON, &*LD_JSON, &mime::APPLICATION_JSON];
let best = accept::best_match(request.headers(), &mimes);
match best {
Some(t) if t == &*AP_JSON || t == &*LD_JSON || *t == mime::APPLICATION_JSON => {
let pool = request.extensions().get::<SqlitePool>().unwrap();
if let Some(article_json) = db::get_article_for_path(request.uri().path(), pool).await {
let headers = [("Content-Type", best.unwrap().to_string())];
Some((headers, article_json).into_response())
} else {
None
}
}
_ => None,
}
}
pub async fn federate_outgoing(pool: &SqlitePool) -> anyhow::Result<()> {
let articles_objs = db::get_unfederated_articles(pool).await?;
let followers = db::get_followers(pool).await?;
let mut inboxes = vec![];
for actor in followers {
let inbox = actor
.shared_inbox
.clone()
.unwrap_or_else(|| actor.inbox.clone());
if !inboxes.contains(&inbox) {
inboxes.push(inbox);
}
}
for (article_id, article_obj) in articles_objs {
info!("Federating out article {}", article_id);
let article = serde_json::from_str::<Article>(&article_obj)?;
let to = article.to().unwrap().clone().many().unwrap();
let cc = article.cc().unwrap().clone().many().unwrap();
let mut create = Create::new(ID.as_str(), article.into_any_base()?);
create.add_context(context());
create.add_context(conversation_context());
create.set_id(iri!(gen_ap_id()));
create.set_many_tos(to);
create.set_many_ccs(cc);
for inbox in &inboxes {
match federate::sign_and_send(&create, inbox).await {
Ok(()) => (),
Err(e) => error!(
"federating outbound article {} to {}: {:?}",
article_id, inbox, e
),
}
}
db::set_has_federated(&article_id, true, pool).await?;
}
Ok(())
}