From ef3463e8dc4daae9a9fce5989513333c8d8d214f Mon Sep 17 00:00:00 2001 From: Laura Hausmann Date: Mon, 27 Nov 2023 17:56:03 +0100 Subject: [PATCH] [backend] Rework note hard mutes It's been shown that the current approach doesn't scale. This implementation should scale perfectly fine. --- .config/example-docker.yml | 5 ++ .config/example.yml | 5 ++ packages/backend/src/config/load.ts | 8 ++- packages/backend/src/config/types.ts | 5 ++ packages/backend/src/db/postgre.ts | 2 - .../1701108527387-rework-hard-mutes.ts | 27 +++++++++ packages/backend/src/misc/check-word-mute.ts | 26 ++++----- packages/backend/src/misc/is-filtered.ts | 16 ++++++ packages/backend/src/models/entities/meta.ts | 2 +- .../backend/src/models/entities/muted-note.ts | 55 ------------------- packages/backend/src/models/entities/note.ts | 2 +- packages/backend/src/models/index.ts | 2 - .../backend/src/models/repositories/note.ts | 23 +++++++- packages/backend/src/models/schema/note.ts | 7 ++- .../api/common/generate-muted-note-query.ts | 16 ------ packages/backend/src/server/api/endpoints.ts | 2 - .../endpoints/i/get-word-muted-notes-count.ts | 38 ------------- .../api/endpoints/notes/global-timeline.ts | 2 - .../api/endpoints/notes/hybrid-timeline.ts | 2 - .../api/endpoints/notes/local-timeline.ts | 3 - .../endpoints/notes/recommended-timeline.ts | 3 - .../server/api/endpoints/notes/timeline.ts | 2 - .../server/api/mastodon/converters/note.ts | 6 +- .../server/api/mastodon/helpers/timeline.ts | 4 -- .../api/mastodon/streaming/channels/list.ts | 4 +- .../api/mastodon/streaming/channels/public.ts | 4 +- .../api/mastodon/streaming/channels/tag.ts | 4 +- .../api/mastodon/streaming/channels/user.ts | 4 +- .../api/stream/channels/global-timeline.ts | 5 +- .../api/stream/channels/home-timeline.ts | 5 +- .../api/stream/channels/hybrid-timeline.ts | 5 +- .../api/stream/channels/local-timeline.ts | 5 +- .../stream/channels/recommended-timeline.ts | 5 +- packages/backend/src/services/note/create.ts | 35 ------------ packages/backend/src/types.ts | 2 - .../src/components/MkDateSeparatedList.vue | 2 +- .../client/src/components/MkPagination.vue | 35 +++++------- .../client/src/pages/settings/word-mute.vue | 14 ----- .../iceshrimp-js/markdown/iceshrimp-js.md | 1 - packages/iceshrimp-js/src/api.types.ts | 5 -- packages/iceshrimp-js/src/consts.ts | 2 - packages/iceshrimp-js/src/index.ts | 1 - 42 files changed, 148 insertions(+), 253 deletions(-) create mode 100644 packages/backend/src/migration/1701108527387-rework-hard-mutes.ts create mode 100644 packages/backend/src/misc/is-filtered.ts delete mode 100644 packages/backend/src/models/entities/muted-note.ts delete mode 100644 packages/backend/src/server/api/common/generate-muted-note-query.ts delete mode 100644 packages/backend/src/server/api/endpoints/i/get-word-muted-notes-count.ts diff --git a/.config/example-docker.yml b/.config/example-docker.yml index 8105f5bec..58ca01d7e 100644 --- a/.config/example-docker.yml +++ b/.config/example-docker.yml @@ -203,6 +203,11 @@ reservedUsernames: [ # prewarm: false # dbFallback: false +# Duration hard muted notes are stored in redis for. +# Increasing this trades higher memory consumption for lower cpu usage on repeated requests within the specified ttl. +#wordMuteCache: +# ttl: 24h + #━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ # Congrats, you've reached the end of the config file needed for most deployments! # Enjoy your Iceshrimp server! diff --git a/.config/example.yml b/.config/example.yml index 53b21523c..944e51965 100644 --- a/.config/example.yml +++ b/.config/example.yml @@ -203,6 +203,11 @@ reservedUsernames: [ # prewarm: false # dbFallback: false +# Duration hard muted notes are stored in redis for. +# Increasing this trades higher memory consumption for lower cpu usage on repeated requests within the specified ttl. +#wordMuteCache: +# ttl: 24h + #━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ # Congrats, you've reached the end of the config file needed for most deployments! # Enjoy your Iceshrimp server! diff --git a/packages/backend/src/config/load.ts b/packages/backend/src/config/load.ts index 0302e45c9..0c1971cdd 100644 --- a/packages/backend/src/config/load.ts +++ b/packages/backend/src/config/load.ts @@ -61,7 +61,13 @@ export default function load() { ...config.htmlCache, } - if (config.htmlCache.ttlSeconds == null) throw new Error('Failed to parse config.ttl'); + if (config.htmlCache.ttlSeconds == null) throw new Error('Failed to parse config.htmlCache.ttl'); + + config.wordMuteCache = { + ttlSeconds: parseDuration(config.wordMuteCache?.ttl ?? '24h', 's')!, + } + + if (config.wordMuteCache.ttlSeconds == null) throw new Error('Failed to parse config.wordMuteCache.ttl'); config.searchEngine = config.searchEngine ?? 'https://duckduckgo.com/?q='; diff --git a/packages/backend/src/config/types.ts b/packages/backend/src/config/types.ts index c3def3add..b9f76519e 100644 --- a/packages/backend/src/config/types.ts +++ b/packages/backend/src/config/types.ts @@ -48,6 +48,11 @@ export type Source = { dbFallback?: boolean; } + wordMuteCache?: { + ttl?: string; + ttlSeconds?: number; + } + searchEngine?: string; proxy?: string; diff --git a/packages/backend/src/db/postgre.ts b/packages/backend/src/db/postgre.ts index 6bab35e6b..3e3f3a289 100644 --- a/packages/backend/src/db/postgre.ts +++ b/packages/backend/src/db/postgre.ts @@ -61,7 +61,6 @@ import { Antenna } from "@/models/entities/antenna.js"; import { PromoNote } from "@/models/entities/promo-note.js"; import { PromoRead } from "@/models/entities/promo-read.js"; import { Relay } from "@/models/entities/relay.js"; -import { MutedNote } from "@/models/entities/muted-note.js"; import { Channel } from "@/models/entities/channel.js"; import { ChannelFollowing } from "@/models/entities/channel-following.js"; import { ChannelNotePining } from "@/models/entities/channel-note-pining.js"; @@ -168,7 +167,6 @@ export const entities = [ PromoNote, PromoRead, Relay, - MutedNote, Channel, ChannelFollowing, ChannelNotePining, diff --git a/packages/backend/src/migration/1701108527387-rework-hard-mutes.ts b/packages/backend/src/migration/1701108527387-rework-hard-mutes.ts new file mode 100644 index 000000000..d115f1aaa --- /dev/null +++ b/packages/backend/src/migration/1701108527387-rework-hard-mutes.ts @@ -0,0 +1,27 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class ReworkHardMutes1701108527387 implements MigrationInterface { + name = 'ReworkHardMutes1701108527387' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "muted_note" DROP CONSTRAINT "FK_d8e07aa18c2d64e86201601aec1"`); + await queryRunner.query(`ALTER TABLE "muted_note" DROP CONSTRAINT "FK_70ab9786313d78e4201d81cdb89"`); + await queryRunner.query(`DROP INDEX "public"."IDX_a8c6bfd637d3f1d67a27c48e27"`); + await queryRunner.query(`DROP INDEX "public"."IDX_636e977ff90b23676fb5624b25"`); + await queryRunner.query(`DROP INDEX "public"."IDX_d8e07aa18c2d64e86201601aec"`); + await queryRunner.query(`DROP INDEX "public"."IDX_70ab9786313d78e4201d81cdb8"`); + await queryRunner.query(`DROP TABLE "muted_note"`); + await queryRunner.query(`DROP TYPE "public"."muted_note_reason_enum"`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`CREATE TYPE "public"."muted_note_reason_enum" AS ENUM('word', 'manual', 'spam', 'other')`); + await queryRunner.query(`CREATE TABLE "muted_note" ("id" character varying(32) NOT NULL, "noteId" character varying(32) NOT NULL, "userId" character varying(32) NOT NULL, "reason" "public"."muted_note_reason_enum" NOT NULL, CONSTRAINT "PK_897e2eff1c0b9b64e55ca1418a4" PRIMARY KEY ("id")); COMMENT ON COLUMN "muted_note"."noteId" IS 'The note ID.'; COMMENT ON COLUMN "muted_note"."userId" IS 'The user ID.'; COMMENT ON COLUMN "muted_note"."reason" IS 'The reason of the MutedNote.'`); + await queryRunner.query(`CREATE INDEX "IDX_70ab9786313d78e4201d81cdb8" ON "muted_note" ("noteId") `); + await queryRunner.query(`CREATE INDEX "IDX_d8e07aa18c2d64e86201601aec" ON "muted_note" ("userId") `); + await queryRunner.query(`CREATE INDEX "IDX_636e977ff90b23676fb5624b25" ON "muted_note" ("reason") `); + await queryRunner.query(`CREATE UNIQUE INDEX "IDX_a8c6bfd637d3f1d67a27c48e27" ON "muted_note" ("noteId", "userId") `); + await queryRunner.query(`ALTER TABLE "muted_note" ADD CONSTRAINT "FK_70ab9786313d78e4201d81cdb89" FOREIGN KEY ("noteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "muted_note" ADD CONSTRAINT "FK_d8e07aa18c2d64e86201601aec1" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + } +} diff --git a/packages/backend/src/misc/check-word-mute.ts b/packages/backend/src/misc/check-word-mute.ts index 341b3b52f..868008c18 100644 --- a/packages/backend/src/misc/check-word-mute.ts +++ b/packages/backend/src/misc/check-word-mute.ts @@ -1,12 +1,16 @@ import RE2 from "re2"; import type { Note } from "@/models/entities/note.js"; import type { User } from "@/models/entities/user.js"; +import { Packed } from "@/misc/schema.js"; type NoteLike = { userId: Note["userId"]; text: Note["text"]; files?: Note["files"]; cw?: Note["cw"]; + reply?: Note["reply"]; + renote?: Note["renote"]; + isFiltered?: Packed<"Note">["isFiltered"]; }; type UserLike = { @@ -14,7 +18,7 @@ type UserLike = { }; function checkWordMute( - note: NoteLike, + note: NoteLike | null | undefined, mutedWords: Array, ): boolean { if (note == null) return false; @@ -63,17 +67,13 @@ export async function getWordHardMute( mutedWords: Array, ): Promise { // 自分自身 - if (me && note.userId === me.id) { - return false; - } + if (me && note.userId === me.id) return false; + if (mutedWords.length <= 0) return false; + if (note.isFiltered) return true; - if (mutedWords.length > 0) { - return ( - checkWordMute(note, mutedWords) || - checkWordMute(note.reply, mutedWords) || - checkWordMute(note.renote, mutedWords) - ); - } - - return false; + return ( + checkWordMute(note, mutedWords) || + checkWordMute(note.reply, mutedWords) || + checkWordMute(note.renote, mutedWords) + ); } diff --git a/packages/backend/src/misc/is-filtered.ts b/packages/backend/src/misc/is-filtered.ts new file mode 100644 index 000000000..460e8be3b --- /dev/null +++ b/packages/backend/src/misc/is-filtered.ts @@ -0,0 +1,16 @@ +import { User } from "@/models/entities/user.js"; +import { Note } from "@/models/entities/note.js"; +import { UserProfile } from "@/models/entities/user-profile.js"; +import { getWordHardMute } from "@/misc/check-word-mute.js"; +import { Cache } from "@/misc/cache.js"; +import { unique } from "@/prelude/array.js"; +import config from "@/config/index.js"; + +const filteredNoteCache = new Cache("filteredNote", config.wordMuteCache?.ttlSeconds ?? 60 * 60 * 24); + +export function isFiltered(note: Note, user: { id: User["id"] } | null | undefined, profile: { mutedWords: UserProfile["mutedWords"] } | null): boolean | Promise { + if (!user || !profile) return false; + if (profile.mutedWords.length < 1) return false; + return filteredNoteCache.fetch(`${note.id}:${(note.updatedAt ?? note.createdAt).getTime()}:${user.id}`, + () => getWordHardMute(note, user, unique(profile.mutedWords))); +} diff --git a/packages/backend/src/models/entities/meta.ts b/packages/backend/src/models/entities/meta.ts index 23cd629e3..4f5bff08c 100644 --- a/packages/backend/src/models/entities/meta.ts +++ b/packages/backend/src/models/entities/meta.ts @@ -520,7 +520,7 @@ export class Meta { public donationLink: string | null; @Column("varchar", { - length: 64, + length: 128, nullable: true, }) public autofollowedAccount: string | null; diff --git a/packages/backend/src/models/entities/muted-note.ts b/packages/backend/src/models/entities/muted-note.ts deleted file mode 100644 index 0ee245aea..000000000 --- a/packages/backend/src/models/entities/muted-note.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { - Entity, - Index, - JoinColumn, - Column, - ManyToOne, - PrimaryColumn, -} from "typeorm"; -import { Note } from "./note.js"; -import { User } from "./user.js"; -import { id } from "../id.js"; -import { mutedNoteReasons } from "../../types.js"; - -@Entity() -@Index(["noteId", "userId"], { unique: true }) -export class MutedNote { - @PrimaryColumn(id()) - public id: string; - - @Index() - @Column({ - ...id(), - comment: "The note ID.", - }) - public noteId: Note["id"]; - - @ManyToOne((type) => Note, { - onDelete: "CASCADE", - }) - @JoinColumn() - public note: Note | null; - - @Index() - @Column({ - ...id(), - comment: "The user ID.", - }) - public userId: User["id"]; - - @ManyToOne((type) => User, { - onDelete: "CASCADE", - }) - @JoinColumn() - public user: User | null; - - /** - * ミュートされた理由。 - */ - @Index() - @Column("enum", { - enum: mutedNoteReasons, - comment: "The reason of the MutedNote.", - }) - public reason: typeof mutedNoteReasons[number]; -} diff --git a/packages/backend/src/models/entities/note.ts b/packages/backend/src/models/entities/note.ts index 4a50c556e..a99cb8033 100644 --- a/packages/backend/src/models/entities/note.ts +++ b/packages/backend/src/models/entities/note.ts @@ -259,7 +259,7 @@ export class Note { nullable: true, comment: "The updated date of the Note.", }) - public updatedAt: Date; + public updatedAt: Date | null; //#endregion constructor(data: Partial) { diff --git a/packages/backend/src/models/index.ts b/packages/backend/src/models/index.ts index 2fafc9037..2f229689b 100644 --- a/packages/backend/src/models/index.ts +++ b/packages/backend/src/models/index.ts @@ -56,7 +56,6 @@ import { PromoRead } from "./entities/promo-read.js"; import { EmojiRepository } from "./repositories/emoji.js"; import { RelayRepository } from "./repositories/relay.js"; import { ChannelRepository } from "./repositories/channel.js"; -import { MutedNote } from "./entities/muted-note.js"; import { ChannelFollowing } from "./entities/channel-following.js"; import { ChannelNotePining } from "./entities/channel-note-pining.js"; import { RegistryItem } from "./entities/registry-item.js"; @@ -129,7 +128,6 @@ export const Antennas = AntennaRepository; export const PromoNotes = db.getRepository(PromoNote); export const PromoReads = db.getRepository(PromoRead); export const Relays = RelayRepository; -export const MutedNotes = db.getRepository(MutedNote); export const Channels = ChannelRepository; export const ChannelFollowings = db.getRepository(ChannelFollowing); export const ChannelNotePinings = db.getRepository(ChannelNotePining); diff --git a/packages/backend/src/models/repositories/note.ts b/packages/backend/src/models/repositories/note.ts index 76319b8a6..e43374173 100644 --- a/packages/backend/src/models/repositories/note.ts +++ b/packages/backend/src/models/repositories/note.ts @@ -10,7 +10,7 @@ import { Followings, Polls, Channels, - Notes, + Notes, UserProfiles, } from "../index.js"; import type { Packed } from "@/misc/schema.js"; import { nyaize } from "@/misc/nyaize.js"; @@ -29,6 +29,11 @@ import { import { db } from "@/db/postgre.js"; import { IdentifiableError } from "@/misc/identifiable-error.js"; import { PackedUserCache } from "@/models/repositories/user.js"; +import { isFiltered } from "@/misc/is-filtered.js"; +import { UserProfile } from "@/models/entities/user-profile.js"; +import { Cache } from "@/misc/cache.js"; + +const mutedWordsCache = new Cache("mutedWords", 60 * 5); export async function populatePoll(note: Note, meId: User["id"] | null) { const poll = await Polls.findOneByOrFail({ noteId: note.id }); @@ -175,6 +180,7 @@ export const NoteRepository = db.getRepository(Note).extend({ }; }, userCache: PackedUserCache = Users.getFreshPackedUserCache(), + profile?: { mutedWords: UserProfile["mutedWords"] } | null, ): Promise> { const opts = Object.assign( { @@ -187,6 +193,11 @@ export const NoteRepository = db.getRepository(Note).extend({ const note = typeof src === "object" ? src : await this.findOneByOrFail({ id: src }); const host = note.userHost; + const meProfile = profile !== undefined + ? profile + : meId !== null + ? { mutedWords: await mutedWordsCache.fetch(meId, async () => UserProfiles.findOneBy({ userId: meId }).then(p => p?.mutedWords ?? [])) } + : null; if (!(await this.isVisibleForMe(note, meId))) { throw new IdentifiableError( @@ -257,7 +268,8 @@ export const NoteRepository = db.getRepository(Note).extend({ ...(meId ? { myReaction: populateMyReaction(note, meId, options?._hint_), - isRenoted: populateIsRenoted(note, meId, options?._hint_) + isRenoted: populateIsRenoted(note, meId, options?._hint_), + isFiltered: isFiltered(note, me, await meProfile), } : {}), @@ -326,6 +338,7 @@ export const NoteRepository = db.getRepository(Note).extend({ options?: { detail?: boolean; }, + profile?: { mutedWords: UserProfile["mutedWords"] } | null, ) { if (notes.length === 0) return []; @@ -361,6 +374,10 @@ export const NoteRepository = db.getRepository(Note).extend({ !!myRenotes.find(p => p.renoteId == target), ); } + + profile = profile !== undefined + ? profile + : { mutedWords: await mutedWordsCache.fetch(meId, async () => UserProfiles.findOneBy({ userId: meId }).then(p => p?.mutedWords ?? [])) }; } await prefetchEmojis(aggregateNoteEmojis(notes)); @@ -373,7 +390,7 @@ export const NoteRepository = db.getRepository(Note).extend({ myReactions: myReactionsMap, myRenotes: myRenotesMap }, - }), + }, undefined, profile), ), ); diff --git a/packages/backend/src/models/schema/note.ts b/packages/backend/src/models/schema/note.ts index b956e7d57..be41da2ce 100644 --- a/packages/backend/src/models/schema/note.ts +++ b/packages/backend/src/models/schema/note.ts @@ -199,6 +199,11 @@ export const packedNoteSchema = { type: "boolean", optional: true, nullable: true, - } + }, + isFiltered: { + type: "boolean", + optional: true, + nullable: true, + }, }, } as const; diff --git a/packages/backend/src/server/api/common/generate-muted-note-query.ts b/packages/backend/src/server/api/common/generate-muted-note-query.ts deleted file mode 100644 index 86da2ea88..000000000 --- a/packages/backend/src/server/api/common/generate-muted-note-query.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { User } from "@/models/entities/user.js"; -import { MutedNotes } from "@/models/index.js"; -import type { SelectQueryBuilder } from "typeorm"; - -export function generateMutedNoteQuery( - q: SelectQueryBuilder, - me: { id: User["id"] }, -) { - const mutedQuery = MutedNotes.createQueryBuilder("muted") - .select("muted.noteId") - .where("muted.userId = :userId", { userId: me.id }); - - q.andWhere(`note.id NOT IN (${mutedQuery.getQuery()})`); - - q.setParameters(mutedQuery.getParameters()); -} diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts index c42bdde5d..430a0496e 100644 --- a/packages/backend/src/server/api/endpoints.ts +++ b/packages/backend/src/server/api/endpoints.ts @@ -186,7 +186,6 @@ import * as ep___i_exportUserLists from "./endpoints/i/export-user-lists.js"; import * as ep___i_favorites from "./endpoints/i/favorites.js"; import * as ep___i_gallery_likes from "./endpoints/i/gallery/likes.js"; import * as ep___i_gallery_posts from "./endpoints/i/gallery/posts.js"; -import * as ep___i_getWordMutedNotesCount from "./endpoints/i/get-word-muted-notes-count.js"; import * as ep___i_importBlocking from "./endpoints/i/import-blocking.js"; import * as ep___i_importFollowing from "./endpoints/i/import-following.js"; import * as ep___i_importMuting from "./endpoints/i/import-muting.js"; @@ -535,7 +534,6 @@ const eps = [ ["i/favorites", ep___i_favorites], ["i/gallery/likes", ep___i_gallery_likes], ["i/gallery/posts", ep___i_gallery_posts], - ["i/get-word-muted-notes-count", ep___i_getWordMutedNotesCount], ["i/import-blocking", ep___i_importBlocking], ["i/import-following", ep___i_importFollowing], ["i/import-muting", ep___i_importMuting], diff --git a/packages/backend/src/server/api/endpoints/i/get-word-muted-notes-count.ts b/packages/backend/src/server/api/endpoints/i/get-word-muted-notes-count.ts deleted file mode 100644 index bd58f9257..000000000 --- a/packages/backend/src/server/api/endpoints/i/get-word-muted-notes-count.ts +++ /dev/null @@ -1,38 +0,0 @@ -import define from "../../define.js"; -import { MutedNotes } from "@/models/index.js"; - -export const meta = { - tags: ["account"], - - requireCredential: true, - - kind: "read:account", - - res: { - type: "object", - optional: false, - nullable: false, - properties: { - count: { - type: "number", - optional: false, - nullable: false, - }, - }, - }, -} as const; - -export const paramDef = { - type: "object", - properties: {}, - required: [], -} as const; - -export default define(meta, paramDef, async (ps, user) => { - return { - count: await MutedNotes.countBy({ - userId: user.id, - reason: "word", - }), - }; -}); diff --git a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts index 8f4f805ac..f4bf1ac94 100644 --- a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts @@ -6,7 +6,6 @@ import { ApiError } from "../../error.js"; import { makePaginationQuery } from "../../common/make-pagination-query.js"; import { generateMutedUserQuery } from "../../common/generate-muted-user-query.js"; import { generateRepliesQuery } from "../../common/generate-replies-query.js"; -import { generateMutedNoteQuery } from "../../common/generate-muted-note-query.js"; import { generateBlockedUserQuery } from "../../common/generate-block-query.js"; import { generateMutedUserRenotesQueryForNotes } from "../../common/generated-muted-renote-query.js"; @@ -89,7 +88,6 @@ export default define(meta, paramDef, async (ps, user) => { generateRepliesQuery(query, ps.withReplies, user); if (user) { generateMutedUserQuery(query, user); - generateMutedNoteQuery(query, user); generateBlockedUserQuery(query, user); generateMutedUserRenotesQueryForNotes(query, user); } diff --git a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts index 0f1d0f8c1..bcc6170c9 100644 --- a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts @@ -8,7 +8,6 @@ import { makePaginationQuery } from "../../common/make-pagination-query.js"; import { generateVisibilityQuery } from "../../common/generate-visibility-query.js"; import { generateMutedUserQuery } from "../../common/generate-muted-user-query.js"; import { generateRepliesQuery } from "../../common/generate-replies-query.js"; -import { generateMutedNoteQuery } from "../../common/generate-muted-note-query.js"; import { generateChannelQuery } from "../../common/generate-channel-query.js"; import { generateBlockedUserQuery } from "../../common/generate-block-query.js"; import { generateMutedUserRenotesQueryForNotes } from "../../common/generated-muted-renote-query.js"; @@ -108,7 +107,6 @@ export default define(meta, paramDef, async (ps, user) => { generateRepliesQuery(query, ps.withReplies, user); generateVisibilityQuery(query, user); generateMutedUserQuery(query, user); - generateMutedNoteQuery(query, user); generateBlockedUserQuery(query, user); generateMutedUserRenotesQueryForNotes(query, user); diff --git a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts index 3bf10c65d..443222964 100644 --- a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts @@ -6,9 +6,7 @@ import define from "../../define.js"; import { ApiError } from "../../error.js"; import { generateMutedUserQuery } from "../../common/generate-muted-user-query.js"; import { makePaginationQuery } from "../../common/make-pagination-query.js"; -import { generateVisibilityQuery } from "../../common/generate-visibility-query.js"; import { generateRepliesQuery } from "../../common/generate-replies-query.js"; -import { generateMutedNoteQuery } from "../../common/generate-muted-note-query.js"; import { generateChannelQuery } from "../../common/generate-channel-query.js"; import { generateBlockedUserQuery } from "../../common/generate-block-query.js"; import { generateMutedUserRenotesQueryForNotes } from "../../common/generated-muted-renote-query.js"; @@ -99,7 +97,6 @@ export default define(meta, paramDef, async (ps, user) => { generateChannelQuery(query, user); generateRepliesQuery(query, ps.withReplies, user); if (user) generateMutedUserQuery(query, user); - if (user) generateMutedNoteQuery(query, user); if (user) generateBlockedUserQuery(query, user); if (user) generateMutedUserRenotesQueryForNotes(query, user); diff --git a/packages/backend/src/server/api/endpoints/notes/recommended-timeline.ts b/packages/backend/src/server/api/endpoints/notes/recommended-timeline.ts index 9bf783feb..17f2abc1c 100644 --- a/packages/backend/src/server/api/endpoints/notes/recommended-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/recommended-timeline.ts @@ -6,9 +6,7 @@ import define from "../../define.js"; import { ApiError } from "../../error.js"; import { generateMutedUserQuery } from "../../common/generate-muted-user-query.js"; import { makePaginationQuery } from "../../common/make-pagination-query.js"; -import { generateVisibilityQuery } from "../../common/generate-visibility-query.js"; import { generateRepliesQuery } from "../../common/generate-replies-query.js"; -import { generateMutedNoteQuery } from "../../common/generate-muted-note-query.js"; import { generateChannelQuery } from "../../common/generate-channel-query.js"; import { generateBlockedUserQuery } from "../../common/generate-block-query.js"; import { generateMutedUserRenotesQueryForNotes } from "../../common/generated-muted-renote-query.js"; @@ -99,7 +97,6 @@ export default define(meta, paramDef, async (ps, user) => { generateChannelQuery(query, user); generateRepliesQuery(query, ps.withReplies, user); if (user) generateMutedUserQuery(query, user); - if (user) generateMutedNoteQuery(query, user); if (user) generateBlockedUserQuery(query, user); if (user) generateMutedUserRenotesQueryForNotes(query, user); diff --git a/packages/backend/src/server/api/endpoints/notes/timeline.ts b/packages/backend/src/server/api/endpoints/notes/timeline.ts index 0bb674fd9..eb487ea0f 100644 --- a/packages/backend/src/server/api/endpoints/notes/timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/timeline.ts @@ -6,7 +6,6 @@ import { makePaginationQuery } from "../../common/make-pagination-query.js"; import { generateVisibilityQuery } from "../../common/generate-visibility-query.js"; import { generateMutedUserQuery } from "../../common/generate-muted-user-query.js"; import { generateRepliesQuery } from "../../common/generate-replies-query.js"; -import { generateMutedNoteQuery } from "../../common/generate-muted-note-query.js"; import { generateChannelQuery } from "../../common/generate-channel-query.js"; import { generateBlockedUserQuery } from "../../common/generate-block-query.js"; import { generateMutedUserRenotesQueryForNotes } from "../../common/generated-muted-renote-query.js"; @@ -86,7 +85,6 @@ export default define(meta, paramDef, async (ps, user) => { generateRepliesQuery(query, ps.withReplies, user); generateVisibilityQuery(query, user); generateMutedUserQuery(query, user); - generateMutedNoteQuery(query, user); generateBlockedUserQuery(query, user); generateMutedUserRenotesQueryForNotes(query, user); diff --git a/packages/backend/src/server/api/mastodon/converters/note.ts b/packages/backend/src/server/api/mastodon/converters/note.ts index c259cd345..6951bee5a 100644 --- a/packages/backend/src/server/api/mastodon/converters/note.ts +++ b/packages/backend/src/server/api/mastodon/converters/note.ts @@ -172,7 +172,7 @@ export class NoteConverter { reactions: populated.then(populated => Promise.resolve(reaction).then(reaction => this.encodeReactions(note.reactions, reaction?.reaction, populated))), bookmarked: isBookmarked, quote: reblog.then(reblog => isQuote(note) ? reblog : null), - edited_at: note.updatedAt?.toISOString() + edited_at: note.updatedAt?.toISOString() ?? null }); } @@ -336,7 +336,7 @@ export class NoteConverter { .then(p => p ?? escapeMFM(text)) : null; - HtmlNoteCacheEntries.upsert({ noteId: note.id, updatedAt: note.updatedAt, content: await content }, ["noteId"]); + HtmlNoteCacheEntries.upsert({ noteId: note.id, updatedAt: note.updatedAt ?? note.createdAt, content: await content }, ["noteId"]); await this.noteContentHtmlCache.set(identifier, await content); return { content } as HtmlNoteCacheEntry; }); @@ -367,6 +367,6 @@ export class NoteConverter { this.noteContentHtmlCache.set(identifier, await content); if (config.htmlCache?.dbFallback) - HtmlNoteCacheEntries.upsert({ noteId: note.id, updatedAt: note.updatedAt, content: await content }, ["noteId"]); + HtmlNoteCacheEntries.upsert({ noteId: note.id, updatedAt: note.updatedAt ?? note.createdAt, content: await content }, ["noteId"]); } } diff --git a/packages/backend/src/server/api/mastodon/helpers/timeline.ts b/packages/backend/src/server/api/mastodon/helpers/timeline.ts index 6e034d2b8..21cc6876e 100644 --- a/packages/backend/src/server/api/mastodon/helpers/timeline.ts +++ b/packages/backend/src/server/api/mastodon/helpers/timeline.ts @@ -6,7 +6,6 @@ import { generateChannelQuery } from "@/server/api/common/generate-channel-query import { generateRepliesQuery } from "@/server/api/common/generate-replies-query.js"; import { generateVisibilityQuery } from "@/server/api/common/generate-visibility-query.js"; import { generateMutedUserQuery } from "@/server/api/common/generate-muted-user-query.js"; -import { generateMutedNoteQuery } from "@/server/api/common/generate-muted-note-query.js"; import { generateBlockedUserQuery } from "@/server/api/common/generate-block-query.js"; import { generateMutedUserRenotesQueryForNotes } from "@/server/api/common/generated-muted-renote-query.js"; import { fetchMeta } from "@/misc/fetch-meta.js"; @@ -43,7 +42,6 @@ export class TimelineHelpers { generateRepliesQuery(query, true, user); generateVisibilityQuery(query, user); generateMutedUserQuery(query, user); - generateMutedNoteQuery(query, user); generateBlockedUserQuery(query, user); generateMutedUserRenotesQueryForNotes(query, user); @@ -88,7 +86,6 @@ export class TimelineHelpers { generateRepliesQuery(query, true, user); if (user) { generateMutedUserQuery(query, user); - generateMutedNoteQuery(query, user); generateBlockedUserQuery(query, user); generateMutedUserRenotesQueryForNotes(query, user); } @@ -158,7 +155,6 @@ export class TimelineHelpers { generateRepliesQuery(query, true, user); if (user) { generateMutedUserQuery(query, user); - generateMutedNoteQuery(query, user); generateBlockedUserQuery(query, user); generateMutedUserRenotesQueryForNotes(query, user); } diff --git a/packages/backend/src/server/api/mastodon/streaming/channels/list.ts b/packages/backend/src/server/api/mastodon/streaming/channels/list.ts index 4b6b7def4..e550193a1 100644 --- a/packages/backend/src/server/api/mastodon/streaming/channels/list.ts +++ b/packages/backend/src/server/api/mastodon/streaming/channels/list.ts @@ -1,11 +1,11 @@ import { MastodonStream } from "../channel.js"; -import { getWordHardMute } from "@/misc/check-word-mute.js"; import { Note } from "@/models/entities/note.js"; import { NoteConverter } from "@/server/api/mastodon/converters/note.js"; import { StreamMessages } from "@/server/api/stream/types.js"; import { Packed } from "@/misc/schema.js"; import { User } from "@/models/entities/user.js"; import { UserListJoinings } from "@/models/index.js"; +import { isFiltered } from "@/misc/is-filtered.js"; export class MastodonStreamList extends MastodonStream { public static shouldShare = false; @@ -75,7 +75,7 @@ export class MastodonStreamList extends MastodonStream { if (!this.listUsers.includes(note.userId)) return false; if (note.channelId) return false; if (note.renoteId !== null && !note.text && this.renoteMuting.has(note.userId)) return false; - if (this.userProfile && (await getWordHardMute(note, this.user, this.userProfile.mutedWords))) return false; + if (this.userProfile && (await isFiltered(note as Note, this.user, this.userProfile))) return false; if (note.visibility === "specified") return !!note.visibleUserIds?.includes(this.user.id); if (note.visibility === "followers") return this.following.has(note.userId); return true; diff --git a/packages/backend/src/server/api/mastodon/streaming/channels/public.ts b/packages/backend/src/server/api/mastodon/streaming/channels/public.ts index ada120301..58bcc0c6e 100644 --- a/packages/backend/src/server/api/mastodon/streaming/channels/public.ts +++ b/packages/backend/src/server/api/mastodon/streaming/channels/public.ts @@ -1,5 +1,4 @@ import { MastodonStream } from "../channel.js"; -import { getWordHardMute } from "@/misc/check-word-mute.js"; import { isUserRelated } from "@/misc/is-user-related.js"; import { isInstanceMuted } from "@/misc/is-instance-muted.js"; import { Note } from "@/models/entities/note.js"; @@ -7,6 +6,7 @@ import { NoteConverter } from "@/server/api/mastodon/converters/note.js"; import { StreamMessages } from "@/server/api/stream/types.js"; import { fetchMeta } from "@/misc/fetch-meta.js"; import isQuote from "@/misc/is-quote.js"; +import { isFiltered } from "@/misc/is-filtered.js"; export class MastodonStreamPublic extends MastodonStream { public static shouldShare = true; @@ -72,7 +72,7 @@ export class MastodonStreamPublic extends MastodonStream { if (isUserRelated(note, this.muting)) return false; if (isUserRelated(note, this.blocking)) return false; if (note.renoteId !== null && !isQuote(note) && this.renoteMuting.has(note.userId)) return false; - if (this.userProfile && (await getWordHardMute(note, this.user, this.userProfile.mutedWords))) return false; + if (this.userProfile && (await isFiltered(note as Note, this.user, this.userProfile))) return false; return true; } diff --git a/packages/backend/src/server/api/mastodon/streaming/channels/tag.ts b/packages/backend/src/server/api/mastodon/streaming/channels/tag.ts index f7728f45b..29462c755 100644 --- a/packages/backend/src/server/api/mastodon/streaming/channels/tag.ts +++ b/packages/backend/src/server/api/mastodon/streaming/channels/tag.ts @@ -1,11 +1,11 @@ import { MastodonStream } from "../channel.js"; -import { getWordHardMute } from "@/misc/check-word-mute.js"; import { isUserRelated } from "@/misc/is-user-related.js"; import { isInstanceMuted } from "@/misc/is-instance-muted.js"; import { Note } from "@/models/entities/note.js"; import { NoteConverter } from "@/server/api/mastodon/converters/note.js"; import { StreamMessages } from "@/server/api/stream/types.js"; import isQuote from "@/misc/is-quote.js"; +import { isFiltered } from "@/misc/is-filtered.js"; export class MastodonStreamTag extends MastodonStream { public static shouldShare = false; @@ -64,7 +64,7 @@ export class MastodonStreamTag extends MastodonStream { if (isUserRelated(note, this.muting)) return false; if (isUserRelated(note, this.blocking)) return false; if (note.renoteId !== null && !isQuote(note) && this.renoteMuting.has(note.userId)) return false; - if (this.userProfile && (await getWordHardMute(note, this.user, this.userProfile.mutedWords))) return false; + if (this.userProfile && (await isFiltered(note as Note, this.user, this.userProfile))) return false; return true; } diff --git a/packages/backend/src/server/api/mastodon/streaming/channels/user.ts b/packages/backend/src/server/api/mastodon/streaming/channels/user.ts index 113cb843a..acc4be83e 100644 --- a/packages/backend/src/server/api/mastodon/streaming/channels/user.ts +++ b/packages/backend/src/server/api/mastodon/streaming/channels/user.ts @@ -1,5 +1,4 @@ import { MastodonStream } from "../channel.js"; -import { getWordHardMute } from "@/misc/check-word-mute.js"; import { isUserRelated } from "@/misc/is-user-related.js"; import { isInstanceMuted } from "@/misc/is-instance-muted.js"; import { Note } from "@/models/entities/note.js"; @@ -8,6 +7,7 @@ import { StreamMessages } from "@/server/api/stream/types.js"; import { NotificationConverter } from "@/server/api/mastodon/converters/notification.js"; import { AnnouncementConverter } from "@/server/api/mastodon/converters/announcement.js"; import isQuote from "@/misc/is-quote.js"; +import { isFiltered } from "@/misc/is-filtered.js"; export class MastodonStreamUser extends MastodonStream { public static shouldShare = true; @@ -99,7 +99,7 @@ export class MastodonStreamUser extends MastodonStream { if (isUserRelated(note, this.blocking)) return false; if (isUserRelated(note, this.hidden)) return false; if (note.renoteId !== null && !isQuote(note) && this.renoteMuting.has(note.userId)) return false; - if (this.userProfile && (await getWordHardMute(note, this.user, this.userProfile.mutedWords))) return false; + if (this.userProfile && (await isFiltered(note as Note, this.user, this.userProfile))) return false; return true; } diff --git a/packages/backend/src/server/api/stream/channels/global-timeline.ts b/packages/backend/src/server/api/stream/channels/global-timeline.ts index 2257be2b8..3a5fed7f8 100644 --- a/packages/backend/src/server/api/stream/channels/global-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/global-timeline.ts @@ -1,9 +1,10 @@ import Channel from "../channel.js"; import { fetchMeta } from "@/misc/fetch-meta.js"; -import { getWordHardMute } from "@/misc/check-word-mute.js"; import { isInstanceMuted } from "@/misc/is-instance-muted.js"; import { isUserRelated } from "@/misc/is-user-related.js"; import type { Packed } from "@/misc/schema.js"; +import { isFiltered } from "@/misc/is-filtered.js"; +import { Note } from "@/models/entities/note.js"; export default class extends Channel { public readonly chName = "globalTimeline"; @@ -68,7 +69,7 @@ export default class extends Channel { // そのためレコードが存在するかのチェックでは不十分なので、改めてgetWordHardMuteを呼んでいる if ( this.userProfile && - (await getWordHardMute(note, this.user, this.userProfile.mutedWords)) + (await isFiltered(note as unknown as Note, this.user, this.userProfile)) ) return; diff --git a/packages/backend/src/server/api/stream/channels/home-timeline.ts b/packages/backend/src/server/api/stream/channels/home-timeline.ts index 4582b3183..40e600585 100644 --- a/packages/backend/src/server/api/stream/channels/home-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/home-timeline.ts @@ -1,8 +1,9 @@ import Channel from "../channel.js"; -import { getWordHardMute } from "@/misc/check-word-mute.js"; import { isUserRelated } from "@/misc/is-user-related.js"; import { isInstanceMuted } from "@/misc/is-instance-muted.js"; import type { Packed } from "@/misc/schema.js"; +import { isFiltered } from "@/misc/is-filtered.js"; +import { Note } from "@/models/entities/note.js"; export default class extends Channel { public readonly chName = "homeTimeline"; @@ -69,7 +70,7 @@ export default class extends Channel { // そのためレコードが存在するかのチェックでは不十分なので、改めてgetWordHardMuteを呼んでいる if ( this.userProfile && - (await getWordHardMute(note, this.user, this.userProfile.mutedWords)) + (await isFiltered(note as unknown as Note, this.user, this.userProfile)) ) return; diff --git a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts index fed204165..fff2a0308 100644 --- a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts @@ -1,9 +1,10 @@ import Channel from "../channel.js"; import { fetchMeta } from "@/misc/fetch-meta.js"; -import { getWordHardMute } from "@/misc/check-word-mute.js"; import { isUserRelated } from "@/misc/is-user-related.js"; import { isInstanceMuted } from "@/misc/is-instance-muted.js"; import type { Packed } from "@/misc/schema.js"; +import { isFiltered } from "@/misc/is-filtered.js"; +import { Note } from "@/models/entities/note.js"; export default class extends Channel { public readonly chName = "hybridTimeline"; @@ -86,7 +87,7 @@ export default class extends Channel { // そのためレコードが存在するかのチェックでは不十分なので、改めてgetWordHardMuteを呼んでいる if ( this.userProfile && - (await getWordHardMute(note, this.user, this.userProfile.mutedWords)) + (await isFiltered(note as unknown as Note, this.user, this.userProfile)) ) return; diff --git a/packages/backend/src/server/api/stream/channels/local-timeline.ts b/packages/backend/src/server/api/stream/channels/local-timeline.ts index bd488bdd7..39f59d4a9 100644 --- a/packages/backend/src/server/api/stream/channels/local-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/local-timeline.ts @@ -1,8 +1,9 @@ import Channel from "../channel.js"; import { fetchMeta } from "@/misc/fetch-meta.js"; -import { getWordHardMute } from "@/misc/check-word-mute.js"; import { isUserRelated } from "@/misc/is-user-related.js"; import type { Packed } from "@/misc/schema.js"; +import { isFiltered } from "@/misc/is-filtered.js"; +import { Note } from "@/models/entities/note.js"; export default class extends Channel { public readonly chName = "localTimeline"; @@ -60,7 +61,7 @@ export default class extends Channel { // そのためレコードが存在するかのチェックでは不十分なので、改めてgetWordHardMuteを呼んでいる if ( this.userProfile && - (await getWordHardMute(note, this.user, this.userProfile.mutedWords)) + (await isFiltered(note as unknown as Note, this.user, this.userProfile)) ) return; diff --git a/packages/backend/src/server/api/stream/channels/recommended-timeline.ts b/packages/backend/src/server/api/stream/channels/recommended-timeline.ts index 0b78d8b66..655bd6e0c 100644 --- a/packages/backend/src/server/api/stream/channels/recommended-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/recommended-timeline.ts @@ -1,9 +1,10 @@ import Channel from "../channel.js"; import { fetchMeta } from "@/misc/fetch-meta.js"; -import { getWordHardMute } from "@/misc/check-word-mute.js"; import { isUserRelated } from "@/misc/is-user-related.js"; import { isInstanceMuted } from "@/misc/is-instance-muted.js"; import type { Packed } from "@/misc/schema.js"; +import { isFiltered } from "@/misc/is-filtered.js"; +import { Note } from "@/models/entities/note.js"; export default class extends Channel { public readonly chName = "recommendedTimeline"; @@ -82,7 +83,7 @@ export default class extends Channel { // そのためレコードが存在するかのチェックでは不十分なので、改めてgetWordHardMuteを呼んでいる if ( this.userProfile && - (await getWordHardMute(note, this.user, this.userProfile.mutedWords)) + (await isFiltered(note as unknown as Note, this.user, this.userProfile)) ) return; diff --git a/packages/backend/src/services/note/create.ts b/packages/backend/src/services/note/create.ts index 9085f9c9d..87dc63a48 100644 --- a/packages/backend/src/services/note/create.ts +++ b/packages/backend/src/services/note/create.ts @@ -27,7 +27,6 @@ import { Notes, Instances, UserProfiles, - MutedNotes, Channels, ChannelFollowings, NoteThreadMutings, @@ -48,7 +47,6 @@ import { Poll } from "@/models/entities/poll.js"; import { createNotification } from "../create-notification.js"; import { isDuplicateKeyValueError } from "@/misc/is-duplicate-key-value-error.js"; import { checkHitAntenna } from "@/misc/check-hit-antenna.js"; -import { getWordHardMute } from "@/misc/check-word-mute.js"; import { addNoteToAntenna } from "../add-note-to-antenna.js"; import { countSameRenotes } from "@/misc/count-same-renotes.js"; import { deliverToRelays, getCachedRelays } from "../relay.js"; @@ -57,8 +55,6 @@ 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"; -import type { UserProfile } from "@/models/entities/user-profile.js"; import { db } from "@/db/postgre.js"; import { getActiveWebhooks } from "@/misc/webhook-cache.js"; import { shouldSilenceInstance } from "@/misc/should-block-instance.js"; @@ -67,10 +63,6 @@ import { Mutex } from "redis-semaphore"; import { RecursionLimiter } from "@/models/repositories/user-profile.js"; import { NoteConverter } from "@/server/api/mastodon/converters/note.js"; -const mutedWordsCache = new Cache< - { userId: UserProfile["userId"]; mutedWords: UserProfile["mutedWords"] }[] ->("mutedWords", 60 * 5); - type NotificationType = "reply" | "renote" | "quote" | "mention"; class NotificationManager { @@ -367,33 +359,6 @@ export default async ( // Increment notes count (user) incNotesCountOfUser(user); - // Word mute - mutedWordsCache - .fetch(null, () => - UserProfiles.find({ - where: { - enableWordMute: true, - }, - select: ["userId", "mutedWords"], - }), - ) - .then((us) => { - for (const u of us) { - getWordHardMute(data, { id: u.userId }, u.mutedWords).then( - (shouldMute) => { - if (shouldMute) { - MutedNotes.insert({ - id: genId(), - userId: u.userId, - noteId: note.id, - reason: "word", - }); - } - }, - ); - } - }); - // Antenna for (const antenna of await getAntennas()) { checkHitAntenna(antenna, note, user).then((hit) => { diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts index 2ba6da2f7..b77b5afb4 100644 --- a/packages/backend/src/types.ts +++ b/packages/backend/src/types.ts @@ -21,6 +21,4 @@ export const noteVisibilities = [ "hidden", ] as const; -export const mutedNoteReasons = ["word", "manual", "spam", "other"] as const; - export const ffVisibility = ["public", "followers", "private"] as const; diff --git a/packages/client/src/components/MkDateSeparatedList.vue b/packages/client/src/components/MkDateSeparatedList.vue index 19461a93d..4f3466db2 100644 --- a/packages/client/src/components/MkDateSeparatedList.vue +++ b/packages/client/src/components/MkDateSeparatedList.vue @@ -8,7 +8,7 @@ export default defineComponent({ props: { items: { type: Array as PropType< - { id: string; createdAt: string; _shouldInsertAd_: boolean }[] + { id: string; createdAt: string; }[] >, required: true, }, diff --git a/packages/client/src/components/MkPagination.vue b/packages/client/src/components/MkPagination.vue index 977e1fb02..913885662 100644 --- a/packages/client/src/components/MkPagination.vue +++ b/packages/client/src/components/MkPagination.vue @@ -162,17 +162,13 @@ const init = async (): Promise => { redisPaginationStr = res.pagination; res = res.notes; } - for (let i = 0; i < res.length; i++) { - const item = res[i]; - if (props.pagination.reversed) { - if (i === res.length - 2) item._shouldInsertAd_ = true; - } else { - if (i === 3) item._shouldInsertAd_ = true; - } - } + + const length = res.length; + res = (res as Item[]).filter(p => !p.isFiltered); + if ( !props.pagination.noPaging && - res.length > (props.pagination.limit || 10) + length > (props.pagination.limit || 10) ) { res.pop(); items.value = props.pagination.reversed @@ -201,7 +197,7 @@ const reload = (): void => { init(); }; -const refresh = async (): void => { +const refresh = async (): Promise => { const params = props.pagination.params ? isRef(props.pagination.params) ? props.pagination.params.value @@ -290,15 +286,10 @@ const fetchMore = async (): Promise => { res = res.notes; } - for (let i = 0; i < res.length; i++) { - const item = res[i]; - if (props.pagination.reversed) { - if (i === res.length - 9) item._shouldInsertAd_ = true; - } else { - if (i === 10) item._shouldInsertAd_ = true; - } - } - if (res.length > SECOND_FETCH_LIMIT) { + const length = res.length; + res = (res as Item[]).filter(p => !p.isFiltered); + + if (length > SECOND_FETCH_LIMIT) { res.pop(); items.value = props.pagination.reversed ? [...res].reverse().concat(items.value) @@ -361,7 +352,9 @@ const fetchMoreAhead = async (): Promise => { res = res.notes; } - if (res.length > SECOND_FETCH_LIMIT) { + const length = res.length; + res = (res as Item[]).filter(p => !p.isFiltered); + if (length > SECOND_FETCH_LIMIT) { res.pop(); items.value = props.pagination.reversed ? [...res].reverse().concat(items.value) @@ -383,6 +376,7 @@ const fetchMoreAhead = async (): Promise => { }; const prepend = (item: Item): void => { + if (item.isFiltered) return; if (props.pagination.reversed) { if (rootEl.value) { const container = getScrollContainer(rootEl.value); @@ -446,6 +440,7 @@ const prepend = (item: Item): void => { }; const append = (item: Item): void => { + if (item.isFiltered) return; items.value.push(item); }; diff --git a/packages/client/src/pages/settings/word-mute.vue b/packages/client/src/pages/settings/word-mute.vue index 9c69358bf..d5dec958e 100644 --- a/packages/client/src/pages/settings/word-mute.vue +++ b/packages/client/src/pages/settings/word-mute.vue @@ -31,15 +31,6 @@ }} - - - - const tab = ref("soft"); const softMutedWords = ref(render(defaultStore.state.mutedWords)); const hardMutedWords = ref(render($i!.mutedWords)); -const hardWordMutedNotesCount = ref(null); const changed = ref(false); -os.api("i/get-word-muted-notes-count", {}).then((response) => { - hardWordMutedNotesCount.value = response?.count; -}); - watch(softMutedWords, () => { changed.value = true; }); diff --git a/packages/iceshrimp-js/markdown/iceshrimp-js.md b/packages/iceshrimp-js/markdown/iceshrimp-js.md index 54f0fde9d..9da2a0140 100644 --- a/packages/iceshrimp-js/markdown/iceshrimp-js.md +++ b/packages/iceshrimp-js/markdown/iceshrimp-js.md @@ -28,7 +28,6 @@ | Variable | Description | | --- | --- | | [ffVisibility](./iceshrimp-js.ffvisibility.md) | | -| [mutedNoteReasons](./iceshrimp-js.mutednotereasons.md) | | | [noteVisibilities](./iceshrimp-js.notevisibilities.md) | | | [notificationTypes](./iceshrimp-js.notificationtypes.md) | | | [permissions](./iceshrimp-js.permissions.md) | | diff --git a/packages/iceshrimp-js/src/api.types.ts b/packages/iceshrimp-js/src/api.types.ts index 0f7d68c5b..9bad7ec2d 100644 --- a/packages/iceshrimp-js/src/api.types.ts +++ b/packages/iceshrimp-js/src/api.types.ts @@ -90,10 +90,6 @@ export type Endpoints = { "admin/update-meta": { req: TODO; res: TODO }; "admin/vacuum": { req: TODO; res: TODO }; "admin/accounts/create": { req: TODO; res: TODO }; - "admin/ad/create": { req: TODO; res: TODO }; - "admin/ad/delete": { req: { id: Ad["id"] }; res: null }; - "admin/ad/list": { req: TODO; res: TODO }; - "admin/ad/update": { req: TODO; res: TODO }; "admin/announcements/create": { req: TODO; res: TODO }; "admin/announcements/delete": { req: { id: Announcement["id"] }; res: null }; "admin/announcements/list": { req: TODO; res: TODO }; @@ -630,7 +626,6 @@ export type Endpoints = { }; "i/gallery/likes": { req: TODO; res: TODO }; "i/gallery/posts": { req: TODO; res: TODO }; - "i/get-word-muted-notes-count": { req: TODO; res: TODO }; "i/import-following": { req: TODO; res: TODO }; "i/import-user-lists": { req: TODO; res: TODO }; "i/move": { req: TODO; res: TODO }; diff --git a/packages/iceshrimp-js/src/consts.ts b/packages/iceshrimp-js/src/consts.ts index be33c0d3e..43d045034 100644 --- a/packages/iceshrimp-js/src/consts.ts +++ b/packages/iceshrimp-js/src/consts.ts @@ -20,8 +20,6 @@ export const noteVisibilities = [ "specified", ] as const; -export const mutedNoteReasons = ["word", "manual", "spam", "other"] as const; - export const ffVisibility = ["public", "followers", "private"] as const; export const permissions = [ diff --git a/packages/iceshrimp-js/src/index.ts b/packages/iceshrimp-js/src/index.ts index 128ed8c53..a909d4b32 100644 --- a/packages/iceshrimp-js/src/index.ts +++ b/packages/iceshrimp-js/src/index.ts @@ -9,7 +9,6 @@ export { Endpoints, Stream, Connection as ChannelConnection, Channels, Acct }; export const permissions = consts.permissions; export const notificationTypes = consts.notificationTypes; export const noteVisibilities = consts.noteVisibilities; -export const mutedNoteReasons = consts.mutedNoteReasons; export const ffVisibility = consts.ffVisibility; // api extractor not supported yet