Self reaction and renote fetching

This commit is contained in:
Natty 2023-11-01 22:55:59 +01:00
parent 8e02e46be5
commit 5b9b813037
Signed by: natty
GPG Key ID: BF6CB659ADEE60EC
9 changed files with 349 additions and 179 deletions

2
Cargo.lock generated
View File

@ -1460,6 +1460,7 @@ dependencies = [
"hyper", "hyper",
"idna", "idna",
"itertools 0.11.0", "itertools 0.11.0",
"lazy_static",
"lru", "lru",
"magnetar_calckey_model", "magnetar_calckey_model",
"magnetar_common", "magnetar_common",
@ -1517,6 +1518,7 @@ dependencies = [
"ext_calckey_model_migration", "ext_calckey_model_migration",
"futures-core", "futures-core",
"futures-util", "futures-util",
"lazy_static",
"magnetar_common", "magnetar_common",
"magnetar_sdk", "magnetar_sdk",
"once_cell", "once_cell",

View File

@ -39,6 +39,7 @@ http = "0.2"
hyper = "0.14" hyper = "0.14"
idna = "0.4" idna = "0.4"
itertools = "0.11" itertools = "0.11"
lazy_static = "1.4"
lru = "0.12" lru = "0.12"
miette = "5.9" miette = "5.9"
nom = "7" nom = "7"
@ -90,20 +91,21 @@ tower-http = { workspace = true, features = ["cors", "trace", "fs"] }
idna = { workspace = true } idna = { workspace = true }
regex = { workspace = true }
tracing-subscriber = { workspace = true, features = ["env-filter"] } tracing-subscriber = { workspace = true, features = ["env-filter"] }
tracing = { workspace = true } tracing = { workspace = true }
cfg-if = { workspace = true } cfg-if = { workspace = true }
itertools = { workspace = true }
compact_str = { workspace = true } compact_str = { workspace = true }
either = { workspace = true } either = { workspace = true }
futures-util = { workspace = true } futures-util = { workspace = true }
itertools = { workspace = true }
lazy_static = { workspace = true }
miette = { workspace = true, features = ["fancy"] }
strum = { workspace = true, features = ["derive"] } strum = { workspace = true, features = ["derive"] }
thiserror = { workspace = true } thiserror = { workspace = true }
miette = { workspace = true, features = ["fancy"] }
regex = { workspace = true }
percent-encoding = { workspace = true } percent-encoding = { workspace = true }

View File

@ -16,6 +16,7 @@ magnetar_sdk = { path = "../magnetar_sdk" }
dotenvy = { workspace = true} dotenvy = { workspace = true}
futures-core = { workspace = true } futures-core = { workspace = true }
futures-util = { workspace = true } futures-util = { workspace = true }
lazy_static = { workspace = true }
tokio = { workspace = true, features = ["full"] } tokio = { workspace = true, features = ["full"] }
tokio-util = { workspace = true} tokio-util = { workspace = true}
redis = { workspace = true, features = ["tokio-comp", "json", "serde_json"]} redis = { workspace = true, features = ["tokio-comp", "json", "serde_json"]}

View File

@ -13,8 +13,8 @@ use redis::IntoConnectionInfo;
use sea_orm::sea_query::IntoIden; use sea_orm::sea_query::IntoIden;
use sea_orm::ActiveValue::Set; use sea_orm::ActiveValue::Set;
use sea_orm::{ use sea_orm::{
ColumnTrait, ConnectOptions, DatabaseConnection, DbErr, EntityTrait, QueryFilter, QueryOrder, ColumnTrait, ConnectOptions, DatabaseConnection, DbErr, EntityTrait, QueryFilter, RelationDef,
RelationDef, RelationTrait, TransactionTrait, RelationTrait, TransactionTrait,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::future::Future; use std::future::Future;

View File

@ -1,19 +1,52 @@
use sea_orm::sea_query::{Alias, Expr, IntoIden, SelectExpr, SimpleExpr}; use lazy_static::lazy_static;
use sea_orm::sea_query::{Alias, Asterisk, Expr, IntoIden, Query, SelectExpr, SimpleExpr};
use sea_orm::{ use sea_orm::{
ColumnTrait, DbErr, EntityTrait, FromQueryResult, Iden, Iterable, JoinType, QueryFilter, ColumnTrait, DbErr, EntityName, EntityTrait, FromQueryResult, Iden, Iterable, JoinType,
QueryResult, QuerySelect, QueryTrait, RelationTrait, Select, QueryFilter, QueryResult, QuerySelect, QueryTrait, RelationTrait, Select,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tracing::info;
use ck::{drive_file, note, user}; use ck::{drive_file, note, note_reaction, user};
use magnetar_sdk::types::RangeFilter; use magnetar_sdk::types::RangeFilter;
use once_cell::unsync::Lazy;
use crate::{AliasSourceExt, CalckeyDbError, CalckeyModel}; use crate::{AliasSourceExt, CalckeyDbError, CalckeyModel};
pub mod sub_interaction_renote {
use sea_orm::{DeriveColumn, EnumIter, FromQueryResult};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, FromQueryResult, Serialize, Deserialize)]
pub struct Model {
pub renotes: Option<i64>,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
pub enum Column {
Renotes,
}
}
pub mod sub_interaction_reaction {
use sea_orm::{DeriveColumn, EnumIter, FromQueryResult};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, FromQueryResult, Serialize, Deserialize)]
pub struct Model {
pub reaction_name: Option<String>,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
pub enum Column {
ReactionName,
}
}
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NoteData { pub struct NoteData {
pub note: note::Model, pub note: note::Model,
pub interaction_user_renote: Option<sub_interaction_renote::Model>,
pub interaction_user_reaction: Option<sub_interaction_reaction::Model>,
pub user: user::Model, pub user: user::Model,
pub avatar: Option<drive_file::Model>, pub avatar: Option<drive_file::Model>,
pub banner: Option<drive_file::Model>, pub banner: Option<drive_file::Model>,
@ -21,14 +54,20 @@ pub struct NoteData {
pub renote: Option<Box<NoteData>>, pub renote: Option<Box<NoteData>>,
} }
const INTERACTION_REACTION: &str = "interaction.reaction.";
const INTERACTION_RENOTE: &str = "interaction.renote.";
const USER: &str = "user."; const USER: &str = "user.";
const USER_AVATAR: &str = "user.avatar."; const USER_AVATAR: &str = "user.avatar.";
const USER_BANNER: &str = "user.banner."; const USER_BANNER: &str = "user.banner.";
const REPLY: &str = "reply."; const REPLY: &str = "reply.";
const REPLY_INTERACTION_REACTION: &str = "reply.interaction.reaction.";
const REPLY_INTERACTION_RENOTE: &str = "reply.interaction.renote.";
const REPLY_USER: &str = "reply.user."; const REPLY_USER: &str = "reply.user.";
const REPLY_USER_AVATAR: &str = "reply.user.avatar."; const REPLY_USER_AVATAR: &str = "reply.user.avatar.";
const REPLY_USER_BANNER: &str = "reply.user.banner."; const REPLY_USER_BANNER: &str = "reply.user.banner.";
const RENOTE: &str = "renote."; const RENOTE: &str = "renote.";
const RENOTE_INTERACTION_REACTION: &str = "renote.interaction.reaction.";
const RENOTE_INTERACTION_RENOTE: &str = "renote.interaction.renote.";
const RENOTE_USER: &str = "renote.user."; const RENOTE_USER: &str = "renote.user.";
const RENOTE_USER_AVATAR: &str = "renote.user.avatar."; const RENOTE_USER_AVATAR: &str = "renote.user.avatar.";
const RENOTE_USER_BANNER: &str = "renote.user.banner."; const RENOTE_USER_BANNER: &str = "renote.user.banner.";
@ -39,6 +78,16 @@ impl FromQueryResult for NoteData {
.map::<Result<_, DbErr>, _>(|r| { .map::<Result<_, DbErr>, _>(|r| {
Ok(Box::new(NoteData { Ok(Box::new(NoteData {
note: r, note: r,
interaction_user_renote:
sub_interaction_renote::Model::from_query_result_optional(
res,
REPLY_INTERACTION_RENOTE,
)?,
interaction_user_reaction:
sub_interaction_reaction::Model::from_query_result_optional(
res,
REPLY_INTERACTION_REACTION,
)?,
user: user::Model::from_query_result(res, REPLY_USER)?, user: user::Model::from_query_result(res, REPLY_USER)?,
avatar: drive_file::Model::from_query_result_optional(res, REPLY_USER_AVATAR)?, avatar: drive_file::Model::from_query_result_optional(res, REPLY_USER_AVATAR)?,
banner: drive_file::Model::from_query_result_optional(res, REPLY_USER_BANNER)?, banner: drive_file::Model::from_query_result_optional(res, REPLY_USER_BANNER)?,
@ -52,6 +101,16 @@ impl FromQueryResult for NoteData {
.map::<Result<_, DbErr>, _>(|r| { .map::<Result<_, DbErr>, _>(|r| {
Ok(Box::new(NoteData { Ok(Box::new(NoteData {
note: r, note: r,
interaction_user_renote:
sub_interaction_renote::Model::from_query_result_optional(
res,
RENOTE_INTERACTION_RENOTE,
)?,
interaction_user_reaction:
sub_interaction_reaction::Model::from_query_result_optional(
res,
RENOTE_INTERACTION_REACTION,
)?,
user: user::Model::from_query_result(res, RENOTE_USER)?, user: user::Model::from_query_result(res, RENOTE_USER)?,
avatar: drive_file::Model::from_query_result_optional(res, RENOTE_USER_AVATAR)?, avatar: drive_file::Model::from_query_result_optional(res, RENOTE_USER_AVATAR)?,
banner: drive_file::Model::from_query_result_optional(res, RENOTE_USER_BANNER)?, banner: drive_file::Model::from_query_result_optional(res, RENOTE_USER_BANNER)?,
@ -63,6 +122,14 @@ impl FromQueryResult for NoteData {
Ok(NoteData { Ok(NoteData {
note: note::Model::from_query_result(res, "")?, note: note::Model::from_query_result(res, "")?,
interaction_user_renote: sub_interaction_renote::Model::from_query_result_optional(
res,
INTERACTION_RENOTE,
)?,
interaction_user_reaction: sub_interaction_reaction::Model::from_query_result_optional(
res,
INTERACTION_REACTION,
)?,
user: user::Model::from_query_result(res, USER)?, user: user::Model::from_query_result(res, USER)?,
avatar: drive_file::Model::from_query_result_optional(res, USER_AVATAR)?, avatar: drive_file::Model::from_query_result_optional(res, USER_AVATAR)?,
banner: drive_file::Model::from_query_result_optional(res, USER_BANNER)?, banner: drive_file::Model::from_query_result_optional(res, USER_BANNER)?,
@ -87,10 +154,25 @@ pub struct NoteResolveOptions {
pub with_user: bool, pub with_user: bool,
pub with_reply_target: bool, pub with_reply_target: bool,
pub with_renote_target: bool, pub with_renote_target: bool,
pub with_interactions_from: Option<String>, // User ID
} }
trait SelectColumnsExt { trait SelectColumnsExt {
fn add_aliased_columns<T: EntityTrait>(self, alias: Option<&str>, entity: T) -> Self; fn add_aliased_columns<T: EntityTrait>(self, alias: Option<&str>, entity: T) -> Self;
fn add_sub_select_reaction(
self,
source_note_alias: Option<&str>,
alias: &str,
user_id: &str,
) -> Select<note::Entity>;
fn add_sub_select_renote(
self,
source_note_alias: Option<&str>,
alias: &str,
user_id: &str,
) -> Select<note::Entity>;
} }
impl SelectColumnsExt for Select<note::Entity> { impl SelectColumnsExt for Select<note::Entity> {
@ -116,19 +198,89 @@ impl SelectColumnsExt for Select<note::Entity> {
self self
} }
fn add_sub_select_reaction(
mut self: Select<note::Entity>,
source_note_alias: Option<&str>,
prefix_alias: &str,
user_id: &str,
) -> Select<note::Entity> {
let iden = source_note_alias.unwrap_or_else(|| note::Entity.table_name());
let note_id_col = Expr::col((Alias::new(iden), note::Column::Id));
let column = sub_interaction_reaction::Column::ReactionName;
let alias = format!("{}{}", prefix_alias, column.to_string());
let sub_query = Query::select()
.expr(SelectExpr {
expr: Expr::col(note_reaction::Column::Reaction).into(),
alias: Some(Alias::new(alias).into_iden()),
window: None,
})
.from(note_reaction::Entity)
.cond_where(Expr::col(note_reaction::Column::NoteId).eq(note_id_col))
.and_where(Expr::col(note_reaction::Column::UserId).eq(user_id))
.take()
.into_sub_query_statement();
QuerySelect::query(&mut self).expr(SimpleExpr::SubQuery(None, Box::new(sub_query)));
self
}
fn add_sub_select_renote(
mut self: Select<note::Entity>,
source_note_alias: Option<&str>,
prefix_alias: &str,
user_id: &str,
) -> Select<note::Entity> {
let iden = source_note_alias.unwrap_or_else(|| note::Entity.table_name());
let note_id_col = Expr::col((Alias::new(iden), note::Column::Id));
let renote_note_tbl = Alias::new(format!("{}{}", prefix_alias, "note"));
let column = sub_interaction_renote::Column::Renotes;
let alias = format!("{}{}", prefix_alias, column.to_string());
let sub_query = Query::select()
.expr(SelectExpr {
expr: Expr::count(Expr::col(Asterisk)),
alias: Some(Alias::new(alias).into_iden()),
window: None,
})
.from_as(note::Entity, renote_note_tbl.clone())
.cond_where(
Expr::col((renote_note_tbl.clone(), note::Column::RenoteId)).eq(note_id_col),
)
.and_where(Expr::col((renote_note_tbl.clone(), note::Column::UserId)).eq(user_id))
.take()
.into_sub_query_statement();
QuerySelect::query(&mut self).expr(SimpleExpr::SubQuery(None, Box::new(sub_query)));
self
}
} }
const ALIAS_USER: Lazy<Alias> = Lazy::new(|| Alias::new(USER)); lazy_static! {
const ALIAS_USER_AVATAR: Lazy<Alias> = Lazy::new(|| Alias::new(USER_AVATAR)); static ref ALIAS_INTERACTION_RENOTE: Alias = Alias::new(INTERACTION_RENOTE);
const ALIAS_USER_BANNER: Lazy<Alias> = Lazy::new(|| Alias::new(USER_BANNER)); static ref ALIAS_INTERACTION_REACTION: Alias = Alias::new(INTERACTION_REACTION);
const ALIAS_REPLY: Lazy<Alias> = Lazy::new(|| Alias::new(REPLY)); static ref ALIAS_USER: Alias = Alias::new(USER);
const ALIAS_REPLY_USER: Lazy<Alias> = Lazy::new(|| Alias::new(REPLY_USER)); static ref ALIAS_USER_AVATAR: Alias = Alias::new(USER_AVATAR);
const ALIAS_REPLY_USER_AVATAR: Lazy<Alias> = Lazy::new(|| Alias::new(REPLY_USER_AVATAR)); static ref ALIAS_USER_BANNER: Alias = Alias::new(USER_BANNER);
const ALIAS_REPLY_USER_BANNER: Lazy<Alias> = Lazy::new(|| Alias::new(REPLY_USER_BANNER)); static ref ALIAS_REPLY: Alias = Alias::new(REPLY);
const ALIAS_RENOTE: Lazy<Alias> = Lazy::new(|| Alias::new(RENOTE)); static ref ALIAS_REPLY_INTERACTION_RENOTE: Alias = Alias::new(REPLY_INTERACTION_RENOTE);
const ALIAS_RENOTE_USER: Lazy<Alias> = Lazy::new(|| Alias::new(RENOTE_USER)); static ref ALIAS_REPLY_INTERACTION_REACTION: Alias = Alias::new(REPLY_INTERACTION_REACTION);
const ALIAS_RENOTE_USER_AVATAR: Lazy<Alias> = Lazy::new(|| Alias::new(RENOTE_USER_AVATAR)); static ref ALIAS_REPLY_USER: Alias = Alias::new(REPLY_USER);
const ALIAS_RENOTE_USER_BANNER: Lazy<Alias> = Lazy::new(|| Alias::new(RENOTE_USER_BANNER)); static ref ALIAS_REPLY_USER_AVATAR: Alias = Alias::new(REPLY_USER_AVATAR);
static ref ALIAS_REPLY_USER_BANNER: Alias = Alias::new(REPLY_USER_BANNER);
static ref ALIAS_RENOTE: Alias = Alias::new(RENOTE);
static ref ALIAS_RENOTE_INTERACTION_RENOTE: Alias = Alias::new(RENOTE_INTERACTION_RENOTE);
static ref ALIAS_RENOTE_INTERACTION_REACTION: Alias = Alias::new(RENOTE_INTERACTION_REACTION);
static ref ALIAS_RENOTE_USER: Alias = Alias::new(RENOTE_USER);
static ref ALIAS_RENOTE_USER_AVATAR: Alias = Alias::new(RENOTE_USER_AVATAR);
static ref ALIAS_RENOTE_USER_BANNER: Alias = Alias::new(RENOTE_USER_BANNER);
}
impl NoteResolver { impl NoteResolver {
pub fn new(db: CalckeyModel) -> Self { pub fn new(db: CalckeyModel) -> Self {
@ -142,11 +294,11 @@ impl NoteResolver {
let select = self.resolve(options); let select = self.resolve(options);
let visibility_filter = options.visibility_filter.with_note_and_user_tables(None); let visibility_filter = options.visibility_filter.with_note_and_user_tables(None);
let time_filter = options.time_range.as_ref().map(|f| match f { let time_filter = options.time_range.as_ref().map(|f| match f {
RangeFilter::TimeStart(start) => note::Column::CreatedAt.gte(start.clone()), RangeFilter::TimeStart(start) => note::Column::CreatedAt.gte(*start),
RangeFilter::TimeRange(range) => { RangeFilter::TimeRange(range) => {
note::Column::CreatedAt.between(range.start().clone(), range.end().clone()) note::Column::CreatedAt.between(*range.start(), *range.end())
} }
RangeFilter::TimeEnd(end) => note::Column::CreatedAt.lt(end.clone()), RangeFilter::TimeEnd(end) => note::Column::CreatedAt.lt(*end),
}); });
let id_filter = options.ids.as_ref().map(|ids| { let id_filter = options.ids.as_ref().map(|ids| {
@ -164,12 +316,19 @@ impl NoteResolver {
.into_model::<NoteData>() .into_model::<NoteData>()
.one(self.db.inner()) .one(self.db.inner())
.await?; .await?;
Ok(notes) Ok(notes)
} }
pub fn resolve(&self, options: &NoteResolveOptions) -> Select<note::Entity> { pub fn resolve(&self, options: &NoteResolveOptions) -> Select<note::Entity> {
let mut select = note::Entity::find().add_aliased_columns(Some(USER), user::Entity); let mut select = note::Entity::find().add_aliased_columns(Some(USER), user::Entity);
if let Some(user_id) = &options.with_interactions_from {
select = select
.add_sub_select_reaction(None, INTERACTION_REACTION, user_id)
.add_sub_select_renote(None, INTERACTION_RENOTE, user_id);
}
if options.with_user { if options.with_user {
select = select select = select
.add_aliased_columns(Some(USER_AVATAR), drive_file::Entity) .add_aliased_columns(Some(USER_AVATAR), drive_file::Entity)
@ -181,6 +340,12 @@ impl NoteResolver {
.add_aliased_columns(Some(REPLY), note::Entity) .add_aliased_columns(Some(REPLY), note::Entity)
.add_aliased_columns(Some(REPLY_USER), user::Entity); .add_aliased_columns(Some(REPLY_USER), user::Entity);
if let Some(user_id) = &options.with_interactions_from {
select = select
.add_sub_select_reaction(Some(REPLY), REPLY_INTERACTION_REACTION, user_id)
.add_sub_select_renote(Some(REPLY), REPLY_INTERACTION_RENOTE, user_id);
}
if options.with_user { if options.with_user {
select = select select = select
.add_aliased_columns(Some(REPLY_USER_AVATAR), drive_file::Entity) .add_aliased_columns(Some(REPLY_USER_AVATAR), drive_file::Entity)
@ -193,6 +358,12 @@ impl NoteResolver {
.add_aliased_columns(Some(RENOTE), note::Entity) .add_aliased_columns(Some(RENOTE), note::Entity)
.add_aliased_columns(Some(RENOTE_USER), user::Entity); .add_aliased_columns(Some(RENOTE_USER), user::Entity);
if let Some(user_id) = &options.with_interactions_from {
select = select
.add_sub_select_reaction(Some(RENOTE), RENOTE_INTERACTION_REACTION, user_id)
.add_sub_select_renote(Some(RENOTE), RENOTE_INTERACTION_RENOTE, user_id);
}
if options.with_user { if options.with_user {
select = select select = select
.add_aliased_columns(Some(RENOTE_USER_AVATAR), drive_file::Entity) .add_aliased_columns(Some(RENOTE_USER_AVATAR), drive_file::Entity)

View File

@ -74,6 +74,12 @@ pub struct NoteBase {
pack!(PackNoteBase, Required<Id> as id & Required<NoteBase> as note); pack!(PackNoteBase, Required<Id> as id & Required<NoteBase> as note);
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
#[ts(export)]
pub struct NoteSelfContextExt {
pub self_renote_count: Option<u64>,
}
#[derive(Clone, Debug, Deserialize, Serialize, TS)] #[derive(Clone, Debug, Deserialize, Serialize, TS)]
#[ts(export)] #[ts(export)]
pub struct NoteAttachmentExt { pub struct NoteAttachmentExt {
@ -82,19 +88,19 @@ pub struct NoteAttachmentExt {
} }
pack!( pack!(
PackNoteWithMaybeAttachments, PackNoteMaybeAttachments,
Required<Id> as id & Required<NoteBase> as note & Option<NoteAttachmentExt> as attachment Required<Id> as id & Required<NoteBase> as note & Option<NoteSelfContextExt> as user_context & Option<NoteAttachmentExt> as attachment
); );
#[derive(Clone, Debug, Deserialize, Serialize, TS)] #[derive(Clone, Debug, Deserialize, Serialize, TS)]
pub struct NoteDetailExt { pub struct NoteDetailExt {
pub parent_note: Option<Box<PackNoteWithMaybeAttachments>>, pub parent_note: Option<Box<PackNoteMaybeAttachments>>,
pub renoted_note: Option<Box<PackNoteWithMaybeAttachments>>, pub renoted_note: Option<Box<PackNoteMaybeAttachments>>,
} }
pack!( pack!(
PackNoteMaybeFull, PackNoteMaybeFull,
Required<Id> as id & Required<NoteBase> as note & Option<NoteAttachmentExt> as attachment & Option<NoteDetailExt> as detail Required<Id> as id & Required<NoteBase> as note & Option<NoteSelfContextExt> as user_context & Option<NoteAttachmentExt> as attachment & Option<NoteDetailExt> as detail
); );
#[derive(Clone, Debug, Deserialize, Serialize, TS)] #[derive(Clone, Debug, Deserialize, Serialize, TS)]
@ -111,4 +117,9 @@ pub enum Reaction {
}, },
} }
pub type ReactionPair = (Reaction, usize); #[derive(Clone, Debug, Deserialize, Serialize, TS)]
#[serde(untagged)]
pub enum ReactionPair {
WithoutContext(Reaction, u64),
WithContext(Reaction, u64, bool),
}

View File

@ -27,7 +27,7 @@ pub async fn handle_note(
} }
.fetch_single(&ctx, self_user.as_deref(), &id) .fetch_single(&ctx, self_user.as_deref(), &id)
.await? .await?
.ok_or_else(|| ObjectNotFound(id))?; .ok_or(ObjectNotFound(id))?;
Ok(Json(note.into())) Ok(Json(note))
} }

View File

@ -1,6 +1,7 @@
use magnetar_calckey_model::ck; use magnetar_calckey_model::ck;
use magnetar_calckey_model::note_model::sub_interaction_renote;
use magnetar_sdk::types::note::{ use magnetar_sdk::types::note::{
NoteDetailExt, PackNoteWithMaybeAttachments, PackPollBase, ReactionPair, NoteDetailExt, NoteSelfContextExt, PackNoteMaybeAttachments, PackPollBase, ReactionPair,
}; };
use magnetar_sdk::types::user::PackUserBase; use magnetar_sdk::types::user::PackUserBase;
use magnetar_sdk::types::{ use magnetar_sdk::types::{
@ -70,9 +71,17 @@ impl PackType<NoteBaseSource<'_>> for NoteBase {
} }
} }
impl PackType<&sub_interaction_renote::Model> for NoteSelfContextExt {
fn extract(_context: &PackingContext, data: &sub_interaction_renote::Model) -> Self {
NoteSelfContextExt {
self_renote_count: data.renotes.map(|v| v as u64),
}
}
}
pub struct NoteDetailSource<'a> { pub struct NoteDetailSource<'a> {
pub parent_note: Option<&'a PackNoteWithMaybeAttachments>, pub parent_note: Option<&'a PackNoteMaybeAttachments>,
pub renoted_note: Option<&'a PackNoteWithMaybeAttachments>, pub renoted_note: Option<&'a PackNoteMaybeAttachments>,
} }
impl<'a> PackType<NoteDetailSource<'a>> for NoteDetailExt { impl<'a> PackType<NoteDetailSource<'a>> for NoteDetailExt {

View File

@ -22,8 +22,8 @@ use magnetar_sdk::mmm::Token;
use magnetar_sdk::types::drive::PackDriveFileBase; use magnetar_sdk::types::drive::PackDriveFileBase;
use magnetar_sdk::types::emoji::EmojiContext; use magnetar_sdk::types::emoji::EmojiContext;
use magnetar_sdk::types::note::{ use magnetar_sdk::types::note::{
NoteAttachmentExt, NoteBase, NoteDetailExt, PackNoteBase, PackNoteMaybeFull, NoteAttachmentExt, NoteBase, NoteDetailExt, NoteSelfContextExt, PackNoteBase,
PackNoteWithMaybeAttachments, Reaction, PackNoteMaybeAttachments, PackNoteMaybeFull, Reaction, ReactionPair,
}; };
use magnetar_sdk::types::{Id, MmXml}; use magnetar_sdk::types::{Id, MmXml};
use magnetar_sdk::{mmm, Packed, Required}; use magnetar_sdk::{mmm, Packed, Required};
@ -151,7 +151,7 @@ impl NoteVisibilityFilterModel {
return Ok(following || user.host.is_some() && note.user_host.is_some()); return Ok(following || user.host.is_some() && note.user_host.is_some());
} }
return Ok(false); Ok(false)
} }
pub fn new_note_visibility_filter(&self, user: Option<&str>) -> NoteVisibilityFilterSimple { pub fn new_note_visibility_filter(&self, user: Option<&str>) -> NoteVisibilityFilterSimple {
@ -225,18 +225,28 @@ impl NoteModel {
let reactions_raw = let reactions_raw =
serde_json::Map::<String, serde_json::Value>::deserialize(&note_data.note.reactions)? serde_json::Map::<String, serde_json::Value>::deserialize(&note_data.note.reactions)?
.into_iter() .into_iter()
.map(|(code, count)| { .map(|(ref code, count)| {
let reaction = parse_reaction(code)
.map_or_else(|| Either::Left(code.to_string()), Either::Right);
( (
parse_reaction(&code).map_or_else(|| Either::Left(code), Either::Right), reaction,
count, count,
note_data
.interaction_user_reaction
.as_ref()
.and_then(|r| r.reaction_name.as_deref())
.map(|r| r == code),
) )
}) })
.map(|(code, count)| Ok((code, usize::deserialize(count)?))) .map(|(code, count, self_reacted)| {
Ok((code, u64::deserialize(count)?, self_reacted))
})
.collect::<Result<Vec<_>, serde_json::Error>>()?; .collect::<Result<Vec<_>, serde_json::Error>>()?;
// Pick out all successfully-parsed shortcode emojis // Pick out all successfully-parsed shortcode emojis
let reactions_to_resolve = reactions_raw let reactions_to_resolve = reactions_raw
.iter() .iter()
.map(|(code, _)| code) .map(|(code, _, _)| code)
.map(Either::as_ref) .map(Either::as_ref)
.filter_map(Either::right) .filter_map(Either::right)
.filter_map(|c| match c { .filter_map(|c| match c {
@ -261,34 +271,38 @@ impl NoteModel {
// Left reactions and the Right ones that didn't resolve to any emoji are turned back into Unknown // Left reactions and the Right ones that didn't resolve to any emoji are turned back into Unknown
let reactions = &reactions_raw let reactions = &reactions_raw
.into_iter() .into_iter()
.map(|(raw, count)| { .map(|(raw, count, self_reaction)| {
( let reaction = raw.either(
raw.either( |raw| Reaction::Unknown { raw },
|raw| Reaction::Unknown { raw }, |raw| match raw {
|raw| match raw { RawReaction::Unicode(text) => Reaction::Unicode(text),
RawReaction::Unicode(text) => Reaction::Unicode(text), RawReaction::Shortcode { shortcode, host } => reactions_fetched
RawReaction::Shortcode { shortcode, host } => reactions_fetched .iter()
.iter() .find(|e| e.host == host && e.name == shortcode)
.find(|e| e.host == host && e.name == shortcode) .map_or_else(
.map_or_else( || Reaction::Unknown {
|| Reaction::Unknown { raw: format!(
raw: format!( ":{shortcode}{}:",
":{shortcode}{}:", host.as_deref()
host.as_deref() .map(|h| format!("@{h}"))
.map(|h| format!("@{h}")) .unwrap_or_default()
.unwrap_or_default() ),
), },
}, |e| Reaction::Shortcode {
|e| Reaction::Shortcode { name: shortcode.clone(),
name: shortcode.clone(), host: host.clone(),
host: host.clone(), url: e.public_url.clone(),
url: e.public_url.clone(), },
}, ),
), },
}, );
),
count, match self_reaction {
) Some(self_reaction) => {
ReactionPair::WithContext(reaction, count, self_reaction)
}
None => ReactionPair::WithoutContext(reaction, count),
}
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
@ -322,54 +336,15 @@ impl NoteModel {
))) )))
} }
async fn extract_reply_target_base( fn extract_interaction(
&self, &self,
ctx: &PackingContext, ctx: &PackingContext,
note: &NoteData, note: &NoteData,
) -> PackResult<Option<PackNoteBase>> { ) -> PackResult<Option<NoteSelfContextExt>> {
match note.reply.as_ref() { Ok(note
Some(r) if self.with_context => self.extract_base(ctx, r).await.map(Some), .interaction_user_renote
_ => Ok(None), .as_ref()
} .map(|renote_info| NoteSelfContextExt::extract(ctx, renote_info)))
}
async fn extract_renote_target_base(
&self,
ctx: &PackingContext,
note: &NoteData,
) -> PackResult<Option<PackNoteBase>> {
match note.renote.as_ref() {
Some(r) if self.with_context => self.extract_base(ctx, r).await.map(Some),
_ => Ok(None),
}
}
async fn extract_reply_target_attachemnts(
&self,
ctx: &PackingContext,
drive_model: &DriveModel,
note: &NoteData,
) -> PackResult<Option<Vec<PackDriveFileBase>>> {
match note.reply.as_ref() {
Some(r) if self.with_context => {
self.extract_attachments(ctx, drive_model, &r.note).await
}
_ => Ok(None),
}
}
async fn extract_renote_target_attachemnts(
&self,
ctx: &PackingContext,
drive_model: &DriveModel,
note: &NoteData,
) -> PackResult<Option<Vec<PackDriveFileBase>>> {
match note.renote.as_ref() {
Some(r) if self.with_context => {
self.extract_attachments(ctx, drive_model, &r.note).await
}
_ => Ok(None),
}
} }
async fn extract_attachments( async fn extract_attachments(
@ -393,6 +368,33 @@ impl NoteModel {
Ok(None) Ok(None)
} }
async fn pack_single_attachments(
&self,
ctx: &PackingContext,
drive_model: &DriveModel,
note_data: &NoteData,
) -> PackResult<PackNoteMaybeAttachments> {
let (PackNoteBase { id, note }, attachments_pack) = try_join!(
self.extract_base(ctx, note_data),
self.extract_attachments(ctx, drive_model, &note_data.note),
)?;
Ok(PackNoteMaybeAttachments::pack_from((
id,
note,
self.extract_interaction(ctx, note_data)?,
attachments_pack.map(|attachments| {
NoteAttachmentExt::extract(
ctx,
NoteAttachmentSource {
attachments: &attachments,
poll: None, // TODO
},
)
}),
)))
}
pub async fn fetch_single( pub async fn fetch_single(
&self, &self,
ctx: &PackingContext, ctx: &PackingContext,
@ -411,6 +413,10 @@ impl NoteModel {
with_user: self.with_context, with_user: self.with_context,
with_reply_target: self.with_context, with_reply_target: self.with_context,
with_renote_target: self.with_context, with_renote_target: self.with_context,
with_interactions_from: self
.with_context
.then(|| as_user.map(ck::user::Model::get_id).map(str::to_string))
.flatten(),
}) })
.await? .await?
else { else {
@ -419,80 +425,47 @@ impl NoteModel {
let drive_model = DriveModel; let drive_model = DriveModel;
let extract_reply_target = self.extract_reply_target_base(ctx, &note); let reply_target = async {
let extract_renote_target = self.extract_renote_target_base(ctx, &note); match note.reply.as_ref() {
let extract_attachments = self.extract_attachments(ctx, &drive_model, &note.note); Some(r) if self.with_context => self
let extract_reply_attachments = .pack_single_attachments(ctx, &drive_model, r)
self.extract_reply_target_attachemnts(ctx, &drive_model, &note); .await
let extract_renote_attachments = .map(Some),
self.extract_renote_target_attachemnts(ctx, &drive_model, &note); _ => Ok(None),
}
};
// TODO: Polls, ... let renote_target = async {
match note.renote.as_ref() {
Some(r) if self.with_context => self
.pack_single_attachments(ctx, &drive_model, r)
.await
.map(Some),
_ => Ok(None),
}
};
let ( let (
PackNoteBase { id, note }, PackNoteMaybeAttachments {
id,
note,
user_context,
attachment,
},
reply_target_pack, reply_target_pack,
renote_target_pack, renote_target_pack,
attachments_pack,
reply_attachments_pack,
renote_attachments_pack,
) = try_join!( ) = try_join!(
self.extract_base(ctx, &note), self.pack_single_attachments(ctx, &drive_model, &note),
extract_reply_target, reply_target,
extract_renote_target, renote_target
extract_attachments,
extract_reply_attachments,
extract_renote_attachments
)?; )?;
let detail = self.with_context.then(|| { let detail = self.with_context.then(|| {
NoteDetailExt::extract( NoteDetailExt::extract(
ctx, ctx,
NoteDetailSource { NoteDetailSource {
parent_note: reply_target_pack parent_note: reply_target_pack.as_ref(),
.map(|base| { renoted_note: renote_target_pack.as_ref(),
PackNoteWithMaybeAttachments::pack_from((
base.id,
base.note,
reply_attachments_pack.map(|attachments| {
NoteAttachmentExt::extract(
ctx,
NoteAttachmentSource {
attachments: &attachments,
poll: None, // TODO
},
)
}),
))
})
.as_ref(),
renoted_note: renote_target_pack
.map(|base| {
PackNoteWithMaybeAttachments::pack_from((
base.id,
base.note,
renote_attachments_pack.map(|attachments| {
NoteAttachmentExt::extract(
ctx,
NoteAttachmentSource {
attachments: &attachments,
poll: None, // TODO
},
)
}),
))
})
.as_ref(),
},
)
});
let attachments = attachments_pack.map(|attachments| {
NoteAttachmentExt::extract(
ctx,
NoteAttachmentSource {
attachments: &attachments,
poll: None, // TODO
}, },
) )
}); });
@ -500,7 +473,8 @@ impl NoteModel {
Ok(Some(PackNoteMaybeFull::pack_from(( Ok(Some(PackNoteMaybeFull::pack_from((
id, id,
note, note,
attachments, user_context,
attachment,
detail, detail,
)))) ))))
} }