Self reaction and renote fetching
This commit is contained in:
parent
8e02e46be5
commit
5b9b813037
|
@ -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",
|
||||
|
|
10
Cargo.toml
10
Cargo.toml
|
@ -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 }
|
||||
|
||||
|
|
|
@ -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"]}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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(¬e_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, ¬e_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, ¬e);
|
||||
let extract_renote_target = self.extract_renote_target_base(ctx, ¬e);
|
||||
let extract_attachments = self.extract_attachments(ctx, &drive_model, ¬e.note);
|
||||
let extract_reply_attachments =
|
||||
self.extract_reply_target_attachemnts(ctx, &drive_model, ¬e);
|
||||
let extract_renote_attachments =
|
||||
self.extract_renote_target_attachemnts(ctx, &drive_model, ¬e);
|
||||
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, ¬e),
|
||||
extract_reply_target,
|
||||
extract_renote_target,
|
||||
extract_attachments,
|
||||
extract_reply_attachments,
|
||||
extract_renote_attachments
|
||||
self.pack_single_attachments(ctx, &drive_model, ¬e),
|
||||
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,
|
||||
))))
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue