Merge branch 'develop' into iceshrimp_mastodon

This commit is contained in:
naskya 2024-06-09 18:30:27 +09:00
commit 870d625565
No known key found for this signature in database
GPG key ID: 712D413B3A9FED5C
8 changed files with 157 additions and 123 deletions

View file

@ -378,6 +378,13 @@ export function isSilencedServer(host: string): Promise<boolean>
* ```
*/
export function isAllowedServer(host: string): Promise<boolean>
export interface PartialNoteToCheckWordMute {
fileIds: Array<string>
text: string | null
cw: string | null
renoteId: string | null
replyId: string | null
}
/**
* Returns whether `note` should be hard-muted.
*
@ -390,11 +397,11 @@ export function isAllowedServer(host: string): Promise<boolean>
*
* # Arguments
*
* * `note` : [PartialNoteToElaborate] object
* * `note` : [PartialNoteToCheckWordMute] object
* * `muted_words` : list of muted keyword lists (each array item is a space-separated keyword list that represents an AND condition)
* * `muted_patterns` : list of JavaScript-style (e.g., `/foo/i`) regular expressions
*/
export function checkWordMute(note: PartialNoteToElaborate, mutedWords: Array<string>, mutedPatterns: Array<string>): Promise<boolean>
export function checkWordMute(note: PartialNoteToCheckWordMute, mutedWords: Array<string>, mutedPatterns: Array<string>): Promise<boolean>
export function getFullApAccount(username: string, host?: string | undefined | null): string
export function isSelfHost(host?: string | undefined | null): boolean
export function isSameOrigin(uri: string): boolean
@ -413,14 +420,6 @@ export interface ImageSize {
height: number
}
export function getImageSizeFromUrl(url: string): Promise<ImageSize>
export interface PartialNoteToElaborate {
fileIds: Array<string>
userId: string
text: string | null
cw: string | null
renoteId: string | null
replyId: string | null
}
export interface PartialNoteToSummarize {
fileIds: Array<string>
text: string | null

View file

@ -1,8 +1,17 @@
use crate::misc::get_note_all_texts::{all_texts, PartialNoteToElaborate};
use crate::misc::get_note_all_texts::all_texts;
use once_cell::sync::Lazy;
use regex::Regex;
use sea_orm::DbErr;
#[crate::export(object)]
pub struct PartialNoteToCheckWordMute {
pub file_ids: Vec<String>,
pub text: Option<String>,
pub cw: Option<String>,
pub renote_id: Option<String>,
pub reply_id: Option<String>,
}
fn convert_regex(js_regex: &str) -> String {
static RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"^/(.+)/(.*)$").unwrap());
RE.replace(js_regex, "(?$2)$1").to_string()
@ -37,12 +46,12 @@ fn check_word_mute_impl(
///
/// # Arguments
///
/// * `note` : [PartialNoteToElaborate] object
/// * `note` : [PartialNoteToCheckWordMute] object
/// * `muted_words` : list of muted keyword lists (each array item is a space-separated keyword list that represents an AND condition)
/// * `muted_patterns` : list of JavaScript-style (e.g., `/foo/i`) regular expressions
#[crate::export]
pub async fn check_word_mute(
note: PartialNoteToElaborate,
note: PartialNoteToCheckWordMute,
muted_words: &[String],
muted_patterns: &[String],
) -> Result<bool, DbErr> {
@ -50,7 +59,7 @@ pub async fn check_word_mute(
Ok(false)
} else {
Ok(check_word_mute_impl(
&all_texts(note, true).await?,
&all_texts!(note, true).await?,
muted_words,
muted_patterns,
))

View file

@ -4,40 +4,33 @@ use crate::{
};
use sea_orm::{prelude::*, QuerySelect};
#[crate::export(object)]
pub struct PartialNoteToElaborate {
pub file_ids: Vec<String>,
pub user_id: String,
pub text: Option<String>,
pub cw: Option<String>,
pub renote_id: Option<String>,
pub reply_id: Option<String>,
}
/// Returns [`Vec<String>`] containing the post text, content warning,
/// those of the "parent" (replied/quoted) posts, and alt texts of attached files.
/// Consider using [`all_texts`] macro instead
/// when dealing with a note ([`note::Model`])-like instance.
///
/// # Arguments
///
/// * `note` : [PartialNoteToElaborate] object
/// * `file_ids` : IDs of attached files ([`drive_file::Model`])
/// * `text`, `cw`, `renote_id`, `reply_id` : note ([`note::Model`]) fields
/// * `include_parent` : whether to take the reply-to post and quoted post into account
pub async fn all_texts(
note: PartialNoteToElaborate,
pub async fn all_texts_impl(
file_ids: &[String],
text: Option<String>,
cw: Option<String>,
renote_id: &Option<String>,
reply_id: &Option<String>,
include_parent: bool,
) -> Result<Vec<String>, DbErr> {
let db = db_conn().await?;
let mut texts: Vec<String> = vec![];
let is_renote: bool;
let is_renote = text.is_none();
if let Some(text) = note.text {
is_renote = false;
if let Some(text) = text {
texts.push(text);
} else {
is_renote = true;
}
if let Some(cw) = note.cw {
if let Some(cw) = cw {
texts.push(cw);
}
@ -45,7 +38,7 @@ pub async fn all_texts(
drive_file::Entity::find()
.select_only()
.column(drive_file::Column::Comment)
.filter(drive_file::Column::Id.is_in(note.file_ids))
.filter(drive_file::Column::Id.is_in(file_ids))
.into_tuple::<Option<String>>()
.all(db)
.await?
@ -53,45 +46,87 @@ pub async fn all_texts(
.flatten(),
);
if note.renote_id.is_some() && (include_parent || is_renote) {
let renote_id = note.renote_id.unwrap();
if let Some((text, cw)) = note::Entity::find_by_id(&renote_id)
let mut query_note_ids = Vec::<&str>::with_capacity(2);
if let Some(renote_id) = renote_id {
if include_parent || is_renote {
query_note_ids.push(renote_id);
}
}
if let Some(reply_id) = reply_id {
if include_parent {
query_note_ids.push(reply_id);
}
}
if !query_note_ids.is_empty() {
texts.extend(
note::Entity::find()
.filter(note::Column::Id.is_in(query_note_ids))
.select_only()
.columns([note::Column::Text, note::Column::Cw])
.into_tuple::<(Option<String>, Option<String>)>()
.one(db)
.await?
{
if let Some(t) = text {
texts.push(t);
}
if let Some(c) = cw {
texts.push(c);
}
} else {
tracing::warn!("nonexistent renote id: {}", renote_id);
}
}
if include_parent && note.reply_id.is_some() {
if let Some((text, cw)) = note::Entity::find_by_id(note.reply_id.as_ref().unwrap())
.select_only()
.columns([note::Column::Text, note::Column::Cw])
.into_tuple::<(Option<String>, Option<String>)>()
.one(db)
.await?
{
if let Some(t) = text {
texts.push(t);
}
if let Some(c) = cw {
texts.push(c);
}
} else {
tracing::warn!("nonexistent reply id: {}", note.reply_id.unwrap());
}
.into_iter()
.flat_map(|(text, cw)| [text, cw])
.flatten(),
);
}
Ok(texts)
}
/// Returns [`Vec<String>`] containing the post text, content warning,
/// those of the "parent" (replied/quoted) posts, and alt texts of attached files.
///
/// # Arguments
///
/// * `note_like` : a note ([`note::Model`])-like instance containing
/// `file_ids`, `text`, `cw`, `renote_id`, `reply_id` fields
/// * `include_parent` ([bool]) : whether to take the reply-to post and quoted post into account
///
/// # Caveats
///
/// The `note_like` argument should not contain function calls
/// (e.g., `all_texts!(note.clone(), false)`)
/// since the function will be called multiple times after macro expansion.
///
/// # Examples
///
/// ```
/// # use backend_rs::misc::get_note_all_texts::all_texts;
/// // note-like struct
/// struct SomeNoteLikeStruct {
/// // required fields
/// file_ids: Vec<String>,
/// text: Option<String>,
/// cw: Option<String>,
/// renote_id: Option<String>,
/// reply_id: Option<String>,
/// // arbitrary extra fields
/// extra_field_1: u32,
/// extra_field_2: Vec<String>,
/// }
///
/// async fn all_texts_from_some_note_like_struct(
/// note_like: &SomeNoteLikeStruct,
/// include_parent: bool,
/// ) -> Result<Vec<String>, sea_orm::DbErr> {
/// all_texts!(note_like, include_parent).await
/// }
/// ```
#[doc(hidden)] // hide the macro in the top doc page
#[macro_export]
macro_rules! all_texts {
($note_like:expr, $include_parent:expr) => {
$crate::misc::get_note_all_texts::all_texts_impl(
&$note_like.file_ids,
$note_like.text.clone(),
$note_like.cw.clone(),
&$note_like.renote_id,
&$note_like.reply_id,
$include_parent,
)
};
}
#[doc(inline)] // show the macro in the module doc page
pub use all_texts;

View file

@ -2,24 +2,23 @@
use crate::{database::db_conn, model::entity::antenna};
use sea_orm::prelude::*;
use std::sync::Mutex;
use std::sync::{Arc, Mutex};
static CACHE: Mutex<Option<Vec<antenna::Model>>> = Mutex::new(None);
static CACHE: Mutex<Option<Arc<[antenna::Model]>>> = Mutex::new(None);
fn set(antennas: &[antenna::Model]) {
let _ = CACHE
.lock()
.map(|mut cache| *cache = Some(antennas.to_owned()));
fn set(antennas: Arc<[antenna::Model]>) {
let _ = CACHE.lock().map(|mut cache| *cache = Some(antennas));
}
pub(super) async fn update() -> Result<Vec<antenna::Model>, DbErr> {
pub(super) async fn update() -> Result<Arc<[antenna::Model]>, DbErr> {
tracing::debug!("updating cache");
let antennas = antenna::Entity::find().all(db_conn().await?).await?;
set(&antennas);
let antennas: Arc<[antenna::Model]> =
antenna::Entity::find().all(db_conn().await?).await?.into();
set(antennas.clone());
Ok(antennas)
}
pub(super) async fn get() -> Result<Vec<antenna::Model>, DbErr> {
pub(super) async fn get() -> Result<Arc<[antenna::Model]>, DbErr> {
if let Some(cache) = CACHE.lock().ok().and_then(|cache| cache.clone()) {
return Ok(cache);
}

View file

@ -12,8 +12,6 @@ pub enum AntennaCheckError {
Db(#[from] DbErr),
#[error("Cache error: {0}")]
Cache(#[from] cache::Error),
#[error("User profile not found: {0}")]
UserProfileNotFound(String),
}
fn match_all(space_separated_words: &str, text: &str, case_sensitive: bool) -> bool {
@ -29,7 +27,7 @@ fn match_all(space_separated_words: &str, text: &str, case_sensitive: bool) -> b
}
}
pub async fn check_hit_antenna(
pub(super) async fn check_hit_antenna(
antenna: &antenna::Model,
note: &note::Model,
note_all_texts: &[String],

View file

@ -1,7 +1,7 @@
use crate::{
database::{cache, redis_conn, redis_key, RedisConnError},
federation::acct::Acct,
misc::get_note_all_texts::{all_texts, PartialNoteToElaborate},
misc::get_note_all_texts::all_texts,
model::entity::note,
service::{
antenna,
@ -37,31 +37,19 @@ type Note = note::Model;
#[crate::export]
pub async fn update_antennas_on_new_note(
note: Note,
note: &Note,
note_author: &Acct,
note_muted_users: &[String],
) -> Result<(), Error> {
let note_cloned = note.clone();
let note_all_texts = all_texts(
PartialNoteToElaborate {
file_ids: note.file_ids,
user_id: note.user_id,
text: note.text,
cw: note.cw,
renote_id: note.renote_id,
reply_id: note.reply_id,
},
false,
)
.await?;
let note_all_texts = all_texts!(note, false).await?;
// TODO: do this in parallel
for antenna in antenna::cache::get().await?.iter() {
if note_muted_users.contains(&antenna.user_id) {
continue;
}
if check_hit_antenna(antenna, &note_cloned, &note_all_texts, note_author).await? {
add_note_to_antenna(&antenna.id, &note_cloned).await?;
if check_hit_antenna(antenna, note, &note_all_texts, note_author).await? {
add_note_to_antenna(&antenna.id, note).await?;
}
}

View file

@ -90,6 +90,6 @@
"vue-draggable-plus": "0.5.0",
"vue-plyr": "7.0.0",
"vue-prism-editor": "2.0.0-alpha.2",
"vue-tsc": "2.0.19"
"vue-tsc": "2.0.21"
}
}

View file

@ -762,8 +762,8 @@ importers:
specifier: 2.0.0-alpha.2
version: 2.0.0-alpha.2(vue@3.4.27(typescript@5.4.5))
vue-tsc:
specifier: 2.0.19
version: 2.0.19(typescript@5.4.5)
specifier: 2.0.21
version: 2.0.21(typescript@5.4.5)
packages/firefish-js:
dependencies:
@ -2586,14 +2586,14 @@ packages:
vite: ^5.0.0
vue: ^3.2.25
'@volar/language-core@2.2.5':
resolution: {integrity: sha512-2htyAuxRrAgETmFeUhT4XLELk3LiEcqoW/B8YUXMF6BrGWLMwIR09MFaZYvrA2UhbdAeSyeQ726HaWSWkexUcQ==}
'@volar/language-core@2.3.0-alpha.15':
resolution: {integrity: sha512-uSfn1Dsl1w9o2aN9nnS6N/4FcjSbmpY6P/ypfW4kRhasEyICstu4swIz2joNR6532R02JwJY9Ta0pxRmXbBOqw==}
'@volar/source-map@2.2.5':
resolution: {integrity: sha512-wrOEIiZNf4E+PWB0AxyM4tfhkfldPsb3bxg8N6FHrxJH2ohar7aGu48e98bp3pR9HUA7P/pR9VrLmkTrgCCnWQ==}
'@volar/source-map@2.3.0-alpha.15':
resolution: {integrity: sha512-DQr3FwhRxtxX4W6BoJkwajWjj6BAF5H/SgtzFaUP9z8txn6Y5oFxZPPDG+3Xwu3pTV3gvVlE7AL5E/G1jUr5Yg==}
'@volar/typescript@2.2.5':
resolution: {integrity: sha512-eSV/n75+ppfEVugMC/salZsI44nXDPAyL6+iTYCNLtiLHGJsnMv9GwiDMujrvAUj/aLQyqRJgYtXRoxop2clCw==}
'@volar/typescript@2.3.0-alpha.15':
resolution: {integrity: sha512-sTzUyqGC1wkzVyY0XZBO5smCPDRvIqxlMTUw7bZebuD/7sGaVfyk9ryE29aG6CLpuYDev/ugpQsKoFVhFaQQ8A==}
'@vue/compiler-core@3.4.27':
resolution: {integrity: sha512-E+RyqY24KnyDXsCuQrI+mlcdW3ALND6U7Gqa/+bVwbcpcR3BRRIckFoz7Qyd4TTlnugtwuI7YgjbvsLmxb+yvg==}
@ -2610,8 +2610,8 @@ packages:
'@vue/compiler-ssr@3.4.27':
resolution: {integrity: sha512-CVRzSJIltzMG5FcidsW0jKNQnNRYC8bT21VegyMMtHmhW3UOI7knmUehzswXLrExDLE6lQCZdrhD4ogI7c+vuw==}
'@vue/language-core@2.0.19':
resolution: {integrity: sha512-A9EGOnvb51jOvnCYoRLnMP+CcoPlbZVxI9gZXE/y2GksRWM6j/PrLEIC++pnosWTN08tFpJgxhSS//E9v/Sg+Q==}
'@vue/language-core@2.0.21':
resolution: {integrity: sha512-vjs6KwnCK++kIXT+eI63BGpJHfHNVJcUCr3RnvJsccT3vbJnZV5IhHR2puEkoOkIbDdp0Gqi1wEnv3hEd3WsxQ==}
peerDependencies:
typescript: '*'
peerDependenciesMeta:
@ -7482,6 +7482,9 @@ packages:
resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==}
engines: {node: '>=0.10.0'}
vscode-uri@3.0.8:
resolution: {integrity: sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==}
vue-draggable-plus@0.5.0:
resolution: {integrity: sha512-A5TT5+M5JceROSjPO9aDZTsrSN1TetEs419czPlboomarSiGIBIxTp2WD7XH53EHMrbO7Qo+leRiHWV/rMlyjA==}
peerDependencies:
@ -7509,8 +7512,8 @@ packages:
vue-template-compiler@2.7.16:
resolution: {integrity: sha512-AYbUWAJHLGGQM7+cNTELw+KsOG9nl2CnSv467WobS5Cv9uk3wFcnr1Etsz2sEIHEZvw1U+o9mRlEO6QbZvUPGQ==}
vue-tsc@2.0.19:
resolution: {integrity: sha512-JWay5Zt2/871iodGF72cELIbcAoPyhJxq56mPPh+M2K7IwI688FMrFKc/+DvB05wDWEuCPexQJ6L10zSwzzapg==}
vue-tsc@2.0.21:
resolution: {integrity: sha512-E6x1p1HaHES6Doy8pqtm7kQern79zRtIewkf9fiv7Y43Zo4AFDS5hKi+iHi2RwEhqRmuiwliB1LCEFEGwvxQnw==}
hasBin: true
peerDependencies:
typescript: '*'
@ -9677,18 +9680,19 @@ snapshots:
vite: 5.2.13(@types/node@20.14.2)(sass@1.77.4)(stylus@0.57.0)(terser@5.31.0)
vue: 3.4.27(typescript@5.4.5)
'@volar/language-core@2.2.5':
'@volar/language-core@2.3.0-alpha.15':
dependencies:
'@volar/source-map': 2.2.5
'@volar/source-map': 2.3.0-alpha.15
'@volar/source-map@2.2.5':
'@volar/source-map@2.3.0-alpha.15':
dependencies:
muggle-string: 0.4.1
'@volar/typescript@2.2.5':
'@volar/typescript@2.3.0-alpha.15':
dependencies:
'@volar/language-core': 2.2.5
'@volar/language-core': 2.3.0-alpha.15
path-browserify: 1.0.1
vscode-uri: 3.0.8
'@vue/compiler-core@3.4.27':
dependencies:
@ -9728,9 +9732,9 @@ snapshots:
'@vue/compiler-dom': 3.4.27
'@vue/shared': 3.4.27
'@vue/language-core@2.0.19(typescript@5.4.5)':
'@vue/language-core@2.0.21(typescript@5.4.5)':
dependencies:
'@volar/language-core': 2.2.5
'@volar/language-core': 2.3.0-alpha.15
'@vue/compiler-dom': 3.4.27
'@vue/shared': 3.4.27
computeds: 0.0.1
@ -15167,6 +15171,8 @@ snapshots:
void-elements@3.1.0: {}
vscode-uri@3.0.8: {}
vue-draggable-plus@0.5.0(@types/sortablejs@1.15.8):
dependencies:
'@types/sortablejs': 1.15.8
@ -15198,10 +15204,10 @@ snapshots:
de-indent: 1.0.2
he: 1.2.0
vue-tsc@2.0.19(typescript@5.4.5):
vue-tsc@2.0.21(typescript@5.4.5):
dependencies:
'@volar/typescript': 2.2.5
'@vue/language-core': 2.0.19(typescript@5.4.5)
'@volar/typescript': 2.3.0-alpha.15
'@vue/language-core': 2.0.21(typescript@5.4.5)
semver: 7.6.2
typescript: 5.4.5