diff --git a/packages/backend/src/core/NoteEditService.ts b/packages/backend/src/core/NoteEditService.ts
index dae3f485ff..d7a79920eb 100644
--- a/packages/backend/src/core/NoteEditService.ts
+++ b/packages/backend/src/core/NoteEditService.ts
@@ -46,6 +46,11 @@ import { MetaService } from '@/core/MetaService.js';
 import { SearchService } from '@/core/SearchService.js';
 import { FanoutTimelineService } from '@/core/FanoutTimelineService.js';
 import { UtilityService } from '@/core/UtilityService.js';
+import { UserBlockingService } from '@/core/UserBlockingService.js';
+import { CacheService } from '@/core/CacheService.js';
+import { isReply } from '@/misc/is-reply.js';
+import { trackPromise } from '@/misc/promise-tracker.js';
+import { isUserRelated } from '@/misc/is-user-related.js';
 
 type NotificationType = 'reply' | 'renote' | 'quote' | 'mention';
 
@@ -206,6 +211,8 @@ export class NoteEditService implements OnApplicationShutdown {
 		private activeUsersChart: ActiveUsersChart,
 		private instanceChart: InstanceChart,
 		private utilityService: UtilityService,
+		private userBlockingService: UserBlockingService,
+		private cacheService: CacheService,
 	) { }
 
 	@bindThis
