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
|
hasPoll: boolean
|
||||||
}
|
}
|
||||||
export function getNoteSummary(note: NoteLikeForGetNoteSummary): string
|
export function getNoteSummary(note: NoteLikeForGetNoteSummary): string
|
||||||
|
export function isQuote(note: Note): boolean
|
||||||
export function isSafeUrl(url: string): boolean
|
export function isSafeUrl(url: string): boolean
|
||||||
export function latestVersion(): Promise<string>
|
export function latestVersion(): Promise<string>
|
||||||
export function toMastodonId(firefishId: string): string | null
|
export function toMastodonId(firefishId: string): string | null
|
||||||
|
@ -1175,7 +1176,7 @@ export interface Webhook {
|
||||||
latestSentAt: Date | null
|
latestSentAt: Date | null
|
||||||
latestStatus: number | 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 fetchNodeinfo(host: string): Promise<Nodeinfo>
|
||||||
export function nodeinfo_2_1(): Promise<any>
|
export function nodeinfo_2_1(): Promise<any>
|
||||||
export function nodeinfo_2_0(): Promise<any>
|
export function nodeinfo_2_0(): Promise<any>
|
||||||
|
|
|
@ -310,7 +310,7 @@ if (!nativeBinding) {
|
||||||
throw new Error(`Failed to load native binding`)
|
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.SECOND = SECOND
|
||||||
module.exports.MINUTE = MINUTE
|
module.exports.MINUTE = MINUTE
|
||||||
|
@ -340,6 +340,7 @@ module.exports.safeForSql = safeForSql
|
||||||
module.exports.formatMilliseconds = formatMilliseconds
|
module.exports.formatMilliseconds = formatMilliseconds
|
||||||
module.exports.getImageSizeFromUrl = getImageSizeFromUrl
|
module.exports.getImageSizeFromUrl = getImageSizeFromUrl
|
||||||
module.exports.getNoteSummary = getNoteSummary
|
module.exports.getNoteSummary = getNoteSummary
|
||||||
|
module.exports.isQuote = isQuote
|
||||||
module.exports.isSafeUrl = isSafeUrl
|
module.exports.isSafeUrl = isSafeUrl
|
||||||
module.exports.latestVersion = latestVersion
|
module.exports.latestVersion = latestVersion
|
||||||
module.exports.toMastodonId = toMastodonId
|
module.exports.toMastodonId = toMastodonId
|
||||||
|
@ -369,7 +370,7 @@ module.exports.RelayStatusEnum = RelayStatusEnum
|
||||||
module.exports.UserEmojimodpermEnum = UserEmojimodpermEnum
|
module.exports.UserEmojimodpermEnum = UserEmojimodpermEnum
|
||||||
module.exports.UserProfileFfvisibilityEnum = UserProfileFfvisibilityEnum
|
module.exports.UserProfileFfvisibilityEnum = UserProfileFfvisibilityEnum
|
||||||
module.exports.UserProfileMutingnotificationtypesEnum = UserProfileMutingnotificationtypesEnum
|
module.exports.UserProfileMutingnotificationtypesEnum = UserProfileMutingnotificationtypesEnum
|
||||||
module.exports.updateAntennaOnCreateNote = updateAntennaOnCreateNote
|
module.exports.updateAntennasOnNewNote = updateAntennasOnNewNote
|
||||||
module.exports.fetchNodeinfo = fetchNodeinfo
|
module.exports.fetchNodeinfo = fetchNodeinfo
|
||||||
module.exports.nodeinfo_2_1 = nodeinfo_2_1
|
module.exports.nodeinfo_2_1 = nodeinfo_2_1
|
||||||
module.exports.nodeinfo_2_0 = nodeinfo_2_0
|
module.exports.nodeinfo_2_0 = nodeinfo_2_0
|
||||||
|
|
|
@ -10,8 +10,6 @@ pub enum Category {
|
||||||
Block,
|
Block,
|
||||||
#[strum(serialize = "following")]
|
#[strum(serialize = "following")]
|
||||||
Follow,
|
Follow,
|
||||||
#[strum(serialize = "wordMute")]
|
|
||||||
WordMute,
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[strum(serialize = "usedOnlyForTesting")]
|
#[strum(serialize = "usedOnlyForTesting")]
|
||||||
Test,
|
Test,
|
||||||
|
|
|
@ -1,11 +1,8 @@
|
||||||
use crate::config::CONFIG;
|
use crate::config::CONFIG;
|
||||||
use crate::database::{cache, db_conn};
|
use crate::database::{cache, db_conn};
|
||||||
use crate::federation::acct::Acct;
|
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::misc::get_note_all_texts::{all_texts, NoteLike};
|
||||||
use crate::model::entity::{
|
use crate::model::entity::{antenna, blocking, following, note, sea_orm_active_enums::*};
|
||||||
antenna, blocking, following, note, sea_orm_active_enums::*, user_profile,
|
|
||||||
};
|
|
||||||
use sea_orm::{ColumnTrait, DbErr, EntityTrait, QueryFilter, QuerySelect};
|
use sea_orm::{ColumnTrait, DbErr, EntityTrait, QueryFilter, QuerySelect};
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[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)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ fn convert_regex(js_regex: &str) -> String {
|
||||||
RE.replace(js_regex, "(?$2)$1").to_string()
|
RE.replace(js_regex, "(?$2)$1").to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn check_word_mute_bare(
|
fn check_word_mute_impl(
|
||||||
texts: &[String],
|
texts: &[String],
|
||||||
muted_words: &[String],
|
muted_words: &[String],
|
||||||
muted_patterns: &[String],
|
muted_patterns: &[String],
|
||||||
|
@ -35,7 +35,7 @@ pub async fn check_word_mute(
|
||||||
if muted_words.is_empty() && muted_patterns.is_empty() {
|
if muted_words.is_empty() && muted_patterns.is_empty() {
|
||||||
Ok(false)
|
Ok(false)
|
||||||
} else {
|
} else {
|
||||||
Ok(check_word_mute_bare(
|
Ok(check_word_mute_impl(
|
||||||
&all_texts(note).await?,
|
&all_texts(note).await?,
|
||||||
muted_words,
|
muted_words,
|
||||||
muted_patterns,
|
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_image_size;
|
||||||
pub mod get_note_all_texts;
|
pub mod get_note_all_texts;
|
||||||
pub mod get_note_summary;
|
pub mod get_note_summary;
|
||||||
|
pub mod is_quote;
|
||||||
pub mod is_safe_url;
|
pub mod is_safe_url;
|
||||||
pub mod latest_version;
|
pub mod latest_version;
|
||||||
pub mod mastodon_id;
|
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::federation::acct::Acct;
|
||||||
use crate::misc::check_hit_antenna::{check_hit_antenna, AntennaCheckError};
|
use crate::misc::check_hit_antenna::{check_hit_antenna, AntennaCheckError};
|
||||||
use crate::model::entity::{antenna, note};
|
use crate::model::entity::{antenna, note};
|
||||||
use crate::service::stream;
|
use crate::service::stream;
|
||||||
use crate::util::id::{get_timestamp, InvalidIdErr};
|
use crate::util::id::{get_timestamp, InvalidIdErr};
|
||||||
use redis::{streams::StreamMaxlen, Commands, RedisError};
|
use redis::{streams::StreamMaxlen, Commands, RedisError};
|
||||||
|
use sea_orm::{DbErr, EntityTrait};
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
|
#[error("Database error: {0}")]
|
||||||
|
DbErr(#[from] DbErr),
|
||||||
|
#[error("Cache error: {0}")]
|
||||||
|
CacheErr(#[from] cache::Error),
|
||||||
#[error("Redis error: {0}")]
|
#[error("Redis error: {0}")]
|
||||||
RedisErr(#[from] RedisError),
|
RedisErr(#[from] RedisError),
|
||||||
#[error("Invalid ID: {0}")]
|
#[error("Invalid ID: {0}")]
|
||||||
|
@ -22,15 +28,33 @@ pub enum Error {
|
||||||
type Antenna = antenna::Model;
|
type Antenna = antenna::Model;
|
||||||
type Note = note::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]
|
#[crate::export]
|
||||||
pub async fn update_antenna_on_create_note(
|
pub async fn update_antennas_on_new_note(
|
||||||
antenna: &Antenna,
|
|
||||||
note: Note,
|
note: Note,
|
||||||
note_author: &Acct,
|
note_author: &Acct,
|
||||||
|
note_muted_users: Vec<String>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
|
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? {
|
if check_hit_antenna(antenna, note.clone(), note_author).await? {
|
||||||
add_note_to_antenna(&antenna.id, ¬e)?;
|
add_note_to_antenna(&antenna.id, ¬e)?;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
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 { createNotification } from "@/services/create-notification.js";
|
||||||
import { isDuplicateKeyValueError } from "@/misc/is-duplicate-key-value-error.js";
|
import { isDuplicateKeyValueError } from "@/misc/is-duplicate-key-value-error.js";
|
||||||
import {
|
import {
|
||||||
updateAntennaOnCreateNote,
|
updateAntennasOnNewNote,
|
||||||
checkWordMute,
|
checkWordMute,
|
||||||
genId,
|
genId,
|
||||||
genIdAt,
|
genIdAt,
|
||||||
|
isQuote,
|
||||||
isSilencedServer,
|
isSilencedServer,
|
||||||
} from "backend-rs";
|
} from "backend-rs";
|
||||||
import { countSameRenotes } from "@/misc/count-same-renotes.js";
|
import { countSameRenotes } from "@/misc/count-same-renotes.js";
|
||||||
import { deliverToRelays, getCachedRelays } from "../relay.js";
|
import { deliverToRelays, getCachedRelays } from "../relay.js";
|
||||||
import type { Channel } from "@/models/entities/channel.js";
|
import type { Channel } from "@/models/entities/channel.js";
|
||||||
import { normalizeForSearch } from "@/misc/normalize-for-search.js";
|
import { normalizeForSearch } from "@/misc/normalize-for-search.js";
|
||||||
import { getAntennas } from "@/misc/antenna-cache.js";
|
|
||||||
import { endedPollNotificationQueue } from "@/queue/queues.js";
|
import { endedPollNotificationQueue } from "@/queue/queues.js";
|
||||||
import { webhookDeliver } from "@/queue/index.js";
|
import { webhookDeliver } from "@/queue/index.js";
|
||||||
import { Cache } from "@/misc/cache.js";
|
import { Cache } from "@/misc/cache.js";
|
||||||
|
@ -370,8 +370,10 @@ export default async (
|
||||||
// Increment notes count (user)
|
// Increment notes count (user)
|
||||||
incNotesCountOfUser(user);
|
incNotesCountOfUser(user);
|
||||||
|
|
||||||
// Word mute
|
// Word mutes & antenna
|
||||||
hardMutesCache
|
const thisNoteIsMutedBy: string[] = [];
|
||||||
|
|
||||||
|
await hardMutesCache
|
||||||
.fetch(null, () =>
|
.fetch(null, () =>
|
||||||
UserProfiles.find({
|
UserProfiles.find({
|
||||||
where: {
|
where: {
|
||||||
|
@ -380,12 +382,13 @@ export default async (
|
||||||
select: ["userId", "mutedWords", "mutedPatterns"],
|
select: ["userId", "mutedWords", "mutedPatterns"],
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.then((us) => {
|
.then(async (us) => {
|
||||||
for (const u of us) {
|
for (const u of us) {
|
||||||
if (u.userId === user.id) return;
|
if (u.userId === user.id) return;
|
||||||
checkWordMute(note, u.mutedWords, u.mutedPatterns).then(
|
await checkWordMute(note, u.mutedWords, u.mutedPatterns).then(
|
||||||
(shouldMute: boolean) => {
|
(shouldMute: boolean) => {
|
||||||
if (shouldMute) {
|
if (shouldMute) {
|
||||||
|
thisNoteIsMutedBy.push(u.userId);
|
||||||
MutedNotes.insert({
|
MutedNotes.insert({
|
||||||
id: genId(),
|
id: genId(),
|
||||||
userId: u.userId,
|
userId: u.userId,
|
||||||
|
@ -398,12 +401,13 @@ export default async (
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Antenna
|
// type errors will be resolved by https://github.com/napi-rs/napi-rs/pull/2054
|
||||||
for (const antenna of await getAntennas()) {
|
const _note = toRustObject(note)
|
||||||
await updateAntennaOnCreateNote(
|
if (note.renoteId == null || isQuote(_note)) {
|
||||||
toRustObject(antenna),
|
await updateAntennasOnNewNote(
|
||||||
toRustObject(note),
|
_note,
|
||||||
user,
|
user,
|
||||||
|
thisNoteIsMutedBy,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue