Compare commits

..

4 commits

Author SHA1 Message Date
af994da294
fix: duplicate context to conversation properly 2024-05-24 00:26:22 +02:00
08ec2da814
feat: find also object replies ids, optional
configurable by instance admin, should be cheap with index but still
2024-05-24 00:25:41 +02:00
d939d3d90e
fix: count is updated while inserting 2024-05-24 00:22:04 +02:00
79236699cc
fix: don't fetch while inserting, just give up
because we insert while fetching ehehe so basically if we can't figure
out the context we set it to None and we'll have to do it another time.
we cant trust mastodon because it doesnt produce reliable contexts and
misskeys just dont use contexts at all (wtf!!!)
2024-05-24 00:21:02 +02:00
7 changed files with 49 additions and 35 deletions

View file

@ -40,7 +40,7 @@ sea-orm = { version = "0.12", features = ["macros", "sqlx-sqlite", "runtime-toki
reqwest = { version = "0.12", features = ["json"] } reqwest = { version = "0.12", features = ["json"] }
axum = "0.7" axum = "0.7"
tower-http = { version = "0.5", features = ["cors", "trace"] } tower-http = { version = "0.5", features = ["cors", "trace"] }
apb = { path = "apb", features = ["unstructured", "orm", "activitypub-fe", "activitypub-counters", "litepub"] } apb = { path = "apb", features = ["unstructured", "orm", "activitypub-fe", "activitypub-counters", "litepub", "ostatus", "toot"] }
# nodeinfo = "0.0.2" # the version on crates.io doesn't re-export necessary types to build the struct!!! # nodeinfo = "0.0.2" # the version on crates.io doesn't re-export necessary types to build the struct!!!
nodeinfo = { git = "https://codeberg.org/thefederationinfo/nodeinfo-rs", rev = "e865094804" } nodeinfo = { git = "https://codeberg.org/thefederationinfo/nodeinfo-rs", rev = "e865094804" }
# migrations # migrations

View file

@ -67,6 +67,9 @@ pub struct SecurityConfig {
#[serde(default)] #[serde(default)]
pub allow_public_debugger: bool, pub allow_public_debugger: bool,
#[serde_inline_default(true)]
pub show_reply_ids: bool,
} }

View file

@ -69,6 +69,7 @@ impl Model {
.set_summary(self.summary.as_deref()) .set_summary(self.summary.as_deref())
.set_content(self.content.as_deref()) .set_content(self.content.as_deref())
.set_context(apb::Node::maybe_link(self.context.clone())) .set_context(apb::Node::maybe_link(self.context.clone()))
.set_conversation(apb::Node::maybe_link(self.context.clone())) // duplicate context for mastodon
.set_in_reply_to(apb::Node::maybe_link(self.in_reply_to.clone())) .set_in_reply_to(apb::Node::maybe_link(self.in_reply_to.clone()))
.set_published(Some(self.published)) .set_published(Some(self.published))
.set_updated(self.updated) .set_updated(self.updated)

View file

@ -33,6 +33,8 @@ impl LD for serde_json::Value {
Some(_) => { Some(_) => {
ctx.insert("fe".to_string(), serde_json::Value::String("https://ns.alemi.dev/as/fe/#".into())); ctx.insert("fe".to_string(), serde_json::Value::String("https://ns.alemi.dev/as/fe/#".into()));
ctx.insert("likedByMe".to_string(), serde_json::Value::String("fe:likedByMe".into())); ctx.insert("likedByMe".to_string(), serde_json::Value::String("fe:likedByMe".into()));
ctx.insert("ostatus".to_string(), serde_json::Value::String("http://ostatus.org#".into()));
ctx.insert("conversation".to_string(), serde_json::Value::String("ostatus:conversation".into()));
}, },
None => {}, None => {},
} }

View file

@ -2,7 +2,7 @@ pub mod replies;
use apb::{CollectionMut, ObjectMut}; use apb::{CollectionMut, ObjectMut};
use axum::extract::{Path, Query, State}; use axum::extract::{Path, Query, State};
use sea_orm::{ColumnTrait, ModelTrait, QueryFilter}; use sea_orm::{ColumnTrait, EntityTrait, ModelTrait, QueryFilter, QuerySelect, SelectColumns};
use crate::{errors::UpubError, model::{self, addressing::Event}, server::{auth::AuthIdentity, fetcher::Fetcher, Context}}; use crate::{errors::UpubError, model::{self, addressing::Event}, server::{auth::AuthIdentity, fetcher::Fetcher, Context}};
@ -31,11 +31,11 @@ pub async fn view(
.await? .await?
.ok_or_else(UpubError::not_found)?; .ok_or_else(UpubError::not_found)?;
let (object, liked) = match item { let object = match item {
Event::Tombstone => return Err(UpubError::not_found()), Event::Tombstone => return Err(UpubError::not_found()),
Event::Activity(_) => return Err(UpubError::not_found()), Event::Activity(_) => return Err(UpubError::not_found()),
Event::StrayObject { object, liked } => (object, liked), Event::StrayObject { liked: _, object } => object,
Event::DeepActivity { activity: _, liked, object } => (object, liked), Event::DeepActivity { activity: _, liked: _, object } => object,
}; };
let attachments = object.find_related(model::attachment::Entity) let attachments = object.find_related(model::attachment::Entity)
@ -45,24 +45,32 @@ pub async fn view(
.map(|x| x.ap()) .map(|x| x.ap())
.collect::<Vec<serde_json::Value>>(); .collect::<Vec<serde_json::Value>>();
// let replies = let mut replies = apb::Node::Empty;
// serde_json::Value::new_object()
// .set_id(Some(&crate::url!(ctx, "/objects/{id}/replies")))
// .set_collection_type(Some(apb::CollectionType::OrderedCollection))
// .set_first(apb::Node::link(crate::url!(ctx, "/objects/{id}/replies/page")))
// .set_total_items(Some(object.comments as u64));
let likes_count = object.likes as u64; if ctx.cfg().security.show_reply_ids {
let mut obj = object.ap().set_attachment(apb::Node::array(attachments)); let replies_ids = model::addressing::Entity::find_addressed(None)
.filter(model::object::Column::InReplyTo.eq(oid))
.filter(auth.filter_condition())
.select_only()
.select_column(model::object::Column::Id)
.into_tuple::<String>()
.all(ctx.db())
.await?;
if let Some(liked) = liked { replies = apb::Node::object(
obj = obj.set_audience(apb::Node::object( // TODO setting this again ewww...
serde_json::Value::new_object() serde_json::Value::new_object()
.set_collection_type(Some(apb::CollectionType::OrderedCollection)) // .set_id(Some(&crate::url!(ctx, "/objects/{id}/replies")))
.set_total_items(Some(likes_count)) // .set_first(apb::Node::link(crate::url!(ctx, "/objects/{id}/replies/page")))
.set_ordered_items(apb::Node::links(vec![liked])) .set_collection_type(Some(apb::CollectionType::Collection))
)); .set_total_items(Some(object.comments as u64))
.set_items(apb::Node::links(replies_ids))
);
} }
Ok(JsonLD(obj.ld_context())) Ok(JsonLD(
object.ap()
.set_attachment(apb::Node::array(attachments))
.set_replies(replies)
.ld_context()
))
} }

View file

@ -307,11 +307,6 @@ async fn fetch_object_inner(ctx: &Context, id: &str, depth: usize) -> crate::Res
if let Some(reply) = object.in_reply_to().id() { if let Some(reply) = object.in_reply_to().id() {
if depth <= 16 { if depth <= 16 {
fetch_object_inner(ctx, &reply, depth + 1).await?; fetch_object_inner(ctx, &reply, depth + 1).await?;
model::object::Entity::update_many()
.filter(model::object::Column::Id.eq(reply))
.col_expr(model::object::Column::Comments, Expr::col(model::object::Column::Comments).add(1))
.exec(ctx.db())
.await?;
} else { } else {
tracing::warn!("thread deeper than 16, giving up fetching more replies"); tracing::warn!("thread deeper than 16, giving up fetching more replies");
} }

View file

@ -31,15 +31,20 @@ impl Normalizer for super::Context {
object_model.content = Some(mdhtml::safe_html(&content)); object_model.content = Some(mdhtml::safe_html(&content));
} }
// fix context also for remote posts // fix context for remote posts
// TODO this is not really appropriate because we're mirroring incorrectly remote objects, but // > note that this will effectively recursively try to fetch the parent object, in order to find
// it makes it SOO MUCH EASIER for us to fetch threads and stuff, so we're filling it for them // > the context (which is id of topmost object). there's a recursion limit of 16 hidden inside
match (&object_model.in_reply_to, &object_model.context) { // > btw! also if any link is broken or we get rate limited, the whole insertion fails which is
(Some(reply_id), None) => // get context from replied object // > kind of dumb. there should be a job system so this can be done in waves. or maybe there's
object_model.context = self.fetch_object(reply_id).await?.context, // > some whole other way to do this?? im thinking but misskey aaaa!! TODO
(None, None) => // generate a new context if let Some(ref reply) = object_model.in_reply_to {
object_model.context = Some(object_model.id.clone()), if let Some(o) = model::object::Entity::find_by_id(reply).one(self.db()).await? {
(_, Some(_)) => {}, // leave it as set by user object_model.context = o.context;
} else {
object_model.context = None; // TODO to be filled by some other task
}
} else {
object_model.context = Some(object_model.id.clone());
} }
model::object::Entity::insert(object_model.clone().into_active_model()).exec(self.db()).await?; model::object::Entity::insert(object_model.clone().into_active_model()).exec(self.db()).await?;