@@ -222,7 +229,7 @@ export class NoteEditService implements OnApplicationShutdown {
 
 		const oldnote = await this.notesRepository.findOneBy({
 			id: editid,
-		});	
+		});
 
 		if (oldnote == null) {
 			throw new Error('no such note');
@@ -255,16 +262,18 @@ export class NoteEditService implements OnApplicationShutdown {
 		if (data.channel != null) data.localOnly = true;
 		if (data.updatedAt == null) data.updatedAt = new Date();
 
+		const meta = await this.metaService.fetch();
+
 		if (data.visibility === 'public' && data.channel == null) {
-			const sensitiveWords = (await this.metaService.fetch()).sensitiveWords;
-			if (this.isSensitive(data, sensitiveWords)) {
+			const sensitiveWords = meta.sensitiveWords;
+			if (this.utilityService.isSensitiveWordIncluded(data.cw ?? data.text ?? '', sensitiveWords)) {
 				data.visibility = 'home';
 			} else if ((await this.roleService.getUserPolicies(user.id)).canPublicNote === false) {
 				data.visibility = 'home';
 			}
 		}
 
-		const inSilencedInstance = this.utilityService.isSilencedHost((await this.metaService.fetch()).silencedHosts, user.host);
+		const inSilencedInstance = this.utilityService.isSilencedHost((meta).silencedHosts, user.host);
 
 		if (data.visibility === 'public' && inSilencedInstance && user.host !== null) {
 			data.visibility = 'home';
@@ -296,6 +305,18 @@ export class NoteEditService implements OnApplicationShutdown {
 			}
 		}
 
+		// Check blocking
+		if (data.renote && !this.isQuote(data)) {
+			if (data.renote.userHost === null) {
+				if (data.renote.userId !== user.id) {
+					const blocked = await this.userBlockingService.checkBlocked(data.renote.userId, user.id);
+					if (blocked) {
+						throw new Error('blocked');
+					}
+				}
+			}
+		}
+
 		// 返信対象がpublicではないならhomeにする
 		if (data.reply && data.reply.visibility !== 'public' && data.visibility === 'public') {
 			data.visibility = 'home';
@@ -316,6 +337,9 @@ export class NoteEditService implements OnApplicationShutdown {
 				data.text = data.text.slice(0, DB_MAX_NOTE_TEXT_LENGTH);
 			}
 			data.text = data.text.trim();
+			if (data.text === '') {
+				data.text = null;
+			}
 		} else {
 			data.text = null;
 		}
@@ -361,6 +385,14 @@ export class NoteEditService implements OnApplicationShutdown {
 			}
 		}
 
+		if (user.host && !data.cw) {
+			await this.federatedInstanceService.fetch(user.host).then(async i => {
+				if (i.isNSFW) {
+					data.cw = 'Instance is marked as NSFW';
+				}
+			});
+		}
+
 		const update: Partial<MiNote> = {};
 		if (data.text !== oldnote.text) {
 			update.text = data.text;
@@ -587,7 +619,15 @@ export class NoteEditService implements OnApplicationShutdown {
 						},
 					});
 
-					if (!isThreadMuted) {
+					const [
+						userIdsWhoMeMuting,
+					] = data.reply.userId ? await Promise.all([
+						this.cacheService.userMutingsCache.fetch(data.reply.userId),
+					]) : [new Set<string>()];
+
+					const muted = isUserRelated(note, userIdsWhoMeMuting);
+
+					if (!isThreadMuted || !muted) {
 						nm.push(data.reply.userId, 'reply');
 						this.globalEventService.publishMainStream(data.reply.userId, 'reply', noteObj);
 
@@ -603,11 +643,28 @@ export class NoteEditService implements OnApplicationShutdown {
 
 			// If it is renote
 			if (data.renote) {
-				const type = data.text ? 'quote' : 'renote';
+				const type = this.isQuote(data) ? 'quote' : 'renote';
 
 				// Notify
 				if (data.renote.userHost === null) {
-					nm.push(data.renote.userId, type);
+					const isThreadMuted = await this.noteThreadMutingsRepository.exist({
+						where: {
+							userId: data.renote.userId,
+							threadId: data.renote.threadId ?? data.renote.id,
+						},
+					});
+
+					const [
+						userIdsWhoMeMuting,
+					] = data.renote.userId ? await Promise.all([
+						this.cacheService.userMutingsCache.fetch(data.renote.userId),
+					]) : [new Set<string>()];
+
+					const muted = isUserRelated(note, userIdsWhoMeMuting);
+
+					if (!isThreadMuted || !muted) {
+						nm.push(data.renote.userId, type);
+					}
 				}
 
 				// Publish event
@@ -657,7 +714,7 @@ export class NoteEditService implements OnApplicationShutdown {
 						this.relayService.deliverToRelays(user, noteActivity);
 					}
 
-					dm.execute();
+					trackPromise(dm.execute());
 				})();
 			}
 			//#endregion
@@ -686,28 +743,9 @@ export class NoteEditService implements OnApplicationShutdown {
 	}
 
 	@bindThis
-	private isSensitive(note: Option, sensitiveWord: string[]): boolean {
-		if (sensitiveWord.length > 0) {
-			const text = note.cw ?? note.text ?? '';
-			if (text === '') return false;
-			const matched = sensitiveWord.some(filter => {
-				// represents RegExp
-				const regexp = filter.match(/^\/(.+)\/(.*)$/);
-				// This should never happen due to input sanitisation.
-				if (!regexp) {
-					const words = filter.split(' ');
-					return words.every(keyword => text.includes(keyword));
-				}
-				try {
-					return new RE2(regexp[1], regexp[2]).test(text);
-				} catch (err) {
-					// This should never happen due to input sanitisation.
-					return false;
-				}
-			});
-			if (matched) return true;
-		}
-		return false;
+	private isQuote(note: Option): note is Option & { renote: MiNote } {
+		// sync with misc/is-quote.ts
+		return !!note.renote && (!!note.text || !!note.cw || (!!note.files && !!note.files.length) || !!note.poll);
 	}
 
 	@bindThis
@@ -720,7 +758,15 @@ export class NoteEditService implements OnApplicationShutdown {
 				},
 			});
 
-			if (isThreadMuted) {
+			const [
+				userIdsWhoMeMuting,
+			] = u.id ? await Promise.all([
+				this.cacheService.userMutingsCache.fetch(u.id),
+			]) : [new Set<string>()];
+
+			const muted = isUserRelated(note, userIdsWhoMeMuting);
+
+			if (isThreadMuted || muted) {
 				continue;
 			}
 
@@ -748,7 +794,7 @@ export class NoteEditService implements OnApplicationShutdown {
 		const user = await this.usersRepository.findOneBy({ id: note.userId });
 		if (user == null) throw new Error('user not found');
 
-		const content = data.renote && data.text == null && data.poll == null && (data.files == null || data.files.length === 0)
+		const content = data.renote && !this.isQuote(data)
 			? this.apRendererService.renderAnnounce(data.renote.uri ? data.renote.uri : `${this.config.url}/notes/${data.renote.id}`, note)
 			: this.apRendererService.renderUpdate(await this.apRendererService.renderUpNote(note, false), user);
 
@@ -782,6 +828,7 @@ export class NoteEditService implements OnApplicationShutdown {
 	@bindThis
 	private async pushToTl(note: MiNote, user: { id: MiUser['id']; host: MiUser['host']; }) {
 		const meta = await this.metaService.fetch();
+		if (!meta.enableFanoutTimeline) return;
 
 		const r = this.redisForTimelines.pipeline();
 
@@ -825,7 +872,7 @@ export class NoteEditService implements OnApplicationShutdown {
 
 			if (note.visibility === 'followers') {
 				// TODO: 重そうだから何とかしたい Set 使う?
-				userListMemberships = userListMemberships.filter(x => followings.some(f => f.followerId === x.userListUserId));
+				userListMemberships = userListMemberships.filter(x => x.userListUserId === user.id || followings.some(f => f.followerId === x.userListUserId));
 			}
 
 			// TODO: あまりにも数が多いと redisPipeline.exec に失敗する(理由は不明)ため、3万件程度を目安に分割して実行するようにする
@@ -834,7 +881,7 @@ export class NoteEditService implements OnApplicationShutdown {
 				if (note.visibility === 'specified' && !note.visibleUserIds.some(v => v === following.followerId)) continue;
 
 				// 「自分自身への返信 or そのフォロワーへの返信」のどちらでもない場合
-				if (note.replyId && !(note.replyUserId === note.userId || note.replyUserId === following.followerId)) {
+				if (isReply(note, following.followerId)) {
 					if (!following.withReplies) continue;
 				}
 
@@ -848,11 +895,12 @@ export class NoteEditService implements OnApplicationShutdown {
 				// ダイレクトのとき、そのリストが対象外のユーザーの場合
 				if (
 					note.visibility === 'specified' &&
+					note.userId !== userListMembership.userListUserId &&
 					!note.visibleUserIds.some(v => v === userListMembership.userListUserId)
 				) continue;
 
 				// 「自分自身への返信 or そのリストの作成者への返信」のどちらでもない場合
-				if (note.replyId && !(note.replyUserId === note.userId || note.replyUserId === userListMembership.userListUserId)) {
+				if (isReply(note, userListMembership.userListUserId)) {
 					if (!userListMembership.withReplies) continue;
 				}
 
@@ -870,11 +918,14 @@ export class NoteEditService implements OnApplicationShutdown {
 			}
 
 			// 自分自身以外への返信
-			if (note.replyId && note.replyUserId !== note.userId) {
+			if (isReply(note)) {
 				this.fanoutTimelineService.push(`userTimelineWithReplies:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r);
 
 				if (note.visibility === 'public' && note.userHost == null) {
 					this.fanoutTimelineService.push('localTimelineWithReplies', note.id, 300, r);
+					if (note.replyUserHost == null) {
+						this.fanoutTimelineService.push(`localTimelineWithReplyTo:${note.replyUserId}`, note.id, 300 / 10, r);
+					}
 				}
 			} else {
 				this.fanoutTimelineService.push(`userTimeline:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r);