refactor (backend): don't check word mute twice
This commit is contained in:
parent
5e53f9a8cf
commit
ffa08748d0
10 changed files with 62 additions and 92 deletions
3
packages/backend-rs/index.d.ts
vendored
3
packages/backend-rs/index.d.ts
vendored
|
@ -267,6 +267,7 @@ export interface NoteLikeForGetNoteSummary {
|
|||
hasPoll: boolean
|
||||
}
|
||||
export function getNoteSummary(note: NoteLikeForGetNoteSummary): string
|
||||
export function isQuote(note: Note): boolean
|
||||
export function isSafeUrl(url: string): boolean
|
||||
export function latestVersion(): Promise<string>
|
||||
export function toMastodonId(firefishId: string): string | null
|
||||
|
@ -1175,7 +1176,7 @@ export interface Webhook {
|
|||
latestSentAt: Date | null
|
||||
latestStatus: number | null
|
||||
}
|
||||
export function updateAntennaOnCreateNote(antenna: Antenna, note: Note, noteAuthor: Acct): Promise<void>
|
||||
export function updateAntennasOnNewNote(note: Note, noteAuthor: Acct, noteMutedUsers: Array<string>): Promise<void>
|
||||
export function fetchNodeinfo(host: string): Promise<Nodeinfo>
|
||||
export function nodeinfo_2_1(): Promise<any>
|
||||
export function nodeinfo_2_0(): Promise<any>
|
||||
|
|
|
@ -310,7 +310,7 @@ if (!nativeBinding) {
|
|||
throw new Error(`Failed to load native binding`)
|
||||
}
|
||||
|
||||
const { SECOND, MINUTE, HOUR, DAY, USER_ONLINE_THRESHOLD, USER_ACTIVE_THRESHOLD, FILE_TYPE_BROWSERSAFE, loadEnv, loadConfig, stringToAcct, acctToString, initializeRustLogger, showServerInfo, isBlockedServer, isSilencedServer, isAllowedServer, checkWordMute, getFullApAccount, isSelfHost, isSameOrigin, extractHost, toPuny, isUnicodeEmoji, sqlLikeEscape, safeForSql, formatMilliseconds, getImageSizeFromUrl, getNoteSummary, isSafeUrl, latestVersion, toMastodonId, fromMastodonId, fetchMeta, metaToPugArgs, nyaify, hashPassword, verifyPassword, isOldPasswordAlgorithm, decodeReaction, countReactions, toDbReaction, removeOldAttestationChallenges, cpuInfo, cpuUsage, memoryUsage, storageUsage, AntennaSrcEnum, DriveFileUsageHintEnum, MutedNoteReasonEnum, NoteVisibilityEnum, NotificationTypeEnum, PageVisibilityEnum, PollNotevisibilityEnum, RelayStatusEnum, UserEmojimodpermEnum, UserProfileFfvisibilityEnum, UserProfileMutingnotificationtypesEnum, updateAntennaOnCreateNote, fetchNodeinfo, nodeinfo_2_1, nodeinfo_2_0, Protocol, Inbound, Outbound, watchNote, unwatchNote, PushNotificationKind, sendPushNotification, publishToChannelStream, ChatEvent, publishToChatStream, ChatIndexEvent, publishToChatIndexStream, publishToBroadcastStream, publishToGroupChatStream, publishToModerationStream, getTimestamp, genId, genIdAt, generateSecureRandomString, generateUserToken } = nativeBinding
|
||||
const { SECOND, MINUTE, HOUR, DAY, USER_ONLINE_THRESHOLD, USER_ACTIVE_THRESHOLD, FILE_TYPE_BROWSERSAFE, loadEnv, loadConfig, stringToAcct, acctToString, initializeRustLogger, showServerInfo, isBlockedServer, isSilencedServer, isAllowedServer, checkWordMute, getFullApAccount, isSelfHost, isSameOrigin, extractHost, toPuny, isUnicodeEmoji, sqlLikeEscape, safeForSql, formatMilliseconds, getImageSizeFromUrl, getNoteSummary, isQuote, isSafeUrl, latestVersion, toMastodonId, fromMastodonId, fetchMeta, metaToPugArgs, nyaify, hashPassword, verifyPassword, isOldPasswordAlgorithm, decodeReaction, countReactions, toDbReaction, removeOldAttestationChallenges, cpuInfo, cpuUsage, memoryUsage, storageUsage, AntennaSrcEnum, DriveFileUsageHintEnum, MutedNoteReasonEnum, NoteVisibilityEnum, NotificationTypeEnum, PageVisibilityEnum, PollNotevisibilityEnum, RelayStatusEnum, UserEmojimodpermEnum, UserProfileFfvisibilityEnum, UserProfileMutingnotificationtypesEnum, updateAntennasOnNewNote, fetchNodeinfo, nodeinfo_2_1, nodeinfo_2_0, Protocol, Inbound, Outbound, watchNote, unwatchNote, PushNotificationKind, sendPushNotification, publishToChannelStream, ChatEvent, publishToChatStream, ChatIndexEvent, publishToChatIndexStream, publishToBroadcastStream, publishToGroupChatStream, publishToModerationStream, getTimestamp, genId, genIdAt, generateSecureRandomString, generateUserToken } = nativeBinding
|
||||
|
||||
module.exports.SECOND = SECOND
|
||||
module.exports.MINUTE = MINUTE
|
||||
|
@ -340,6 +340,7 @@ module.exports.safeForSql = safeForSql
|
|||
module.exports.formatMilliseconds = formatMilliseconds
|
||||
module.exports.getImageSizeFromUrl = getImageSizeFromUrl
|
||||
module.exports.getNoteSummary = getNoteSummary
|
||||
module.exports.isQuote = isQuote
|
||||
module.exports.isSafeUrl = isSafeUrl
|
||||
module.exports.latestVersion = latestVersion
|
||||
module.exports.toMastodonId = toMastodonId
|
||||
|
@ -369,7 +370,7 @@ module.exports.RelayStatusEnum = RelayStatusEnum
|
|||
module.exports.UserEmojimodpermEnum = UserEmojimodpermEnum
|
||||
module.exports.UserProfileFfvisibilityEnum = UserProfileFfvisibilityEnum
|
||||
module.exports.UserProfileMutingnotificationtypesEnum = UserProfileMutingnotificationtypesEnum
|
||||
module.exports.updateAntennaOnCreateNote = updateAntennaOnCreateNote
|
||||
module.exports.updateAntennasOnNewNote = updateAntennasOnNewNote
|
||||
module.exports.fetchNodeinfo = fetchNodeinfo
|
||||
module.exports.nodeinfo_2_1 = nodeinfo_2_1
|
||||
module.exports.nodeinfo_2_0 = nodeinfo_2_0
|
||||
|
|
|
@ -10,8 +10,6 @@ pub enum Category {
|
|||
Block,
|
||||
#[strum(serialize = "following")]
|
||||
Follow,
|
||||
#[strum(serialize = "wordMute")]
|
||||
WordMute,
|
||||
#[cfg(test)]
|
||||
#[strum(serialize = "usedOnlyForTesting")]
|
||||
Test,
|
||||
|
|
|
@ -1,11 +1,8 @@
|
|||
use crate::config::CONFIG;
|
||||
use crate::database::{cache, db_conn};
|
||||
use crate::federation::acct::Acct;
|
||||
use crate::misc::check_word_mute::check_word_mute_bare;
|
||||
use crate::misc::get_note_all_texts::{all_texts, NoteLike};
|
||||
use crate::model::entity::{
|
||||
antenna, blocking, following, note, sea_orm_active_enums::*, user_profile,
|
||||
};
|
||||
use crate::model::entity::{antenna, blocking, following, note, sea_orm_active_enums::*};
|
||||
use sea_orm::{ColumnTrait, DbErr, EntityTrait, QueryFilter, QuerySelect};
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
|
@ -152,35 +149,6 @@ pub async fn check_hit_antenna(
|
|||
}
|
||||
}
|
||||
|
||||
type WordMute = (
|
||||
Vec<String>, // muted words
|
||||
Vec<String>, // muted patterns
|
||||
);
|
||||
|
||||
let word_mute: WordMute = cache::get_one(cache::Category::WordMute, &antenna.user_id)?
|
||||
.unwrap_or({
|
||||
// cache miss
|
||||
let mute = user_profile::Entity::find()
|
||||
.select_only()
|
||||
.columns([
|
||||
user_profile::Column::MutedWords,
|
||||
user_profile::Column::MutedPatterns,
|
||||
])
|
||||
.into_tuple::<WordMute>()
|
||||
.one(db)
|
||||
.await?
|
||||
.ok_or({
|
||||
tracing::warn!("there is no user_profile for user {}", &antenna.user_id);
|
||||
AntennaCheckError::UserProfileNotFoundErr(antenna.user_id.clone())
|
||||
})?;
|
||||
cache::set_one(cache::Category::WordMute, &antenna.user_id, &mute, 10 * 60)?;
|
||||
mute
|
||||
});
|
||||
|
||||
if check_word_mute_bare(¬e_texts, &word_mute.0, &word_mute.1) {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ fn convert_regex(js_regex: &str) -> String {
|
|||
RE.replace(js_regex, "(?$2)$1").to_string()
|
||||
}
|
||||
|
||||
pub fn check_word_mute_bare(
|
||||
fn check_word_mute_impl(
|
||||
texts: &[String],
|
||||
muted_words: &[String],
|
||||
muted_patterns: &[String],
|
||||
|
@ -35,7 +35,7 @@ pub async fn check_word_mute(
|
|||
if muted_words.is_empty() && muted_patterns.is_empty() {
|
||||
Ok(false)
|
||||
} else {
|
||||
Ok(check_word_mute_bare(
|
||||
Ok(check_word_mute_impl(
|
||||
&all_texts(note).await?,
|
||||
muted_words,
|
||||
muted_patterns,
|
||||
|
|
9
packages/backend-rs/src/misc/is_quote.rs
Normal file
9
packages/backend-rs/src/misc/is_quote.rs
Normal file
|
@ -0,0 +1,9 @@
|
|||
use crate::model::entity::note;
|
||||
|
||||
// https://github.com/napi-rs/napi-rs/issues/2060
|
||||
type Note = note::Model;
|
||||
|
||||
#[crate::export]
|
||||
pub fn is_quote(note: Note) -> bool {
|
||||
note.renote_id.is_some() && (note.text.is_some() || note.has_poll || !note.file_ids.is_empty())
|
||||
}
|
|
@ -8,6 +8,7 @@ pub mod format_milliseconds;
|
|||
pub mod get_image_size;
|
||||
pub mod get_note_all_texts;
|
||||
pub mod get_note_summary;
|
||||
pub mod is_quote;
|
||||
pub mod is_safe_url;
|
||||
pub mod latest_version;
|
||||
pub mod mastodon_id;
|
||||
|
|
|
@ -1,13 +1,19 @@
|
|||
use crate::database::{redis_conn, redis_key};
|
||||
use crate::database::cache;
|
||||
use crate::database::{db_conn, redis_conn, redis_key};
|
||||
use crate::federation::acct::Acct;
|
||||
use crate::misc::check_hit_antenna::{check_hit_antenna, AntennaCheckError};
|
||||
use crate::model::entity::{antenna, note};
|
||||
use crate::service::stream;
|
||||
use crate::util::id::{get_timestamp, InvalidIdErr};
|
||||
use redis::{streams::StreamMaxlen, Commands, RedisError};
|
||||
use sea_orm::{DbErr, EntityTrait};
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum Error {
|
||||
#[error("Database error: {0}")]
|
||||
DbErr(#[from] DbErr),
|
||||
#[error("Cache error: {0}")]
|
||||
CacheErr(#[from] cache::Error),
|
||||
#[error("Redis error: {0}")]
|
||||
RedisErr(#[from] RedisError),
|
||||
#[error("Invalid ID: {0}")]
|
||||
|
@ -22,15 +28,33 @@ pub enum Error {
|
|||
type Antenna = antenna::Model;
|
||||
type Note = note::Model;
|
||||
|
||||
// TODO?: it might be better to store this directly in memory
|
||||
// (like fetch_meta) instead of Redis as it's used so much
|
||||
async fn antennas() -> Result<Vec<Antenna>, Error> {
|
||||
const CACHE_KEY: &str = "antennas";
|
||||
|
||||
Ok(cache::get::<Vec<Antenna>>(CACHE_KEY)?.unwrap_or({
|
||||
let antennas = antenna::Entity::find().all(db_conn().await?).await?;
|
||||
cache::set(CACHE_KEY, &antennas, 5 * 60)?;
|
||||
antennas
|
||||
}))
|
||||
}
|
||||
|
||||
#[crate::export]
|
||||
pub async fn update_antenna_on_create_note(
|
||||
antenna: &Antenna,
|
||||
pub async fn update_antennas_on_new_note(
|
||||
note: Note,
|
||||
note_author: &Acct,
|
||||
note_muted_users: Vec<String>,
|
||||
) -> Result<(), Error> {
|
||||
if check_hit_antenna(antenna, note.clone(), note_author).await? {
|
||||
add_note_to_antenna(&antenna.id, ¬e)?;
|
||||
for antenna in antennas().await?.iter() {
|
||||
if note_muted_users.contains(&antenna.user_id) {
|
||||
continue;
|
||||
}
|
||||
if check_hit_antenna(antenna, note.clone(), note_author).await? {
|
||||
add_note_to_antenna(&antenna.id, ¬e)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
import { Antennas } from "@/models/index.js";
|
||||
import type { Antenna } from "@/models/entities/antenna.js";
|
||||
import { subscriber } from "@/db/redis.js";
|
||||
|
||||
let antennasFetched = false;
|
||||
let antennas: Antenna[] = [];
|
||||
|
||||
export async function getAntennas() {
|
||||
if (!antennasFetched) {
|
||||
antennas = await Antennas.find();
|
||||
antennasFetched = true;
|
||||
}
|
||||
|
||||
return antennas;
|
||||
}
|
||||
|
||||
subscriber.on("message", async (_, data) => {
|
||||
const obj = JSON.parse(data);
|
||||
|
||||
if (obj.channel === "internal") {
|
||||
const { type, body } = obj.message;
|
||||
switch (type) {
|
||||
case "antennaCreated":
|
||||
antennas.push(body);
|
||||
break;
|
||||
case "antennaUpdated":
|
||||
antennas[antennas.findIndex((a) => a.id === body.id)] = body;
|
||||
break;
|
||||
case "antennaDeleted":
|
||||
antennas = antennas.filter((a) => a.id !== body.id);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
|
@ -43,17 +43,17 @@ import { Poll } from "@/models/entities/poll.js";
|
|||
import { createNotification } from "@/services/create-notification.js";
|
||||
import { isDuplicateKeyValueError } from "@/misc/is-duplicate-key-value-error.js";
|
||||
import {
|
||||
updateAntennaOnCreateNote,
|
||||
updateAntennasOnNewNote,
|
||||
checkWordMute,
|
||||
genId,
|
||||
genIdAt,
|
||||
isQuote,
|
||||
isSilencedServer,
|
||||
} from "backend-rs";
|
||||
import { countSameRenotes } from "@/misc/count-same-renotes.js";
|
||||
import { deliverToRelays, getCachedRelays } from "../relay.js";
|
||||
import type { Channel } from "@/models/entities/channel.js";
|
||||
import { normalizeForSearch } from "@/misc/normalize-for-search.js";
|
||||
import { getAntennas } from "@/misc/antenna-cache.js";
|
||||
import { endedPollNotificationQueue } from "@/queue/queues.js";
|
||||
import { webhookDeliver } from "@/queue/index.js";
|
||||
import { Cache } from "@/misc/cache.js";
|
||||
|
@ -370,8 +370,10 @@ export default async (
|
|||
// Increment notes count (user)
|
||||
incNotesCountOfUser(user);
|
||||
|
||||
// Word mute
|
||||
hardMutesCache
|
||||
// Word mutes & antenna
|
||||
const thisNoteIsMutedBy: string[] = [];
|
||||
|
||||
await hardMutesCache
|
||||
.fetch(null, () =>
|
||||
UserProfiles.find({
|
||||
where: {
|
||||
|
@ -380,12 +382,13 @@ export default async (
|
|||
select: ["userId", "mutedWords", "mutedPatterns"],
|
||||
}),
|
||||
)
|
||||
.then((us) => {
|
||||
.then(async (us) => {
|
||||
for (const u of us) {
|
||||
if (u.userId === user.id) return;
|
||||
checkWordMute(note, u.mutedWords, u.mutedPatterns).then(
|
||||
await checkWordMute(note, u.mutedWords, u.mutedPatterns).then(
|
||||
(shouldMute: boolean) => {
|
||||
if (shouldMute) {
|
||||
thisNoteIsMutedBy.push(u.userId);
|
||||
MutedNotes.insert({
|
||||
id: genId(),
|
||||
userId: u.userId,
|
||||
|
@ -398,12 +401,13 @@ export default async (
|
|||
}
|
||||
});
|
||||
|
||||
// Antenna
|
||||
for (const antenna of await getAntennas()) {
|
||||
await updateAntennaOnCreateNote(
|
||||
toRustObject(antenna),
|
||||
toRustObject(note),
|
||||
// type errors will be resolved by https://github.com/napi-rs/napi-rs/pull/2054
|
||||
const _note = toRustObject(note)
|
||||
if (note.renoteId == null || isQuote(_note)) {
|
||||
await updateAntennasOnNewNote(
|
||||
_note,
|
||||
user,
|
||||
thisNoteIsMutedBy,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue