diff --git a/.config/docker_example.yml b/.config/docker_example.yml
index 2921746295..940b095fe2 100644
--- a/.config/docker_example.yml
+++ b/.config/docker_example.yml
@@ -95,14 +95,6 @@ redis:
 #  #prefix: example-prefix
 #  #db: 1
 
-#redisForTimelines:
-#  host: redis
-#  port: 6379
-#  #family: 0  # 0=Both, 4=IPv4, 6=IPv6
-#  #pass: example-pass
-#  #prefix: example-prefix
-#  #db: 1
-
 #   ┌───────────────────────────┐
 #───┘ MeiliSearch configuration └─────────────────────────────
 
diff --git a/.config/example.yml b/.config/example.yml
index 0e4f2f5a15..03864a3299 100644
--- a/.config/example.yml
+++ b/.config/example.yml
@@ -105,16 +105,6 @@ redis:
 #  # You can specify more ioredis options...
 #  #username: example-username
 
-#redisForTimelines:
-#  host: localhost
-#  port: 6379
-#  #family: 0  # 0=Both, 4=IPv4, 6=IPv6
-#  #pass: example-pass
-#  #prefix: example-prefix
-#  #db: 1
-#  # You can specify more ioredis options...
-#  #username: example-username
-
 #   ┌───────────────────────────┐
 #───┘ MeiliSearch configuration └─────────────────────────────
 
diff --git a/.devcontainer/devcontainer.yml b/.devcontainer/devcontainer.yml
index 3d57d1245d..5dcd41599a 100644
--- a/.devcontainer/devcontainer.yml
+++ b/.devcontainer/devcontainer.yml
@@ -95,14 +95,6 @@ redis:
 #  #prefix: example-prefix
 #  #db: 1
 
-#redisForTimelines:
-#  host: redis
-#  port: 6379
-#  #family: 0  # 0=Both, 4=IPv4, 6=IPv6
-#  #pass: example-pass
-#  #prefix: example-prefix
-#  #db: 1
-
 #   ┌───────────────────────────┐
 #───┘ MeiliSearch configuration └─────────────────────────────
 
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a761b79b82..a5b3f32c42 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,25 +13,11 @@
 -->
 
 ## 2023.10.0
-### NOTE
-- muted_noteテーブルは使われなくなったため手動で削除を行ってください。
-
-### Changes
-- API: users/notes, notes/local-timeline で fileType 指定はできなくなりました
-- API: notes/global-timeline は現在常に `[]` を返します
-
-### General
-- Feat: ユーザーごとに他ユーザーへの返信をタイムラインに含めるか設定可能になりました
-- Feat: ユーザーリスト内のメンバーごとに他ユーザーへの返信をユーザーリストタイムラインに含めるか設定可能になりました
-- Enhance: ソフトワードミュートとハードワードミュートは統合されました
 
 ### Client
 - Enhance: 二要素認証のバックアップコード一覧をテキストファイルでダウンロード可能に
 - Fix: リアクションしたユーザ一覧のUIが稀に左上に残ってしまう不具合を修正
 
-### Server
-- Enhance: タイムライン取得時のパフォーマンスを改善
-
 ## 2023.9.3
 ### General
 - Enhance: ノートの翻訳機能の利用可否をロールで設定可能に
diff --git a/chart/files/default.yml b/chart/files/default.yml
index 87b2f677eb..90b574b99f 100644
--- a/chart/files/default.yml
+++ b/chart/files/default.yml
@@ -116,14 +116,6 @@ redis:
 #  #prefix: example-prefix
 #  #db: 1
 
-#redisForTimelines:
-#  host: redis
-#  port: 6379
-#  #family: 0  # 0=Both, 4=IPv4, 6=IPv6
-#  #pass: example-pass
-#  #prefix: example-prefix
-#  #db: 1
-
 #   ┌───────────────────────────┐
 #───┘ MeiliSearch configuration └─────────────────────────────
 
diff --git a/locales/index.d.ts b/locales/index.d.ts
index 418e1c67ff..a9de0ad965 100644
--- a/locales/index.d.ts
+++ b/locales/index.d.ts
@@ -1129,8 +1129,6 @@ export interface Locale {
     "notificationRecieveConfig": string;
     "mutualFollow": string;
     "fileAttachedOnly": string;
-    "showRepliesToOthersInTimeline": string;
-    "hideRepliesToOthersInTimeline": string;
     "_announcement": {
         "forExistingUsers": string;
         "forExistingUsersDescription": string;
@@ -1721,6 +1719,11 @@ export interface Locale {
         "muteWords": string;
         "muteWordsDescription": string;
         "muteWordsDescription2": string;
+        "softDescription": string;
+        "hardDescription": string;
+        "soft": string;
+        "hard": string;
+        "mutedNotes": string;
     };
     "_instanceMute": {
         "instanceMuteDescription": string;
diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index 80e4466a74..c459498729 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -1126,8 +1126,6 @@ edited: "編集済み"
 notificationRecieveConfig: "通知の受信設定"
 mutualFollow: "相互フォロー"
 fileAttachedOnly: "ファイル付きのみ"
-showRepliesToOthersInTimeline: "TLに他の人への返信を含める"
-hideRepliesToOthersInTimeline: "TLに他の人への返信を含めない"
 
 _announcement:
   forExistingUsers: "既存ユーザーのみ"
@@ -1638,6 +1636,11 @@ _wordMute:
   muteWords: "ミュートするワード"
   muteWordsDescription: "スペースで区切るとAND指定になり、改行で区切るとOR指定になります。"
   muteWordsDescription2: "キーワードをスラッシュで囲むと正規表現になります。"
+  softDescription: "指定した条件のノートをタイムラインから隠します。"
+  hardDescription: "指定した条件のノートをタイムラインに追加しないようにします。追加されなかったノートは、条件を変更しても除外されたままになります。"
+  soft: "ソフト"
+  hard: "ハード"
+  mutedNotes: "ミュートされたノート"
 
 _instanceMute:
   instanceMuteDescription: "ミュートしたサーバーのユーザーへの返信を含めて、設定したサーバーの全てのノートとRenoteをミュートします。"
diff --git a/packages/backend/jest.config.cjs b/packages/backend/jest.config.cjs
index 97d777c862..6b1afec734 100644
--- a/packages/backend/jest.config.cjs
+++ b/packages/backend/jest.config.cjs
@@ -216,6 +216,4 @@ module.exports = {
 	maxWorkers: 1, // Make it use worker (that can be killed and restarted)
 	logHeapUsage: true, // To debug when out-of-memory happens on CI
 	workerIdleMemoryLimit: '1GiB', // Limit the worker to 1GB (GitHub Workflows dies at 2GB)
-
-	maxConcurrency: 32,
 };
diff --git a/packages/backend/migration/1696222183852-withReplies.js b/packages/backend/migration/1696222183852-withReplies.js
deleted file mode 100644
index 9f65d5f6a1..0000000000
--- a/packages/backend/migration/1696222183852-withReplies.js
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
- * SPDX-License-Identifier: AGPL-3.0-only
- */
-
-export class WithReplies1696222183852 {
-    name = 'WithReplies1696222183852'
-
-    async up(queryRunner) {
-        await queryRunner.query(`ALTER TABLE "following" ADD "withReplies" boolean NOT NULL DEFAULT false`);
-        await queryRunner.query(`ALTER TABLE "user_list_joining" ADD "withReplies" boolean NOT NULL DEFAULT false`);
-        await queryRunner.query(`CREATE INDEX "IDX_d74d8ab5efa7e3bb82825c0fa2" ON "following" ("followeeId", "followerHost") `);
-    }
-
-    async down(queryRunner) {
-        await queryRunner.query(`DROP INDEX "public"."IDX_d74d8ab5efa7e3bb82825c0fa2"`);
-        await queryRunner.query(`ALTER TABLE "user_list_joining" DROP COLUMN "withReplies"`);
-        await queryRunner.query(`ALTER TABLE "following" DROP COLUMN "withReplies"`);
-    }
-}
diff --git a/packages/backend/migration/1696323464251-user-list-membership.js b/packages/backend/migration/1696323464251-user-list-membership.js
deleted file mode 100644
index 7534040c4c..0000000000
--- a/packages/backend/migration/1696323464251-user-list-membership.js
+++ /dev/null
@@ -1,11 +0,0 @@
-export class UserListMembership1696323464251 {
-    name = 'UserListMembership1696323464251'
-
-    async up(queryRunner) {
-        await queryRunner.query(`ALTER TABLE "user_list_joining" RENAME TO "user_list_membership"`);
-    }
-
-    async down(queryRunner) {
-			await queryRunner.query(`ALTER TABLE "user_list_membership" RENAME TO "user_list_joining"`);
-    }
-}
diff --git a/packages/backend/migration/1696331570827-hibernation.js b/packages/backend/migration/1696331570827-hibernation.js
deleted file mode 100644
index 119d35913f..0000000000
--- a/packages/backend/migration/1696331570827-hibernation.js
+++ /dev/null
@@ -1,17 +0,0 @@
-export class Hibernation1696331570827 {
-    name = 'Hibernation1696331570827'
-
-    async up(queryRunner) {
-				await queryRunner.query(`DROP INDEX "public"."IDX_d74d8ab5efa7e3bb82825c0fa2"`);
-        await queryRunner.query(`ALTER TABLE "user" ADD "isHibernated" boolean NOT NULL DEFAULT false`);
-        await queryRunner.query(`ALTER TABLE "following" ADD "isFollowerHibernated" boolean NOT NULL DEFAULT false`);
-        await queryRunner.query(`CREATE INDEX "IDX_ce62b50d882d4e9dee10ad0d2f" ON "following" ("followeeId", "followerHost", "isFollowerHibernated") `);
-    }
-
-    async down(queryRunner) {
-        await queryRunner.query(`DROP INDEX "public"."IDX_ce62b50d882d4e9dee10ad0d2f"`);
-        await queryRunner.query(`ALTER TABLE "following" DROP COLUMN "isFollowerHibernated"`);
-        await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "isHibernated"`);
-        await queryRunner.query(`CREATE INDEX "IDX_d74d8ab5efa7e3bb82825c0fa2" ON "following" ("followeeId", "followerHost") `);
-    }
-}
diff --git a/packages/backend/src/GlobalModule.ts b/packages/backend/src/GlobalModule.ts
index 3e9d19f825..9f1ee9fcaa 100644
--- a/packages/backend/src/GlobalModule.ts
+++ b/packages/backend/src/GlobalModule.ts
@@ -70,19 +70,11 @@ const $redisForSub: Provider = {
 	inject: [DI.config],
 };
 
-const $redisForTimelines: Provider = {
-	provide: DI.redisForTimelines,
-	useFactory: (config: Config) => {
-		return new Redis.Redis(config.redisForTimelines);
-	},
-	inject: [DI.config],
-};
-
 @Global()
 @Module({
 	imports: [RepositoryModule],
-	providers: [$config, $db, $meilisearch, $redis, $redisForPub, $redisForSub, $redisForTimelines],
-	exports: [$config, $db, $meilisearch, $redis, $redisForPub, $redisForSub, $redisForTimelines, RepositoryModule],
+	providers: [$config, $db, $meilisearch, $redis, $redisForPub, $redisForSub],
+	exports: [$config, $db, $meilisearch, $redis, $redisForPub, $redisForSub, RepositoryModule],
 })
 export class GlobalModule implements OnApplicationShutdown {
 	constructor(
@@ -90,7 +82,6 @@ export class GlobalModule implements OnApplicationShutdown {
 		@Inject(DI.redis) private redisClient: Redis.Redis,
 		@Inject(DI.redisForPub) private redisForPub: Redis.Redis,
 		@Inject(DI.redisForSub) private redisForSub: Redis.Redis,
-		@Inject(DI.redisForTimelines) private redisForTimelines: Redis.Redis,
 	) {}
 
 	public async dispose(): Promise<void> {
@@ -107,7 +98,6 @@ export class GlobalModule implements OnApplicationShutdown {
 			this.redisClient.disconnect(),
 			this.redisForPub.disconnect(),
 			this.redisForSub.disconnect(),
-			this.redisForTimelines.disconnect(),
 		]);
 	}
 
diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts
index ef59a80950..f89879d535 100644
--- a/packages/backend/src/config.ts
+++ b/packages/backend/src/config.ts
@@ -47,7 +47,6 @@ type Source = {
 	redis: RedisOptionsSource;
 	redisForPubsub?: RedisOptionsSource;
 	redisForJobQueue?: RedisOptionsSource;
-	redisForTimelines?: RedisOptionsSource;
 	meilisearch?: {
 		host: string;
 		port: string;
@@ -162,7 +161,6 @@ export type Config = {
 	redis: RedisOptions & RedisOptionsSource;
 	redisForPubsub: RedisOptions & RedisOptionsSource;
 	redisForJobQueue: RedisOptions & RedisOptionsSource;
-	redisForTimelines: RedisOptions & RedisOptionsSource;
 	perChannelMaxNoteCacheCount: number;
 	perUserNotificationsMaxCount: number;
 	deactivateAntennaThreshold: number;
@@ -229,7 +227,6 @@ export function loadConfig(): Config {
 		redis,
 		redisForPubsub: config.redisForPubsub ? convertRedisOptions(config.redisForPubsub, host) : redis,
 		redisForJobQueue: config.redisForJobQueue ? convertRedisOptions(config.redisForJobQueue, host) : redis,
-		redisForTimelines: config.redisForTimelines ? convertRedisOptions(config.redisForTimelines, host) : redis,
 		id: config.id,
 		proxy: config.proxy,
 		proxySmtp: config.proxySmtp,
diff --git a/packages/backend/src/core/AccountMoveService.ts b/packages/backend/src/core/AccountMoveService.ts
index ba3413007d..ec1d013922 100644
--- a/packages/backend/src/core/AccountMoveService.ts
+++ b/packages/backend/src/core/AccountMoveService.ts
@@ -9,7 +9,7 @@ import { IsNull, In, MoreThan, Not } from 'typeorm';
 import { bindThis } from '@/decorators.js';
 import { DI } from '@/di-symbols.js';
 import type { MiLocalUser, MiRemoteUser, MiUser } from '@/models/User.js';
-import type { BlockingsRepository, FollowingsRepository, InstancesRepository, MutingsRepository, UserListMembershipsRepository, UsersRepository } from '@/models/_.js';
+import type { BlockingsRepository, FollowingsRepository, InstancesRepository, MutingsRepository, UserListJoiningsRepository, UsersRepository } from '@/models/_.js';
 import type { RelationshipJobData, ThinUser } from '@/queue/types.js';
 
 import { IdService } from '@/core/IdService.js';
@@ -42,8 +42,8 @@ export class AccountMoveService {
 		@Inject(DI.mutingsRepository)
 		private mutingsRepository: MutingsRepository,
 
-		@Inject(DI.userListMembershipsRepository)
-		private userListMembershipsRepository: UserListMembershipsRepository,
+		@Inject(DI.userListJoiningsRepository)
+		private userListJoiningsRepository: UserListJoiningsRepository,
 
 		@Inject(DI.instancesRepository)
 		private instancesRepository: InstancesRepository,
@@ -215,40 +215,40 @@ export class AccountMoveService {
 	@bindThis
 	public async updateLists(src: ThinUser, dst: MiUser): Promise<void> {
 		// Return if there is no list to be updated.
-		const oldMemberships = await this.userListMembershipsRepository.find({
+		const oldJoinings = await this.userListJoiningsRepository.find({
 			where: {
 				userId: src.id,
 			},
 		});
-		if (oldMemberships.length === 0) return;
+		if (oldJoinings.length === 0) return;
 
-		const existingUserListIds = await this.userListMembershipsRepository.find({
+		const existingUserListIds = await this.userListJoiningsRepository.find({
 			where: {
 				userId: dst.id,
 			},
-		}).then(memberships => memberships.map(membership => membership.userListId));
+		}).then(joinings => joinings.map(joining => joining.userListId));
 
-		const newMemberships: Map<string, { createdAt: Date; userId: string; userListId: string; }> = new Map();
+		const newJoinings: Map<string, { createdAt: Date; userId: string; userListId: string; }> = new Map();
 
 		// 重複しないようにIDを生成
 		const genId = (): string => {
 			let id: string;
 			do {
 				id = this.idService.genId();
-			} while (newMemberships.has(id));
+			} while (newJoinings.has(id));
 			return id;
 		};
-		for (const membership of oldMemberships) {
-			if (existingUserListIds.includes(membership.userListId)) continue; // skip if dst exists in this user's list
-			newMemberships.set(genId(), {
+		for (const joining of oldJoinings) {
+			if (existingUserListIds.includes(joining.userListId)) continue; // skip if dst exists in this user's list
+			newJoinings.set(genId(), {
 				createdAt: new Date(),
 				userId: dst.id,
-				userListId: membership.userListId,
+				userListId: joining.userListId,
 			});
 		}
 
-		const arrayToInsert = Array.from(newMemberships.entries()).map(entry => ({ ...entry[1], id: entry[0] }));
-		await this.userListMembershipsRepository.insert(arrayToInsert);
+		const arrayToInsert = Array.from(newJoinings.entries()).map(entry => ({ ...entry[1], id: entry[0] }));
+		await this.userListJoiningsRepository.insert(arrayToInsert);
 
 		// Have the proxy account follow the new account in the same way as UserListService.push
 		if (this.userEntityService.isRemoteUser(dst)) {
diff --git a/packages/backend/src/core/AntennaService.ts b/packages/backend/src/core/AntennaService.ts
index 95712b35b7..d9f27b8c63 100644
--- a/packages/backend/src/core/AntennaService.ts
+++ b/packages/backend/src/core/AntennaService.ts
@@ -12,7 +12,7 @@ import { GlobalEventService } from '@/core/GlobalEventService.js';
 import * as Acct from '@/misc/acct.js';
 import type { Packed } from '@/misc/json-schema.js';
 import { DI } from '@/di-symbols.js';
-import type { AntennasRepository, UserListMembershipsRepository } from '@/models/_.js';
+import type { AntennasRepository, UserListJoiningsRepository } from '@/models/_.js';
 import { UtilityService } from '@/core/UtilityService.js';
 import { bindThis } from '@/decorators.js';
 import type { GlobalEvents } from '@/core/GlobalEventService.js';
@@ -24,8 +24,8 @@ export class AntennaService implements OnApplicationShutdown {
 	private antennas: MiAntenna[];
 
 	constructor(
-		@Inject(DI.redisForTimelines)
-		private redisForTimelines: Redis.Redis,
+		@Inject(DI.redis)
+		private redisClient: Redis.Redis,
 
 		@Inject(DI.redisForSub)
 		private redisForSub: Redis.Redis,
@@ -33,8 +33,8 @@ export class AntennaService implements OnApplicationShutdown {
 		@Inject(DI.antennasRepository)
 		private antennasRepository: AntennasRepository,
 
-		@Inject(DI.userListMembershipsRepository)
-		private userListMembershipsRepository: UserListMembershipsRepository,
+		@Inject(DI.userListJoiningsRepository)
+		private userListJoiningsRepository: UserListJoiningsRepository,
 
 		private utilityService: UtilityService,
 		private globalEventService: GlobalEventService,
@@ -81,7 +81,7 @@ export class AntennaService implements OnApplicationShutdown {
 		const antennasWithMatchResult = await Promise.all(antennas.map(antenna => this.checkHitAntenna(antenna, note, noteUser).then(hit => [antenna, hit] as const)));
 		const matchedAntennas = antennasWithMatchResult.filter(([, hit]) => hit).map(([antenna]) => antenna);
 
-		const redisPipeline = this.redisForTimelines.pipeline();
+		const redisPipeline = this.redisClient.pipeline();
 
 		for (const antenna of matchedAntennas) {
 			redisPipeline.xadd(
@@ -108,7 +108,7 @@ export class AntennaService implements OnApplicationShutdown {
 		if (antenna.src === 'home') {
 			// TODO
 		} else if (antenna.src === 'list') {
-			const listUsers = (await this.userListMembershipsRepository.findBy({
+			const listUsers = (await this.userListJoiningsRepository.findBy({
 				userListId: antenna.userListId!,
 			})).map(x => x.userId);
 
diff --git a/packages/backend/src/core/CacheService.ts b/packages/backend/src/core/CacheService.ts
index 22c510cc37..561979c4bf 100644
--- a/packages/backend/src/core/CacheService.ts
+++ b/packages/backend/src/core/CacheService.ts
@@ -5,7 +5,7 @@
 
 import { Inject, Injectable } from '@nestjs/common';
 import * as Redis from 'ioredis';
-import type { BlockingsRepository, ChannelFollowingsRepository, FollowingsRepository, MutingsRepository, RenoteMutingsRepository, MiUserProfile, UserProfilesRepository, UsersRepository, MiFollowing } from '@/models/_.js';
+import type { BlockingsRepository, ChannelFollowingsRepository, FollowingsRepository, MutingsRepository, RenoteMutingsRepository, MiUserProfile, UserProfilesRepository, UsersRepository } from '@/models/_.js';
 import { MemoryKVCache, RedisKVCache } from '@/misc/cache.js';
 import type { MiLocalUser, MiUser } from '@/models/User.js';
 import { DI } from '@/di-symbols.js';
@@ -25,7 +25,7 @@ export class CacheService implements OnApplicationShutdown {
 	public userBlockingCache: RedisKVCache<Set<string>>;
 	public userBlockedCache: RedisKVCache<Set<string>>; // NOTE: 「被」Blockキャッシュ
 	public renoteMutingsCache: RedisKVCache<Set<string>>;
-	public userFollowingsCache: RedisKVCache<Record<string, Pick<MiFollowing, 'withReplies'> | undefined>>;
+	public userFollowingsCache: RedisKVCache<Set<string>>;
 	public userFollowingChannelsCache: RedisKVCache<Set<string>>;
 
 	constructor(
@@ -136,18 +136,12 @@ export class CacheService implements OnApplicationShutdown {
 			fromRedisConverter: (value) => new Set(JSON.parse(value)),
 		});
 
-		this.userFollowingsCache = new RedisKVCache<Record<string, Pick<MiFollowing, 'withReplies'> | undefined>>(this.redisClient, 'userFollowings', {
+		this.userFollowingsCache = new RedisKVCache<Set<string>>(this.redisClient, 'userFollowings', {
 			lifetime: 1000 * 60 * 30, // 30m
 			memoryCacheLifetime: 1000 * 60, // 1m
-			fetcher: (key) => this.followingsRepository.find({ where: { followerId: key }, select: ['followeeId', 'withReplies'] }).then(xs => {
-				const obj: Record<string, Pick<MiFollowing, 'withReplies'> | undefined> = {};
-				for (const x of xs) {
-					obj[x.followeeId] = { withReplies: x.withReplies };
-				}
-				return obj;
-			}),
-			toRedisConverter: (value) => JSON.stringify(value),
-			fromRedisConverter: (value) => JSON.parse(value),
+			fetcher: (key) => this.followingsRepository.find({ where: { followerId: key }, select: ['followeeId'] }).then(xs => new Set(xs.map(x => x.followeeId))),
+			toRedisConverter: (value) => JSON.stringify(Array.from(value)),
+			fromRedisConverter: (value) => new Set(JSON.parse(value)),
 		});
 
 		this.userFollowingChannelsCache = new RedisKVCache<Set<string>>(this.redisClient, 'userFollowingChannels', {
@@ -194,7 +188,6 @@ export class CacheService implements OnApplicationShutdown {
 					if (follower) follower.followingCount++;
 					const followee = this.userByIdCache.get(body.followeeId);
 					if (followee) followee.followersCount++;
-					this.userFollowingsCache.delete(body.followerId);
 					break;
 				}
 				default:
diff --git a/packages/backend/src/core/CoreModule.ts b/packages/backend/src/core/CoreModule.ts
index 955b9fdcf6..7d6b76e9c2 100644
--- a/packages/backend/src/core/CoreModule.ts
+++ b/packages/backend/src/core/CoreModule.ts
@@ -47,7 +47,6 @@ import { SignupService } from './SignupService.js';
 import { WebAuthnService } from './WebAuthnService.js';
 import { UserBlockingService } from './UserBlockingService.js';
 import { CacheService } from './CacheService.js';
-import { UserService } from './UserService.js';
 import { UserFollowingService } from './UserFollowingService.js';
 import { UserKeypairService } from './UserKeypairService.js';
 import { UserListService } from './UserListService.js';
@@ -176,7 +175,6 @@ const $SignupService: Provider = { provide: 'SignupService', useExisting: Signup
 const $WebAuthnService: Provider = { provide: 'WebAuthnService', useExisting: WebAuthnService };
 const $UserBlockingService: Provider = { provide: 'UserBlockingService', useExisting: UserBlockingService };
 const $CacheService: Provider = { provide: 'CacheService', useExisting: CacheService };
-const $UserService: Provider = { provide: 'UserService', useExisting: UserService };
 const $UserFollowingService: Provider = { provide: 'UserFollowingService', useExisting: UserFollowingService };
 const $UserKeypairService: Provider = { provide: 'UserKeypairService', useExisting: UserKeypairService };
 const $UserListService: Provider = { provide: 'UserListService', useExisting: UserListService };
@@ -308,7 +306,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		WebAuthnService,
 		UserBlockingService,
 		CacheService,
-		UserService,
 		UserFollowingService,
 		UserKeypairService,
 		UserListService,
@@ -433,7 +430,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		$WebAuthnService,
 		$UserBlockingService,
 		$CacheService,
-		$UserService,
 		$UserFollowingService,
 		$UserKeypairService,
 		$UserListService,
@@ -559,7 +555,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		WebAuthnService,
 		UserBlockingService,
 		CacheService,
-		UserService,
 		UserFollowingService,
 		UserKeypairService,
 		UserListService,
@@ -683,7 +678,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		$WebAuthnService,
 		$UserBlockingService,
 		$CacheService,
-		$UserService,
 		$UserFollowingService,
 		$UserKeypairService,
 		$UserListService,
diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts
index 8fb34fd637..f20727ce41 100644
--- a/packages/backend/src/core/NoteCreateService.ts
+++ b/packages/backend/src/core/NoteCreateService.ts
@@ -5,7 +5,7 @@
 
 import { setImmediate } from 'node:timers/promises';
 import * as mfm from 'mfm-js';
-import { In, DataSource, IsNull, LessThan } from 'typeorm';
+import { In, DataSource } from 'typeorm';
 import * as Redis from 'ioredis';
 import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
 import RE2 from 're2';
@@ -14,7 +14,7 @@ import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mf
 import { extractHashtags } from '@/misc/extract-hashtags.js';
 import type { IMentionedRemoteUsers } from '@/models/Note.js';
 import { MiNote } from '@/models/Note.js';
-import type { ChannelFollowingsRepository, ChannelsRepository, FollowingsRepository, InstancesRepository, MiFollowing, MutingsRepository, NotesRepository, NoteThreadMutingsRepository, UserListMembershipsRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js';
+import type { ChannelsRepository, FollowingsRepository, InstancesRepository, MutedNotesRepository, MutingsRepository, NotesRepository, NoteThreadMutingsRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js';
 import type { MiDriveFile } from '@/models/DriveFile.js';
 import type { MiApp } from '@/models/App.js';
 import { concat } from '@/misc/prelude/array.js';
@@ -54,6 +54,8 @@ import { RoleService } from '@/core/RoleService.js';
 import { MetaService } from '@/core/MetaService.js';
 import { SearchService } from '@/core/SearchService.js';
 
+const mutedWordsCache = new MemorySingleCache<{ userId: MiUserProfile['userId']; mutedWords: MiUserProfile['mutedWords']; }[]>(1000 * 60 * 5);
+
 type NotificationType = 'reply' | 'renote' | 'quote' | 'mention';
 
 class NotificationManager {
@@ -155,8 +157,8 @@ export class NoteCreateService implements OnApplicationShutdown {
 		@Inject(DI.db)
 		private db: DataSource,
 
-		@Inject(DI.redisForTimelines)
-		private redisForTimelines: Redis.Redis,
+		@Inject(DI.redis)
+		private redisClient: Redis.Redis,
 
 		@Inject(DI.usersRepository)
 		private usersRepository: UsersRepository,
@@ -173,8 +175,8 @@ export class NoteCreateService implements OnApplicationShutdown {
 		@Inject(DI.userProfilesRepository)
 		private userProfilesRepository: UserProfilesRepository,
 
-		@Inject(DI.userListMembershipsRepository)
-		private userListMembershipsRepository: UserListMembershipsRepository,
+		@Inject(DI.mutedNotesRepository)
+		private mutedNotesRepository: MutedNotesRepository,
 
 		@Inject(DI.channelsRepository)
 		private channelsRepository: ChannelsRepository,
@@ -185,9 +187,6 @@ export class NoteCreateService implements OnApplicationShutdown {
 		@Inject(DI.followingsRepository)
 		private followingsRepository: FollowingsRepository,
 
-		@Inject(DI.channelFollowingsRepository)
-		private channelFollowingsRepository: ChannelFollowingsRepository,
-
 		private userEntityService: UserEntityService,
 		private noteEntityService: NoteEntityService,
 		private idService: IdService,
@@ -335,7 +334,7 @@ export class NoteCreateService implements OnApplicationShutdown {
 		const note = await this.insertNote(user, data, tags, emojis, mentionedUsers);
 
 		if (data.channel) {
-			this.redisForTimelines.xadd(
+			this.redisClient.xadd(
 				`channelTimeline:${data.channel.id}`,
 				'MAXLEN', '~', this.config.perChannelMaxNoteCacheCount.toString(),
 				'*',
@@ -481,13 +480,26 @@ export class NoteCreateService implements OnApplicationShutdown {
 		// Increment notes count (user)
 		this.incNotesCountOfUser(user);
 
-		if (data.visibility === 'public' || data.visibility === 'home') {
-			this.pushToTl(note, user);
-		} else if (data.visibility === 'followers') {
-			this.pushToTl(note, user);
-		} else if (data.visibility === 'specified') {
-			// TODO
-		}
+		// Word mute
+		mutedWordsCache.fetch(() => this.userProfilesRepository.find({
+			where: {
+				enableWordMute: true,
+			},
+			select: ['userId', 'mutedWords'],
+		})).then(us => {
+			for (const u of us) {
+				checkWordMute(note, { id: u.userId }, u.mutedWords).then(shouldMute => {
+					if (shouldMute) {
+						this.mutedNotesRepository.insert({
+							id: this.idService.genId(),
+							userId: u.userId,
+							noteId: note.id,
+							reason: 'word',
+						});
+					}
+				});
+			}
+		});
 
 		this.antennaService.addNoteToAntennas(note, user);
 
@@ -496,13 +508,11 @@ export class NoteCreateService implements OnApplicationShutdown {
 		}
 
 		if (data.reply == null) {
-			// TODO: キャッシュ
 			this.followingsRepository.findBy({
 				followeeId: user.id,
 				notify: 'normal',
 			}).then(followings => {
 				for (const following of followings) {
-					// TODO: ワードミュート考慮
 					this.notificationService.createNotification(following.followerId, 'note', {
 						noteId: note.id,
 					}, user.id);
@@ -801,205 +811,6 @@ export class NoteCreateService implements OnApplicationShutdown {
 		return mentionedUsers;
 	}
 
-	@bindThis
-	private async pushToTl(note: MiNote, user: { id: MiUser['id']; host: MiUser['host']; }) {
-		const redisPipeline = this.redisForTimelines.pipeline();
-
-		if (note.channelId) {
-			const channelFollowings = await this.channelFollowingsRepository.find({
-				where: {
-					followeeId: note.channelId,
-				},
-				select: ['followerId'],
-			});
-
-			for (const channelFollowing of channelFollowings) {
-				redisPipeline.xadd(
-					`homeTimeline:${channelFollowing.followerId}`,
-					'MAXLEN', '~', '200',
-					'*',
-					'note', note.id);
-
-				if (note.fileIds.length > 0) {
-					redisPipeline.xadd(
-						`homeTimelineWithFiles:${channelFollowing.followerId}`,
-						'MAXLEN', '~', '100',
-						'*',
-						'note', note.id);
-				}
-			}
-		} else {
-			// TODO: キャッシュ?
-			const followings = await this.followingsRepository.find({
-				where: {
-					followeeId: user.id,
-					followerHost: IsNull(),
-					isFollowerHibernated: false,
-				},
-				select: ['followerId', 'withReplies'],
-			});
-
-			const userListMemberships = await this.userListMembershipsRepository.find({
-				where: {
-					userId: user.id,
-				},
-				select: ['userListId', 'withReplies'],
-			});
-
-			// TODO: あまりにも数が多いと redisPipeline.exec に失敗する(理由は不明)ため、3万件程度を目安に分割して実行するようにする
-			for (const following of followings) {
-				// 自分自身以外への返信
-				if (note.replyId && note.replyUserId !== note.userId) {
-					if (!following.withReplies) continue;
-				}
-
-				redisPipeline.xadd(
-					`homeTimeline:${following.followerId}`,
-					'MAXLEN', '~', '200',
-					'*',
-					'note', note.id);
-
-				if (note.fileIds.length > 0) {
-					redisPipeline.xadd(
-						`homeTimelineWithFiles:${following.followerId}`,
-						'MAXLEN', '~', '100',
-						'*',
-						'note', note.id);
-				}
-			}
-
-			// TODO
-			//if (note.visibility === 'followers') {
-			//	// TODO: 重そうだから何とかしたい Set 使う?
-			//	userLists = userLists.filter(x => followings.some(f => f.followerId === x.userListUserId));
-			//}
-
-			for (const userListMembership of userListMemberships) {
-				// 自分自身以外への返信
-				if (note.replyId && note.replyUserId !== note.userId) {
-					if (!userListMembership.withReplies) continue;
-				}
-
-				redisPipeline.xadd(
-					`userListTimeline:${userListMembership.userListId}`,
-					'MAXLEN', '~', '200',
-					'*',
-					'note', note.id);
-
-				if (note.fileIds.length > 0) {
-					redisPipeline.xadd(
-						`userListTimelineWithFiles:${userListMembership.userListId}`,
-						'MAXLEN', '~', '100',
-						'*',
-						'note', note.id);
-				}
-			}
-
-			{ // 自分自身のHTL
-				redisPipeline.xadd(
-					`homeTimeline:${user.id}`,
-					'MAXLEN', '~', '200',
-					'*',
-					'note', note.id);
-
-				if (note.fileIds.length > 0) {
-					redisPipeline.xadd(
-						`homeTimelineWithFiles:${user.id}`,
-						'MAXLEN', '~', '100',
-						'*',
-						'note', note.id);
-				}
-			}
-
-			if (note.visibility === 'public' || note.visibility === 'home') {
-				// 自分自身以外への返信
-				if (note.replyId && note.replyUserId !== note.userId) {
-					redisPipeline.xadd(
-						`userTimelineWithReplies:${user.id}`,
-						'MAXLEN', '~', '1000',
-						'*',
-						'note', note.id);
-				} else {
-					redisPipeline.xadd(
-						`userTimeline:${user.id}`,
-						'MAXLEN', '~', '1000',
-						'*',
-						'note', note.id);
-
-					if (note.fileIds.length > 0) {
-						redisPipeline.xadd(
-							`userTimelineWithFiles:${user.id}`,
-							'MAXLEN', '~', '500',
-							'*',
-							'note', note.id);
-					}
-
-					if (note.visibility === 'public' && note.userHost == null) {
-						redisPipeline.xadd(
-							'localTimeline',
-							'MAXLEN', '~', '1000',
-							'*',
-							'note', note.id);
-
-						if (note.fileIds.length > 0) {
-							redisPipeline.xadd(
-								'localTimelineWithFiles',
-								'MAXLEN', '~', '500',
-								'*',
-								'note', note.id);
-						}
-					}
-				}
-			}
-
-			if (Math.random() < 0.1) {
-				process.nextTick(() => {
-					this.checkHibernation(followings);
-				});
-			}
-		}
-
-		redisPipeline.exec();
-	}
-
-	@bindThis
-	public async checkHibernation(followings: MiFollowing[]) {
-		if (followings.length === 0) return;
-
-		const shuffle = (array: MiFollowing[]) => {
-			for (let i = array.length - 1; i > 0; i--) {
-				const j = Math.floor(Math.random() * (i + 1));
-				[array[i], array[j]] = [array[j], array[i]];
-			}
-			return array;
-		};
-
-		// ランダムに最大1000件サンプリング
-		const samples = shuffle(followings).slice(0, Math.min(followings.length, 1000));
-
-		const hibernatedUsers = await this.usersRepository.find({
-			where: {
-				id: In(samples.map(x => x.followerId)),
-				lastActiveDate: LessThan(new Date(Date.now() - (1000 * 60 * 60 * 24 * 50))),
-			},
-			select: ['id'],
-		});
-
-		if (hibernatedUsers.length > 0) {
-			this.usersRepository.update({
-				id: In(hibernatedUsers.map(x => x.id)),
-			}, {
-				isHibernated: true,
-			});
-
-			this.followingsRepository.update({
-				followerId: In(hibernatedUsers.map(x => x.id)),
-			}, {
-				isFollowerHibernated: true,
-			});
-		}
-	}
-
 	@bindThis
 	public dispose(): void {
 		this.#shutdownController.abort();
diff --git a/packages/backend/src/core/NoteEditService.ts b/packages/backend/src/core/NoteEditService.ts
index f331c8a9a8..d38962fe34 100644
--- a/packages/backend/src/core/NoteEditService.ts
+++ b/packages/backend/src/core/NoteEditService.ts
@@ -14,7 +14,7 @@ import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mf
 import { extractHashtags } from '@/misc/extract-hashtags.js';
 import type { IMentionedRemoteUsers } from '@/models/Note.js';
 import { MiNote } from '@/models/Note.js';
-import type { NoteEditRepository, ChannelsRepository, InstancesRepository, MutingsRepository, NotesRepository, NoteThreadMutingsRepository, UserProfilesRepository, UsersRepository, UserListMembershipsRepository, ChannelFollowingsRepository, MiFollowing } from '@/models/_.js';
+import type { NoteEditRepository, ChannelsRepository, InstancesRepository, MutedNotesRepository, MutingsRepository, NotesRepository, NoteThreadMutingsRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js';
 import type { MiDriveFile } from '@/models/DriveFile.js';
 import type { MiApp } from '@/models/App.js';
 import { concat } from '@/misc/prelude/array.js';
@@ -49,6 +49,8 @@ import { RoleService } from '@/core/RoleService.js';
 import { MetaService } from '@/core/MetaService.js';
 import { SearchService } from '@/core/SearchService.js';
 
+const mutedWordsCache = new MemorySingleCache<{ userId: MiUserProfile['userId']; mutedWords: MiUserProfile['mutedWords']; }[]>(1000 * 60 * 5);
+
 type NotificationType = 'reply' | 'renote' | 'quote' | 'mention';
 
 class NotificationManager {
@@ -149,8 +151,8 @@ export class NoteEditService implements OnApplicationShutdown {
 		@Inject(DI.config)
 		private config: Config,
 
-		@Inject(DI.redisForTimelines)
-		private redisForTimelines: Redis.Redis,
+		@Inject(DI.redis)
+		private redisClient: Redis.Redis,
 
 		@Inject(DI.usersRepository)
 		private usersRepository: UsersRepository,
@@ -170,11 +172,8 @@ export class NoteEditService implements OnApplicationShutdown {
 		@Inject(DI.channelsRepository)
 		private channelsRepository: ChannelsRepository,
 
-		@Inject(DI.userListMembershipsRepository)
-		private userListMembershipsRepository: UserListMembershipsRepository,
-
-		@Inject(DI.channelFollowingsRepository)
-		private channelFollowingsRepository: ChannelFollowingsRepository,
+		@Inject(DI.mutedNotesRepository)
+		private mutedNotesRepository: MutedNotesRepository,
 
 		@Inject(DI.noteThreadMutingsRepository)
 		private noteThreadMutingsRepository: NoteThreadMutingsRepository,
@@ -423,7 +422,7 @@ export class NoteEditService implements OnApplicationShutdown {
 		await this.notesRepository.update(oldnote.id, note);
 
 		if (data.channel) {
-			this.redisForTimelines.xadd(
+			this.redisClient.xadd(
 				`channelTimeline:${data.channel.id}`,
 				'MAXLEN', '~', this.config.perChannelMaxNoteCacheCount.toString(),
 				'*',
@@ -461,6 +460,27 @@ export class NoteEditService implements OnApplicationShutdown {
 			this.hashtagService.updateHashtags(user, tags);
 		}
 
+		// Word mute
+		mutedWordsCache.fetch(() => this.userProfilesRepository.find({
+			where: {
+				enableWordMute: true,
+			},
+			select: ['userId', 'mutedWords'],
+		})).then(us => {
+			for (const u of us) {
+				checkWordMute(note, { id: u.userId }, u.mutedWords).then(shouldMute => {
+					if (shouldMute) {
+						this.mutedNotesRepository.insert({
+							id: this.idService.genId(),
+							userId: u.userId,
+							noteId: note.id,
+							reason: 'word',
+						});
+					}
+				});
+			}
+		});
+
 		if (data.poll && data.poll.expiresAt) {
 			const delay = data.poll.expiresAt.getTime() - Date.now();
 			this.queueService.endedPollNotificationQueue.add(note.id, {
@@ -471,14 +491,6 @@ export class NoteEditService implements OnApplicationShutdown {
 			});
 		}
 
-		if (data.visibility === 'public' || data.visibility === 'home') {
-			this.pushToTl(note, user);
-		} else if (data.visibility === 'followers') {
-			this.pushToTl(note, user);
-		} else if (data.visibility === 'specified') {
-			// TODO
-		}
-
 		if (!silent) {
 			if (this.userEntityService.isLocalUser(user)) this.activeUsersChart.write(user);
 
@@ -639,163 +651,6 @@ export class NoteEditService implements OnApplicationShutdown {
 		this.index(note);
 	}
 
-	@bindThis
-	private async pushToTl(note: MiNote, user: { id: MiUser['id']; host: MiUser['host']; }) {
-		const redisPipeline = this.redisForTimelines.pipeline();
-
-		if (note.channelId) {
-			const channelFollowings = await this.channelFollowingsRepository.find({
-				where: {
-					followeeId: note.channelId,
-				},
-				select: ['followerId'],
-			});
-
-			for (const channelFollowing of channelFollowings) {
-				redisPipeline.xadd(
-					`homeTimeline:${channelFollowing.followerId}`,
-					'MAXLEN', '~', '200',
-					'*',
-					'note', note.id);
-
-				if (note.fileIds.length > 0) {
-					redisPipeline.xadd(
-						`homeTimelineWithFiles:${channelFollowing.followerId}`,
-						'MAXLEN', '~', '100',
-						'*',
-						'note', note.id);
-				}
-			}
-		} else {
-			// TODO: キャッシュ?
-
-			const userListMemberships = await this.userListMembershipsRepository.find({
-				where: {
-					userId: user.id,
-				},
-				select: ['userListId', 'withReplies'],
-			});
-
-			// TODO
-			//if (note.visibility === 'followers') {
-			//	// TODO: 重そうだから何とかしたい Set 使う?
-			//	userLists = userLists.filter(x => followings.some(f => f.followerId === x.userListUserId));
-			//}
-
-			for (const userListMembership of userListMemberships) {
-				// 自分自身以外への返信
-				if (note.replyId && note.replyUserId !== note.userId) {
-					if (!userListMembership.withReplies) continue;
-				}
-
-				redisPipeline.xadd(
-					`userListTimeline:${userListMembership.userListId}`,
-					'MAXLEN', '~', '200',
-					'*',
-					'note', note.id);
-
-				if (note.fileIds.length > 0) {
-					redisPipeline.xadd(
-						`userListTimelineWithFiles:${userListMembership.userListId}`,
-						'MAXLEN', '~', '100',
-						'*',
-						'note', note.id);
-				}
-			}
-
-			{ // 自分自身のHTL
-				redisPipeline.xadd(
-					`homeTimeline:${user.id}`,
-					'MAXLEN', '~', '200',
-					'*',
-					'note', note.id);
-
-				if (note.fileIds.length > 0) {
-					redisPipeline.xadd(
-						`homeTimelineWithFiles:${user.id}`,
-						'MAXLEN', '~', '100',
-						'*',
-						'note', note.id);
-				}
-			}
-
-			if (note.visibility === 'public' || note.visibility === 'home') {
-				// 自分自身以外への返信
-				if (note.replyId && note.replyUserId !== note.userId) {
-					redisPipeline.xadd(
-						`userTimelineWithReplies:${user.id}`,
-						'MAXLEN', '~', '1000',
-						'*',
-						'note', note.id);
-				} else {
-					redisPipeline.xadd(
-						`userTimeline:${user.id}`,
-						'MAXLEN', '~', '1000',
-						'*',
-						'note', note.id);
-
-					if (note.fileIds.length > 0) {
-						redisPipeline.xadd(
-							`userTimelineWithFiles:${user.id}`,
-							'MAXLEN', '~', '500',
-							'*',
-							'note', note.id);
-					}
-
-					if (note.visibility === 'public' && note.userHost == null) {
-						redisPipeline.xadd(
-							'localTimeline',
-							'MAXLEN', '~', '1000',
-							'*',
-							'note', note.id);
-
-						if (note.fileIds.length > 0) {
-							redisPipeline.xadd(
-								'localTimelineWithFiles',
-								'MAXLEN', '~', '500',
-								'*',
-								'note', note.id);
-						}
-					}
-				}
-			}
-		}
-
-		redisPipeline.exec();
-	}
-
-	@bindThis
-	public async checkHibernation(followings: MiFollowing[]) {
-		if (followings.length === 0) return;
-
-		const shuffle = (array: MiFollowing[]) => {
-			for (let i = array.length - 1; i > 0; i--) {
-				const j = Math.floor(Math.random() * (i + 1));
-				[array[i], array[j]] = [array[j], array[i]];
-			}
-			return array;
-		};
-
-		// ランダムに最大1000件サンプリング
-		const samples = shuffle(followings).slice(0, Math.min(followings.length, 1000));
-
-		const hibernatedUsers = await this.usersRepository.find({
-			where: {
-				id: In(samples.map(x => x.followerId)),
-				lastActiveDate: LessThan(new Date(Date.now() - (1000 * 60 * 60 * 24 * 50))),
-			},
-			select: ['id'],
-		});
-
-		if (hibernatedUsers.length > 0) {
-			this.usersRepository.update({
-				id: In(hibernatedUsers.map(x => x.id)),
-			}, {
-				isHibernated: true,
-			});
-		}
-	}
-
 	@bindThis
 	private isSensitive(note: Option, sensitiveWord: string[]): boolean {
 		if (sensitiveWord.length > 0) {
diff --git a/packages/backend/src/core/NotificationService.ts b/packages/backend/src/core/NotificationService.ts
index 32d54d2576..ca05989a4a 100644
--- a/packages/backend/src/core/NotificationService.ts
+++ b/packages/backend/src/core/NotificationService.ts
@@ -99,19 +99,19 @@ export class NotificationService implements OnApplicationShutdown {
 			}
 
 			if (recieveConfig?.type === 'following') {
-				const isFollowing = await this.cacheService.userFollowingsCache.fetch(notifieeId).then(followings => Object.hasOwn(followings, notifierId));
+				const isFollowing = await this.cacheService.userFollowingsCache.fetch(notifieeId).then(followings => followings.has(notifierId));
 				if (!isFollowing) {
 					return null;
 				}
 			} else if (recieveConfig?.type === 'follower') {
-				const isFollower = await this.cacheService.userFollowingsCache.fetch(notifierId).then(followings => Object.hasOwn(followings, notifieeId));
+				const isFollower = await this.cacheService.userFollowingsCache.fetch(notifierId).then(followings => followings.has(notifieeId));
 				if (!isFollower) {
 					return null;
 				}
 			} else if (recieveConfig?.type === 'mutualFollow') {
 				const [isFollowing, isFollower] = await Promise.all([
-					this.cacheService.userFollowingsCache.fetch(notifieeId).then(followings => Object.hasOwn(followings, notifierId)),
-					this.cacheService.userFollowingsCache.fetch(notifierId).then(followings => Object.hasOwn(followings, notifieeId)),
+					this.cacheService.userFollowingsCache.fetch(notifieeId).then(followings => followings.has(notifierId)),
+					this.cacheService.userFollowingsCache.fetch(notifierId).then(followings => followings.has(notifieeId)),
 				]);
 				if (!isFollowing && !isFollower) {
 					return null;
diff --git a/packages/backend/src/core/QueryService.ts b/packages/backend/src/core/QueryService.ts
index 18bd49286e..9145726f86 100644
--- a/packages/backend/src/core/QueryService.ts
+++ b/packages/backend/src/core/QueryService.ts
@@ -7,7 +7,7 @@ import { Inject, Injectable } from '@nestjs/common';
 import { Brackets, ObjectLiteral } from 'typeorm';
 import { DI } from '@/di-symbols.js';
 import type { MiUser } from '@/models/User.js';
-import type { UserProfilesRepository, FollowingsRepository, ChannelFollowingsRepository, BlockingsRepository, NoteThreadMutingsRepository, MutingsRepository, RenoteMutingsRepository } from '@/models/_.js';
+import type { UserProfilesRepository, FollowingsRepository, ChannelFollowingsRepository, MutedNotesRepository, BlockingsRepository, NoteThreadMutingsRepository, MutingsRepository, RenoteMutingsRepository } from '@/models/_.js';
 import { bindThis } from '@/decorators.js';
 import type { SelectQueryBuilder } from 'typeorm';
 
@@ -23,6 +23,9 @@ export class QueryService {
 		@Inject(DI.channelFollowingsRepository)
 		private channelFollowingsRepository: ChannelFollowingsRepository,
 
+		@Inject(DI.mutedNotesRepository)
+		private mutedNotesRepository: MutedNotesRepository,
+
 		@Inject(DI.blockingsRepository)
 		private blockingsRepository: BlockingsRepository,
 
@@ -105,6 +108,39 @@ export class QueryService {
 		q.setParameters(blockedQuery.getParameters());
 	}
 
+	@bindThis
+	public generateChannelQuery(q: SelectQueryBuilder<any>, me?: { id: MiUser['id'] } | null): void {
+		if (me == null) {
+			q.andWhere('note.channelId IS NULL');
+		} else {
+			q.leftJoinAndSelect('note.channel', 'channel');
+
+			const channelFollowingQuery = this.channelFollowingsRepository.createQueryBuilder('channelFollowing')
+				.select('channelFollowing.followeeId')
+				.where('channelFollowing.followerId = :followerId', { followerId: me.id });
+
+			q.andWhere(new Brackets(qb => { qb
+				// チャンネルのノートではない
+				.where('note.channelId IS NULL')
+				// または自分がフォローしているチャンネルのノート
+				.orWhere(`note.channelId IN (${ channelFollowingQuery.getQuery() })`);
+			}));
+
+			q.setParameters(channelFollowingQuery.getParameters());
+		}
+	}
+
+	@bindThis
+	public generateMutedNoteQuery(q: SelectQueryBuilder<any>, me: { id: MiUser['id'] }): void {
+		const mutedQuery = this.mutedNotesRepository.createQueryBuilder('muted')
+			.select('muted.noteId')
+			.where('muted.userId = :userId', { userId: me.id });
+
+		q.andWhere(`note.id NOT IN (${ mutedQuery.getQuery() })`);
+
+		q.setParameters(mutedQuery.getParameters());
+	}
+
 	@bindThis
 	public generateMutedNoteThreadQuery(q: SelectQueryBuilder<any>, me: { id: MiUser['id'] }): void {
 		const mutedQuery = this.noteThreadMutingsRepository.createQueryBuilder('threadMuted')
@@ -176,6 +212,32 @@ export class QueryService {
 		q.setParameters(mutingQuery.getParameters());
 	}
 
+	@bindThis
+	public generateRepliesQuery(q: SelectQueryBuilder<any>, withReplies: boolean, me?: Pick<MiUser, 'id'> | null): void {
+		if (me == null) {
+			q.andWhere(new Brackets(qb => { qb
+				.where('note.replyId IS NULL') // 返信ではない
+				.orWhere(new Brackets(qb => { qb // 返信だけど投稿者自身への返信
+					.where('note.replyId IS NOT NULL')
+					.andWhere('note.replyUserId = note.userId');
+				}));
+			}));
+		} else if (!withReplies) {
+			q.andWhere(new Brackets(qb => { qb
+				.where('note.replyId IS NULL') // 返信ではない
+				.orWhere('note.replyUserId = :meId', { meId: me.id }) // 返信だけど自分のノートへの返信
+				.orWhere(new Brackets(qb => { qb // 返信だけど自分の行った返信
+					.where('note.replyId IS NOT NULL')
+					.andWhere('note.userId = :meId', { meId: me.id });
+				}))
+				.orWhere(new Brackets(qb => { qb // 返信だけど投稿者自身への返信
+					.where('note.replyId IS NOT NULL')
+					.andWhere('note.replyUserId = note.userId');
+				}));
+			}));
+		}
+	}
+
 	@bindThis
 	public generateVisibilityQuery(q: SelectQueryBuilder<any>, me?: { id: MiUser['id'] } | null): void {
 		// This code must always be synchronized with the checks in Notes.isVisibleForMe.
diff --git a/packages/backend/src/core/UserBlockingService.ts b/packages/backend/src/core/UserBlockingService.ts
index 087dfd9214..37031e341e 100644
--- a/packages/backend/src/core/UserBlockingService.ts
+++ b/packages/backend/src/core/UserBlockingService.ts
@@ -11,7 +11,7 @@ import type { MiBlocking } from '@/models/Blocking.js';
 import { QueueService } from '@/core/QueueService.js';
 import { GlobalEventService } from '@/core/GlobalEventService.js';
 import { DI } from '@/di-symbols.js';
-import type { FollowRequestsRepository, BlockingsRepository, UserListsRepository, UserListMembershipsRepository } from '@/models/_.js';
+import type { FollowRequestsRepository, BlockingsRepository, UserListsRepository, UserListJoiningsRepository } from '@/models/_.js';
 import Logger from '@/logger.js';
 import { UserEntityService } from '@/core/entities/UserEntityService.js';
 import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
@@ -38,8 +38,8 @@ export class UserBlockingService implements OnModuleInit {
 		@Inject(DI.userListsRepository)
 		private userListsRepository: UserListsRepository,
 
-		@Inject(DI.userListMembershipsRepository)
-		private userListMembershipsRepository: UserListMembershipsRepository,
+		@Inject(DI.userListJoiningsRepository)
+		private userListJoiningsRepository: UserListJoiningsRepository,
 
 		private cacheService: CacheService,
 		private userEntityService: UserEntityService,
@@ -149,7 +149,7 @@ export class UserBlockingService implements OnModuleInit {
 		});
 
 		for (const userList of userLists) {
-			await this.userListMembershipsRepository.delete({
+			await this.userListJoiningsRepository.delete({
 				userListId: userList.id,
 				userId: user.id,
 			});
diff --git a/packages/backend/src/core/UserFollowingService.ts b/packages/backend/src/core/UserFollowingService.ts
index beffcc2e9c..230f6ef261 100644
--- a/packages/backend/src/core/UserFollowingService.ts
+++ b/packages/backend/src/core/UserFollowingService.ts
@@ -123,11 +123,7 @@ export class UserFollowingService implements OnModuleInit {
 		// フォロワーがBotであり、フォロー対象がBotからのフォローに慎重である or
 		// フォロワーがローカルユーザーであり、フォロー対象がリモートユーザーである
 		// 上記のいずれかに当てはまる場合はすぐフォローせずにフォローリクエストを発行しておく
-		if (
-			followee.isLocked ||
-			(followeeProfile.carefulBot && follower.isBot) ||
-			(this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee) && process.env.FORCE_FOLLOW_REMOTE_USER_FOR_TESTING !== 'true')
-		) {
+		if (followee.isLocked || (followeeProfile.carefulBot && follower.isBot) || (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee))) {
 			let autoAccept = false;
 
 			// 鍵アカウントであっても、既にフォローされていた場合はスルー
diff --git a/packages/backend/src/core/UserListService.ts b/packages/backend/src/core/UserListService.ts
index bece1e442e..93dc5edbba 100644
--- a/packages/backend/src/core/UserListService.ts
+++ b/packages/backend/src/core/UserListService.ts
@@ -5,10 +5,10 @@
 
 import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
 import * as Redis from 'ioredis';
-import type { UserListMembershipsRepository } from '@/models/_.js';
+import type { UserListJoiningsRepository } from '@/models/_.js';
 import type { MiUser } from '@/models/User.js';
 import type { MiUserList } from '@/models/UserList.js';
-import type { MiUserListMembership } from '@/models/UserListMembership.js';
+import type { MiUserListJoining } from '@/models/UserListJoining.js';
 import { IdService } from '@/core/IdService.js';
 import { GlobalEventService } from '@/core/GlobalEventService.js';
 import { DI } from '@/di-symbols.js';
@@ -33,8 +33,8 @@ export class UserListService implements OnApplicationShutdown {
 		@Inject(DI.redisForSub)
 		private redisForSub: Redis.Redis,
 
-		@Inject(DI.userListMembershipsRepository)
-		private userListMembershipsRepository: UserListMembershipsRepository,
+		@Inject(DI.userListJoiningsRepository)
+		private userListJoiningsRepository: UserListJoiningsRepository,
 
 		private userEntityService: UserEntityService,
 		private idService: IdService,
@@ -46,7 +46,7 @@ export class UserListService implements OnApplicationShutdown {
 		this.membersCache = new RedisKVCache<Set<string>>(this.redisClient, 'userListMembers', {
 			lifetime: 1000 * 60 * 30, // 30m
 			memoryCacheLifetime: 1000 * 60, // 1m
-			fetcher: (key) => this.userListMembershipsRepository.find({ where: { userListId: key }, select: ['userId'] }).then(xs => new Set(xs.map(x => x.userId))),
+			fetcher: (key) => this.userListJoiningsRepository.find({ where: { userListId: key }, select: ['userId'] }).then(xs => new Set(xs.map(x => x.userId))),
 			toRedisConverter: (value) => JSON.stringify(Array.from(value)),
 			fromRedisConverter: (value) => new Set(JSON.parse(value)),
 		});
@@ -85,19 +85,19 @@ export class UserListService implements OnApplicationShutdown {
 
 	@bindThis
 	public async addMember(target: MiUser, list: MiUserList, me: MiUser) {
-		const currentCount = await this.userListMembershipsRepository.countBy({
+		const currentCount = await this.userListJoiningsRepository.countBy({
 			userListId: list.id,
 		});
 		if (currentCount > (await this.roleService.getUserPolicies(me.id)).userEachUserListsLimit) {
 			throw new UserListService.TooManyUsersError();
 		}
 
-		await this.userListMembershipsRepository.insert({
+		await this.userListJoiningsRepository.insert({
 			id: this.idService.genId(),
 			createdAt: new Date(),
 			userId: target.id,
 			userListId: list.id,
-		} as MiUserListMembership);
+		} as MiUserListJoining);
 
 		this.globalEventService.publishInternalEvent('userListMemberAdded', { userListId: list.id, memberId: target.id });
 		this.globalEventService.publishUserListStream(list.id, 'userAdded', await this.userEntityService.pack(target));
@@ -113,7 +113,7 @@ export class UserListService implements OnApplicationShutdown {
 
 	@bindThis
 	public async removeMember(target: MiUser, list: MiUserList) {
-		await this.userListMembershipsRepository.delete({
+		await this.userListJoiningsRepository.delete({
 			userId: target.id,
 			userListId: list.id,
 		});
@@ -122,24 +122,6 @@ export class UserListService implements OnApplicationShutdown {
 		this.globalEventService.publishUserListStream(list.id, 'userRemoved', await this.userEntityService.pack(target));
 	}
 
-	@bindThis
-	public async updateMembership(target: MiUser, list: MiUserList, options: { withReplies?: boolean }) {
-		const membership = await this.userListMembershipsRepository.findOneBy({
-			userId: target.id,
-			userListId: list.id,
-		});
-
-		if (membership == null) {
-			throw new Error('User is not a member of the list');
-		}
-
-		await this.userListMembershipsRepository.update({
-			id: membership.id,
-		}, {
-			withReplies: options.withReplies,
-		});
-	}
-
 	@bindThis
 	public dispose(): void {
 		this.redisForSub.off('message', this.onMessage);
diff --git a/packages/backend/src/core/UserService.ts b/packages/backend/src/core/UserService.ts
deleted file mode 100644
index d16e1be615..0000000000
--- a/packages/backend/src/core/UserService.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
- * SPDX-License-Identifier: AGPL-3.0-only
- */
-
-import { Inject, Injectable } from '@nestjs/common';
-import type { FollowingsRepository, UsersRepository } from '@/models/_.js';
-import type { MiUser } from '@/models/User.js';
-import { DI } from '@/di-symbols.js';
-import { bindThis } from '@/decorators.js';
-
-@Injectable()
-export class UserService {
-	constructor(
-		@Inject(DI.usersRepository)
-		private usersRepository: UsersRepository,
-
-		@Inject(DI.followingsRepository)
-		private followingsRepository: FollowingsRepository,
-	) {
-	}
-
-	@bindThis
-	public async updateLastActiveDate(user: MiUser): Promise<void> {
-		if (user.isHibernated) {
-			const result = await this.usersRepository.createQueryBuilder().update()
-				.set({
-					lastActiveDate: new Date(),
-				})
-				.where('id = :id', { id: user.id })
-				.returning('*')
-				.execute()
-				.then((response) => {
-					return response.raw[0];
-				});
-			const wokeUp = result.isHibernated;
-			if (wokeUp) {
-				this.usersRepository.update(user.id, {
-					isHibernated: false,
-				});
-				this.followingsRepository.update({
-					followerId: user.id,
-				}, {
-					isFollowerHibernated: false,
-				});
-			}
-		} else {
-			this.usersRepository.update(user.id, {
-				lastActiveDate: new Date(),
-			});
-		}
-	}
-}
diff --git a/packages/backend/src/core/entities/NoteEntityService.ts b/packages/backend/src/core/entities/NoteEntityService.ts
index 4444e4118d..026f84bb39 100644
--- a/packages/backend/src/core/entities/NoteEntityService.ts
+++ b/packages/backend/src/core/entities/NoteEntityService.ts
@@ -102,13 +102,13 @@ export class NoteEntityService implements OnModuleInit {
 			} else if (meId === packedNote.userId) {
 				hide = false;
 			} else if (packedNote.reply && (meId === packedNote.reply.userId)) {
-				// 自分の投稿に対するリプライ
+			// 自分の投稿に対するリプライ
 				hide = false;
 			} else if (packedNote.mentions && packedNote.mentions.some(id => meId === id)) {
-				// 自分へのメンション
+			// 自分へのメンション
 				hide = false;
 			} else {
-				// フォロワーかどうか
+			// フォロワーかどうか
 				const isFollowing = await this.followingsRepository.exist({
 					where: {
 						followeeId: packedNote.userId,
diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts
index b8fb9f8db7..cdd1182f6d 100644
--- a/packages/backend/src/core/entities/UserEntityService.ts
+++ b/packages/backend/src/core/entities/UserEntityService.ts
@@ -493,7 +493,6 @@ export class UserEntityService implements OnModuleInit {
 				isMuted: relation.isMuted,
 				isRenoteMuted: relation.isRenoteMuted,
 				notify: relation.following?.notify ?? 'none',
-				withReplies: relation.following?.withReplies ?? false,
 			} : {}),
 		} as Promiseable<Packed<'User'>> as Promiseable<IsMeAndIsUserDetailed<ExpectsMe, D>>;
 
diff --git a/packages/backend/src/core/entities/UserListEntityService.ts b/packages/backend/src/core/entities/UserListEntityService.ts
index 06b6e852b1..a7f2885194 100644
--- a/packages/backend/src/core/entities/UserListEntityService.ts
+++ b/packages/backend/src/core/entities/UserListEntityService.ts
@@ -5,12 +5,11 @@
 
 import { Inject, Injectable } from '@nestjs/common';
 import { DI } from '@/di-symbols.js';
-import type { MiUserListMembership, UserListMembershipsRepository, UserListsRepository } from '@/models/_.js';
+import type { UserListJoiningsRepository, UserListsRepository } from '@/models/_.js';
 import type { Packed } from '@/misc/json-schema.js';
 import type { } from '@/models/Blocking.js';
 import type { MiUserList } from '@/models/UserList.js';
 import { bindThis } from '@/decorators.js';
-import { UserEntityService } from './UserEntityService.js';
 
 @Injectable()
 export class UserListEntityService {
@@ -18,10 +17,8 @@ export class UserListEntityService {
 		@Inject(DI.userListsRepository)
 		private userListsRepository: UserListsRepository,
 
-		@Inject(DI.userListMembershipsRepository)
-		private userListMembershipsRepository: UserListMembershipsRepository,
-
-		private userEntityService: UserEntityService,
+		@Inject(DI.userListJoiningsRepository)
+		private userListJoiningsRepository: UserListJoiningsRepository,
 	) {
 	}
 
@@ -31,7 +28,7 @@ export class UserListEntityService {
 	): Promise<Packed<'UserList'>> {
 		const userList = typeof src === 'object' ? src : await this.userListsRepository.findOneByOrFail({ id: src });
 
-		const users = await this.userListMembershipsRepository.findBy({
+		const users = await this.userListJoiningsRepository.findBy({
 			userListId: userList.id,
 		});
 
@@ -43,18 +40,5 @@ export class UserListEntityService {
 			isPublic: userList.isPublic,
 		};
 	}
-
-	@bindThis
-	public async packMembershipsMany(
-		memberships: MiUserListMembership[],
-	) {
-		return Promise.all(memberships.map(async x => ({
-			id: x.id,
-			createdAt: x.createdAt.toISOString(),
-			userId: x.userId,
-			user: await this.userEntityService.pack(x.userId),
-			withReplies: x.withReplies,
-		})));
-	}
 }
 
diff --git a/packages/backend/src/di-symbols.ts b/packages/backend/src/di-symbols.ts
index ccaa810f5c..7034fff058 100644
--- a/packages/backend/src/di-symbols.ts
+++ b/packages/backend/src/di-symbols.ts
@@ -10,7 +10,6 @@ export const DI = {
 	redis: Symbol('redis'),
 	redisForPub: Symbol('redisForPub'),
 	redisForSub: Symbol('redisForSub'),
-	redisForTimelines: Symbol('redisForTimelines'),
 
 	//#region Repositories
 	usersRepository: Symbol('usersRepository'),
@@ -31,7 +30,7 @@ export const DI = {
 	userPublickeysRepository: Symbol('userPublickeysRepository'),
 	userListsRepository: Symbol('userListsRepository'),
 	userListFavoritesRepository: Symbol('userListFavoritesRepository'),
-	userListMembershipsRepository: Symbol('userListMembershipsRepository'),
+	userListJoiningsRepository: Symbol('userListJoiningsRepository'),
 	userNotePiningsRepository: Symbol('userNotePiningsRepository'),
 	userIpsRepository: Symbol('userIpsRepository'),
 	usedUsernamesRepository: Symbol('usedUsernamesRepository'),
@@ -64,6 +63,7 @@ export const DI = {
 	promoNotesRepository: Symbol('promoNotesRepository'),
 	promoReadsRepository: Symbol('promoReadsRepository'),
 	relaysRepository: Symbol('relaysRepository'),
+	mutedNotesRepository: Symbol('mutedNotesRepository'),
 	channelsRepository: Symbol('channelsRepository'),
 	channelFollowingsRepository: Symbol('channelFollowingsRepository'),
 	channelFavoritesRepository: Symbol('channelFavoritesRepository'),
diff --git a/packages/backend/src/models/Following.ts b/packages/backend/src/models/Following.ts
index 607538b1e7..8c9f965fad 100644
--- a/packages/backend/src/models/Following.ts
+++ b/packages/backend/src/models/Following.ts
@@ -9,7 +9,6 @@ import { MiUser } from './User.js';
 
 @Entity('following')
 @Index(['followerId', 'followeeId'], { unique: true })
-@Index(['followeeId', 'followerHost', 'isFollowerHibernated'])
 export class MiFollowing {
 	@PrimaryColumn(id())
 	public id: string;
@@ -46,17 +45,6 @@ export class MiFollowing {
 	@JoinColumn()
 	public follower: MiUser | null;
 
-	@Column('boolean', {
-		default: false,
-	})
-	public isFollowerHibernated: boolean;
-
-	// タイムラインにその人のリプライまで含めるかどうか
-	@Column('boolean', {
-		default: false,
-	})
-	public withReplies: boolean;
-
 	@Index()
 	@Column('varchar', {
 		length: 32,
diff --git a/packages/backend/src/models/MutedNote.ts b/packages/backend/src/models/MutedNote.ts
new file mode 100644
index 0000000000..89a678a2a7
--- /dev/null
+++ b/packages/backend/src/models/MutedNote.ts
@@ -0,0 +1,53 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Entity, Index, JoinColumn, Column, ManyToOne, PrimaryColumn } from 'typeorm';
+import { mutedNoteReasons } from '@/types.js';
+import { id } from './util/id.js';
+import { MiNote } from './Note.js';
+import { MiUser } from './User.js';
+
+@Entity('muted_note')
+@Index(['noteId', 'userId'], { unique: true })
+export class MiMutedNote {
+	@PrimaryColumn(id())
+	public id: string;
+
+	@Index()
+	@Column({
+		...id(),
+		comment: 'The note ID.',
+	})
+	public noteId: MiNote['id'];
+
+	@ManyToOne(type => MiNote, {
+		onDelete: 'CASCADE',
+	})
+	@JoinColumn()
+	public note: MiNote | null;
+
+	@Index()
+	@Column({
+		...id(),
+		comment: 'The user ID.',
+	})
+	public userId: MiUser['id'];
+
+	@ManyToOne(type => MiUser, {
+		onDelete: 'CASCADE',
+	})
+	@JoinColumn()
+	public user: MiUser | null;
+
+	/**
+	 * ミュートされた理由。
+	 */
+	@Index()
+	@Column('enum', {
+		enum: mutedNoteReasons,
+		comment: 'The reason of the MutedNote.',
+	})
+	public reason: typeof mutedNoteReasons[number];
+}
diff --git a/packages/backend/src/models/RepositoryModule.ts b/packages/backend/src/models/RepositoryModule.ts
index bef053610b..7e2bee8c44 100644
--- a/packages/backend/src/models/RepositoryModule.ts
+++ b/packages/backend/src/models/RepositoryModule.ts
@@ -5,7 +5,7 @@
 
 import { Module } from '@nestjs/common';
 import { DI } from '@/di-symbols.js';
-import { MiAbuseUserReport, MiAccessToken, MiAd, MiAnnouncement, MiAnnouncementRead, MiAntenna, MiApp, MiAuthSession, MiBlocking, MiChannel, MiChannelFavorite, MiChannelFollowing, MiClip, MiClipFavorite, MiClipNote, MiDriveFile, MiDriveFolder, MiEmoji, MiFlash, MiFlashLike, MiFollowRequest, MiFollowing, MiGalleryLike, MiGalleryPost, MiHashtag, MiInstance, MiMeta, MiModerationLog, MiMuting, MiNote, MiNoteFavorite, MiNoteReaction, MiNoteThreadMuting, MiNoteUnread, MiPage, MiPageLike, MiPasswordResetRequest, MiPoll, MiPollVote, MiPromoNote, MiPromoRead, MiRegistrationTicket, MiRegistryItem, MiRelay, MiRenoteMuting, MiRetentionAggregation, MiRole, MiRoleAssignment, MiSignin, MiSwSubscription, MiUsedUsername, MiUser, MiUserIp, MiUserKeypair, MiUserList, MiUserListFavorite, MiUserListMembership, MiUserMemo, MiUserNotePining, MiUserPending, MiUserProfile, MiUserPublickey, MiUserSecurityKey, MiWebhook, NoteEdit } from './_.js';
+import { MiAbuseUserReport, MiAccessToken, MiAd, MiAnnouncement, MiAnnouncementRead, MiAntenna, MiApp, MiAuthSession, MiBlocking, MiChannel, MiChannelFavorite, MiChannelFollowing, MiClip, MiClipFavorite, MiClipNote, MiDriveFile, MiDriveFolder, MiEmoji, MiFlash, MiFlashLike, MiFollowRequest, MiFollowing, MiGalleryLike, MiGalleryPost, MiHashtag, MiInstance, MiMeta, MiModerationLog, MiMutedNote, MiMuting, MiNote, MiNoteFavorite, MiNoteReaction, MiNoteThreadMuting, MiNoteUnread, MiPage, MiPageLike, MiPasswordResetRequest, MiPoll, MiPollVote, MiPromoNote, MiPromoRead, MiRegistrationTicket, MiRegistryItem, MiRelay, MiRenoteMuting, MiRetentionAggregation, MiRole, MiRoleAssignment, MiSignin, MiSwSubscription, MiUsedUsername, MiUser, MiUserIp, MiUserKeypair, MiUserList, MiUserListFavorite, MiUserListJoining, MiUserMemo, MiUserNotePining, MiUserPending, MiUserProfile, MiUserPublickey, MiUserSecurityKey, MiWebhook, NoteEdit } from './_.js';
 import type { DataSource } from 'typeorm';
 import type { Provider } from '@nestjs/common';
 
@@ -117,9 +117,9 @@ const $userListFavoritesRepository: Provider = {
 	inject: [DI.db],
 };
 
-const $userListMembershipsRepository: Provider = {
-	provide: DI.userListMembershipsRepository,
-	useFactory: (db: DataSource) => db.getRepository(MiUserListMembership),
+const $userListJoiningsRepository: Provider = {
+	provide: DI.userListJoiningsRepository,
+	useFactory: (db: DataSource) => db.getRepository(MiUserListJoining),
 	inject: [DI.db],
 };
 
@@ -315,6 +315,12 @@ const $relaysRepository: Provider = {
 	inject: [DI.db],
 };
 
+const $mutedNotesRepository: Provider = {
+	provide: DI.mutedNotesRepository,
+	useFactory: (db: DataSource) => db.getRepository(MiMutedNote),
+	inject: [DI.db],
+};
+
 const $channelsRepository: Provider = {
 	provide: DI.channelsRepository,
 	useFactory: (db: DataSource) => db.getRepository(MiChannel),
@@ -421,7 +427,7 @@ const $noteEditRepository: Provider = {
 		$userPublickeysRepository,
 		$userListsRepository,
 		$userListFavoritesRepository,
-		$userListMembershipsRepository,
+		$userListJoiningsRepository,
 		$userNotePiningsRepository,
 		$userIpsRepository,
 		$usedUsernamesRepository,
@@ -454,6 +460,7 @@ const $noteEditRepository: Provider = {
 		$promoNotesRepository,
 		$promoReadsRepository,
 		$relaysRepository,
+		$mutedNotesRepository,
 		$channelsRepository,
 		$channelFollowingsRepository,
 		$channelFavoritesRepository,
@@ -488,7 +495,7 @@ const $noteEditRepository: Provider = {
 		$userPublickeysRepository,
 		$userListsRepository,
 		$userListFavoritesRepository,
-		$userListMembershipsRepository,
+		$userListJoiningsRepository,
 		$userNotePiningsRepository,
 		$userIpsRepository,
 		$usedUsernamesRepository,
@@ -521,6 +528,7 @@ const $noteEditRepository: Provider = {
 		$promoNotesRepository,
 		$promoReadsRepository,
 		$relaysRepository,
+		$mutedNotesRepository,
 		$channelsRepository,
 		$channelFollowingsRepository,
 		$channelFavoritesRepository,
diff --git a/packages/backend/src/models/User.ts b/packages/backend/src/models/User.ts
index 9650622dd5..8f0122a90c 100644
--- a/packages/backend/src/models/User.ts
+++ b/packages/backend/src/models/User.ts
@@ -187,11 +187,6 @@ export class MiUser {
 	})
 	public isExplorable: boolean;
 
-	@Column('boolean', {
-		default: false,
-	})
-	public isHibernated: boolean;
-
 	// アカウントが削除されたかどうかのフラグだが、完全に削除される際は物理削除なので実質削除されるまでの「削除が進行しているかどうか」のフラグ
 	@Column('boolean', {
 		default: false,
diff --git a/packages/backend/src/models/UserListMembership.ts b/packages/backend/src/models/UserListJoining.ts
similarity index 76%
rename from packages/backend/src/models/UserListMembership.ts
rename to packages/backend/src/models/UserListJoining.ts
index f337f19a47..4918f2f700 100644
--- a/packages/backend/src/models/UserListMembership.ts
+++ b/packages/backend/src/models/UserListJoining.ts
@@ -8,14 +8,14 @@ import { id } from './util/id.js';
 import { MiUser } from './User.js';
 import { MiUserList } from './UserList.js';
 
-@Entity('user_list_membership')
+@Entity('user_list_joining')
 @Index(['userId', 'userListId'], { unique: true })
-export class MiUserListMembership {
+export class MiUserListJoining {
 	@PrimaryColumn(id())
 	public id: string;
 
 	@Column('timestamp with time zone', {
-		comment: 'The created date of the UserListMembership.',
+		comment: 'The created date of the UserListJoining.',
 	})
 	public createdAt: Date;
 
@@ -44,10 +44,4 @@ export class MiUserListMembership {
 	})
 	@JoinColumn()
 	public userList: MiUserList | null;
-
-	// タイムラインにその人のリプライまで含めるかどうか
-	@Column('boolean', {
-		default: false,
-	})
-	public withReplies: boolean;
 }
diff --git a/packages/backend/src/models/_.ts b/packages/backend/src/models/_.ts
index b76f6d5420..ca047569cb 100644
--- a/packages/backend/src/models/_.ts
+++ b/packages/backend/src/models/_.ts
@@ -28,6 +28,7 @@ import { MiHashtag } from '@/models/Hashtag.js';
 import { MiInstance } from '@/models/Instance.js';
 import { MiMeta } from '@/models/Meta.js';
 import { MiModerationLog } from '@/models/ModerationLog.js';
+import { MiMutedNote } from '@/models/MutedNote.js';
 import { MiMuting } from '@/models/Muting.js';
 import { MiRenoteMuting } from '@/models/RenoteMuting.js';
 import { MiNote } from '@/models/Note.js';
@@ -52,7 +53,7 @@ import { MiUser } from '@/models/User.js';
 import { MiUserIp } from '@/models/UserIp.js';
 import { MiUserKeypair } from '@/models/UserKeypair.js';
 import { MiUserList } from '@/models/UserList.js';
-import { MiUserListMembership } from '@/models/UserListMembership.js';
+import { MiUserListJoining } from '@/models/UserListJoining.js';
 import { MiUserNotePining } from '@/models/UserNotePining.js';
 import { MiUserPending } from '@/models/UserPending.js';
 import { MiUserProfile } from '@/models/UserProfile.js';
@@ -96,6 +97,7 @@ export {
 	MiInstance,
 	MiMeta,
 	MiModerationLog,
+	MiMutedNote,
 	MiMuting,
 	MiRenoteMuting,
 	MiNote,
@@ -121,7 +123,7 @@ export {
 	MiUserKeypair,
 	MiUserList,
 	MiUserListFavorite,
-	MiUserListMembership,
+	MiUserListJoining,
 	MiUserNotePining,
 	MiUserPending,
 	MiUserProfile,
@@ -163,6 +165,7 @@ export type HashtagsRepository = Repository<MiHashtag>;
 export type InstancesRepository = Repository<MiInstance>;
 export type MetasRepository = Repository<MiMeta>;
 export type ModerationLogsRepository = Repository<MiModerationLog>;
+export type MutedNotesRepository = Repository<MiMutedNote>;
 export type MutingsRepository = Repository<MiMuting>;
 export type RenoteMutingsRepository = Repository<MiRenoteMuting>;
 export type NotesRepository = Repository<MiNote>;
@@ -188,7 +191,7 @@ export type UserIpsRepository = Repository<MiUserIp>;
 export type UserKeypairsRepository = Repository<MiUserKeypair>;
 export type UserListsRepository = Repository<MiUserList>;
 export type UserListFavoritesRepository = Repository<MiUserListFavorite>;
-export type UserListMembershipsRepository = Repository<MiUserListMembership>;
+export type UserListJoiningsRepository = Repository<MiUserListJoining>;
 export type UserNotePiningsRepository = Repository<MiUserNotePining>;
 export type UserPendingsRepository = Repository<MiUserPending>;
 export type UserProfilesRepository = Repository<MiUserProfile>;
diff --git a/packages/backend/src/models/json-schema/user.ts b/packages/backend/src/models/json-schema/user.ts
index 25f0547281..79b14bb65f 100644
--- a/packages/backend/src/models/json-schema/user.ts
+++ b/packages/backend/src/models/json-schema/user.ts
@@ -283,10 +283,6 @@ export const packedUserDetailedNotMeOnlySchema = {
 			type: 'string',
 			nullable: false, optional: true,
 		},
-		withReplies: {
-			type: 'boolean',
-			nullable: false, optional: true,
-		},
 		//#endregion
 	},
 } as const;
diff --git a/packages/backend/src/postgres.ts b/packages/backend/src/postgres.ts
index 5cf9d7d1aa..b12a84ac96 100644
--- a/packages/backend/src/postgres.ts
+++ b/packages/backend/src/postgres.ts
@@ -36,6 +36,7 @@ import { MiHashtag } from '@/models/Hashtag.js';
 import { MiInstance } from '@/models/Instance.js';
 import { MiMeta } from '@/models/Meta.js';
 import { MiModerationLog } from '@/models/ModerationLog.js';
+import { MiMutedNote } from '@/models/MutedNote.js';
 import { MiMuting } from '@/models/Muting.js';
 import { MiRenoteMuting } from '@/models/RenoteMuting.js';
 import { MiNote } from '@/models/Note.js';
@@ -61,7 +62,7 @@ import { MiUserIp } from '@/models/UserIp.js';
 import { MiUserKeypair } from '@/models/UserKeypair.js';
 import { MiUserList } from '@/models/UserList.js';
 import { MiUserListFavorite } from '@/models/UserListFavorite.js';
-import { MiUserListMembership } from '@/models/UserListMembership.js';
+import { MiUserListJoining } from '@/models/UserListJoining.js';
 import { MiUserNotePining } from '@/models/UserNotePining.js';
 import { MiUserPending } from '@/models/UserPending.js';
 import { MiUserProfile } from '@/models/UserProfile.js';
@@ -138,7 +139,7 @@ export const entities = [
 	MiUserPublickey,
 	MiUserList,
 	MiUserListFavorite,
-	MiUserListMembership,
+	MiUserListJoining,
 	MiUserNotePining,
 	MiUserSecurityKey,
 	MiUsedUsername,
@@ -174,6 +175,7 @@ export const entities = [
 	MiPromoNote,
 	MiPromoRead,
 	MiRelay,
+	MiMutedNote,
 	MiChannel,
 	MiChannelFollowing,
 	MiChannelFavorite,
diff --git a/packages/backend/src/queue/processors/CleanProcessorService.ts b/packages/backend/src/queue/processors/CleanProcessorService.ts
index e252c5d8a1..f0453f7054 100644
--- a/packages/backend/src/queue/processors/CleanProcessorService.ts
+++ b/packages/backend/src/queue/processors/CleanProcessorService.ts
@@ -6,7 +6,7 @@
 import { Inject, Injectable } from '@nestjs/common';
 import { In, LessThan } from 'typeorm';
 import { DI } from '@/di-symbols.js';
-import type { AntennasRepository, RoleAssignmentsRepository, UserIpsRepository } from '@/models/_.js';
+import type { AntennasRepository, MutedNotesRepository, RoleAssignmentsRepository, UserIpsRepository } from '@/models/_.js';
 import type Logger from '@/logger.js';
 import { bindThis } from '@/decorators.js';
 import { IdService } from '@/core/IdService.js';
@@ -25,6 +25,9 @@ export class CleanProcessorService {
 		@Inject(DI.userIpsRepository)
 		private userIpsRepository: UserIpsRepository,
 
+		@Inject(DI.mutedNotesRepository)
+		private mutedNotesRepository: MutedNotesRepository,
+
 		@Inject(DI.antennasRepository)
 		private antennasRepository: AntennasRepository,
 
@@ -45,6 +48,16 @@ export class CleanProcessorService {
 			createdAt: LessThan(new Date(Date.now() - (1000 * 60 * 60 * 24 * 90))),
 		});
 
+		this.mutedNotesRepository.delete({
+			id: LessThan(this.idService.genId(new Date(Date.now() - (1000 * 60 * 60 * 24 * 90)))),
+			reason: 'word',
+		});
+
+		this.mutedNotesRepository.delete({
+			id: LessThan(this.idService.genId(new Date(Date.now() - (1000 * 60 * 60 * 24 * 90)))),
+			reason: 'word',
+		});
+
 		// 使われてないアンテナを停止
 		if (this.config.deactivateAntennaThreshold > 0) {
 			this.antennasRepository.update({
diff --git a/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts b/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts
index a0afbee3ba..f941fb6e85 100644
--- a/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts
+++ b/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts
@@ -8,7 +8,7 @@ import { Inject, Injectable } from '@nestjs/common';
 import { format as DateFormat } from 'date-fns';
 import { In } from 'typeorm';
 import { DI } from '@/di-symbols.js';
-import type { AntennasRepository, UsersRepository, UserListMembershipsRepository, MiUser } from '@/models/_.js';
+import type { AntennasRepository, UsersRepository, UserListJoiningsRepository, MiUser } from '@/models/_.js';
 import Logger from '@/logger.js';
 import { DriveService } from '@/core/DriveService.js';
 import { bindThis } from '@/decorators.js';
@@ -29,8 +29,8 @@ export class ExportAntennasProcessorService {
 		@Inject(DI.antennasRepository)
 		private antennsRepository: AntennasRepository,
 
-		@Inject(DI.userListMembershipsRepository)
-		private userListMembershipsRepository: UserListMembershipsRepository,
+		@Inject(DI.userListJoiningsRepository)
+		private userListJoiningsRepository: UserListJoiningsRepository,
 
 		private driveService: DriveService,
 		private utilityService: UtilityService,
@@ -65,9 +65,9 @@ export class ExportAntennasProcessorService {
 			for (const [index, antenna] of antennas.entries()) {
 				let users: MiUser[] | undefined;
 				if (antenna.userListId !== null) {
-					const memberships = await this.userListMembershipsRepository.findBy({ userListId: antenna.userListId });
+					const joinings = await this.userListJoiningsRepository.findBy({ userListId: antenna.userListId });
 					users = await this.usersRepository.findBy({
-						id: In(memberships.map(j => j.userId)),
+						id: In(joinings.map(j => j.userId)),
 					});
 				}
 				write(JSON.stringify({
diff --git a/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts b/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts
index a3f9441dc2..7baaa7081a 100644
--- a/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts
+++ b/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts
@@ -8,7 +8,7 @@ import { Inject, Injectable } from '@nestjs/common';
 import { In } from 'typeorm';
 import { format as dateFormat } from 'date-fns';
 import { DI } from '@/di-symbols.js';
-import type { UserListMembershipsRepository, UserListsRepository, UsersRepository } from '@/models/_.js';
+import type { UserListJoiningsRepository, UserListsRepository, UsersRepository } from '@/models/_.js';
 import type Logger from '@/logger.js';
 import { DriveService } from '@/core/DriveService.js';
 import { createTemp } from '@/misc/create-temp.js';
@@ -29,8 +29,8 @@ export class ExportUserListsProcessorService {
 		@Inject(DI.userListsRepository)
 		private userListsRepository: UserListsRepository,
 
-		@Inject(DI.userListMembershipsRepository)
-		private userListMembershipsRepository: UserListMembershipsRepository,
+		@Inject(DI.userListJoiningsRepository)
+		private userListJoiningsRepository: UserListJoiningsRepository,
 
 		private utilityService: UtilityService,
 		private driveService: DriveService,
@@ -61,9 +61,9 @@ export class ExportUserListsProcessorService {
 			const stream = fs.createWriteStream(path, { flags: 'a' });
 
 			for (const list of lists) {
-				const memberships = await this.userListMembershipsRepository.findBy({ userListId: list.id });
+				const joinings = await this.userListJoiningsRepository.findBy({ userListId: list.id });
 				const users = await this.usersRepository.findBy({
-					id: In(memberships.map(j => j.userId)),
+					id: In(joinings.map(j => j.userId)),
 				});
 
 				for (const u of users) {
diff --git a/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts b/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts
index 9be36a9d0d..60a0d1605f 100644
--- a/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts
+++ b/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts
@@ -6,7 +6,7 @@
 import { Inject, Injectable } from '@nestjs/common';
 import { IsNull } from 'typeorm';
 import { DI } from '@/di-symbols.js';
-import type { UsersRepository, DriveFilesRepository, UserListMembershipsRepository, UserListsRepository } from '@/models/_.js';
+import type { UsersRepository, DriveFilesRepository, UserListJoiningsRepository, UserListsRepository } from '@/models/_.js';
 import type Logger from '@/logger.js';
 import * as Acct from '@/misc/acct.js';
 import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js';
@@ -33,8 +33,8 @@ export class ImportUserListsProcessorService {
 		@Inject(DI.userListsRepository)
 		private userListsRepository: UserListsRepository,
 
-		@Inject(DI.userListMembershipsRepository)
-		private userListMembershipsRepository: UserListMembershipsRepository,
+		@Inject(DI.userListJoiningsRepository)
+		private userListJoiningsRepository: UserListJoiningsRepository,
 
 		private utilityService: UtilityService,
 		private idService: IdService,
@@ -99,7 +99,7 @@ export class ImportUserListsProcessorService {
 					target = await this.remoteUserResolveService.resolveUser(username, host);
 				}
 
-				if (await this.userListMembershipsRepository.findOneBy({ userListId: list!.id, userId: target.id }) != null) continue;
+				if (await this.userListJoiningsRepository.findOneBy({ userListId: list!.id, userId: target.id }) != null) continue;
 
 				this.userListService.addMember(target, list!, user);
 			} catch (e) {
diff --git a/packages/backend/src/server/api/EndpointsModule.ts b/packages/backend/src/server/api/EndpointsModule.ts
index 74b26af27a..15a2621da2 100644
--- a/packages/backend/src/server/api/EndpointsModule.ts
+++ b/packages/backend/src/server/api/EndpointsModule.ts
@@ -205,6 +205,7 @@ import * as ep___i_exportAntennas from './endpoints/i/export-antennas.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';
@@ -336,9 +337,7 @@ import * as ep___users_lists_show from './endpoints/users/lists/show.js';
 import * as ep___users_lists_update from './endpoints/users/lists/update.js';
 import * as ep___users_lists_favorite from './endpoints/users/lists/favorite.js';
 import * as ep___users_lists_unfavorite from './endpoints/users/lists/unfavorite.js';
-import * as ep___users_lists_createFromPublic from './endpoints/users/lists/create-from-public.js';
-import * as ep___users_lists_updateMembership from './endpoints/users/lists/update-membership.js';
-import * as ep___users_lists_getMemberships from './endpoints/users/lists/get-memberships.js';
+import * as ep___users_lists_create_from_public from './endpoints/users/lists/create-from-public.js';
 import * as ep___users_notes from './endpoints/users/notes.js';
 import * as ep___users_pages from './endpoints/users/pages.js';
 import * as ep___users_flashs from './endpoints/users/flashs.js';
@@ -556,6 +555,7 @@ const $i_exportAntennas: Provider = { provide: 'ep:i/export-antennas', useClass:
 const $i_favorites: Provider = { provide: 'ep:i/favorites', useClass: ep___i_favorites.default };
 const $i_gallery_likes: Provider = { provide: 'ep:i/gallery/likes', useClass: ep___i_gallery_likes.default };
 const $i_gallery_posts: Provider = { provide: 'ep:i/gallery/posts', useClass: ep___i_gallery_posts.default };
+const $i_getWordMutedNotesCount: Provider = { provide: 'ep:i/get-word-muted-notes-count', useClass: ep___i_getWordMutedNotesCount.default };
 const $i_importBlocking: Provider = { provide: 'ep:i/import-blocking', useClass: ep___i_importBlocking.default };
 const $i_importFollowing: Provider = { provide: 'ep:i/import-following', useClass: ep___i_importFollowing.default };
 const $i_importMuting: Provider = { provide: 'ep:i/import-muting', useClass: ep___i_importMuting.default };
@@ -687,9 +687,7 @@ const $users_lists_show: Provider = { provide: 'ep:users/lists/show', useClass:
 const $users_lists_update: Provider = { provide: 'ep:users/lists/update', useClass: ep___users_lists_update.default };
 const $users_lists_favorite: Provider = { provide: 'ep:users/lists/favorite', useClass: ep___users_lists_favorite.default };
 const $users_lists_unfavorite: Provider = { provide: 'ep:users/lists/unfavorite', useClass: ep___users_lists_unfavorite.default };
-const $users_lists_createFromPublic: Provider = { provide: 'ep:users/lists/create-from-public', useClass: ep___users_lists_createFromPublic.default };
-const $users_lists_updateMembership: Provider = { provide: 'ep:users/lists/update-membership', useClass: ep___users_lists_updateMembership.default };
-const $users_lists_getMemberships: Provider = { provide: 'ep:users/lists/get-memberships', useClass: ep___users_lists_getMemberships.default };
+const $users_lists_create_from_public: Provider = { provide: 'ep:users/lists/create-from-public', useClass: ep___users_lists_create_from_public.default };
 const $users_notes: Provider = { provide: 'ep:users/notes', useClass: ep___users_notes.default };
 const $users_pages: Provider = { provide: 'ep:users/pages', useClass: ep___users_pages.default };
 const $users_flashs: Provider = { provide: 'ep:users/flashs', useClass: ep___users_flashs.default };
@@ -911,6 +909,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
 		$i_favorites,
 		$i_gallery_likes,
 		$i_gallery_posts,
+		$i_getWordMutedNotesCount,
 		$i_importBlocking,
 		$i_importFollowing,
 		$i_importMuting,
@@ -1042,9 +1041,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
 		$users_lists_update,
 		$users_lists_favorite,
 		$users_lists_unfavorite,
-		$users_lists_createFromPublic,
-		$users_lists_updateMembership,
-		$users_lists_getMemberships,
+		$users_lists_create_from_public,
 		$users_notes,
 		$users_pages,
 		$users_flashs,
@@ -1260,6 +1257,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
 		$i_favorites,
 		$i_gallery_likes,
 		$i_gallery_posts,
+		$i_getWordMutedNotesCount,
 		$i_importBlocking,
 		$i_importFollowing,
 		$i_importMuting,
@@ -1388,9 +1386,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
 		$users_lists_update,
 		$users_lists_favorite,
 		$users_lists_unfavorite,
-		$users_lists_createFromPublic,
-		$users_lists_updateMembership,
-		$users_lists_getMemberships,
+		$users_lists_create_from_public,
 		$users_notes,
 		$users_pages,
 		$users_flashs,
diff --git a/packages/backend/src/server/api/StreamingApiServerService.ts b/packages/backend/src/server/api/StreamingApiServerService.ts
index badcec1b33..9acaa688c5 100644
--- a/packages/backend/src/server/api/StreamingApiServerService.ts
+++ b/packages/backend/src/server/api/StreamingApiServerService.ts
@@ -14,7 +14,6 @@ import { NotificationService } from '@/core/NotificationService.js';
 import { bindThis } from '@/decorators.js';
 import { CacheService } from '@/core/CacheService.js';
 import { MiLocalUser } from '@/models/User.js';
-import { UserService } from '@/core/UserService.js';
 import { AuthenticateService, AuthenticationError } from './AuthenticateService.js';
 import MainStreamConnection from './stream/Connection.js';
 import { ChannelsService } from './stream/ChannelsService.js';
@@ -38,7 +37,6 @@ export class StreamingApiServerService {
 		private authenticateService: AuthenticateService,
 		private channelsService: ChannelsService,
 		private notificationService: NotificationService,
-		private usersService: UserService,
 	) {
 	}
 
@@ -132,10 +130,14 @@ export class StreamingApiServerService {
 			this.#connections.set(connection, Date.now());
 
 			const userUpdateIntervalId = user ? setInterval(() => {
-				this.usersService.updateLastActiveDate(user);
+				this.usersRepository.update(user.id, {
+					lastActiveDate: new Date(),
+				});
 			}, 1000 * 60 * 5) : null;
 			if (user) {
-				this.usersService.updateLastActiveDate(user);
+				this.usersRepository.update(user.id, {
+					lastActiveDate: new Date(),
+				});
 			}
 
 			connection.once('close', () => {
diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts
index 279d56d1e5..5ea4121918 100644
--- a/packages/backend/src/server/api/endpoints.ts
+++ b/packages/backend/src/server/api/endpoints.ts
@@ -205,6 +205,7 @@ import * as ep___i_exportAntennas from './endpoints/i/export-antennas.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';
@@ -335,10 +336,8 @@ import * as ep___users_lists_push from './endpoints/users/lists/push.js';
 import * as ep___users_lists_show from './endpoints/users/lists/show.js';
 import * as ep___users_lists_favorite from './endpoints/users/lists/favorite.js';
 import * as ep___users_lists_unfavorite from './endpoints/users/lists/unfavorite.js';
-import * as ep___users_lists_createFromPublic from './endpoints/users/lists/create-from-public.js';
+import * as ep___users_lists_create_from_public from './endpoints/users/lists/create-from-public.js';
 import * as ep___users_lists_update from './endpoints/users/lists/update.js';
-import * as ep___users_lists_updateMembership from './endpoints/users/lists/update-membership.js';
-import * as ep___users_lists_getMemberships from './endpoints/users/lists/get-memberships.js';
 import * as ep___users_notes from './endpoints/users/notes.js';
 import * as ep___users_pages from './endpoints/users/pages.js';
 import * as ep___users_flashs from './endpoints/users/flashs.js';
@@ -554,6 +553,7 @@ 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],
@@ -685,9 +685,7 @@ const eps = [
 	['users/lists/favorite', ep___users_lists_favorite],
 	['users/lists/unfavorite', ep___users_lists_unfavorite],
 	['users/lists/update', ep___users_lists_update],
-	['users/lists/create-from-public', ep___users_lists_createFromPublic],
-	['users/lists/update-membership', ep___users_lists_updateMembership],
-	['users/lists/get-memberships', ep___users_lists_getMemberships],
+	['users/lists/create-from-public', ep___users_lists_create_from_public],
 	['users/notes', ep___users_notes],
 	['users/pages', ep___users_pages],
 	['users/flashs', ep___users_flashs],
diff --git a/packages/backend/src/server/api/endpoints/antennas/notes.ts b/packages/backend/src/server/api/endpoints/antennas/notes.ts
index 63e542cb62..eaae7bff62 100644
--- a/packages/backend/src/server/api/endpoints/antennas/notes.ts
+++ b/packages/backend/src/server/api/endpoints/antennas/notes.ts
@@ -56,8 +56,8 @@ export const paramDef = {
 @Injectable()
 export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
 	constructor(
-		@Inject(DI.redisForTimelines)
-		private redisForTimelines: Redis.Redis,
+		@Inject(DI.redis)
+		private redisClient: Redis.Redis,
 
 		@Inject(DI.notesRepository)
 		private notesRepository: NotesRepository,
@@ -86,7 +86,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 			});
 
 			const limit = ps.limit + (ps.untilId ? 1 : 0) + (ps.sinceId ? 1 : 0); // untilIdに指定したものも含まれるため+1
-			const noteIdsRes = await this.redisForTimelines.xrevrange(
+			const noteIdsRes = await this.redisClient.xrevrange(
 				`antennaTimeline:${antenna.id}`,
 				ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : ps.untilDate ?? '+',
 				ps.sinceId ? this.idService.parse(ps.sinceId).date.getTime() : ps.sinceDate ?? '-',
diff --git a/packages/backend/src/server/api/endpoints/channels/timeline.ts b/packages/backend/src/server/api/endpoints/channels/timeline.ts
index 56b8fc5c36..026b649537 100644
--- a/packages/backend/src/server/api/endpoints/channels/timeline.ts
+++ b/packages/backend/src/server/api/endpoints/channels/timeline.ts
@@ -54,8 +54,8 @@ export const paramDef = {
 @Injectable()
 export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
 	constructor(
-		@Inject(DI.redisForTimelines)
-		private redisForTimelines: Redis.Redis,
+		@Inject(DI.redis)
+		private redisClient: Redis.Redis,
 
 		@Inject(DI.notesRepository)
 		private notesRepository: NotesRepository,
@@ -83,7 +83,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 			let noteIdsRes: [string, string[]][] = [];
 
 			if (!ps.sinceId && !ps.sinceDate) {
-				noteIdsRes = await this.redisForTimelines.xrevrange(
+				noteIdsRes = await this.redisClient.xrevrange(
 					`channelTimeline:${channel.id}`,
 					ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : ps.untilDate ?? '+',
 					'-',
@@ -104,6 +104,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 
 				if (me) {
 					this.queryService.generateMutedUserQuery(query, me);
+					this.queryService.generateMutedNoteQuery(query, me);
 					this.queryService.generateBlockedUserQuery(query, me);
 				}
 				//#endregion
@@ -128,6 +129,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 
 				if (me) {
 					this.queryService.generateMutedUserQuery(query, me);
+					this.queryService.generateMutedNoteQuery(query, me);
 					this.queryService.generateBlockedUserQuery(query, me);
 				}
 				//#endregion
diff --git a/packages/backend/src/server/api/endpoints/following/update.ts b/packages/backend/src/server/api/endpoints/following/update.ts
index db17d151df..25f393e517 100644
--- a/packages/backend/src/server/api/endpoints/following/update.ts
+++ b/packages/backend/src/server/api/endpoints/following/update.ts
@@ -57,9 +57,8 @@ export const paramDef = {
 	properties: {
 		userId: { type: 'string', format: 'misskey:id' },
 		notify: { type: 'string', enum: ['normal', 'none'] },
-		withReplies: { type: 'boolean' },
 	},
-	required: ['userId'],
+	required: ['userId', 'notify'],
 } as const;
 
 @Injectable()
@@ -99,8 +98,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 			await this.followingsRepository.update({
 				id: exist.id,
 			}, {
-				notify: ps.notify != null ? (ps.notify === 'none' ? null : ps.notify) : undefined,
-				withReplies: ps.withReplies != null ? ps.withReplies : undefined,
+				notify: ps.notify === 'none' ? null : ps.notify,
 			});
 
 			return await this.userEntityService.pack(follower.id, me);
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
new file mode 100644
index 0000000000..d62bfbb3ed
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/i/get-word-muted-notes-count.ts
@@ -0,0 +1,51 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Inject, Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import type { MutedNotesRepository } from '@/models/_.js';
+import { DI } from '@/di-symbols.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;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+	constructor(
+		@Inject(DI.mutedNotesRepository)
+		private mutedNotesRepository: MutedNotesRepository,
+	) {
+		super(meta, paramDef, async (ps, me) => {
+			return {
+				count: await this.mutedNotesRepository.countBy({
+					userId: me.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 e5a86905d6..8784e86153 100644
--- a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts
+++ b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts
@@ -40,6 +40,7 @@ export const paramDef = {
 	type: 'object',
 	properties: {
 		withFiles: { type: 'boolean', default: false },
+		withReplies: { type: 'boolean', default: false },
 		withRenotes: { type: 'boolean', default: true },
 		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
 		sinceId: { type: 'string', format: 'misskey:id' },
@@ -67,8 +68,49 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				throw new ApiError(meta.errors.gtlDisabled);
 			}
 
-			// TODO?
-			return [];
+			//#region Construct query
+			const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'),
+				ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
+				.andWhere('note.visibility = \'public\'')
+				.andWhere('note.channelId IS NULL')
+				.innerJoinAndSelect('note.user', 'user')
+				.leftJoinAndSelect('note.reply', 'reply')
+				.leftJoinAndSelect('note.renote', 'renote')
+				.leftJoinAndSelect('reply.user', 'replyUser')
+				.leftJoinAndSelect('renote.user', 'renoteUser');
+
+			this.queryService.generateRepliesQuery(query, ps.withReplies, me);
+			if (me) {
+				this.queryService.generateMutedUserQuery(query, me);
+				this.queryService.generateMutedNoteQuery(query, me);
+				this.queryService.generateBlockedUserQuery(query, me);
+				this.queryService.generateMutedUserRenotesQueryForNotes(query, me);
+			}
+
+			if (ps.withFiles) {
+				query.andWhere('note.fileIds != \'{}\'');
+			}
+
+			if (ps.withRenotes === false) {
+				query.andWhere(new Brackets(qb => {
+					qb.orWhere('note.renoteId IS NULL');
+					qb.orWhere(new Brackets(qb => {
+						qb.orWhere('note.text IS NOT NULL');
+						qb.orWhere('note.fileIds != \'{}\'');
+					}));
+				}));
+			}
+			//#endregion
+
+			const timeline = await query.limit(ps.limit).getMany();
+
+			process.nextTick(() => {
+				if (me) {
+					this.activeUsersChart.read(me);
+				}
+			});
+
+			return await this.noteEntityService.packMany(timeline, me);
 		});
 	}
 }
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 d6ed3db6e3..9bde5dee21 100644
--- a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts
+++ b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts
@@ -5,16 +5,14 @@
 
 import { Brackets } from 'typeorm';
 import { Inject, Injectable } from '@nestjs/common';
-import * as Redis from 'ioredis';
-import type { NotesRepository, FollowingsRepository, MiNote } from '@/models/_.js';
+import type { NotesRepository, FollowingsRepository } from '@/models/_.js';
 import { Endpoint } from '@/server/api/endpoint-base.js';
+import { QueryService } from '@/core/QueryService.js';
 import ActiveUsersChart from '@/core/chart/charts/active-users.js';
 import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
 import { DI } from '@/di-symbols.js';
 import { RoleService } from '@/core/RoleService.js';
 import { IdService } from '@/core/IdService.js';
-import { isUserRelated } from '@/misc/is-user-related.js';
-import { CacheService } from '@/core/CacheService.js';
 import { ApiError } from '../../error.js';
 
 export const meta = {
@@ -53,6 +51,7 @@ export const paramDef = {
 		includeRenotedMyNotes: { type: 'boolean', default: true },
 		includeLocalRenotes: { type: 'boolean', default: true },
 		withFiles: { type: 'boolean', default: false },
+		withReplies: { type: 'boolean', default: false },
 		withRenotes: { type: 'boolean', default: true },
 	},
 	required: [],
@@ -61,17 +60,17 @@ export const paramDef = {
 @Injectable()
 export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
 	constructor(
-		@Inject(DI.redisForTimelines)
-		private redisForTimelines: Redis.Redis,
-
 		@Inject(DI.notesRepository)
 		private notesRepository: NotesRepository,
 
+		@Inject(DI.followingsRepository)
+		private followingsRepository: FollowingsRepository,
+
 		private noteEntityService: NoteEntityService,
+		private queryService: QueryService,
 		private roleService: RoleService,
 		private activeUsersChart: ActiveUsersChart,
 		private idService: IdService,
-		private cacheService: CacheService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
 			const policies = await this.roleService.getUserPolicies(me.id);
@@ -79,75 +78,79 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				throw new ApiError(meta.errors.stlDisabled);
 			}
 
-			const [
-				userIdsWhoMeMuting,
-				userIdsWhoMeMutingRenotes,
-				userIdsWhoBlockingMe,
-			] = await Promise.all([
-				this.cacheService.userMutingsCache.fetch(me.id),
-				this.cacheService.renoteMutingsCache.fetch(me.id),
-				this.cacheService.userBlockedCache.fetch(me.id),
-			]);
+			//#region Construct query
+			const followingQuery = this.followingsRepository.createQueryBuilder('following')
+				.select('following.followeeId')
+				.where('following.followerId = :followerId', { followerId: me.id });
 
-			let timeline: MiNote[] = [];
-
-			const limit = ps.limit + (ps.untilId ? 1 : 0); // untilIdに指定したものも含まれるため+1
-			let htlNoteIdsRes: [string, string[]][] = [];
-			let ltlNoteIdsRes: [string, string[]][] = [];
-
-			if (!ps.sinceId && !ps.sinceDate) {
-				htlNoteIdsRes = await this.redisForTimelines.xrevrange(
-					ps.withFiles ? `homeTimelineWithFiles:${me.id}` : `homeTimeline:${me.id}`,
-					ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : ps.untilDate ?? '+',
-					'-',
-					'COUNT', limit);
-				ltlNoteIdsRes = await this.redisForTimelines.xrevrange(
-					ps.withFiles ? 'localTimelineWithFiles' : 'localTimeline',
-					ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : ps.untilDate ?? '+',
-					'-',
-					'COUNT', limit);
-			}
-
-			const htlNoteIds = htlNoteIdsRes.map(x => x[1][1]).filter(x => x !== ps.untilId);
-			const ltlNoteIds = ltlNoteIdsRes.map(x => x[1][1]).filter(x => x !== ps.untilId);
-			let noteIds = Array.from(new Set([...htlNoteIds, ...ltlNoteIds]));
-			noteIds.sort((a, b) => a > b ? -1 : 1);
-			noteIds = noteIds.slice(0, ps.limit);
-
-			if (noteIds.length === 0) {
-				return [];
-			}
-
-			const query = this.notesRepository.createQueryBuilder('note')
-				.where('note.id IN (:...noteIds)', { noteIds: noteIds })
+			const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'),
+				ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
+				.andWhere('note.id > :minId', { minId: this.idService.genId(new Date(Date.now() - (1000 * 60 * 60 * 24 * 10))) }) // 10日前まで
+				.andWhere(new Brackets(qb => {
+					qb.where(`((note.userId IN (${ followingQuery.getQuery() })) OR (note.userId = :meId))`, { meId: me.id })
+						.orWhere('(note.visibility = \'public\') AND (note.userHost IS NULL)');
+				}))
 				.innerJoinAndSelect('note.user', 'user')
 				.leftJoinAndSelect('note.reply', 'reply')
 				.leftJoinAndSelect('note.renote', 'renote')
 				.leftJoinAndSelect('reply.user', 'replyUser')
 				.leftJoinAndSelect('renote.user', 'renoteUser')
-				.leftJoinAndSelect('note.channel', 'channel');
+				.setParameters(followingQuery.getParameters());
 
-			timeline = await query.getMany();
+			this.queryService.generateChannelQuery(query, me);
+			this.queryService.generateRepliesQuery(query, ps.withReplies, me);
+			this.queryService.generateVisibilityQuery(query, me);
+			this.queryService.generateMutedUserQuery(query, me);
+			this.queryService.generateMutedNoteQuery(query, me);
+			this.queryService.generateBlockedUserQuery(query, me);
+			this.queryService.generateMutedUserRenotesQueryForNotes(query, me);
 
-			timeline = timeline.filter(note => {
-				if (note.userId === me.id) {
-					return true;
-				}
-				if (isUserRelated(note, userIdsWhoBlockingMe)) return false;
-				if (isUserRelated(note, userIdsWhoMeMuting)) return false;
-				if (note.renoteId) {
-					if (note.text == null && note.fileIds.length === 0 && !note.hasPoll) {
-						if (isUserRelated(note, userIdsWhoMeMutingRenotes)) return false;
-						if (ps.withRenotes === false) return false;
-					}
-				}
+			if (ps.includeMyRenotes === false) {
+				query.andWhere(new Brackets(qb => {
+					qb.orWhere('note.userId != :meId', { meId: me.id });
+					qb.orWhere('note.renoteId IS NULL');
+					qb.orWhere('note.text IS NOT NULL');
+					qb.orWhere('note.fileIds != \'{}\'');
+					qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)');
+				}));
+			}
 
-				return true;
-			});
+			if (ps.includeRenotedMyNotes === false) {
+				query.andWhere(new Brackets(qb => {
+					qb.orWhere('note.renoteUserId != :meId', { meId: me.id });
+					qb.orWhere('note.renoteId IS NULL');
+					qb.orWhere('note.text IS NOT NULL');
+					qb.orWhere('note.fileIds != \'{}\'');
+					qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)');
+				}));
+			}
 
-			// TODO: フィルタした結果件数が足りなかった場合の対応
+			if (ps.includeLocalRenotes === false) {
+				query.andWhere(new Brackets(qb => {
+					qb.orWhere('note.renoteUserHost IS NOT NULL');
+					qb.orWhere('note.renoteId IS NULL');
+					qb.orWhere('note.text IS NOT NULL');
+					qb.orWhere('note.fileIds != \'{}\'');
+					qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)');
+				}));
+			}
 
-			timeline.sort((a, b) => a.id > b.id ? -1 : 1);
+			if (ps.withFiles) {
+				query.andWhere('note.fileIds != \'{}\'');
+			}
+
+			if (ps.withRenotes === false) {
+				query.andWhere(new Brackets(qb => {
+					qb.orWhere('note.renoteId IS NULL');
+					qb.orWhere(new Brackets(qb => {
+						qb.orWhere('note.text IS NOT NULL');
+						qb.orWhere('note.fileIds != \'{}\'');
+					}));
+				}));
+			}
+			//#endregion
+
+			const timeline = await query.limit(ps.limit).getMany();
 
 			process.nextTick(() => {
 				this.activeUsersChart.read(me);
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 ed57ca1a30..0fefddc51b 100644
--- a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts
+++ b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts
@@ -5,16 +5,14 @@
 
 import { Brackets } from 'typeorm';
 import { Inject, Injectable } from '@nestjs/common';
-import * as Redis from 'ioredis';
-import type { MiNote, NotesRepository } from '@/models/_.js';
+import type { NotesRepository } from '@/models/_.js';
 import { Endpoint } from '@/server/api/endpoint-base.js';
+import { QueryService } from '@/core/QueryService.js';
 import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
 import ActiveUsersChart from '@/core/chart/charts/active-users.js';
 import { DI } from '@/di-symbols.js';
 import { RoleService } from '@/core/RoleService.js';
 import { IdService } from '@/core/IdService.js';
-import { CacheService } from '@/core/CacheService.js';
-import { isUserRelated } from '@/misc/is-user-related.js';
 import { ApiError } from '../../error.js';
 
 export const meta = {
@@ -43,7 +41,11 @@ export const paramDef = {
 	type: 'object',
 	properties: {
 		withFiles: { type: 'boolean', default: false },
+		withReplies: { type: 'boolean', default: false },
 		withRenotes: { type: 'boolean', default: true },
+		fileType: { type: 'array', items: {
+			type: 'string',
+		} },
 		excludeNsfw: { type: 'boolean', default: false },
 		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
 		sinceId: { type: 'string', format: 'misskey:id' },
@@ -57,17 +59,14 @@ export const paramDef = {
 @Injectable()
 export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
 	constructor(
-		@Inject(DI.redisForTimelines)
-		private redisForTimelines: Redis.Redis,
-
 		@Inject(DI.notesRepository)
 		private notesRepository: NotesRepository,
 
 		private noteEntityService: NoteEntityService,
+		private queryService: QueryService,
 		private roleService: RoleService,
 		private activeUsersChart: ActiveUsersChart,
 		private idService: IdService,
-		private cacheService: CacheService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
 			const policies = await this.roleService.getUserPolicies(me ? me.id : null);
@@ -75,63 +74,56 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				throw new ApiError(meta.errors.ltlDisabled);
 			}
 
-			const [
-				userIdsWhoMeMuting,
-				userIdsWhoMeMutingRenotes,
-				userIdsWhoBlockingMe,
-			] = me ? await Promise.all([
-				this.cacheService.userMutingsCache.fetch(me.id),
-				this.cacheService.renoteMutingsCache.fetch(me.id),
-				this.cacheService.userBlockedCache.fetch(me.id),
-			]) : [new Set<string>(), new Set<string>(), new Set<string>()];
-
-			let timeline: MiNote[] = [];
-
-			const limit = ps.limit + (ps.untilId ? 1 : 0); // untilIdに指定したものも含まれるため+1
-			let noteIdsRes: [string, string[]][] = [];
-
-			if (!ps.sinceId && !ps.sinceDate) {
-				noteIdsRes = await this.redisForTimelines.xrevrange(
-					ps.withFiles ? 'localTimelineWithFiles' : 'localTimeline',
-					ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : ps.untilDate ?? '+',
-					'-',
-					'COUNT', limit);
-			}
-
-			const noteIds = noteIdsRes.map(x => x[1][1]).filter(x => x !== ps.untilId);
-
-			if (noteIds.length === 0) {
-				return [];
-			}
-
-			const query = this.notesRepository.createQueryBuilder('note')
-				.where('note.id IN (:...noteIds)', { noteIds: noteIds })
+			//#region Construct query
+			const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'),
+				ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
+				.andWhere('note.id > :minId', { minId: this.idService.genId(new Date(Date.now() - (1000 * 60 * 60 * 24 * 10))) }) // 10日前まで
+				.andWhere('(note.visibility = \'public\') AND (note.userHost IS NULL)')
 				.innerJoinAndSelect('note.user', 'user')
 				.leftJoinAndSelect('note.reply', 'reply')
 				.leftJoinAndSelect('note.renote', 'renote')
 				.leftJoinAndSelect('reply.user', 'replyUser')
-				.leftJoinAndSelect('renote.user', 'renoteUser')
-				.leftJoinAndSelect('note.channel', 'channel');
+				.leftJoinAndSelect('renote.user', 'renoteUser');
 
-			timeline = await query.getMany();
+			this.queryService.generateChannelQuery(query, me);
+			this.queryService.generateRepliesQuery(query, ps.withReplies, me);
+			this.queryService.generateVisibilityQuery(query, me);
+			if (me) this.queryService.generateMutedUserQuery(query, me);
+			if (me) this.queryService.generateMutedNoteQuery(query, me);
+			if (me) this.queryService.generateBlockedUserQuery(query, me);
+			if (me) this.queryService.generateMutedUserRenotesQueryForNotes(query, me);
 
-			timeline = timeline.filter(note => {
-				if (me && (note.userId === me.id)) {
-					return true;
-				}
-				if (me && isUserRelated(note, userIdsWhoBlockingMe)) return false;
-				if (me && isUserRelated(note, userIdsWhoMeMuting)) return false;
-				if (note.renoteId) {
-					if (note.text == null && note.fileIds.length === 0 && !note.hasPoll) {
-						if (me && isUserRelated(note, userIdsWhoMeMutingRenotes)) return false;
-						if (ps.withRenotes === false) return false;
+			if (ps.withFiles) {
+				query.andWhere('note.fileIds != \'{}\'');
+			}
+
+			if (ps.fileType != null) {
+				query.andWhere('note.fileIds != \'{}\'');
+				query.andWhere(new Brackets(qb => {
+					for (const type of ps.fileType!) {
+						const i = ps.fileType!.indexOf(type);
+						qb.orWhere(`:type${i} = ANY(note.attachedFileTypes)`, { [`type${i}`]: type });
 					}
+				}));
+
+				if (ps.excludeNsfw) {
+					query.andWhere('note.cw IS NULL');
+					query.andWhere('0 = (SELECT COUNT(*) FROM drive_file df WHERE df.id = ANY(note."fileIds") AND df."isSensitive" = TRUE)');
 				}
+			}
 
-				return true;
-			});
+			if (ps.withRenotes === false) {
+				query.andWhere(new Brackets(qb => {
+					qb.orWhere('note.renoteId IS NULL');
+					qb.orWhere(new Brackets(qb => {
+						qb.orWhere('note.text IS NOT NULL');
+						qb.orWhere('note.fileIds != \'{}\'');
+					}));
+				}));
+			}
+			//#endregion
 
-			timeline.sort((a, b) => a.id > b.id ? -1 : 1);
+			const timeline = await query.limit(ps.limit).getMany();
 
 			process.nextTick(() => {
 				if (me) {
diff --git a/packages/backend/src/server/api/endpoints/notes/timeline.ts b/packages/backend/src/server/api/endpoints/notes/timeline.ts
index 2f25d2d7ce..0d47cc1702 100644
--- a/packages/backend/src/server/api/endpoints/notes/timeline.ts
+++ b/packages/backend/src/server/api/endpoints/notes/timeline.ts
@@ -5,16 +5,13 @@
 
 import { Brackets } from 'typeorm';
 import { Inject, Injectable } from '@nestjs/common';
-import * as Redis from 'ioredis';
-import type { NotesRepository, FollowingsRepository, MiNote } from '@/models/_.js';
+import type { NotesRepository, FollowingsRepository } from '@/models/_.js';
 import { Endpoint } from '@/server/api/endpoint-base.js';
 import { QueryService } from '@/core/QueryService.js';
 import ActiveUsersChart from '@/core/chart/charts/active-users.js';
 import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
 import { DI } from '@/di-symbols.js';
 import { IdService } from '@/core/IdService.js';
-import { CacheService } from '@/core/CacheService.js';
-import { isUserRelated } from '@/misc/is-user-related.js';
 
 export const meta = {
 	tags: ['notes'],
@@ -44,6 +41,7 @@ export const paramDef = {
 		includeRenotedMyNotes: { type: 'boolean', default: true },
 		includeLocalRenotes: { type: 'boolean', default: true },
 		withFiles: { type: 'boolean', default: false },
+		withReplies: { type: 'boolean', default: false },
 		withRenotes: { type: 'boolean', default: true },
 	},
 	required: [],
@@ -52,82 +50,96 @@ export const paramDef = {
 @Injectable()
 export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
 	constructor(
-		@Inject(DI.redisForTimelines)
-		private redisForTimelines: Redis.Redis,
-
 		@Inject(DI.notesRepository)
 		private notesRepository: NotesRepository,
 
+		@Inject(DI.followingsRepository)
+		private followingsRepository: FollowingsRepository,
+
 		private noteEntityService: NoteEntityService,
+		private queryService: QueryService,
 		private activeUsersChart: ActiveUsersChart,
 		private idService: IdService,
-		private cacheService: CacheService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
-			const [
-				followings,
-				userIdsWhoMeMuting,
-				userIdsWhoMeMutingRenotes,
-				userIdsWhoBlockingMe,
-			] = await Promise.all([
-				this.cacheService.userFollowingsCache.fetch(me.id),
-				this.cacheService.userMutingsCache.fetch(me.id),
-				this.cacheService.renoteMutingsCache.fetch(me.id),
-				this.cacheService.userBlockedCache.fetch(me.id),
-			]);
+			const followees = await this.followingsRepository.createQueryBuilder('following')
+				.select('following.followeeId')
+				.where('following.followerId = :followerId', { followerId: me.id })
+				.getMany();
 
-			let timeline: MiNote[] = [];
-
-			const limit = ps.limit + (ps.untilId ? 1 : 0); // untilIdに指定したものも含まれるため+1
-			let noteIdsRes: [string, string[]][] = [];
-
-			if (!ps.sinceId && !ps.sinceDate) {
-				noteIdsRes = await this.redisForTimelines.xrevrange(
-					ps.withFiles ? `homeTimelineWithFiles:${me.id}` : `homeTimeline:${me.id}`,
-					ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : ps.untilDate ?? '+',
-					'-',
-					'COUNT', limit);
-			}
-
-			const noteIds = noteIdsRes.map(x => x[1][1]).filter(x => x !== ps.untilId);
-
-			if (noteIds.length === 0) {
-				return [];
-			}
-
-			const query = this.notesRepository.createQueryBuilder('note')
-				.where('note.id IN (:...noteIds)', { noteIds: noteIds })
+			//#region Construct query
+			const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'),
+				ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
+				// パフォーマンス上の利点が無さそう?
+				//.andWhere('note.id > :minId', { minId: this.idService.genId(new Date(Date.now() - (1000 * 60 * 60 * 24 * 10))) }) // 10日前まで
 				.innerJoinAndSelect('note.user', 'user')
 				.leftJoinAndSelect('note.reply', 'reply')
 				.leftJoinAndSelect('note.renote', 'renote')
 				.leftJoinAndSelect('reply.user', 'replyUser')
-				.leftJoinAndSelect('renote.user', 'renoteUser')
-				.leftJoinAndSelect('note.channel', 'channel');
+				.leftJoinAndSelect('renote.user', 'renoteUser');
 
-			timeline = await query.getMany();
+			if (followees.length > 0) {
+				const meOrFolloweeIds = [me.id, ...followees.map(f => f.followeeId)];
 
-			timeline = timeline.filter(note => {
-				if (note.userId === me.id) {
-					return true;
-				}
-				if (isUserRelated(note, userIdsWhoBlockingMe)) return false;
-				if (isUserRelated(note, userIdsWhoMeMuting)) return false;
-				if (note.renoteId) {
-					if (note.text == null && note.fileIds.length === 0 && !note.hasPoll) {
-						if (isUserRelated(note, userIdsWhoMeMutingRenotes)) return false;
-						if (ps.withRenotes === false) return false;
-					}
-				}
-				if (note.reply && note.reply.visibility === 'followers') {
-					if (!Object.hasOwn(followings, note.reply.userId)) return false;
-				}
+				query.andWhere('note.userId IN (:...meOrFolloweeIds)', { meOrFolloweeIds: meOrFolloweeIds });
+			} else {
+				query.andWhere('note.userId = :meId', { meId: me.id });
+			}
 
-				return true;
-			});
+			this.queryService.generateChannelQuery(query, me);
+			this.queryService.generateRepliesQuery(query, ps.withReplies, me);
+			this.queryService.generateVisibilityQuery(query, me);
+			this.queryService.generateMutedUserQuery(query, me);
+			this.queryService.generateMutedNoteQuery(query, me);
+			this.queryService.generateBlockedUserQuery(query, me);
+			this.queryService.generateMutedUserRenotesQueryForNotes(query, me);
 
-			// TODO: フィルタした結果件数が足りなかった場合の対応
+			if (ps.includeMyRenotes === false) {
+				query.andWhere(new Brackets(qb => {
+					qb.orWhere('note.userId != :meId', { meId: me.id });
+					qb.orWhere('note.renoteId IS NULL');
+					qb.orWhere('note.text IS NOT NULL');
+					qb.orWhere('note.fileIds != \'{}\'');
+					qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)');
+				}));
+			}
 
-			timeline.sort((a, b) => a.id > b.id ? -1 : 1);
+			if (ps.includeRenotedMyNotes === false) {
+				query.andWhere(new Brackets(qb => {
+					qb.orWhere('note.renoteUserId != :meId', { meId: me.id });
+					qb.orWhere('note.renoteId IS NULL');
+					qb.orWhere('note.text IS NOT NULL');
+					qb.orWhere('note.fileIds != \'{}\'');
+					qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)');
+				}));
+			}
+
+			if (ps.includeLocalRenotes === false) {
+				query.andWhere(new Brackets(qb => {
+					qb.orWhere('note.renoteUserHost IS NOT NULL');
+					qb.orWhere('note.renoteId IS NULL');
+					qb.orWhere('note.text IS NOT NULL');
+					qb.orWhere('note.fileIds != \'{}\'');
+					qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)');
+				}));
+			}
+
+			if (ps.withFiles) {
+				query.andWhere('note.fileIds != \'{}\'');
+			}
+
+			if (ps.withRenotes === false) {
+				query.andWhere(new Brackets(qb => {
+					qb.orWhere('note.renoteId IS NULL');
+					qb.orWhere(new Brackets(qb => {
+						qb.orWhere('note.text IS NOT NULL');
+						qb.orWhere('note.fileIds != \'{}\'');
+					}));
+				}));
+			}
+			//#endregion
+
+			const timeline = await query.limit(ps.limit).getMany();
 
 			process.nextTick(() => {
 				this.activeUsersChart.read(me);
diff --git a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts
index 8e943826d5..c20274b2ba 100644
--- a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts
+++ b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts
@@ -5,16 +5,12 @@
 
 import { Brackets } from 'typeorm';
 import { Inject, Injectable } from '@nestjs/common';
-import * as Redis from 'ioredis';
-import type { NotesRepository, UserListsRepository, UserListMembershipsRepository, MiNote } from '@/models/_.js';
+import type { NotesRepository, UserListsRepository, UserListJoiningsRepository } from '@/models/_.js';
 import { Endpoint } from '@/server/api/endpoint-base.js';
 import { QueryService } from '@/core/QueryService.js';
 import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
 import ActiveUsersChart from '@/core/chart/charts/active-users.js';
 import { DI } from '@/di-symbols.js';
-import { CacheService } from '@/core/CacheService.js';
-import { IdService } from '@/core/IdService.js';
-import { isUserRelated } from '@/misc/is-user-related.js';
 import { ApiError } from '../../error.js';
 
 export const meta = {
@@ -67,19 +63,18 @@ export const paramDef = {
 @Injectable()
 export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
 	constructor(
-		@Inject(DI.redisForTimelines)
-		private redisForTimelines: Redis.Redis,
-
 		@Inject(DI.notesRepository)
 		private notesRepository: NotesRepository,
 
 		@Inject(DI.userListsRepository)
 		private userListsRepository: UserListsRepository,
 
+		@Inject(DI.userListJoiningsRepository)
+		private userListJoiningsRepository: UserListJoiningsRepository,
+
 		private noteEntityService: NoteEntityService,
+		private queryService: QueryService,
 		private activeUsersChart: ActiveUsersChart,
-		private cacheService: CacheService,
-		private idService: IdService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
 			const list = await this.userListsRepository.findOneBy({
@@ -91,65 +86,72 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				throw new ApiError(meta.errors.noSuchList);
 			}
 
-			const [
-				userIdsWhoMeMuting,
-				userIdsWhoMeMutingRenotes,
-				userIdsWhoBlockingMe,
-			] = await Promise.all([
-				this.cacheService.userMutingsCache.fetch(me.id),
-				this.cacheService.renoteMutingsCache.fetch(me.id),
-				this.cacheService.userBlockedCache.fetch(me.id),
-			]);
-
-			let timeline: MiNote[] = [];
-
-			const limit = ps.limit + (ps.untilId ? 1 : 0); // untilIdに指定したものも含まれるため+1
-			let noteIdsRes: [string, string[]][] = [];
-
-			if (!ps.sinceId && !ps.sinceDate) {
-				noteIdsRes = await this.redisForTimelines.xrevrange(
-					ps.withFiles ? `userListTimelineWithFiles:${list.id}` : `userListTimeline:${list.id}`,
-					ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : ps.untilDate ?? '+',
-					'-',
-					'COUNT', limit);
-			}
-
-			const noteIds = noteIdsRes.map(x => x[1][1]).filter(x => x !== ps.untilId);
-
-			if (noteIds.length === 0) {
-				return [];
-			}
-
-			const query = this.notesRepository.createQueryBuilder('note')
-				.where('note.id IN (:...noteIds)', { noteIds: noteIds })
+			//#region Construct query
+			const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId)
+				.innerJoin(this.userListJoiningsRepository.metadata.targetName, 'userListJoining', 'userListJoining.userId = note.userId')
 				.innerJoinAndSelect('note.user', 'user')
 				.leftJoinAndSelect('note.reply', 'reply')
 				.leftJoinAndSelect('note.renote', 'renote')
 				.leftJoinAndSelect('reply.user', 'replyUser')
 				.leftJoinAndSelect('renote.user', 'renoteUser')
-				.leftJoinAndSelect('note.channel', 'channel');
+				.andWhere('userListJoining.userListId = :userListId', { userListId: list.id });
 
-			timeline = await query.getMany();
+			this.queryService.generateVisibilityQuery(query, me);
+			this.queryService.generateMutedUserQuery(query, me);
+			this.queryService.generateMutedNoteQuery(query, me);
+			this.queryService.generateBlockedUserQuery(query, me);
+			this.queryService.generateMutedUserRenotesQueryForNotes(query, me);
 
-			timeline = timeline.filter(note => {
-				if (note.userId === me.id) {
-					return true;
-				}
-				if (isUserRelated(note, userIdsWhoBlockingMe)) return false;
-				if (isUserRelated(note, userIdsWhoMeMuting)) return false;
-				if (note.renoteId) {
-					if (note.text == null && note.fileIds.length === 0 && !note.hasPoll) {
-						if (isUserRelated(note, userIdsWhoMeMutingRenotes)) return false;
-						if (ps.withRenotes === false) return false;
-					}
-				}
+			if (ps.includeMyRenotes === false) {
+				query.andWhere(new Brackets(qb => {
+					qb.orWhere('note.userId != :meId', { meId: me.id });
+					qb.orWhere('note.renoteId IS NULL');
+					qb.orWhere('note.text IS NOT NULL');
+					qb.orWhere('note.fileIds != \'{}\'');
+					qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)');
+				}));
+			}
 
-				return true;
-			});
+			if (ps.includeRenotedMyNotes === false) {
+				query.andWhere(new Brackets(qb => {
+					qb.orWhere('note.renoteUserId != :meId', { meId: me.id });
+					qb.orWhere('note.renoteId IS NULL');
+					qb.orWhere('note.text IS NOT NULL');
+					qb.orWhere('note.fileIds != \'{}\'');
+					qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)');
+				}));
+			}
 
-			// TODO: フィルタした結果件数が足りなかった場合の対応
+			if (ps.includeLocalRenotes === false) {
+				query.andWhere(new Brackets(qb => {
+					qb.orWhere('note.renoteUserHost IS NOT NULL');
+					qb.orWhere('note.renoteId IS NULL');
+					qb.orWhere('note.text IS NOT NULL');
+					qb.orWhere('note.fileIds != \'{}\'');
+					qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)');
+				}));
+			}
 
-			timeline.sort((a, b) => a.id > b.id ? -1 : 1);
+			if (!ps.withReplies) {
+				query.andWhere('note.replyId IS NULL');
+			}
+
+			if (ps.withRenotes === false) {
+				query.andWhere(new Brackets(qb => {
+					qb.orWhere('note.renoteId IS NULL');
+					qb.orWhere(new Brackets(qb => {
+						qb.orWhere('note.text IS NOT NULL');
+						qb.orWhere('note.fileIds != \'{}\'');
+					}));
+				}));
+			}
+
+			if (ps.withFiles) {
+				query.andWhere('note.fileIds != \'{}\'');
+			}
+			//#endregion
+
+			const timeline = await query.limit(ps.limit).getMany();
 
 			this.activeUsersChart.read(me);
 
diff --git a/packages/backend/src/server/api/endpoints/roles/notes.ts b/packages/backend/src/server/api/endpoints/roles/notes.ts
index f2533efa36..6dc35907e1 100644
--- a/packages/backend/src/server/api/endpoints/roles/notes.ts
+++ b/packages/backend/src/server/api/endpoints/roles/notes.ts
@@ -53,8 +53,8 @@ export const paramDef = {
 @Injectable()
 export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
 	constructor(
-		@Inject(DI.redisForTimelines)
-		private redisForTimelines: Redis.Redis,
+		@Inject(DI.redis)
+		private redisClient: Redis.Redis,
 
 		@Inject(DI.notesRepository)
 		private notesRepository: NotesRepository,
@@ -79,7 +79,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				return [];
 			}
 			const limit = ps.limit + (ps.untilId ? 1 : 0) + (ps.sinceId ? 1 : 0); // untilIdに指定したものも含まれるため+1
-			const noteIdsRes = await this.redisForTimelines.xrevrange(
+			const noteIdsRes = await this.redisClient.xrevrange(
 				`roleTimeline:${role.id}`,
 				ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : ps.untilDate ?? '+',
 				ps.sinceId ? this.idService.parse(ps.sinceId).date.getTime() : ps.sinceDate ?? '-',
diff --git a/packages/backend/src/server/api/endpoints/users/lists/create-from-public.ts b/packages/backend/src/server/api/endpoints/users/lists/create-from-public.ts
index f2f6c4303a..eae55905d3 100644
--- a/packages/backend/src/server/api/endpoints/users/lists/create-from-public.ts
+++ b/packages/backend/src/server/api/endpoints/users/lists/create-from-public.ts
@@ -4,7 +4,7 @@
  */
 
 import { Inject, Injectable } from '@nestjs/common';
-import type { UserListsRepository, UserListMembershipsRepository, BlockingsRepository } from '@/models/_.js';
+import type { UserListsRepository, UserListJoiningsRepository, BlockingsRepository } from '@/models/_.js';
 import { IdService } from '@/core/IdService.js';
 import type { MiUserList } from '@/models/UserList.js';
 import { Endpoint } from '@/server/api/endpoint-base.js';
@@ -76,8 +76,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 		@Inject(DI.userListsRepository)
 		private userListsRepository: UserListsRepository,
 
-		@Inject(DI.userListMembershipsRepository)
-		private userListMembershipsRepository: UserListMembershipsRepository,
+		@Inject(DI.userListJoiningsRepository)
+		private userListJoiningsRepository: UserListJoiningsRepository,
 
 		@Inject(DI.blockingsRepository)
 		private blockingsRepository: BlockingsRepository,
@@ -110,7 +110,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				name: ps.name,
 			} as MiUserList).then(x => this.userListsRepository.findOneByOrFail(x.identifiers[0]));
 
-			const users = (await this.userListMembershipsRepository.findBy({
+			const users = (await this.userListJoiningsRepository.findBy({
 				userListId: ps.listId,
 			})).map(x => x.userId);
 
@@ -132,7 +132,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 					}
 				}
 
-				const exist = await this.userListMembershipsRepository.exist({
+				const exist = await this.userListJoiningsRepository.exist({
 					where: {
 						userListId: userList.id,
 						userId: currentUser.id,
diff --git a/packages/backend/src/server/api/endpoints/users/lists/get-memberships.ts b/packages/backend/src/server/api/endpoints/users/lists/get-memberships.ts
deleted file mode 100644
index ae8b4e9b81..0000000000
--- a/packages/backend/src/server/api/endpoints/users/lists/get-memberships.ts
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
- * SPDX-License-Identifier: AGPL-3.0-only
- */
-
-import { Inject, Injectable } from '@nestjs/common';
-import type { UserListsRepository, UserListFavoritesRepository, UserListMembershipsRepository } from '@/models/_.js';
-import { Endpoint } from '@/server/api/endpoint-base.js';
-import { UserListEntityService } from '@/core/entities/UserListEntityService.js';
-import { DI } from '@/di-symbols.js';
-import { QueryService } from '@/core/QueryService.js';
-import { ApiError } from '../../../error.js';
-
-export const meta = {
-	tags: ['lists', 'account'],
-
-	requireCredential: false,
-
-	kind: 'read:account',
-
-	errors: {
-		noSuchList: {
-			message: 'No such list.',
-			code: 'NO_SUCH_LIST',
-			id: '7bc05c21-1d7a-41ae-88f1-66820f4dc686',
-		},
-	},
-} as const;
-
-export const paramDef = {
-	type: 'object',
-	properties: {
-		listId: { type: 'string', format: 'misskey:id' },
-		forPublic: { type: 'boolean', default: false },
-		limit: { type: 'integer', minimum: 1, maximum: 100, default: 30 },
-		sinceId: { type: 'string', format: 'misskey:id' },
-		untilId: { type: 'string', format: 'misskey:id' },
-	},
-	required: ['listId'],
-} as const;
-
-@Injectable() // eslint-disable-next-line import/no-default-export
-export default class extends Endpoint<typeof meta, typeof paramDef> {
-	constructor(
-		@Inject(DI.userListsRepository)
-		private userListsRepository: UserListsRepository,
-
-		@Inject(DI.userListMembershipsRepository)
-		private userListMembershipsRepository: UserListMembershipsRepository,
-
-		private userListEntityService: UserListEntityService,
-		private queryService: QueryService,
-	) {
-		super(meta, paramDef, async (ps, me) => {
-			// Fetch the list
-			const userList = await this.userListsRepository.findOneBy(!ps.forPublic && me !== null ? {
-				id: ps.listId,
-				userId: me.id,
-			} : {
-				id: ps.listId,
-				isPublic: true,
-			});
-
-			if (userList == null) {
-				throw new ApiError(meta.errors.noSuchList);
-			}
-
-			const query = this.queryService.makePaginationQuery(this.userListMembershipsRepository.createQueryBuilder('membership'), ps.sinceId, ps.untilId)
-				.andWhere('membership.userListId = :userListId', { userListId: userList.id })
-				.innerJoinAndSelect('membership.user', 'user');
-
-			const memberships = await query
-				.limit(ps.limit)
-				.getMany();
-
-			return this.userListEntityService.packMembershipsMany(memberships);
-		});
-	}
-}
diff --git a/packages/backend/src/server/api/endpoints/users/lists/push.ts b/packages/backend/src/server/api/endpoints/users/lists/push.ts
index c4ceec575b..72a6a7380d 100644
--- a/packages/backend/src/server/api/endpoints/users/lists/push.ts
+++ b/packages/backend/src/server/api/endpoints/users/lists/push.ts
@@ -5,7 +5,7 @@
 
 import { Inject, Injectable } from '@nestjs/common';
 import ms from 'ms';
-import type { UserListsRepository, UserListMembershipsRepository, BlockingsRepository } from '@/models/_.js';
+import type { UserListsRepository, UserListJoiningsRepository, BlockingsRepository } from '@/models/_.js';
 import { Endpoint } from '@/server/api/endpoint-base.js';
 import { GetterService } from '@/server/api/GetterService.js';
 import { UserListService } from '@/core/UserListService.js';
@@ -76,8 +76,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 		@Inject(DI.userListsRepository)
 		private userListsRepository: UserListsRepository,
 
-		@Inject(DI.userListMembershipsRepository)
-		private userListMembershipsRepository: UserListMembershipsRepository,
+		@Inject(DI.userListJoiningsRepository)
+		private userListJoiningsRepository: UserListJoiningsRepository,
 
 		@Inject(DI.blockingsRepository)
 		private blockingsRepository: BlockingsRepository,
@@ -115,7 +115,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				}
 			}
 
-			const exist = await this.userListMembershipsRepository.exist({
+			const exist = await this.userListJoiningsRepository.exist({
 				where: {
 					userListId: userList.id,
 					userId: user.id,
diff --git a/packages/backend/src/server/api/endpoints/users/lists/update-membership.ts b/packages/backend/src/server/api/endpoints/users/lists/update-membership.ts
deleted file mode 100644
index b69465b940..0000000000
--- a/packages/backend/src/server/api/endpoints/users/lists/update-membership.ts
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
- * SPDX-License-Identifier: AGPL-3.0-only
- */
-
-import { Inject, Injectable } from '@nestjs/common';
-import type { UserListsRepository } from '@/models/_.js';
-import { Endpoint } from '@/server/api/endpoint-base.js';
-import { GetterService } from '@/server/api/GetterService.js';
-import { DI } from '@/di-symbols.js';
-import { UserListService } from '@/core/UserListService.js';
-import { ApiError } from '../../../error.js';
-
-export const meta = {
-	tags: ['lists', 'users'],
-
-	requireCredential: true,
-
-	prohibitMoved: true,
-
-	kind: 'write:account',
-
-	errors: {
-		noSuchList: {
-			message: 'No such list.',
-			code: 'NO_SUCH_LIST',
-			id: '7f44670e-ab16-43b8-b4c1-ccd2ee89cc02',
-		},
-
-		noSuchUser: {
-			message: 'No such user.',
-			code: 'NO_SUCH_USER',
-			id: '588e7f72-c744-4a61-b180-d354e912bda2',
-		},
-	},
-} as const;
-
-export const paramDef = {
-	type: 'object',
-	properties: {
-		listId: { type: 'string', format: 'misskey:id' },
-		userId: { type: 'string', format: 'misskey:id' },
-		withReplies: { type: 'boolean' },
-	},
-	required: ['listId', 'userId'],
-} as const;
-
-@Injectable()
-export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
-	constructor(
-		@Inject(DI.userListsRepository)
-		private userListsRepository: UserListsRepository,
-
-		private userListService: UserListService,
-		private getterService: GetterService,
-	) {
-		super(meta, paramDef, async (ps, me) => {
-			// Fetch the list
-			const userList = await this.userListsRepository.findOneBy({
-				id: ps.listId,
-				userId: me.id,
-			});
-
-			if (userList == null) {
-				throw new ApiError(meta.errors.noSuchList);
-			}
-
-			// Fetch the user
-			const user = await this.getterService.getUser(ps.userId).catch(err => {
-				if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
-				throw err;
-			});
-
-			await this.userListService.updateMembership(user, userList, {
-				withReplies: ps.withReplies,
-			});
-		});
-	}
-}
diff --git a/packages/backend/src/server/api/endpoints/users/notes.ts b/packages/backend/src/server/api/endpoints/users/notes.ts
index 9321a41d58..e660a0bb25 100644
--- a/packages/backend/src/server/api/endpoints/users/notes.ts
+++ b/packages/backend/src/server/api/endpoints/users/notes.ts
@@ -5,14 +5,12 @@
 
 import { Brackets } from 'typeorm';
 import { Inject, Injectable } from '@nestjs/common';
-import * as Redis from 'ioredis';
-import type { MiNote, NotesRepository } from '@/models/_.js';
+import type { NotesRepository } from '@/models/_.js';
 import { Endpoint } from '@/server/api/endpoint-base.js';
+import { QueryService } from '@/core/QueryService.js';
 import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
 import { DI } from '@/di-symbols.js';
 import { GetterService } from '@/server/api/GetterService.js';
-import { CacheService } from '@/core/CacheService.js';
-import { IdService } from '@/core/IdService.js';
 import { ApiError } from '../../error.js';
 
 export const meta = {
@@ -52,6 +50,9 @@ export const paramDef = {
 		untilDate: { type: 'integer' },
 		includeMyRenotes: { type: 'boolean', default: true },
 		withFiles: { type: 'boolean', default: false },
+		fileType: { type: 'array', items: {
+			type: 'string',
+		} },
 		excludeNsfw: { type: 'boolean', default: false },
 	},
 	required: ['userId'],
@@ -60,52 +61,64 @@ export const paramDef = {
 @Injectable()
 export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
 	constructor(
-		@Inject(DI.redisForTimelines)
-		private redisForTimelines: Redis.Redis,
-
 		@Inject(DI.notesRepository)
 		private notesRepository: NotesRepository,
 
 		private noteEntityService: NoteEntityService,
+		private queryService: QueryService,
 		private getterService: GetterService,
-		private cacheService: CacheService,
-		private idService: IdService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
-			let timeline: MiNote[] = [];
+			// Lookup user
+			const user = await this.getterService.getUser(ps.userId).catch(err => {
+				if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
+				throw err;
+			});
 
-			const limit = ps.limit + (ps.untilId ? 1 : 0); // untilIdに指定したものも含まれるため+1
-			let noteIdsRes: [string, string[]][] = [];
-
-			if (!ps.sinceId && !ps.sinceDate) {
-				noteIdsRes = await this.redisForTimelines.xrevrange(
-					ps.withFiles ? `userTimelineWithFiles:${ps.userId}` : ps.withReplies ? `userTimelineWithReplies:${ps.userId}` : `userTimeline:${ps.userId}`,
-					ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : ps.untilDate ?? '+',
-					'-',
-					'COUNT', limit);
-			}
-
-			const noteIds = noteIdsRes.map(x => x[1][1]).filter(x => x !== ps.untilId);
-
-			if (noteIds.length === 0) {
-				return [];
-			}
-
-			const isFollowing = me ? Object.hasOwn(await this.cacheService.userFollowingsCache.fetch(me.id), ps.userId) : false;
-
-			const query = this.notesRepository.createQueryBuilder('note')
-				.where('note.id IN (:...noteIds)', { noteIds: noteIds })
+			//#region Construct query
+			const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
+				.andWhere('note.userId = :userId', { userId: user.id })
 				.innerJoinAndSelect('note.user', 'user')
 				.leftJoinAndSelect('note.reply', 'reply')
 				.leftJoinAndSelect('note.renote', 'renote')
+				.leftJoinAndSelect('note.channel', 'channel')
 				.leftJoinAndSelect('reply.user', 'replyUser')
-				.leftJoinAndSelect('renote.user', 'renoteUser')
-				.leftJoinAndSelect('note.channel', 'channel');
+				.leftJoinAndSelect('renote.user', 'renoteUser');
+
+			query.andWhere(new Brackets(qb => {
+				qb.orWhere('note.channelId IS NULL');
+				qb.orWhere('channel.isSensitive = false');
+			}));
+
+			this.queryService.generateVisibilityQuery(query, me);
+			if (me) {
+				this.queryService.generateMutedUserQuery(query, me, user);
+				this.queryService.generateBlockedUserQuery(query, me);
+			}
+
+			if (ps.withFiles) {
+				query.andWhere('note.fileIds != \'{}\'');
+			}
+
+			if (ps.fileType != null) {
+				query.andWhere('note.fileIds != \'{}\'');
+				query.andWhere(new Brackets(qb => {
+					for (const type of ps.fileType!) {
+						const i = ps.fileType!.indexOf(type);
+						qb.orWhere(`:type${i} = ANY(note.attachedFileTypes)`, { [`type${i}`]: type });
+					}
+				}));
+
+				if (ps.excludeNsfw) {
+					query.andWhere('note.cw IS NULL');
+					query.andWhere('0 = (SELECT COUNT(*) FROM drive_file df WHERE df.id = ANY(note."fileIds") AND df."isSensitive" = TRUE)');
+				}
+			}
 
 			if (!ps.withReplies) {
 				query.andWhere('note.replyId IS NULL');
 			}
-	
+
 			if (ps.withRenotes === false) {
 				query.andWhere(new Brackets(qb => {
 					qb.orWhere('note.renoteId IS NULL');
@@ -116,21 +129,19 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				}));
 			}
 
-			timeline = await query.getMany();
+			if (ps.includeMyRenotes === false) {
+				query.andWhere(new Brackets(qb => {
+					qb.orWhere('note.userId != :userId', { userId: user.id });
+					qb.orWhere('note.renoteId IS NULL');
+					qb.orWhere('note.text IS NOT NULL');
+					qb.orWhere('note.fileIds != \'{}\'');
+					qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)');
+				}));
+			}
 
-			timeline = timeline.filter(note => {
-				if (note.renoteId) {
-					if (note.text == null && note.fileIds.length === 0 && !note.hasPoll) {
-						if (ps.withRenotes === false) return false;
-					}
-				}
+			//#endregion
 
-				if (note.visibility === 'followers' && !isFollowing) return false;
-
-				return true;
-			});
-
-			timeline.sort((a, b) => a.id > b.id ? -1 : 1);
+			const timeline = await query.limit(ps.limit).getMany();
 
 			return await this.noteEntityService.packMany(timeline, me);
 		});
diff --git a/packages/backend/src/server/api/stream/Connection.ts b/packages/backend/src/server/api/stream/Connection.ts
index f981e63871..a73071ea99 100644
--- a/packages/backend/src/server/api/stream/Connection.ts
+++ b/packages/backend/src/server/api/stream/Connection.ts
@@ -11,7 +11,7 @@ import type { NoteReadService } from '@/core/NoteReadService.js';
 import type { NotificationService } from '@/core/NotificationService.js';
 import { bindThis } from '@/decorators.js';
 import { CacheService } from '@/core/CacheService.js';
-import { MiFollowing, MiUserProfile } from '@/models/_.js';
+import { MiUserProfile } from '@/models/_.js';
 import type { StreamEventEmitter, GlobalEvents } from '@/core/GlobalEventService.js';
 import type { ChannelsService } from './ChannelsService.js';
 import type { EventEmitter } from 'events';
@@ -30,7 +30,7 @@ export default class Connection {
 	private subscribingNotes: any = {};
 	private cachedNotes: Packed<'Note'>[] = [];
 	public userProfile: MiUserProfile | null = null;
-	public following: Record<string, Pick<MiFollowing, 'withReplies'> | undefined> = {};
+	public following: Set<string> = new Set();
 	public followingChannels: Set<string> = new Set();
 	public userIdsWhoMeMuting: Set<string> = new Set();
 	public userIdsWhoBlockingMe: Set<string> = new Set();
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 f0ac50349c..fef52b6856 100644
--- a/packages/backend/src/server/api/stream/channels/global-timeline.ts
+++ b/packages/backend/src/server/api/stream/channels/global-timeline.ts
@@ -18,6 +18,7 @@ class GlobalTimelineChannel extends Channel {
 	public readonly chName = 'globalTimeline';
 	public static shouldShare = true;
 	public static requireCredential = false;
+	private withReplies: boolean;
 	private withRenotes: boolean;
 
 	constructor(
@@ -37,6 +38,7 @@ class GlobalTimelineChannel extends Channel {
 		const policies = await this.roleService.getUserPolicies(this.user ? this.user.id : null);
 		if (!policies.gtlAvailable) return;
 
+		this.withReplies = params.withReplies ?? false;
 		this.withRenotes = params.withRenotes ?? true;
 
 		// Subscribe events
@@ -62,7 +64,7 @@ class GlobalTimelineChannel extends Channel {
 		}
 
 		// 関係ない返信は除外
-		if (note.reply && !this.following[note.userId]?.withReplies) {
+		if (note.reply && !this.withReplies) {
 			const reply = note.reply;
 			// 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合
 			if (reply.userId !== this.user!.id && note.userId !== this.user!.id && reply.userId !== note.userId) return;
@@ -80,6 +82,13 @@ class GlobalTimelineChannel extends Channel {
 
 		if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return;
 
+		// 流れてきたNoteがミュートすべきNoteだったら無視する
+		// TODO: 将来的には、単にMutedNoteテーブルにレコードがあるかどうかで判定したい(以下の理由により難しそうではある)
+		// 現状では、ワードミュートにおけるMutedNoteレコードの追加処理はストリーミングに流す処理と並列で行われるため、
+		// レコードが追加されるNoteでも追加されるより先にここのストリーミングの処理に到達することが起こる。
+		// そのためレコードが存在するかのチェックでは不十分なので、改めてcheckWordMuteを呼んでいる
+		if (this.userProfile && await checkWordMute(note, this.user, this.userProfile.mutedWords)) return;
+
 		this.connection.cacheNote(note);
 
 		this.send('note', note);
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 1c1b1c2ae4..198c68e1c2 100644
--- a/packages/backend/src/server/api/stream/channels/home-timeline.ts
+++ b/packages/backend/src/server/api/stream/channels/home-timeline.ts
@@ -16,6 +16,7 @@ class HomeTimelineChannel extends Channel {
 	public readonly chName = 'homeTimeline';
 	public static shouldShare = true;
 	public static requireCredential = true;
+	private withReplies: boolean;
 	private withRenotes: boolean;
 
 	constructor(
@@ -30,6 +31,7 @@ class HomeTimelineChannel extends Channel {
 
 	@bindThis
 	public async init(params: any) {
+		this.withReplies = params.withReplies ?? false;
 		this.withRenotes = params.withRenotes ?? true;
 
 		this.subscriber.on('notesStream', this.onNote);
@@ -41,7 +43,7 @@ class HomeTimelineChannel extends Channel {
 			if (!this.followingChannels.has(note.channelId)) return;
 		} else {
 			// その投稿のユーザーをフォローしていなかったら弾く
-			if ((this.user!.id !== note.userId) && !Object.hasOwn(this.following, note.userId)) return;
+			if ((this.user!.id !== note.userId) && !this.following.has(note.userId)) return;
 		}
 
 		// Ignore notes from instances the user has muted
@@ -71,7 +73,7 @@ class HomeTimelineChannel extends Channel {
 		}
 
 		// 関係ない返信は除外
-		if (note.reply && !this.following[note.userId]?.withReplies) {
+		if (note.reply && !this.withReplies) {
 			const reply = note.reply;
 			// 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合
 			if (reply.userId !== this.user!.id && note.userId !== this.user!.id && reply.userId !== note.userId) return;
@@ -86,6 +88,13 @@ class HomeTimelineChannel extends Channel {
 
 		if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return;
 
+		// 流れてきたNoteがミュートすべきNoteだったら無視する
+		// TODO: 将来的には、単にMutedNoteテーブルにレコードがあるかどうかで判定したい(以下の理由により難しそうではある)
+		// 現状では、ワードミュートにおけるMutedNoteレコードの追加処理はストリーミングに流す処理と並列で行われるため、
+		// レコードが追加されるNoteでも追加されるより先にここのストリーミングの処理に到達することが起こる。
+		// そのためレコードが存在するかのチェックでは不十分なので、改めてcheckWordMuteを呼んでいる
+		if (await checkWordMute(note, this.user, this.userProfile!.mutedWords)) return;
+
 		this.connection.cacheNote(note);
 
 		this.send('note', note);
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 e2f4817bfa..cde4297478 100644
--- a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts
+++ b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts
@@ -18,6 +18,7 @@ class HybridTimelineChannel extends Channel {
 	public readonly chName = 'hybridTimeline';
 	public static shouldShare = true;
 	public static requireCredential = true;
+	private withReplies: boolean;
 	private withRenotes: boolean;
 
 	constructor(
@@ -37,6 +38,7 @@ class HybridTimelineChannel extends Channel {
 		const policies = await this.roleService.getUserPolicies(this.user ? this.user.id : null);
 		if (!policies.ltlAvailable) return;
 
+		this.withReplies = params.withReplies ?? false;
 		this.withRenotes = params.withRenotes ?? true;
 
 		// Subscribe events
@@ -51,7 +53,7 @@ class HybridTimelineChannel extends Channel {
 		// フォローしているチャンネルの投稿 の場合だけ
 		if (!(
 			(note.channelId == null && this.user!.id === note.userId) ||
-			(note.channelId == null && Object.hasOwn(this.following, note.userId)) ||
+			(note.channelId == null && this.following.has(note.userId)) ||
 			(note.channelId == null && (note.user.host == null && note.visibility === 'public')) ||
 			(note.channelId != null && this.followingChannels.has(note.channelId))
 		)) return;
@@ -83,7 +85,7 @@ class HybridTimelineChannel extends Channel {
 		if (isInstanceMuted(note, new Set<string>(this.userProfile!.mutedInstances ?? []))) return;
 
 		// 関係ない返信は除外
-		if (note.reply && !this.following[note.userId]?.withReplies) {
+		if (note.reply && !this.withReplies) {
 			const reply = note.reply;
 			// 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合
 			if (reply.userId !== this.user!.id && note.userId !== this.user!.id && reply.userId !== note.userId) return;
@@ -98,6 +100,13 @@ class HybridTimelineChannel extends Channel {
 
 		if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return;
 
+		// 流れてきたNoteがミュートすべきNoteだったら無視する
+		// TODO: 将来的には、単にMutedNoteテーブルにレコードがあるかどうかで判定したい(以下の理由により難しそうではある)
+		// 現状では、ワードミュートにおけるMutedNoteレコードの追加処理はストリーミングに流す処理と並列で行われるため、
+		// レコードが追加されるNoteでも追加されるより先にここのストリーミングの処理に到達することが起こる。
+		// そのためレコードが存在するかのチェックでは不十分なので、改めてcheckWordMuteを呼んでいる
+		if (this.userProfile && await checkWordMute(note, this.user, this.userProfile.mutedWords)) return;
+
 		this.connection.cacheNote(note);
 
 		this.send('note', note);
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 ca563b5d19..ef708c4fee 100644
--- a/packages/backend/src/server/api/stream/channels/local-timeline.ts
+++ b/packages/backend/src/server/api/stream/channels/local-timeline.ts
@@ -17,6 +17,7 @@ class LocalTimelineChannel extends Channel {
 	public readonly chName = 'localTimeline';
 	public static shouldShare = true;
 	public static requireCredential = false;
+	private withReplies: boolean;
 	private withRenotes: boolean;
 
 	constructor(
@@ -36,6 +37,7 @@ class LocalTimelineChannel extends Channel {
 		const policies = await this.roleService.getUserPolicies(this.user ? this.user.id : null);
 		if (!policies.ltlAvailable) return;
 
+		this.withReplies = params.withReplies ?? false;
 		this.withRenotes = params.withRenotes ?? true;
 
 		// Subscribe events
@@ -62,7 +64,7 @@ class LocalTimelineChannel extends Channel {
 		}
 
 		// 関係ない返信は除外
-		if (note.reply && this.user && !this.following[note.userId]?.withReplies) {
+		if (note.reply && this.user && !this.withReplies) {
 			const reply = note.reply;
 			// 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合
 			if (reply.userId !== this.user.id && note.userId !== this.user.id && reply.userId !== note.userId) return;
@@ -77,6 +79,13 @@ class LocalTimelineChannel extends Channel {
 
 		if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return;
 
+		// 流れてきたNoteがミュートすべきNoteだったら無視する
+		// TODO: 将来的には、単にMutedNoteテーブルにレコードがあるかどうかで判定したい(以下の理由により難しそうではある)
+		// 現状では、ワードミュートにおけるMutedNoteレコードの追加処理はストリーミングに流す処理と並列で行われるため、
+		// レコードが追加されるNoteでも追加されるより先にここのストリーミングの処理に到達することが起こる。
+		// そのためレコードが存在するかのチェックでは不十分なので、改めてcheckWordMuteを呼んでいる
+		if (this.userProfile && await checkWordMute(note, this.user, this.userProfile.mutedWords)) return;
+
 		this.connection.cacheNote(note);
 
 		this.send('note', note);
diff --git a/packages/backend/src/server/api/stream/channels/user-list.ts b/packages/backend/src/server/api/stream/channels/user-list.ts
index 03f7760d8e..8bbba0b6db 100644
--- a/packages/backend/src/server/api/stream/channels/user-list.ts
+++ b/packages/backend/src/server/api/stream/channels/user-list.ts
@@ -4,7 +4,7 @@
  */
 
 import { Inject, Injectable } from '@nestjs/common';
-import type { MiUserListMembership, UserListMembershipsRepository, UserListsRepository } from '@/models/_.js';
+import type { UserListJoiningsRepository, UserListsRepository } from '@/models/_.js';
 import type { MiUser } from '@/models/User.js';
 import { isUserRelated } from '@/misc/is-user-related.js';
 import type { Packed } from '@/misc/json-schema.js';
@@ -18,12 +18,12 @@ class UserListChannel extends Channel {
 	public static shouldShare = false;
 	public static requireCredential = false;
 	private listId: string;
-	public membershipsMap: Record<string, Pick<MiUserListMembership, 'withReplies'> | undefined> = {};
+	public listUsers: MiUser['id'][] = [];
 	private listUsersClock: NodeJS.Timeout;
 
 	constructor(
 		private userListsRepository: UserListsRepository,
-		private userListMembershipsRepository: UserListMembershipsRepository,
+		private userListJoiningsRepository: UserListJoiningsRepository,
 		private noteEntityService: NoteEntityService,
 
 		id: string,
@@ -58,25 +58,19 @@ class UserListChannel extends Channel {
 
 	@bindThis
 	private async updateListUsers() {
-		const memberships = await this.userListMembershipsRepository.find({
+		const users = await this.userListJoiningsRepository.find({
 			where: {
 				userListId: this.listId,
 			},
 			select: ['userId'],
 		});
 
-		const membershipsMap: Record<string, Pick<MiUserListMembership, 'withReplies'> | undefined> = {};
-		for (const membership of memberships) {
-			membershipsMap[membership.userId] = {
-				withReplies: membership.withReplies,
-			};
-		}
-		this.membershipsMap = membershipsMap;
+		this.listUsers = users.map(x => x.userId);
 	}
 
 	@bindThis
 	private async onNote(note: Packed<'Note'>) {
-		if (!Object.hasOwn(this.membershipsMap, note.userId)) return;
+		if (!this.listUsers.includes(note.userId)) return;
 
 		if (['followers', 'specified'].includes(note.visibility)) {
 			note = await this.noteEntityService.pack(note.id, this.user, {
@@ -101,13 +95,6 @@ class UserListChannel extends Channel {
 			}
 		}
 
-		// 関係ない返信は除外
-		if (note.reply && !this.membershipsMap[note.userId]?.withReplies) {
-			const reply = note.reply;
-			// 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合
-			if (reply.userId !== this.user!.id && note.userId !== this.user!.id && reply.userId !== note.userId) return;
-		}
-
 		// 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
 		if (isUserRelated(note, this.userIdsWhoMeMuting)) return;
 		// 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
@@ -137,8 +124,8 @@ export class UserListChannelService {
 		@Inject(DI.userListsRepository)
 		private userListsRepository: UserListsRepository,
 
-		@Inject(DI.userListMembershipsRepository)
-		private userListMembershipsRepository: UserListMembershipsRepository,
+		@Inject(DI.userListJoiningsRepository)
+		private userListJoiningsRepository: UserListJoiningsRepository,
 
 		private noteEntityService: NoteEntityService,
 	) {
@@ -148,7 +135,7 @@ export class UserListChannelService {
 	public create(id: string, connection: Channel['connection']): UserListChannel {
 		return new UserListChannel(
 			this.userListsRepository,
-			this.userListMembershipsRepository,
+			this.userListJoiningsRepository,
 			this.noteEntityService,
 			id,
 			connection,
diff --git a/packages/backend/test/e2e/renote-mute.ts b/packages/backend/test/e2e/renote-mute.ts
index 7d57ba17b6..c9e1ccc304 100644
--- a/packages/backend/test/e2e/renote-mute.ts
+++ b/packages/backend/test/e2e/renote-mute.ts
@@ -6,7 +6,7 @@
 process.env.NODE_ENV = 'test';
 
 import * as assert from 'assert';
-import { signup, api, post, react, startServer, waitFire, sleep } from '../utils.js';
+import { signup, api, post, react, startServer, waitFire } from '../utils.js';
 import type { INestApplicationContext } from '@nestjs/common';
 import type * as misskey from 'misskey-js';
 
@@ -42,9 +42,6 @@ describe('Renote Mute', () => {
 		const carolRenote = await post(carol, { renoteId: bobNote.id });
 		const carolNote = await post(carol, { text: 'hi' });
 
-		// redisに追加されるのを待つ
-		await sleep(100);
-
 		const res = await api('/notes/local-timeline', {}, alice);
 
 		assert.strictEqual(res.status, 200);
@@ -59,9 +56,6 @@ describe('Renote Mute', () => {
 		const carolRenote = await post(carol, { renoteId: bobNote.id, text: 'kore' });
 		const carolNote = await post(carol, { text: 'hi' });
 
-		// redisに追加されるのを待つ
-		await sleep(100);
-
 		const res = await api('/notes/local-timeline', {}, alice);
 
 		assert.strictEqual(res.status, 200);
diff --git a/packages/backend/test/e2e/timelines.ts b/packages/backend/test/e2e/timelines.ts
deleted file mode 100644
index f4c7ffc82d..0000000000
--- a/packages/backend/test/e2e/timelines.ts
+++ /dev/null
@@ -1,701 +0,0 @@
-/*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
- * SPDX-License-Identifier: AGPL-3.0-only
- */
-
-process.env.NODE_ENV = 'test';
-process.env.FORCE_FOLLOW_REMOTE_USER_FOR_TESTING = 'true';
-
-import * as assert from 'assert';
-import { signup, api, post, react, startServer, waitFire, sleep, uploadUrl } from '../utils.js';
-import type { INestApplicationContext } from '@nestjs/common';
-import type * as misskey from 'misskey-js';
-
-let app: INestApplicationContext;
-
-beforeAll(async () => {
-	app = await startServer();
-}, 1000 * 60 * 2);
-
-afterAll(async () => {
-	await app.close();
-});
-
-describe('Timelines', () => {
-	describe('Home TL', () => {
-		test.concurrent('自分の visibility: followers なノートが含まれる', async () => {
-			const [alice] = await Promise.all([signup()]);
-
-			const aliceNote = await post(alice, { text: 'hi', visibility: 'followers' });
-
-			await sleep(100); // redisに追加されるのを待つ
-
-			const res = await api('/notes/timeline', {}, alice);
-
-			assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), true);
-			assert.strictEqual(res.body.find((note: any) => note.id === aliceNote.id).text, 'hi');
-		});
-
-		test.concurrent('フォローしているユーザーのノートが含まれる', async () => {
-			const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
-
-			await api('/following/create', { userId: bob.id }, alice);
-			const bobNote = await post(bob, { text: 'hi' });
-			const carolNote = await post(carol, { text: 'hi' });
-
-			await sleep(100); // redisに追加されるのを待つ
-
-			const res = await api('/notes/timeline', {}, alice);
-
-			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
-			assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
-		});
-
-		test.concurrent('フォローしているユーザーの visibility: followers なノートが含まれる', async () => {
-			const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
-
-			await api('/following/create', { userId: bob.id }, alice);
-			const bobNote = await post(bob, { text: 'hi', visibility: 'followers' });
-			const carolNote = await post(carol, { text: 'hi' });
-
-			await sleep(100); // redisに追加されるのを待つ
-
-			const res = await api('/notes/timeline', {}, alice);
-
-			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
-			assert.strictEqual(res.body.find((note: any) => note.id === bobNote.id).text, 'hi');
-			assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
-		});
-
-		test.concurrent('withReplies: false でフォローしているユーザーの他人への返信が含まれない', async () => {
-			const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
-
-			await api('/following/create', { userId: bob.id }, alice);
-			const carolNote = await post(carol, { text: 'hi' });
-			const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
-
-			await sleep(100); // redisに追加されるのを待つ
-
-			const res = await api('/notes/timeline', {}, alice);
-
-			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
-			assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
-		});
-
-		test.concurrent('withReplies: true でフォローしているユーザーの他人への返信が含まれる', async () => {
-			const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
-
-			await api('/following/create', { userId: bob.id }, alice);
-			await api('/following/update', { userId: bob.id, withReplies: true }, alice);
-			const carolNote = await post(carol, { text: 'hi' });
-			const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
-
-			await sleep(100); // redisに追加されるのを待つ
-
-			const res = await api('/notes/timeline', {}, alice);
-
-			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
-			assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
-		});
-
-		test.concurrent('withReplies: true でフォローしているユーザーの他人へのDM返信が含まれない', async () => {
-			const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
-
-			await api('/following/create', { userId: bob.id }, alice);
-			await api('/following/update', { userId: bob.id, withReplies: true }, alice);
-			const carolNote = await post(carol, { text: 'hi' });
-			const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id, visibility: 'specified', visibleUserIds: [carolNote.id] });
-
-			await sleep(100); // redisに追加されるのを待つ
-
-			const res = await api('/notes/timeline', {}, alice);
-
-			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
-			assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
-		});
-
-		test.concurrent('withReplies: true でフォローしているユーザーの他人の visibility: followers な投稿への返信が含まれない', async () => {
-			const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
-
-			await api('/following/create', { userId: bob.id }, alice);
-			await api('/following/update', { userId: bob.id, withReplies: true }, alice);
-			const carolNote = await post(carol, { text: 'hi', visibility: 'followers' });
-			const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
-
-			await sleep(100); // redisに追加されるのを待つ
-
-			const res = await api('/notes/timeline', {}, alice);
-
-			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
-			assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
-		});
-
-		test.concurrent('withReplies: true でフォローしているユーザーの行った別のフォローしているユーザーの visibility: followers な投稿への返信が含まれる', async () => {
-			const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
-
-			await api('/following/create', { userId: bob.id }, alice);
-			await api('/following/create', { userId: carol.id }, alice);
-			await api('/following/update', { userId: bob.id, withReplies: true }, alice);
-			const carolNote = await post(carol, { text: 'hi', visibility: 'followers' });
-			const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
-
-			await sleep(100); // redisに追加されるのを待つ
-
-			const res = await api('/notes/timeline', {}, alice);
-
-			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
-			assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), true);
-			assert.strictEqual(res.body.find((note: any) => note.id === carolNote.id).text, 'hi');
-		});
-
-		test.concurrent('withReplies: true でフォローしているユーザーの行った別のフォローしているユーザーの投稿への visibility: specified な返信が含まれない', async () => {
-			const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
-
-			await api('/following/create', { userId: bob.id }, alice);
-			await api('/following/create', { userId: carol.id }, alice);
-			await api('/following/update', { userId: bob.id, withReplies: true }, alice);
-			const carolNote = await post(carol, { text: 'hi' });
-			const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id, visibility: 'specified', visibleUserIds: [carolNote.id] });
-
-			await sleep(100); // redisに追加されるのを待つ
-
-			const res = await api('/notes/timeline', {}, alice);
-
-			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
-			assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), true);
-		});
-
-		test.concurrent('withReplies: false でフォローしているユーザーのそのユーザー自身への返信が含まれる', async () => {
-			const [alice, bob] = await Promise.all([signup(), signup()]);
-
-			await api('/following/create', { userId: bob.id }, alice);
-			const bobNote1 = await post(bob, { text: 'hi' });
-			const bobNote2 = await post(bob, { text: 'hi', replyId: bobNote1.id });
-
-			await sleep(100); // redisに追加されるのを待つ
-
-			const res = await api('/notes/timeline', {}, alice);
-
-			assert.strictEqual(res.body.some((note: any) => note.id === bobNote1.id), true);
-			assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), true);
-		});
-
-		test.concurrent('自分の他人への返信が含まれる', async () => {
-			const [alice, bob] = await Promise.all([signup(), signup()]);
-
-			const bobNote = await post(bob, { text: 'hi' });
-			const aliceNote = await post(alice, { text: 'hi', replyId: bobNote.id });
-
-			await sleep(100); // redisに追加されるのを待つ
-
-			const res = await api('/notes/timeline', {}, alice);
-
-			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
-			assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), true);
-		});
-
-		test.concurrent('フォローしているユーザーの他人の投稿のリノートが含まれる', async () => {
-			const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
-
-			await api('/following/create', { userId: bob.id }, alice);
-			const carolNote = await post(carol, { text: 'hi' });
-			const bobNote = await post(bob, { renoteId: carolNote.id });
-
-			await sleep(100); // redisに追加されるのを待つ
-
-			const res = await api('/notes/timeline', {}, alice);
-
-			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
-			assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
-		});
-
-		test.concurrent('[withRenotes: false] フォローしているユーザーの他人の投稿のリノートが含まれない', async () => {
-			const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
-
-			await api('/following/create', { userId: bob.id }, alice);
-			const carolNote = await post(carol, { text: 'hi' });
-			const bobNote = await post(bob, { renoteId: carolNote.id });
-
-			await sleep(100); // redisに追加されるのを待つ
-
-			const res = await api('/notes/timeline', {
-				withRenotes: false,
-			}, alice);
-
-			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
-			assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
-		});
-
-		test.concurrent('[withRenotes: false] フォローしているユーザーの他人の投稿の引用が含まれる', async () => {
-			const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
-
-			await api('/following/create', { userId: bob.id }, alice);
-			const carolNote = await post(carol, { text: 'hi' });
-			const bobNote = await post(bob, { text: 'hi', renoteId: carolNote.id });
-
-			await sleep(100); // redisに追加されるのを待つ
-
-			const res = await api('/notes/timeline', {
-				withRenotes: false,
-			}, alice);
-
-			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
-			assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
-		});
-
-		test.concurrent('フォローしているユーザーの他人への visibility: specified なノートが含まれない', async () => {
-			const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
-
-			await api('/following/create', { userId: bob.id }, alice);
-			const bobNote = await post(bob, { text: 'hi', visibility: 'specified', visibleUserIds: [carol.id] });
-
-			await sleep(100); // redisに追加されるのを待つ
-
-			const res = await api('/notes/timeline', {}, alice);
-
-			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
-		});
-
-		test.concurrent('フォローしているユーザーが行ったミュートしているユーザーのリノートが含まれない', async () => {
-			const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
-
-			await api('/following/create', { userId: bob.id }, alice);
-			await api('/mute/create', { userId: carol.id }, alice);
-			const carolNote = await post(carol, { text: 'hi' });
-			const bobNote = await post(bob, { text: 'hi', renoteId: carolNote.id });
-
-			await sleep(100); // redisに追加されるのを待つ
-
-			const res = await api('/notes/timeline', {}, alice);
-
-			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
-			assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
-		});
-
-		test.concurrent('withReplies: true でフォローしているユーザーが行ったミュートしているユーザーの投稿への返信が含まれない', async () => {
-			const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
-
-			await api('/following/create', { userId: bob.id }, alice);
-			await api('/following/update', { userId: bob.id, withReplies: true }, alice);
-			await api('/mute/create', { userId: carol.id }, alice);
-			const carolNote = await post(carol, { text: 'hi' });
-			const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
-
-			await sleep(100); // redisに追加されるのを待つ
-
-			const res = await api('/notes/timeline', {}, alice);
-
-			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
-			assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
-		});
-
-		test.concurrent('フォローしているリモートユーザーのノートが含まれる', async () => {
-			const [alice, bob] = await Promise.all([signup(), signup({ host: 'example.com' })]);
-
-			await api('/following/create', { userId: bob.id }, alice);
-			const bobNote = await post(bob, { text: 'hi' });
-
-			await sleep(100); // redisに追加されるのを待つ
-
-			const res = await api('/notes/timeline', {}, alice);
-
-			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
-		});
-
-		test.concurrent('フォローしているリモートユーザーの visibility: home なノートが含まれる', async () => {
-			const [alice, bob] = await Promise.all([signup(), signup({ host: 'example.com' })]);
-
-			await api('/following/create', { userId: bob.id }, alice);
-			const bobNote = await post(bob, { text: 'hi', visibility: 'home' });
-
-			await sleep(100); // redisに追加されるのを待つ
-
-			const res = await api('/notes/timeline', {}, alice);
-
-			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
-		});
-
-		test.concurrent('[withFiles: true] フォローしているユーザーのファイル付きノートのみ含まれる', async () => {
-			const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
-
-			await api('/following/create', { userId: bob.id }, alice);
-			const [bobFile, carolFile] = await Promise.all([
-				uploadUrl(bob, 'https://raw.githubusercontent.com/misskey-dev/assets/main/icon.png'),
-				uploadUrl(carol, 'https://raw.githubusercontent.com/misskey-dev/assets/main/icon.png'),
-			]);
-			const bobNote1 = await post(bob, { text: 'hi' });
-			const bobNote2 = await post(bob, { fileIds: [bobFile.id] });
-			const carolNote1 = await post(carol, { text: 'hi' });
-			const carolNote2 = await post(carol, { fileIds: [carolFile.id] });
-
-			await sleep(100); // redisに追加されるのを待つ
-
-			const res = await api('/notes/timeline', { withFiles: true }, alice);
-
-			assert.strictEqual(res.body.some((note: any) => note.id === bobNote1.id), false);
-			assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), true);
-			assert.strictEqual(res.body.some((note: any) => note.id === carolNote1.id), false);
-			assert.strictEqual(res.body.some((note: any) => note.id === carolNote2.id), false);
-		}, 1000 * 10);
-	});
-
-	describe('Local TL', () => {
-		test.concurrent('visibility: home なノートが含まれない', async () => {
-			const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
-
-			const carolNote = await post(carol, { text: 'hi', visibility: 'home' });
-			const bobNote = await post(bob, { text: 'hi' });
-
-			await sleep(100); // redisに追加されるのを待つ
-
-			const res = await api('/notes/local-timeline', {}, alice);
-
-			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
-			assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
-		});
-
-		test.concurrent('リモートユーザーのノートが含まれない', async () => {
-			const [alice, bob] = await Promise.all([signup(), signup({ host: 'example.com' })]);
-
-			const bobNote = await post(bob, { text: 'hi' });
-
-			await sleep(100); // redisに追加されるのを待つ
-
-			const res = await api('/notes/local-timeline', {}, alice);
-
-			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
-		});
-
-		// 含まれても良いと思うけど実装が面倒なので含まれない
-		test.concurrent('フォローしているユーザーの visibility: home なノートが含まれない', async () => {
-			const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
-
-			await api('/following/create', {
-				userId: carol.id,
-			}, alice);
-			const carolNote = await post(carol, { text: 'hi', visibility: 'home' });
-			const bobNote = await post(bob, { text: 'hi' });
-
-			await sleep(100); // redisに追加されるのを待つ
-
-			const res = await api('/notes/local-timeline', {}, alice);
-
-			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
-			assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
-		});
-
-		test.concurrent('ミュートしているユーザーのノートが含まれない', async () => {
-			const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
-
-			await api('/mute/create', { userId: carol.id }, alice);
-			const carolNote = await post(carol, { text: 'hi' });
-			const bobNote = await post(bob, { text: 'hi' });
-
-			await sleep(100); // redisに追加されるのを待つ
-
-			const res = await api('/notes/local-timeline', {}, alice);
-
-			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
-			assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
-		});
-
-		test.concurrent('フォローしているユーザーが行ったミュートしているユーザーのリノートが含まれない', async () => {
-			const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
-
-			await api('/following/create', { userId: bob.id }, alice);
-			await api('/mute/create', { userId: carol.id }, alice);
-			const carolNote = await post(carol, { text: 'hi' });
-			const bobNote = await post(bob, { text: 'hi', renoteId: carolNote.id });
-
-			await sleep(100); // redisに追加されるのを待つ
-
-			const res = await api('/notes/local-timeline', {}, alice);
-
-			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
-			assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
-		});
-
-		test.concurrent('withReplies: true でフォローしているユーザーが行ったミュートしているユーザーの投稿への返信が含まれない', async () => {
-			const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
-
-			await api('/following/create', { userId: bob.id }, alice);
-			await api('/following/update', { userId: bob.id, withReplies: true }, alice);
-			await api('/mute/create', { userId: carol.id }, alice);
-			const carolNote = await post(carol, { text: 'hi' });
-			const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
-
-			await sleep(100); // redisに追加されるのを待つ
-
-			const res = await api('/notes/local-timeline', {}, alice);
-
-			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
-			assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
-		});
-
-		test.concurrent('[withFiles: true] ファイル付きノートのみ含まれる', async () => {
-			const [alice, bob] = await Promise.all([signup(), signup()]);
-
-			const file = await uploadUrl(bob, 'https://raw.githubusercontent.com/misskey-dev/assets/main/icon.png');
-			const bobNote1 = await post(bob, { text: 'hi' });
-			const bobNote2 = await post(bob, { fileIds: [file.id] });
-
-			await sleep(100); // redisに追加されるのを待つ
-
-			const res = await api('/notes/local-timeline', { withFiles: true }, alice);
-
-			assert.strictEqual(res.body.some((note: any) => note.id === bobNote1.id), false);
-			assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), true);
-		}, 1000 * 10);
-	});
-
-	describe('Social TL', () => {
-		test.concurrent('ローカルユーザーのノートが含まれる', async () => {
-			const [alice, bob] = await Promise.all([signup(), signup()]);
-
-			const bobNote = await post(bob, { text: 'hi' });
-
-			await sleep(100); // redisに追加されるのを待つ
-
-			const res = await api('/notes/hybrid-timeline', {}, alice);
-
-			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
-		});
-
-		test.concurrent('ローカルユーザーの visibility: home なノートが含まれない', async () => {
-			const [alice, bob] = await Promise.all([signup(), signup()]);
-
-			const bobNote = await post(bob, { text: 'hi', visibility: 'home' });
-
-			await sleep(100); // redisに追加されるのを待つ
-
-			const res = await api('/notes/hybrid-timeline', {}, alice);
-
-			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
-		});
-
-		test.concurrent('フォローしているローカルユーザーの visibility: home なノートが含まれる', async () => {
-			const [alice, bob] = await Promise.all([signup(), signup()]);
-
-			await api('/following/create', { userId: bob.id }, alice);
-			const bobNote = await post(bob, { text: 'hi', visibility: 'home' });
-
-			await sleep(100); // redisに追加されるのを待つ
-
-			const res = await api('/notes/hybrid-timeline', {}, alice);
-
-			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
-		});
-
-		test.concurrent('リモートユーザーのノートが含まれない', async () => {
-			const [alice, bob] = await Promise.all([signup(), signup({ host: 'example.com' })]);
-
-			const bobNote = await post(bob, { text: 'hi' });
-
-			await sleep(100); // redisに追加されるのを待つ
-
-			const res = await api('/notes/local-timeline', {}, alice);
-
-			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
-		});
-
-		test.concurrent('フォローしているリモートユーザーのノートが含まれる', async () => {
-			const [alice, bob] = await Promise.all([signup(), signup({ host: 'example.com' })]);
-
-			await api('/following/create', { userId: bob.id }, alice);
-			const bobNote = await post(bob, { text: 'hi' });
-
-			await sleep(100); // redisに追加されるのを待つ
-
-			const res = await api('/notes/hybrid-timeline', {}, alice);
-
-			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
-		});
-
-		test.concurrent('フォローしているリモートユーザーの visibility: home なノートが含まれる', async () => {
-			const [alice, bob] = await Promise.all([signup(), signup({ host: 'example.com' })]);
-
-			await api('/following/create', { userId: bob.id }, alice);
-			const bobNote = await post(bob, { text: 'hi', visibility: 'home' });
-
-			await sleep(100); // redisに追加されるのを待つ
-
-			const res = await api('/notes/hybrid-timeline', {}, alice);
-
-			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
-		});
-
-		test.concurrent('[withFiles: true] ファイル付きノートのみ含まれる', async () => {
-			const [alice, bob] = await Promise.all([signup(), signup()]);
-
-			const file = await uploadUrl(bob, 'https://raw.githubusercontent.com/misskey-dev/assets/main/icon.png');
-			const bobNote1 = await post(bob, { text: 'hi' });
-			const bobNote2 = await post(bob, { fileIds: [file.id] });
-
-			await sleep(100); // redisに追加されるのを待つ
-
-			const res = await api('/notes/hybrid-timeline', { withFiles: true }, alice);
-
-			assert.strictEqual(res.body.some((note: any) => note.id === bobNote1.id), false);
-			assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), true);
-		}, 1000 * 10);
-	});
-
-	describe('User List TL', () => {
-		test.concurrent('リスインしているフォローしていないユーザーのノートが含まれる', async () => {
-			const [alice, bob] = await Promise.all([signup(), signup()]);
-
-			const list = await api('/users/lists/create', { name: 'list' }, alice).then(res => res.body);
-			await api('/users/lists/push', { listId: list.id, userId: bob.id }, alice);
-			const bobNote = await post(bob, { text: 'hi' });
-
-			await sleep(100); // redisに追加されるのを待つ
-
-			const res = await api('/notes/user-list-timeline', { listId: list.id }, alice);
-
-			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
-		});
-
-		test.concurrent('リスインしているフォローしていないユーザーの visibility: home なノートが含まれる', async () => {
-			const [alice, bob] = await Promise.all([signup(), signup()]);
-
-			const list = await api('/users/lists/create', { name: 'list' }, alice).then(res => res.body);
-			await api('/users/lists/push', { listId: list.id, userId: bob.id }, alice);
-			const bobNote = await post(bob, { text: 'hi', visibility: 'home' });
-
-			await sleep(100); // redisに追加されるのを待つ
-
-			const res = await api('/notes/user-list-timeline', { listId: list.id }, alice);
-
-			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
-		});
-
-		/* 未実装
-		test.concurrent('リスインしているフォローしていないユーザーの visibility: followers なノートが含まれない', async () => {
-			const [alice, bob] = await Promise.all([signup(), signup()]);
-
-			const list = await api('/users/lists/create', { name: 'list' }, alice).then(res => res.body);
-			await api('/users/lists/push', { listId: list.id, userId: bob.id }, alice);
-			const bobNote = await post(bob, { text: 'hi', visibility: 'followers' });
-
-			await sleep(100); // redisに追加されるのを待つ
-
-			const res = await api('/notes/user-list-timeline', { listId: list.id }, alice);
-
-			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
-		});
-		*/
-
-		test.concurrent('リスインしているフォローしていないユーザーの visibility: followers なノートが含まれるが隠される', async () => {
-			const [alice, bob] = await Promise.all([signup(), signup()]);
-
-			const list = await api('/users/lists/create', { name: 'list' }, alice).then(res => res.body);
-			await api('/users/lists/push', { listId: list.id, userId: bob.id }, alice);
-			const bobNote = await post(bob, { text: 'hi', visibility: 'followers' });
-
-			await sleep(100); // redisに追加されるのを待つ
-
-			const res = await api('/notes/user-list-timeline', { listId: list.id }, alice);
-
-			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
-			assert.strictEqual(res.body.find((note: any) => note.id === bobNote.id).text, null);
-		});
-
-		test.concurrent('リスインしているフォローしていないユーザーの他人への返信が含まれない', async () => {
-			const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
-
-			const list = await api('/users/lists/create', { name: 'list' }, alice).then(res => res.body);
-			await api('/users/lists/push', { listId: list.id, userId: bob.id }, alice);
-			const carolNote = await post(carol, { text: 'hi' });
-			const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
-
-			await sleep(100); // redisに追加されるのを待つ
-
-			const res = await api('/notes/user-list-timeline', { listId: list.id }, alice);
-
-			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
-		});
-
-		test.concurrent('リスインしているフォローしていないユーザーのユーザー自身への返信が含まれる', async () => {
-			const [alice, bob] = await Promise.all([signup(), signup()]);
-
-			const list = await api('/users/lists/create', { name: 'list' }, alice).then(res => res.body);
-			await api('/users/lists/push', { listId: list.id, userId: bob.id }, alice);
-			const bobNote1 = await post(bob, { text: 'hi' });
-			const bobNote2 = await post(bob, { text: 'hi', replyId: bobNote1.id });
-
-			await sleep(100); // redisに追加されるのを待つ
-
-			const res = await api('/notes/user-list-timeline', { listId: list.id }, alice);
-
-			assert.strictEqual(res.body.some((note: any) => note.id === bobNote1.id), true);
-			assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), true);
-		});
-
-		test.concurrent('withReplies: true でリスインしているフォローしていないユーザーの他人への返信が含まれる', async () => {
-			const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
-
-			const list = await api('/users/lists/create', { name: 'list' }, alice).then(res => res.body);
-			await api('/users/lists/push', { listId: list.id, userId: bob.id }, alice);
-			await api('/users/lists/update-membership', { listId: list.id, userId: bob.id, withReplies: true }, alice);
-			const carolNote = await post(carol, { text: 'hi' });
-			const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
-
-			await sleep(100); // redisに追加されるのを待つ
-
-			const res = await api('/notes/user-list-timeline', { listId: list.id }, alice);
-
-			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
-		});
-
-		test.concurrent('リスインしているフォローしているユーザーの visibility: home なノートが含まれる', async () => {
-			const [alice, bob] = await Promise.all([signup(), signup()]);
-
-			await api('/following/create', { userId: bob.id }, alice);
-			const list = await api('/users/lists/create', { name: 'list' }, alice).then(res => res.body);
-			await api('/users/lists/push', { listId: list.id, userId: bob.id }, alice);
-			const bobNote = await post(bob, { text: 'hi', visibility: 'home' });
-
-			await sleep(100); // redisに追加されるのを待つ
-
-			const res = await api('/notes/user-list-timeline', { listId: list.id }, alice);
-
-			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
-		});
-
-		test.concurrent('リスインしているフォローしているユーザーの visibility: followers なノートが含まれる', async () => {
-			const [alice, bob] = await Promise.all([signup(), signup()]);
-
-			await api('/following/create', { userId: bob.id }, alice);
-			const list = await api('/users/lists/create', { name: 'list' }, alice).then(res => res.body);
-			await api('/users/lists/push', { listId: list.id, userId: bob.id }, alice);
-			const bobNote = await post(bob, { text: 'hi', visibility: 'followers' });
-
-			await sleep(100); // redisに追加されるのを待つ
-
-			const res = await api('/notes/user-list-timeline', { listId: list.id }, alice);
-
-			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
-			assert.strictEqual(res.body.find((note: any) => note.id === bobNote.id).text, 'hi');
-		});
-
-		test.concurrent('[withFiles: true] リスインしているユーザーのファイル付きノートのみ含まれる', async () => {
-			const [alice, bob] = await Promise.all([signup(), signup()]);
-
-			const list = await api('/users/lists/create', { name: 'list' }, alice).then(res => res.body);
-			await api('/users/lists/push', { listId: list.id, userId: bob.id }, alice);
-			const file = await uploadUrl(bob, 'https://raw.githubusercontent.com/misskey-dev/assets/main/icon.png');
-			const bobNote1 = await post(bob, { text: 'hi' });
-			const bobNote2 = await post(bob, { fileIds: [file.id] });
-
-			await sleep(100); // redisに追加されるのを待つ
-
-			const res = await api('/notes/user-list-timeline', { listId: list.id, withFiles: true }, alice);
-
-			assert.strictEqual(res.body.some((note: any) => note.id === bobNote1.id), false);
-			assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), true);
-		}, 1000 * 10);
-	});
-
-	// TODO: リノートミュート済みユーザーのテスト
-	// TODO: ページネーションのテスト
-});
diff --git a/packages/backend/test/e2e/user-notes.ts b/packages/backend/test/e2e/user-notes.ts
index b5f00a6327..121070787d 100644
--- a/packages/backend/test/e2e/user-notes.ts
+++ b/packages/backend/test/e2e/user-notes.ts
@@ -38,10 +38,23 @@ describe('users/notes', () => {
 		await app.close();
 	});
 
-	test('withFiles', async () => {
+	test('ファイルタイプ指定 (jpg)', async () => {
 		const res = await api('/users/notes', {
 			userId: alice.id,
-			withFiles: true,
+			fileType: ['image/jpeg'],
+		}, alice);
+
+		assert.strictEqual(res.status, 200);
+		assert.strictEqual(Array.isArray(res.body), true);
+		assert.strictEqual(res.body.length, 2);
+		assert.strictEqual(res.body.some((note: any) => note.id === jpgNote.id), true);
+		assert.strictEqual(res.body.some((note: any) => note.id === jpgPngNote.id), true);
+	});
+
+	test('ファイルタイプ指定 (jpg or png)', async () => {
+		const res = await api('/users/notes', {
+			userId: alice.id,
+			fileType: ['image/jpeg', 'image/png'],
 		}, alice);
 
 		assert.strictEqual(res.status, 200);
diff --git a/packages/backend/test/e2e/users.ts b/packages/backend/test/e2e/users.ts
index 53db1ac28a..0f5d5f7344 100644
--- a/packages/backend/test/e2e/users.ts
+++ b/packages/backend/test/e2e/users.ts
@@ -133,7 +133,6 @@ describe('ユーザー', () => {
 			isMuted: user.isMuted ?? false,
 			isRenoteMuted: user.isRenoteMuted ?? false,
 			notify: user.notify ?? 'none',
-			withReplies: user.withReplies ?? false,
 		});
 	};
 
diff --git a/packages/backend/test/utils.ts b/packages/backend/test/utils.ts
index fae9018422..adc532bbe7 100644
--- a/packages/backend/test/utils.ts
+++ b/packages/backend/test/utils.ts
@@ -99,17 +99,9 @@ export const relativeFetch = async (path: string, init?: RequestInit | undefined
 	return await fetch(new URL(path, `http://127.0.0.1:${port}/`).toString(), init);
 };
 
-function randomString(chars = 'abcdefghijklmnopqrstuvwxyz0123456789', length = 16) {
-	let randomString = '';
-	for (let i = 0; i < length; i++) {
-		randomString += chars[Math.floor(Math.random() * chars.length)];
-	}
-	return randomString;
-}
-
 export const signup = async (params?: Partial<misskey.Endpoints['signup']['req']>): Promise<NonNullable<misskey.Endpoints['signup']['res']>> => {
 	const q = Object.assign({
-		username: randomString(),
+		username: 'test',
 		password: 'test',
 	}, params);
 
diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue
index a844dcec19..5999854399 100644
--- a/packages/frontend/src/components/MkNote.vue
+++ b/packages/frontend/src/components/MkNote.vue
@@ -169,7 +169,7 @@ import { deepClone } from '@/scripts/clone.js';
 import { useTooltip } from '@/scripts/use-tooltip.js';
 import { claimAchievement } from '@/scripts/achievements.js';
 import { getNoteSummary } from '@/scripts/get-note-summary.js';
-import { MenuItem } from '@/types/menu.js';
+import { MenuItem } from '@/types/menu';
 import MkRippleEffect from '@/components/MkRippleEffect.vue';
 import { showMovedDialog } from '@/scripts/show-moved-dialog.js';
 import { shouldCollapsed } from '@/scripts/collapsed.js';
@@ -226,7 +226,7 @@ const urls = appearNote.text ? extractUrlFromMfm(mfm.parse(appearNote.text)).fil
 const isLong = shouldCollapsed(appearNote);
 const collapsed = ref(appearNote.cw == null && isLong);
 const isDeleted = ref(false);
-const muted = ref($i ? checkWordMute(appearNote, $i, $i.mutedWords) : false);
+const muted = ref(checkWordMute(appearNote, $i, defaultStore.state.mutedWords));
 const translation = ref<any>(null);
 const translating = ref(false);
 const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && appearNote.user.instance);
diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue
index 0acc609221..2d9566bce3 100644
--- a/packages/frontend/src/components/MkNoteDetailed.vue
+++ b/packages/frontend/src/components/MkNoteDetailed.vue
@@ -218,7 +218,7 @@ import { useNoteCapture } from '@/scripts/use-note-capture.js';
 import { deepClone } from '@/scripts/clone.js';
 import { useTooltip } from '@/scripts/use-tooltip.js';
 import { claimAchievement } from '@/scripts/achievements.js';
-import { MenuItem } from '@/types/menu.js';
+import { MenuItem } from '@/types/menu';
 import MkRippleEffect from '@/components/MkRippleEffect.vue';
 import { showMovedDialog } from '@/scripts/show-moved-dialog.js';
 import MkUserCardMini from '@/components/MkUserCardMini.vue';
@@ -266,7 +266,7 @@ const renoteUri = appearNote.renote ? appearNote.renote.uri : null;
 const isMyRenote = $i && ($i.id === note.userId);
 const showContent = ref(false);
 const isDeleted = ref(false);
-const muted = ref($i ? checkWordMute(appearNote, $i, $i.mutedWords) : false);
+const muted = ref(checkWordMute(appearNote, $i, defaultStore.state.mutedWords));
 const translation = ref(null);
 const translating = ref(false);
 const urls = appearNote.text ? extractUrlFromMfm(mfm.parse(appearNote.text)).filter(u => u !== renoteUrl && u !== renoteUri) : null;
diff --git a/packages/frontend/src/components/MkNoteSub.vue b/packages/frontend/src/components/MkNoteSub.vue
index 23cff2a03a..a5213e1ccc 100644
--- a/packages/frontend/src/components/MkNoteSub.vue
+++ b/packages/frontend/src/components/MkNoteSub.vue
@@ -108,7 +108,7 @@ const props = withDefaults(defineProps<{
 });
 
 const el = shallowRef<HTMLElement>();
-const muted = ref($i ? checkWordMute(props.note, $i, $i.mutedWords) : false);
+const muted = ref(checkWordMute(props.note, $i, defaultStore.state.mutedWords));
 const translation = ref(null);
 const translating = ref(false);
 const isDeleted = ref(false);
diff --git a/packages/frontend/src/components/MkTimeline.vue b/packages/frontend/src/components/MkTimeline.vue
index c4a34667ef..1dcafd6be1 100644
--- a/packages/frontend/src/components/MkTimeline.vue
+++ b/packages/frontend/src/components/MkTimeline.vue
@@ -23,9 +23,11 @@ const props = withDefaults(defineProps<{
 	role?: string;
 	sound?: boolean;
 	withRenotes?: boolean;
+	withReplies?: boolean;
 	onlyFiles?: boolean;
 }>(), {
 	withRenotes: true,
+	withReplies: false,
 	onlyFiles: false,
 });
 
@@ -68,10 +70,12 @@ if (props.src === 'antenna') {
 	endpoint = 'notes/timeline';
 	query = {
 		withRenotes: props.withRenotes,
+		withReplies: props.withReplies,
 		withFiles: props.onlyFiles ? true : undefined,
 	};
 	connection = stream.useChannel('homeTimeline', {
 		withRenotes: props.withRenotes,
+		withReplies: props.withReplies,
 		withFiles: props.onlyFiles ? true : undefined,
 	});
 	connection.on('note', prepend);
@@ -81,10 +85,12 @@ if (props.src === 'antenna') {
 	endpoint = 'notes/local-timeline';
 	query = {
 		withRenotes: props.withRenotes,
+		withReplies: props.withReplies,
 		withFiles: props.onlyFiles ? true : undefined,
 	};
 	connection = stream.useChannel('localTimeline', {
 		withRenotes: props.withRenotes,
+		withReplies: props.withReplies,
 		withFiles: props.onlyFiles ? true : undefined,
 	});
 	connection.on('note', prepend);
@@ -92,10 +98,12 @@ if (props.src === 'antenna') {
 	endpoint = 'notes/hybrid-timeline';
 	query = {
 		withRenotes: props.withRenotes,
+		withReplies: props.withReplies,
 		withFiles: props.onlyFiles ? true : undefined,
 	};
 	connection = stream.useChannel('hybridTimeline', {
 		withRenotes: props.withRenotes,
+		withReplies: props.withReplies,
 		withFiles: props.onlyFiles ? true : undefined,
 	});
 	connection.on('note', prepend);
@@ -103,10 +111,12 @@ if (props.src === 'antenna') {
 	endpoint = 'notes/global-timeline';
 	query = {
 		withRenotes: props.withRenotes,
+		withReplies: props.withReplies,
 		withFiles: props.onlyFiles ? true : undefined,
 	};
 	connection = stream.useChannel('globalTimeline', {
 		withRenotes: props.withRenotes,
+		withReplies: props.withReplies,
 		withFiles: props.onlyFiles ? true : undefined,
 	});
 	connection.on('note', prepend);
@@ -130,11 +140,13 @@ if (props.src === 'antenna') {
 	endpoint = 'notes/user-list-timeline';
 	query = {
 		withRenotes: props.withRenotes,
+		withReplies: props.withReplies,
 		withFiles: props.onlyFiles ? true : undefined,
 		listId: props.list,
 	};
 	connection = stream.useChannel('userList', {
 		withRenotes: props.withRenotes,
+		withReplies: props.withReplies,
 		withFiles: props.onlyFiles ? true : undefined,
 		listId: props.list,
 	});
diff --git a/packages/frontend/src/pages/my-lists/list.vue b/packages/frontend/src/pages/my-lists/list.vue
index cadc9f6d1e..41f3a9c23f 100644
--- a/packages/frontend/src/pages/my-lists/list.vue
+++ b/packages/frontend/src/pages/my-lists/list.vue
@@ -29,22 +29,16 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 				<div class="_gaps_s">
 					<MkButton rounded primary style="margin: 0 auto;" @click="addUser()">{{ i18n.ts.addUser }}</MkButton>
-
-					<MkPagination ref="paginationEl" :pagination="membershipsPagination">
-						<template #default="{ items }">
-							<div class="_gaps_s">
-								<div v-for="item in items" :key="item.id">
-									<div :class="$style.userItem">
-										<MkA :class="$style.userItemBody" :to="`${userPage(item.user)}`">
-											<MkUserCardMini :user="item.user"/>
-										</MkA>
-										<button class="_button" :class="$style.menu" @click="showMembershipMenu(item, $event)"><i class="ph-dots-three ph-bold ph-lg"></i></button>
-										<button class="_button" :class="$style.remove" @click="removeUser(item, $event)"><i class="ph-x ph-bold ph-lg"></i></button>
-									</div>
-								</div>
-							</div>
-						</template>
-					</MkPagination>
+					<div v-for="user in users" :key="user.id" :class="$style.userItem">
+						<MkA :class="$style.userItemBody" :to="`${userPage(user)}`">
+							<MkUserCardMini :user="user"/>
+						</MkA>
+						<button class="_button" :class="$style.remove" @click="removeUser(user, $event)"><i class="ph-x ph-bold ph-lg"></i></button>
+					</div>
+					<MkButton v-if="!fetching && queueUserIds.length !== 0" v-appear="enableInfiniteScroll ? fetchMoreUsers : null" :class="$style.more" :style="{ cursor: 'pointer' }" primary rounded @click="fetchMoreUsers">
+						{{ i18n.ts.loadMore }}
+					</MkButton>
+					<MkLoading v-if="fetching" class="loading"/>
 				</div>
 			</MkFolder>
 		</div>
@@ -65,11 +59,9 @@ import MkUserCardMini from '@/components/MkUserCardMini.vue';
 import MkSwitch from '@/components/MkSwitch.vue';
 import MkFolder from '@/components/MkFolder.vue';
 import MkInput from '@/components/MkInput.vue';
-import { userListsCache } from '@/cache.js';
+import { userListsCache } from '@/cache';
 import { $i } from '@/account.js';
 import { defaultStore } from '@/store.js';
-import MkPagination, { Paging } from '@/components/MkPagination.vue';
-
 const {
 	enableInfiniteScroll,
 } = defaultStore.reactiveState;
@@ -78,25 +70,40 @@ const props = defineProps<{
 	listId: string;
 }>();
 
-const paginationEl = ref<InstanceType<typeof MkPagination>>();
+const FETCH_USERS_LIMIT = 20;
+
 let list = $ref<Misskey.entities.UserList | null>(null);
+let users = $ref<Misskey.entities.UserLite[]>([]);
+let queueUserIds = $ref<string[]>([]);
+let fetching = $ref(true);
 const isPublic = ref(false);
 const name = ref('');
-const membershipsPagination = {
-	endpoint: 'users/lists/get-memberships' as const,
-	limit: 30,
-	params: computed(() => ({
-		listId: props.listId,
-	})),
-};
 
 function fetchList() {
+	fetching = true;
 	os.api('users/lists/show', {
 		listId: props.listId,
 	}).then(_list => {
 		list = _list;
 		name.value = list.name;
 		isPublic.value = list.isPublic;
+		queueUserIds = list.userIds;
+
+		return fetchMoreUsers();
+	});
+}
+
+function fetchMoreUsers() {
+	if (!list) return;
+	if (fetching && users.length !== 0) return; // fetchingがtrueならやめるが、usersが空なら続行
+	fetching = true;
+	os.api('users/show', {
+		userIds: queueUserIds.slice(0, FETCH_USERS_LIMIT),
+	}).then(_users => {
+		users = users.concat(_users);
+		queueUserIds = queueUserIds.slice(FETCH_USERS_LIMIT);
+	}).finally(() => {
+		fetching = false;
 	});
 }
 
@@ -107,12 +114,12 @@ function addUser() {
 			listId: list.id,
 			userId: user.id,
 		}).then(() => {
-			paginationEl.value.reload();
+			users.push(user);
 		});
 	});
 }
 
-async function removeUser(item, ev) {
+async function removeUser(user, ev) {
 	os.popupMenu([{
 		text: i18n.ts.remove,
 		icon: 'ph-x ph-bold ph-lg',
@@ -121,28 +128,9 @@ async function removeUser(item, ev) {
 			if (!list) return;
 			os.api('users/lists/pull', {
 				listId: list.id,
-				userId: item.userId,
+				userId: user.id,
 			}).then(() => {
-				paginationEl.value.removeItem(item.id);
-			});
-		},
-	}], ev.currentTarget ?? ev.target);
-}
-
-async function showMembershipMenu(item, ev) {
-	os.popupMenu([{
-		text: item.withReplies ? i18n.ts.hideRepliesToOthersInTimeline : i18n.ts.showRepliesToOthersInTimeline,
-		icon: item.withReplies ? 'ti ti-messages-off' : 'ti ti-messages',
-		action: async () => {
-			os.api('users/lists/update-membership', {
-				listId: list.id,
-				userId: item.userId,
-				withReplies: !item.withReplies,
-			}).then(() => {
-				paginationEl.value.updateItem(item.id, (old) => ({
-					...old,
-					withReplies: !item.withReplies,
-				}));
+				users = users.filter(x => x.id !== user.id);
 			});
 		},
 	}], ev.currentTarget ?? ev.target);
@@ -214,12 +202,6 @@ definePageMetadata(computed(() => list ? {
 	align-self: center;
 }
 
-.menu {
-	width: 32px;
-	height: 32px;
-	align-self: center;
-}
-
 .more {
 	margin-left: auto;
 	margin-right: auto;
diff --git a/packages/frontend/src/pages/settings/word-mute.vue b/packages/frontend/src/pages/settings/word-mute.vue
index bbd6fdfac5..cfac5a4fd1 100644
--- a/packages/frontend/src/pages/settings/word-mute.vue
+++ b/packages/frontend/src/pages/settings/word-mute.vue
@@ -5,11 +5,29 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <template>
 <div class="_gaps_m">
+	<MkTab v-model="tab">
+		<option value="soft">{{ i18n.ts._wordMute.soft }}</option>
+		<option value="hard">{{ i18n.ts._wordMute.hard }}</option>
+	</MkTab>
 	<div>
-		<MkTextarea v-model="mutedWords">
-			<span>{{ i18n.ts._wordMute.muteWords }}</span>
-			<template #caption>{{ i18n.ts._wordMute.muteWordsDescription }}<br>{{ i18n.ts._wordMute.muteWordsDescription2 }}</template>
-		</MkTextarea>
+		<div v-show="tab === 'soft'" class="_gaps_m">
+			<MkInfo>{{ i18n.ts._wordMute.softDescription }}</MkInfo>
+			<MkTextarea v-model="softMutedWords">
+				<span>{{ i18n.ts._wordMute.muteWords }}</span>
+				<template #caption>{{ i18n.ts._wordMute.muteWordsDescription }}<br>{{ i18n.ts._wordMute.muteWordsDescription2 }}</template>
+			</MkTextarea>
+		</div>
+		<div v-show="tab === 'hard'" class="_gaps_m">
+			<MkInfo>{{ i18n.ts._wordMute.hardDescription }} {{ i18n.ts.reflectMayTakeTime }}</MkInfo>
+			<MkTextarea v-model="hardMutedWords">
+				<span>{{ i18n.ts._wordMute.muteWords }}</span>
+				<template #caption>{{ i18n.ts._wordMute.muteWordsDescription }}<br>{{ i18n.ts._wordMute.muteWordsDescription2 }}</template>
+			</MkTextarea>
+			<MkKeyValue v-if="hardWordMutedNotesCount != null">
+				<template #key>{{ i18n.ts._wordMute.mutedNotes }}</template>
+				<template #value>{{ number(hardWordMutedNotesCount) }}</template>
+			</MkKeyValue>
+		</div>
 	</div>
 	<MkButton primary inline :disabled="!changed" @click="save()"><i class="ph-floppy-disk ph-bold pg-lg"></i> {{ i18n.ts.save }}</MkButton>
 </div>
@@ -38,15 +56,25 @@ const render = (mutedWords) => mutedWords.map(x => {
 }).join('\n');
 
 const tab = ref('soft');
-const mutedWords = ref(render($i!.mutedWords));
+const softMutedWords = ref(render(defaultStore.state.mutedWords));
+const hardMutedWords = ref(render($i!.mutedWords));
+const hardWordMutedNotesCount = ref(null);
 const changed = ref(false);
 
-watch(mutedWords, () => {
+os.api('i/get-word-muted-notes-count', {}).then(response => {
+	hardWordMutedNotesCount.value = response?.count;
+});
+
+watch(softMutedWords, () => {
+	changed.value = true;
+});
+
+watch(hardMutedWords, () => {
 	changed.value = true;
 });
 
 async function save() {
-	const parseMutes = (mutes) => {
+	const parseMutes = (mutes, tab) => {
 		// split into lines, remove empty lines and unnecessary whitespace
 		let lines = mutes.trim().split('\n').map(line => line.trim()).filter(line => line !== '');
 
@@ -64,7 +92,7 @@ async function save() {
 					os.alert({
 						type: 'error',
 						title: i18n.ts.regexpError,
-						text: i18n.t('regexpErrorDescription', { tab: 'word mute', line: i + 1 }) + '\n' + err.toString(),
+						text: i18n.t('regexpErrorDescription', { tab, line: i + 1 }) + '\n' + err.toString(),
 					});
 					// re-throw error so these invalid settings are not saved
 					throw err;
@@ -77,16 +105,18 @@ async function save() {
 		return lines;
 	};
 
-	let parsed;
+	let softMutes, hardMutes;
 	try {
-		parsed = parseMutes(mutedWords.value);
+		softMutes = parseMutes(softMutedWords.value, i18n.ts._wordMute.soft);
+		hardMutes = parseMutes(hardMutedWords.value, i18n.ts._wordMute.hard);
 	} catch (err) {
 		// already displayed error message in parseMutes
 		return;
 	}
 
+	defaultStore.set('mutedWords', softMutes);
 	await os.api('i/update', {
-		mutedWords: parsed,
+		mutedWords: hardMutes,
 	});
 
 	changed.value = false;
diff --git a/packages/frontend/src/pages/timeline.vue b/packages/frontend/src/pages/timeline.vue
index daec6ba5bd..a5674cfbb3 100644
--- a/packages/frontend/src/pages/timeline.vue
+++ b/packages/frontend/src/pages/timeline.vue
@@ -15,10 +15,11 @@ SPDX-License-Identifier: AGPL-3.0-only
 			<div :class="$style.tl">
 				<MkTimeline
 					ref="tlComponent"
-					:key="src + withRenotes + onlyFiles"
+					:key="src + withRenotes + withReplies + onlyFiles"
 					:src="src.split(':')[0]"
 					:list="src.split(':')[1]"
 					:withRenotes="withRenotes"
+					:withReplies="withReplies"
 					:onlyFiles="onlyFiles"
 					:sound="true"
 					@queue="queueUpdated"
@@ -61,6 +62,7 @@ let queue = $ref(0);
 let srcWhenNotSignin = $ref(isLocalTimelineAvailable ? 'local' : 'global');
 const src = $computed({ get: () => ($i ? defaultStore.reactiveState.tl.value.src : srcWhenNotSignin), set: (x) => saveSrc(x) });
 const withRenotes = $ref(true);
+const withReplies = $ref(false);
 const onlyFiles = $ref(false);
 
 watch($$(src), () => queue = 0);
@@ -142,6 +144,11 @@ const headerActions = $computed(() => [{
 			text: i18n.ts.showRenotes,
 			icon: 'ph-repeat ph-bold ph-lg',
 			ref: $$(withRenotes),
+		}, {
+			type: 'switch',
+			text: i18n.ts.withReplies,
+			icon: 'ti ti-arrow-back-up',
+			ref: $$(withReplies),
 		}, {
 			type: 'switch',
 			text: i18n.ts.fileAttachedOnly,
diff --git a/packages/frontend/src/pages/user/home.vue b/packages/frontend/src/pages/user/home.vue
index 8b43ba9393..a4a4ac2fbf 100644
--- a/packages/frontend/src/pages/user/home.vue
+++ b/packages/frontend/src/pages/user/home.vue
@@ -128,7 +128,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 				</div>
 				<MkInfo v-else-if="$i && $i.id === user.id">{{ i18n.ts.userPagePinTip }}</MkInfo>
 				<template v-if="narrow">
-					<XFiles :key="user.id" :user="user"/>
+					<XPhotos :key="user.id" :user="user"/>
 					<XActivity :key="user.id" :user="user"/>
 				</template>
 				<MkStickyContainer>
@@ -144,7 +144,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 			</div>
 		</div>
 		<div v-if="!narrow" class="sub _gaps" style="container-type: inline-size;">
-			<XFiles :key="user.id" :user="user"/>
+			<XPhotos :key="user.id" :user="user"/>
 			<XActivity :key="user.id" :user="user"/>
 			<XListenBrainz v-if="user.listenbrainz && listenbrainzdata" :key="user.id" :user="user"/>
 		</div>
@@ -193,7 +193,7 @@ function calcAge(birthdate: string): number {
 	return yearDiff;
 }
 
-const XFiles = defineAsyncComponent(() => import('./index.files.vue'));
+const XPhotos = defineAsyncComponent(() => import('./index.photos.vue'));
 const XActivity = defineAsyncComponent(() => import('./index.activity.vue'));
 const XListenBrainz = defineAsyncComponent(() => import("./index.listenbrainz.vue"));
 
diff --git a/packages/frontend/src/pages/user/index.files.vue b/packages/frontend/src/pages/user/index.photos.vue
similarity index 75%
rename from packages/frontend/src/pages/user/index.files.vue
rename to packages/frontend/src/pages/user/index.photos.vue
index e440c1419b..383261d890 100644
--- a/packages/frontend/src/pages/user/index.files.vue
+++ b/packages/frontend/src/pages/user/index.photos.vue
@@ -9,18 +9,17 @@ SPDX-License-Identifier: AGPL-3.0-only
 	<template #header>{{ i18n.ts.images }}</template>
 	<div :class="$style.root">
 		<MkLoading v-if="fetching"/>
-		<div v-if="!fetching && files.length > 0" :class="$style.stream">
+		<div v-if="!fetching && images.length > 0" :class="$style.stream">
 			<MkA
-				v-for="file in files"
-				:key="file.note.id + file.file.id"
+				v-for="image in images"
+				:key="image.note.id + image.file.id"
 				:class="$style.img"
-				:to="notePage(file.note)"
+				:to="notePage(image.note)"
 			>
-				<!-- TODO: 画像以外のファイルに対応 -->
-				<ImgWithBlurhash :hash="file.file.blurhash" :src="thumbnail(file.file)" :title="file.file.name"/>
+				<ImgWithBlurhash :hash="image.file.blurhash" :src="thumbnail(image.file)" :title="image.file.name"/>
 			</MkA>
 		</div>
-		<p v-if="!fetching && files.length == 0" :class="$style.empty">{{ i18n.ts.nothing }}</p>
+		<p v-if="!fetching && images.length == 0" :class="$style.empty">{{ i18n.ts.nothing }}</p>
 	</div>
 </MkContainer>
 </template>
@@ -41,7 +40,7 @@ const props = defineProps<{
 }>();
 
 let fetching = $ref(true);
-let files = $ref<{
+let images = $ref<{
 	note: Misskey.entities.Note;
 	file: Misskey.entities.DriveFile;
 }[]>([]);
@@ -53,15 +52,24 @@ function thumbnail(image: Misskey.entities.DriveFile): string {
 }
 
 onMounted(() => {
+	const image = [
+		'image/jpeg',
+		'image/webp',
+		'image/avif',
+		'image/png',
+		'image/gif',
+		'image/apng',
+		'image/vnd.mozilla.apng',
+	];
 	os.api('users/notes', {
 		userId: props.user.id,
-		withFiles: true,
+		fileType: image,
 		excludeNsfw: defaultStore.state.nsfw !== 'ignore',
-		limit: 15,
+		limit: 10,
 	}).then(notes => {
 		for (const note of notes) {
 			for (const file of note.files) {
-				files.push({
+				images.push({
 					note,
 					file,
 				});
diff --git a/packages/frontend/src/scripts/get-user-menu.ts b/packages/frontend/src/scripts/get-user-menu.ts
index 79b952689d..24ca93495a 100644
--- a/packages/frontend/src/scripts/get-user-menu.ts
+++ b/packages/frontend/src/scripts/get-user-menu.ts
@@ -80,15 +80,6 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: Router
 		});
 	}
 
-	async function toggleWithReplies() {
-		os.apiWithDialog('following/update', {
-			userId: user.id,
-			withReplies: !user.withReplies,
-		}).then(() => {
-			user.withReplies = !user.withReplies;
-		});
-	}
-
 	async function toggleNotify() {
 		os.apiWithDialog('following/update', {
 			userId: user.id,
@@ -291,10 +282,6 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: Router
 		// フォローしたとしても user.isFollowing はリアルタイム更新されないので不便なため
 		//if (user.isFollowing) {
 		menu = menu.concat([{
-			icon: user.withReplies ? 'ph-envelope ph-bold pg-lg-off' : 'ph-envelope ph-bold',
-			text: user.withReplies ? i18n.ts.hideRepliesToOthersInTimeline : i18n.ts.showRepliesToOthersInTimeline,
-			action: toggleWithReplies,
-		}, {
 			icon: user.notify === 'none' ? 'ph-bell ph-bold pg-lg' : 'ph-bell ph-bold pg-lg-off',
 			text: user.notify === 'none' ? i18n.ts.notifyNotes : i18n.ts.unnotifyNotes,
 			action: toggleNotify,
diff --git a/packages/frontend/src/store.ts b/packages/frontend/src/store.ts
index bd6e1f7f7a..2bf518b2f9 100644
--- a/packages/frontend/src/store.ts
+++ b/packages/frontend/src/store.ts
@@ -5,7 +5,7 @@
 
 import { markRaw, ref } from 'vue';
 import * as Misskey from 'misskey-js';
-import { miLocalStorage } from './local-storage.js';
+import { miLocalStorage } from './local-storage';
 import { Storage } from '@/pizzax.js';
 
 interface PostFormAction {
@@ -105,6 +105,10 @@ export const defaultStore = markRaw(new Storage('base', {
 		where: 'account',
 		default: 'nonSensitiveOnly' as 'likeOnly' | 'likeOnlyForRemote' | 'nonSensitiveOnly' | 'nonSensitiveOnlyForLocalLikeOnlyForRemote' | null,
 	},
+	mutedWords: {
+		where: 'account',
+		default: [],
+	},
 	mutedAds: {
 		where: 'account',
 		default: [] as string[],
diff --git a/packages/frontend/src/ui/deck/tl-column.vue b/packages/frontend/src/ui/deck/tl-column.vue
index a91fa3c5bb..554f067df0 100644
--- a/packages/frontend/src/ui/deck/tl-column.vue
+++ b/packages/frontend/src/ui/deck/tl-column.vue
@@ -23,9 +23,10 @@ SPDX-License-Identifier: AGPL-3.0-only
 	<MkTimeline
 		v-else-if="column.tl"
 		ref="timeline"
-		:key="column.tl + withRenotes + onlyFiles"
+		:key="column.tl + withRenotes + withReplies + onlyFiles"
 		:src="column.tl"
 		:withRenotes="withRenotes"
+		:withReplies="withReplies"
 		:onlyFiles="onlyFiles"
 	/>
 </XColumn>
@@ -51,6 +52,7 @@ let disabled = $ref(false);
 const isLocalTimelineAvailable = (($i == null && instance.policies.ltlAvailable) || ($i != null && $i.policies.ltlAvailable));
 const isGlobalTimelineAvailable = (($i == null && instance.policies.gtlAvailable) || ($i != null && $i.policies.gtlAvailable));
 const withRenotes = $ref(props.column.withRenotes ?? true);
+const withReplies = $ref(props.column.withReplies ?? false);
 const onlyFiles = $ref(props.column.onlyFiles ?? false);
 
 watch($$(withRenotes), v => {
@@ -59,6 +61,12 @@ watch($$(withRenotes), v => {
 	});
 });
 
+watch($$(withReplies), v => {
+	updateColumn(props.column.id, {
+		withReplies: v,
+	});
+});
+
 watch($$(onlyFiles), v => {
 	updateColumn(props.column.id, {
 		onlyFiles: v,
@@ -107,6 +115,10 @@ const menu = [{
 	type: 'switch',
 	text: i18n.ts.showRenotes,
 	ref: $$(withRenotes),
+}, {
+	type: 'switch',
+	text: i18n.ts.withReplies,
+	ref: $$(withReplies),
 }, {
 	type: 'switch',
 	text: i18n.ts.fileAttachedOnly,
diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md
index 4df7e8332b..f0fc47c207 100644
--- a/packages/misskey-js/etc/misskey-js.api.md
+++ b/packages/misskey-js/etc/misskey-js.api.md
@@ -1381,6 +1381,10 @@ export type Endpoints = {
         req: TODO;
         res: TODO;
     };
+    'i/get-word-muted-notes-count': {
+        req: TODO;
+        res: TODO;
+    };
     'i/import-following': {
         req: TODO;
         res: TODO;
@@ -2977,7 +2981,7 @@ type UserSorting = '+follower' | '-follower' | '+createdAt' | '-createdAt' | '+u
 //
 // src/api.types.ts:16:32 - (ae-forgotten-export) The symbol "TODO" needs to be exported by the entry point index.d.ts
 // src/api.types.ts:18:25 - (ae-forgotten-export) The symbol "NoParams" needs to be exported by the entry point index.d.ts
-// src/api.types.ts:630:18 - (ae-forgotten-export) The symbol "ShowUserReq" needs to be exported by the entry point index.d.ts
+// src/api.types.ts:631:18 - (ae-forgotten-export) The symbol "ShowUserReq" needs to be exported by the entry point index.d.ts
 // src/entities.ts:107:2 - (ae-forgotten-export) The symbol "notificationTypes_2" needs to be exported by the entry point index.d.ts
 // src/entities.ts:595:2 - (ae-forgotten-export) The symbol "ModerationLogPayloads" needs to be exported by the entry point index.d.ts
 // src/streaming.types.ts:33:4 - (ae-forgotten-export) The symbol "FIXME" needs to be exported by the entry point index.d.ts
diff --git a/packages/misskey-js/src/api.types.ts b/packages/misskey-js/src/api.types.ts
index 1c423610d1..7a8b6872dc 100644
--- a/packages/misskey-js/src/api.types.ts
+++ b/packages/misskey-js/src/api.types.ts
@@ -371,6 +371,7 @@ export type Endpoints = {
 	'i/favorites': { req: { limit?: number; sinceId?: NoteFavorite['id']; untilId?: NoteFavorite['id']; }; res: NoteFavorite[]; };
 	'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; };