mirror of
https://iceshrimp.dev/limepotato/jormungandr-bite.git
synced 2024-11-21 17:37:29 -07:00
[backend] Rework note hard mutes
It's been shown that the current approach doesn't scale. This implementation should scale perfectly fine.
This commit is contained in:
parent
2d475cb632
commit
ef3463e8dc
42 changed files with 148 additions and 253 deletions
|
@ -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!
|
||||
|
|
|
@ -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!
|
||||
|
|
|
@ -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=';
|
||||
|
||||
|
|
|
@ -48,6 +48,11 @@ export type Source = {
|
|||
dbFallback?: boolean;
|
||||
}
|
||||
|
||||
wordMuteCache?: {
|
||||
ttl?: string;
|
||||
ttlSeconds?: number;
|
||||
}
|
||||
|
||||
searchEngine?: string;
|
||||
|
||||
proxy?: string;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class ReworkHardMutes1701108527387 implements MigrationInterface {
|
||||
name = 'ReworkHardMutes1701108527387'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
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<void> {
|
||||
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`);
|
||||
}
|
||||
}
|
|
@ -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<string | string[]>,
|
||||
): boolean {
|
||||
if (note == null) return false;
|
||||
|
@ -63,17 +67,13 @@ export async function getWordHardMute(
|
|||
mutedWords: Array<string | string[]>,
|
||||
): Promise<boolean> {
|
||||
// 自分自身
|
||||
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)
|
||||
);
|
||||
}
|
||||
|
|
16
packages/backend/src/misc/is-filtered.ts
Normal file
16
packages/backend/src/misc/is-filtered.ts
Normal file
|
@ -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<boolean>("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<boolean> {
|
||||
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)));
|
||||
}
|
|
@ -520,7 +520,7 @@ export class Meta {
|
|||
public donationLink: string | null;
|
||||
|
||||
@Column("varchar", {
|
||||
length: 64,
|
||||
length: 128,
|
||||
nullable: true,
|
||||
})
|
||||
public autofollowedAccount: string | 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];
|
||||
}
|
|
@ -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<Note>) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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<UserProfile["mutedWords"]>("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<Packed<"Note">> {
|
||||
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),
|
||||
),
|
||||
);
|
||||
|
||||
|
|
|
@ -199,6 +199,11 @@ export const packedNoteSchema = {
|
|||
type: "boolean",
|
||||
optional: true,
|
||||
nullable: true,
|
||||
}
|
||||
},
|
||||
isFiltered: {
|
||||
type: "boolean",
|
||||
optional: true,
|
||||
nullable: true,
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
|
|
@ -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<any>,
|
||||
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());
|
||||
}
|
|
@ -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],
|
||||
|
|
|
@ -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",
|
||||
}),
|
||||
};
|
||||
});
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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"]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
|
|
|
@ -162,17 +162,13 @@ const init = async (): Promise<void> => {
|
|||
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<void> => {
|
||||
const params = props.pagination.params
|
||||
? isRef(props.pagination.params)
|
||||
? props.pagination.params.value
|
||||
|
@ -290,15 +286,10 @@ const fetchMore = async (): Promise<void> => {
|
|||
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<void> => {
|
|||
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<void> => {
|
|||
};
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
|
|
|
@ -31,15 +31,6 @@
|
|||
}}</template
|
||||
>
|
||||
</FormTextarea>
|
||||
<MkKeyValue
|
||||
v-if="hardWordMutedNotesCount != null"
|
||||
class="_formBlock"
|
||||
>
|
||||
<template #key>{{ i18n.ts._wordMute.mutedNotes }}</template>
|
||||
<template #value>{{
|
||||
number(hardWordMutedNotesCount)
|
||||
}}</template>
|
||||
</MkKeyValue>
|
||||
</div>
|
||||
</div>
|
||||
<MkButton primary inline :disabled="!changed" @click="save()"
|
||||
|
@ -77,13 +68,8 @@ const render = (mutedWords) =>
|
|||
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;
|
||||
});
|
||||
|
|
|
@ -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) | |
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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 = [
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue