use crate::activitypub::{actor::ID, db, gen_ap_id}; use activitystreams::{ activity::Create, base::AnyBase, context, iri, link::Mention, object::{Article, Note}, prelude::{ApActorExt, BaseExt, ExtendsExt, LinkExt, ObjectExt}, time::OffsetDateTime, }; use anyhow::anyhow; use log::{error, info}; use sqlx::SqlitePool; use std::collections::HashMap; use std::fmt::Write; use super::{ conversation_context, federate, gen_converation_id, get_actor, types::{Conversation, NoteExt}, }; pub async fn send_digest_if_necessary(pool: &SqlitePool) { match do_send_digest(pool).await { Ok(()) => (), Err(e) => error!("Sending digest failed: {:?}", e), } } async fn do_send_digest(pool: &SqlitePool) -> anyhow::Result<()> { let digest_recipient = std::env::var("DIGEST_RECIPIENT_URL").map_err(|_| anyhow!("No digest recipient set"))?; let digest_recipient_actor = get_actor(&digest_recipient, pool).await?; let undigested = db::get_undigested_notes(pool).await?; if undigested.is_empty() { info!("No undigested notes, not sending digest"); return Ok(()); } else { info!("Sending digest for {} notes", undigested.len()); } // conversation => count let mut counts = HashMap::::new(); for (_, conv) in undigested.iter() { if let Some(count) = counts.get_mut(conv) { *count += 1; } else { counts.insert(conv.to_owned(), 1); } } let mut content = format!( r#"

@{} Received {} comment{} on {} post{}:

"#, digest_recipient, digest_recipient_actor.preferred_username().unwrap(), undigested.len(), if undigested.len() == 1 { "" } else { "s" }, counts.len(), if counts.len() == 1 { "" } else { "s" }, ); for (conv, count) in counts { let article = db::get_article_for_conversation(&conv, pool) .await .ok_or(anyhow!("Missing article for conversation"))?; // all this unwrapping is safe because we generate this article ourselves let mut article: Article = serde_json::from_str(&article).unwrap(); let url = article.take_id().unwrap(); let title = article .take_name() .unwrap() .one() .unwrap() .xsd_string() .unwrap(); write!( content, r#"

{}: {} comment{}

"#, url, title, count, if count == 1 { "" } else { "s" } ) .unwrap(); } let mut note = NoteExt::new(Note::new(), Conversation::new()); note.add_context(context()); note.add_context(conversation_context()); note.set_id(iri!(gen_ap_id())); note.add_to(digest_recipient_actor.id_unchecked().unwrap().to_owned()); note.set_many_ccs::<_, AnyBase>([]); note.set_attributed_to(ID.clone()); note.set_content(content); note.set_published(OffsetDateTime::now_utc()); note.ext_one.conversation = Some(gen_converation_id()); let mut mention = Mention::new(); mention.set_href(digest_recipient_actor.id_unchecked().unwrap().to_owned()); mention.set_name(format!( "@{}", digest_recipient_actor .preferred_username() .unwrap() .to_owned() )); note.set_tag(AnyBase::from_extended(mention)?); let mut create = Create::new(ID.as_str(), note.into_any_base()?); create.add_context(context()); create.add_context(conversation_context()); create.set_id(iri!(gen_ap_id())); create.add_to(digest_recipient_actor.id_unchecked().unwrap().to_owned()); create.set_many_ccs::<_, AnyBase>([]); let inbox = digest_recipient_actor.inbox()?.as_str(); federate::sign_and_send(&create, inbox).await?; for (id, _) in undigested { db::set_note_digested(pool, &id).await?; } Ok(()) }