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",
"idna",
"itertools 0.11.0",
"lazy_static",
"lru",
"magnetar_calckey_model",
"magnetar_common",
@ -1517,6 +1518,7 @@ dependencies = [
"ext_calckey_model_migration",
"futures-core",
"futures-util",
"lazy_static",
"magnetar_common",
"magnetar_sdk",
"once_cell",

View File

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

View File

@ -16,6 +16,7 @@ magnetar_sdk = { path = "../magnetar_sdk" }
dotenvy = { workspace = true}
futures-core = { workspace = true }
futures-util = { workspace = true }
lazy_static = { workspace = true }
tokio = { workspace = true, features = ["full"] }
tokio-util = { workspace = true}
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::ActiveValue::Set;
use sea_orm::{
ColumnTrait, ConnectOptions, DatabaseConnection, DbErr, EntityTrait, QueryFilter, QueryOrder,
RelationDef, RelationTrait, TransactionTrait,
ColumnTrait, ConnectOptions, DatabaseConnection, DbErr, EntityTrait, QueryFilter, RelationDef,
RelationTrait, TransactionTrait,
};
use serde::{Deserialize, Serialize};
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::{
ColumnTrait, DbErr, EntityTrait, FromQueryResult, Iden, Iterable, JoinType, QueryFilter,
QueryResult, QuerySelect, QueryTrait, RelationTrait, Select,
ColumnTrait, DbErr, EntityName, EntityTrait, FromQueryResult, Iden, Iterable, JoinType,
QueryFilter, QueryResult, QuerySelect, QueryTrait, RelationTrait, Select,
};
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 once_cell::unsync::Lazy;
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)]
pub struct NoteData {
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 avatar: Option<drive_file::Model>,
pub banner: Option<drive_file::Model>,
@ -21,14 +54,20 @@ pub struct NoteData {
pub renote: Option<Box<NoteData>>,
}
const INTERACTION_REACTION: &str = "interaction.reaction.";
const INTERACTION_RENOTE: &str = "interaction.renote.";
const USER: &str = "user.";
const USER_AVATAR: &str = "user.avatar.";
const USER_BANNER: &str = "user.banner.";
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_AVATAR: &str = "reply.user.avatar.";
const REPLY_USER_BANNER: &str = "reply.user.banner.";
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_AVATAR: &str = "renote.user.avatar.";
const RENOTE_USER_BANNER: &str = "renote.user.banner.";
@ -39,6 +78,16 @@ impl FromQueryResult for NoteData {
.map::<Result<_, DbErr>, _>(|r| {
Ok(Box::new(NoteData {
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)?,
avatar: drive_file::Model::from_query_result_optional(res, REPLY_USER_AVATAR)?,
banner: drive_file::Model::from_query_result_optional(res, REPLY_USER_BANNER)?,
@ -52,6 +101,16 @@ impl FromQueryResult for NoteData {
.map::<Result<_, DbErr>, _>(|r| {
Ok(Box::new(NoteData {
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)?,
avatar: drive_file::Model::from_query_result_optional(res, RENOTE_USER_AVATAR)?,
banner: drive_file::Model::from_query_result_optional(res, RENOTE_USER_BANNER)?,
@ -63,6 +122,14 @@ impl FromQueryResult for NoteData {
Ok(NoteData {
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)?,
avatar: drive_file::Model::from_query_result_optional(res, USER_AVATAR)?,
banner: drive_file::Model::from_query_result_optional(res, USER_BANNER)?,
@ -87,10 +154,25 @@ pub struct NoteResolveOptions {
pub with_user: bool,
pub with_reply_target: bool,
pub with_renote_target: bool,
pub with_interactions_from: Option<String>, // User ID
}
trait SelectColumnsExt {
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> {
@ -116,19 +198,89 @@ impl SelectColumnsExt for Select<note::Entity> {
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));
const ALIAS_USER_AVATAR: Lazy<Alias> = Lazy::new(|| Alias::new(USER_AVATAR));
const ALIAS_USER_BANNER: Lazy<Alias> = Lazy::new(|| Alias::new(USER_BANNER));
const ALIAS_REPLY: Lazy<Alias> = Lazy::new(|| Alias::new(REPLY));
const ALIAS_REPLY_USER: Lazy<Alias> = Lazy::new(|| Alias::new(REPLY_USER));
const ALIAS_REPLY_USER_AVATAR: Lazy<Alias> = Lazy::new(|| Alias::new(REPLY_USER_AVATAR));
const ALIAS_REPLY_USER_BANNER: Lazy<Alias> = Lazy::new(|| Alias::new(REPLY_USER_BANNER));
const ALIAS_RENOTE: Lazy<Alias> = Lazy::new(|| Alias::new(RENOTE));
const ALIAS_RENOTE_USER: Lazy<Alias> = Lazy::new(|| Alias::new(RENOTE_USER));
const ALIAS_RENOTE_USER_AVATAR: Lazy<Alias> = Lazy::new(|| Alias::new(RENOTE_USER_AVATAR));
const ALIAS_RENOTE_USER_BANNER: Lazy<Alias> = Lazy::new(|| Alias::new(RENOTE_USER_BANNER));
lazy_static! {
static ref ALIAS_INTERACTION_RENOTE: Alias = Alias::new(INTERACTION_RENOTE);
static ref ALIAS_INTERACTION_REACTION: Alias = Alias::new(INTERACTION_REACTION);
static ref ALIAS_USER: Alias = Alias::new(USER);
static ref ALIAS_USER_AVATAR: Alias = Alias::new(USER_AVATAR);
static ref ALIAS_USER_BANNER: Alias = Alias::new(USER_BANNER);
static ref ALIAS_REPLY: Alias = Alias::new(REPLY);
static ref ALIAS_REPLY_INTERACTION_RENOTE: Alias = Alias::new(REPLY_INTERACTION_RENOTE);
static ref ALIAS_REPLY_INTERACTION_REACTION: Alias = Alias::new(REPLY_INTERACTION_REACTION);
static ref ALIAS_REPLY_USER: Alias = Alias::new(REPLY_USER);
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 {
pub fn new(db: CalckeyModel) -> Self {
@ -142,11 +294,11 @@ impl NoteResolver {
let select = self.resolve(options);
let visibility_filter = options.visibility_filter.with_note_and_user_tables(None);
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) => {
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| {
@ -164,12 +316,19 @@ impl NoteResolver {
.into_model::<NoteData>()
.one(self.db.inner())
.await?;
Ok(notes)
}
pub fn resolve(&self, options: &NoteResolveOptions) -> Select<note::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 {
select = select
.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_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 {
select = select
.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_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 {
select = select
.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);
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
#[ts(export)]
pub struct NoteSelfContextExt {
pub self_renote_count: Option<u64>,
}
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
#[ts(export)]
pub struct NoteAttachmentExt {
@ -82,19 +88,19 @@ pub struct NoteAttachmentExt {
}
pack!(
PackNoteWithMaybeAttachments,
Required<Id> as id & Required<NoteBase> as note & Option<NoteAttachmentExt> as attachment
PackNoteMaybeAttachments,
Required<Id> as id & Required<NoteBase> as note & Option<NoteSelfContextExt> as user_context & Option<NoteAttachmentExt> as attachment
);
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
pub struct NoteDetailExt {
pub parent_note: Option<Box<PackNoteWithMaybeAttachments>>,
pub renoted_note: Option<Box<PackNoteWithMaybeAttachments>>,
pub parent_note: Option<Box<PackNoteMaybeAttachments>>,
pub renoted_note: Option<Box<PackNoteMaybeAttachments>>,
}
pack!(
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)]
@ -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)
.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::note_model::sub_interaction_renote;
use magnetar_sdk::types::note::{
NoteDetailExt, PackNoteWithMaybeAttachments, PackPollBase, ReactionPair,
NoteDetailExt, NoteSelfContextExt, PackNoteMaybeAttachments, PackPollBase, ReactionPair,
};
use magnetar_sdk::types::user::PackUserBase;
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 parent_note: Option<&'a PackNoteWithMaybeAttachments>,
pub renoted_note: Option<&'a PackNoteWithMaybeAttachments>,
pub parent_note: Option<&'a PackNoteMaybeAttachments>,
pub renoted_note: Option<&'a PackNoteMaybeAttachments>,
}
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::emoji::EmojiContext;
use magnetar_sdk::types::note::{
NoteAttachmentExt, NoteBase, NoteDetailExt, PackNoteBase, PackNoteMaybeFull,
PackNoteWithMaybeAttachments, Reaction,
NoteAttachmentExt, NoteBase, NoteDetailExt, NoteSelfContextExt, PackNoteBase,
PackNoteMaybeAttachments, PackNoteMaybeFull, Reaction, ReactionPair,
};
use magnetar_sdk::types::{Id, MmXml};
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(false);
Ok(false)
}
pub fn new_note_visibility_filter(&self, user: Option<&str>) -> NoteVisibilityFilterSimple {
@ -225,18 +225,28 @@ impl NoteModel {
let reactions_raw =
serde_json::Map::<String, serde_json::Value>::deserialize(&note_data.note.reactions)?
.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,
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>>()?;
// Pick out all successfully-parsed shortcode emojis
let reactions_to_resolve = reactions_raw
.iter()
.map(|(code, _)| code)
.map(|(code, _, _)| code)
.map(Either::as_ref)
.filter_map(Either::right)
.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
let reactions = &reactions_raw
.into_iter()
.map(|(raw, count)| {
(
raw.either(
|raw| Reaction::Unknown { raw },
|raw| match raw {
RawReaction::Unicode(text) => Reaction::Unicode(text),
RawReaction::Shortcode { shortcode, host } => reactions_fetched
.iter()
.find(|e| e.host == host && e.name == shortcode)
.map_or_else(
|| Reaction::Unknown {
raw: format!(
":{shortcode}{}:",
host.as_deref()
.map(|h| format!("@{h}"))
.unwrap_or_default()
),
},
|e| Reaction::Shortcode {
name: shortcode.clone(),
host: host.clone(),
url: e.public_url.clone(),
},
),
},
),
count,
)
.map(|(raw, count, self_reaction)| {
let reaction = raw.either(
|raw| Reaction::Unknown { raw },
|raw| match raw {
RawReaction::Unicode(text) => Reaction::Unicode(text),
RawReaction::Shortcode { shortcode, host } => reactions_fetched
.iter()
.find(|e| e.host == host && e.name == shortcode)
.map_or_else(
|| Reaction::Unknown {
raw: format!(
":{shortcode}{}:",
host.as_deref()
.map(|h| format!("@{h}"))
.unwrap_or_default()
),
},
|e| Reaction::Shortcode {
name: shortcode.clone(),
host: host.clone(),
url: e.public_url.clone(),
},
),
},
);
match self_reaction {
Some(self_reaction) => {
ReactionPair::WithContext(reaction, count, self_reaction)
}
None => ReactionPair::WithoutContext(reaction, count),
}
})
.collect::<Vec<_>>();
@ -322,54 +336,15 @@ impl NoteModel {
)))
}
async fn extract_reply_target_base(
fn extract_interaction(
&self,
ctx: &PackingContext,
note: &NoteData,
) -> PackResult<Option<PackNoteBase>> {
match note.reply.as_ref() {
Some(r) if self.with_context => self.extract_base(ctx, r).await.map(Some),
_ => Ok(None),
}
}
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),
}
) -> PackResult<Option<NoteSelfContextExt>> {
Ok(note
.interaction_user_renote
.as_ref()
.map(|renote_info| NoteSelfContextExt::extract(ctx, renote_info)))
}
async fn extract_attachments(
@ -393,6 +368,33 @@ impl NoteModel {
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(
&self,
ctx: &PackingContext,
@ -411,6 +413,10 @@ impl NoteModel {
with_user: self.with_context,
with_reply_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?
else {
@ -419,80 +425,47 @@ impl NoteModel {
let drive_model = DriveModel;
let extract_reply_target = self.extract_reply_target_base(ctx, &note);
let extract_renote_target = self.extract_renote_target_base(ctx, &note);
let extract_attachments = self.extract_attachments(ctx, &drive_model, &note.note);
let extract_reply_attachments =
self.extract_reply_target_attachemnts(ctx, &drive_model, &note);
let extract_renote_attachments =
self.extract_renote_target_attachemnts(ctx, &drive_model, &note);
let reply_target = async {
match note.reply.as_ref() {
Some(r) if self.with_context => self
.pack_single_attachments(ctx, &drive_model, r)
.await
.map(Some),
_ => 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 (
PackNoteBase { id, note },
PackNoteMaybeAttachments {
id,
note,
user_context,
attachment,
},
reply_target_pack,
renote_target_pack,
attachments_pack,
reply_attachments_pack,
renote_attachments_pack,
) = try_join!(
self.extract_base(ctx, &note),
extract_reply_target,
extract_renote_target,
extract_attachments,
extract_reply_attachments,
extract_renote_attachments
self.pack_single_attachments(ctx, &drive_model, &note),
reply_target,
renote_target
)?;
let detail = self.with_context.then(|| {
NoteDetailExt::extract(
ctx,
NoteDetailSource {
parent_note: reply_target_pack
.map(|base| {
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
parent_note: reply_target_pack.as_ref(),
renoted_note: renote_target_pack.as_ref(),
},
)
});
@ -500,7 +473,8 @@ impl NoteModel {
Ok(Some(PackNoteMaybeFull::pack_from((
id,
note,
attachments,
user_context,
attachment,
detail,
))))
}