diff --git a/packages/backend/src/db/scylla.ts b/packages/backend/src/db/scylla.ts index 08c08a0430..1577dbc85c 100644 --- a/packages/backend/src/db/scylla.ts +++ b/packages/backend/src/db/scylla.ts @@ -428,13 +428,20 @@ export async function filterReply( export async function filterMutedUser( notes: ScyllaNote[], user: { id: User["id"] }, + mutedIds?: User["id"][], exclude?: User, ): Promise { - const userCache = await UserMutingsCache.init(user.id); - let mutedUserIds = await userCache.getAll(); + let ids: User["id"][]; + + if (mutedIds) { + ids = mutedIds; + } else { + const userCache = await UserMutingsCache.init(user.id); + ids = await userCache.getAll(); + } if (exclude) { - mutedUserIds = mutedUserIds.filter((id) => id !== exclude.id); + ids = ids.filter((id) => id !== exclude.id); } const instanceCache = await InstanceMutingsCache.init(user.id); @@ -442,9 +449,9 @@ export async function filterMutedUser( return notes.filter( (note) => - !mutedUserIds.includes(note.userId) && - !(note.replyUserId && mutedUserIds.includes(note.replyUserId)) && - !(note.renoteUserId && mutedUserIds.includes(note.renoteUserId)) && + !ids.includes(note.userId) && + !(note.replyUserId && ids.includes(note.replyUserId)) && + !(note.renoteUserId && ids.includes(note.renoteUserId)) && !(note.userHost && mutedInstances.includes(note.userHost)) && !(note.replyUserHost && mutedInstances.includes(note.replyUserHost)) && !(note.renoteUserHost && mutedInstances.includes(note.renoteUserHost)), @@ -454,44 +461,65 @@ export async function filterMutedUser( export async function filterMutedNote( notes: ScyllaNote[], user: { id: User["id"] }, + mutedWords?: string[][], ): Promise { - const mutedWords = await userWordMuteCache.fetchMaybe(user.id, () => - UserProfiles.findOne({ - select: ["mutedWords"], - where: { userId: user.id }, - }).then((profile) => profile?.mutedWords), - ); + let words = mutedWords; - if (!mutedWords) { - return notes; + if (!words) { + words = await userWordMuteCache.fetchMaybe(user.id, () => + UserProfiles.findOne({ + select: ["mutedWords"], + where: { userId: user.id }, + }).then((profile) => profile?.mutedWords), + ); } - return notes.filter((note) => !getWordHardMute(note, user, mutedWords)); + if (words && words.length > 0) { + return notes.filter( + (note) => !getWordHardMute(note, user, words as string[][]), + ); + } + + return notes; } export async function filterBlockedUser( notes: ScyllaNote[], user: { id: User["id"] }, + blockerIds?: User["id"][], ): Promise { - const cache = await UserBlockedCache.init(user.id); - const blockerIds = await cache.getAll(); + let ids: User["id"][]; + + if (blockerIds) { + ids = blockerIds; + } else { + const cache = await UserBlockedCache.init(user.id); + ids = await cache.getAll(); + } return notes.filter( (note) => - !blockerIds.includes(note.userId) && - !(note.replyUserId && blockerIds.includes(note.replyUserId)) && - !(note.renoteUserId && blockerIds.includes(note.renoteUserId)), + !ids.includes(note.userId) && + !(note.replyUserId && ids.includes(note.replyUserId)) && + !(note.renoteUserId && ids.includes(note.renoteUserId)), ); } export async function filterMutedRenotes( notes: ScyllaNote[], user: { id: User["id"] }, + muteeIds?: User["id"][], ): Promise { - const cache = await RenoteMutingsCache.init(user.id); - const muteeIds = await cache.getAll(); + let ids: User["id"][]; + + if (muteeIds) { + ids = muteeIds; + } else { + const cache = await RenoteMutingsCache.init(user.id); + ids = await cache.getAll(); + } return notes.filter( - (note) => note.text || !note.renoteId || !muteeIds.includes(note.userId), + (note) => note.text || !note.renoteId || !ids.includes(note.userId), ); } diff --git a/packages/backend/src/server/api/endpoints/notes/timeline.ts b/packages/backend/src/server/api/endpoints/notes/timeline.ts index 581dc4aa21..50ed46bbb8 100644 --- a/packages/backend/src/server/api/endpoints/notes/timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/timeline.ts @@ -1,5 +1,5 @@ import { Brackets } from "typeorm"; -import { Notes, Followings } from "@/models/index.js"; +import { Notes, Followings, UserProfiles } from "@/models/index.js"; import { activeUsersChart } from "@/services/chart/index.js"; import define from "../../define.js"; import { makePaginationQuery } from "../../common/make-pagination-query.js"; @@ -23,7 +23,7 @@ import { filterBlockedUser, filterMutedRenotes, } from "@/db/scylla.js"; -import { ChannelFollowingsCache, LocalFollowingsCache } from "@/misc/cache.js"; +import { ChannelFollowingsCache, LocalFollowingsCache, RenoteMutingsCache, UserBlockedCache, UserMutingsCache, userWordMuteCache } from "@/misc/cache.js"; export const meta = { tags: ["notes"], @@ -79,21 +79,54 @@ export const paramDef = { export default define(meta, paramDef, async (ps, user) => { const followingsCache = await LocalFollowingsCache.init(user.id); + process.nextTick(() => { + activeUsersChart.read(user); + }); + if (scyllaClient) { const channelCache = await ChannelFollowingsCache.init(user.id); const followingChannelIds = await channelCache.getAll(); const followingUserIds = await followingsCache.getAll(); const validUserIds = [user.id].concat(followingUserIds); + const userMutingsCache = await UserMutingsCache.init(user.id); + const mutedUserIds = await userMutingsCache.getAll(); + const mutedWords = await userWordMuteCache.fetchMaybe(user.id, () => + UserProfiles.findOne({ + select: ["mutedWords"], + where: { userId: user.id }, + }).then((profile) => profile?.mutedWords), + ); + const blockedCache = await UserBlockedCache.init(user.id); + const blockerIds = await blockedCache.getAll(); + const renoteMutingsCache = await RenoteMutingsCache.init(user.id); + const renoteMutedIds = await renoteMutingsCache.getAll(); + const optFilter = (n: ScyllaNote) => + !n.renoteId || !!n.text || n.files.length > 0 || n.hasPoll; const filter = async (notes: ScyllaNote[]) => { let filtered = notes.filter((n) => validUserIds.includes(n.userId)); filtered = await filterChannel(filtered, user, followingChannelIds); filtered = await filterReply(filtered, ps.withReplies, user); filtered = await filterVisibility(filtered, user, followingUserIds); - filtered = await filterMutedUser(filtered, user); - filtered = await filterMutedNote(filtered, user); - filtered = await filterBlockedUser(filtered, user); - filtered = await filterMutedRenotes(filtered, user); + filtered = await filterMutedUser(filtered, user, mutedUserIds); + filtered = await filterMutedNote(filtered, user, mutedWords ?? []); + filtered = await filterBlockedUser(filtered, user, blockerIds); + filtered = await filterMutedRenotes(filtered, user, renoteMutedIds); + if (!ps.includeMyRenotes) { + filtered = filtered.filter((n) => n.userId !== user.id || optFilter(n)); + } + if (!ps.includeRenotedMyNotes) { + filtered = filtered.filter( + (n) => n.renoteUserId !== user.id || optFilter(n), + ); + } + if (!ps.includeLocalRenotes) { + filtered = filtered.filter((n) => n.renoteUserHost || optFilter(n)); + } + if (ps.withFiles) { + filtered = filtered.filter((n) => n.files.length > 0); + } + filtered = filtered.filter((n) => n.visibility !== "hidden"); return filtered; }; @@ -183,10 +216,6 @@ export default define(meta, paramDef, async (ps, user) => { query.andWhere("note.visibility != 'hidden'"); //#endregion - process.nextTick(() => { - activeUsersChart.read(user); - }); - // We fetch more than requested because some may be filtered out, and if there's less than // requested, the pagination stops. const found = [];