Merge a909210053 into f74c38f313
				
					
				
			This commit is contained in:
		
						commit
						4639d0bcd0
					
				|  | @ -40,6 +40,7 @@ | |||
| - Fix: チャットルームが削除された場合・チャットルームから抜けた場合に、未読状態が残り続けることがあるのを修正 | ||||
| - Fix: ユーザ除外アンテナをインポートできない問題を修正 | ||||
| - Fix: アンテナのセンシティブなチャンネルのノートを含むかどうかの情報がエクスポートされない問題を修正 | ||||
| - Fix: ミュート対象ユーザーが引用されているノートがRNされたときにミュートを貫通してしまう問題を修正 #16009 | ||||
| 
 | ||||
| 
 | ||||
| ## 2025.5.0 | ||||
|  |  | |||
|  | @ -120,6 +120,8 @@ export class FanoutTimelineEndpointService { | |||
| 				filter = (note) => { | ||||
| 					if (isUserRelated(note, userIdsWhoBlockingMe, ps.ignoreAuthorFromBlock)) return false; | ||||
| 					if (isUserRelated(note, userIdsWhoMeMuting, ps.ignoreAuthorFromMute)) return false; | ||||
| 					if (isUserRelated(note.renote, userIdsWhoBlockingMe, ps.ignoreAuthorFromBlock)) return false; | ||||
| 					if (isUserRelated(note.renote, userIdsWhoMeMuting, ps.ignoreAuthorFromMute)) return false; | ||||
| 					if (!ps.ignoreAuthorFromMute && isRenote(note) && !isQuote(note) && userIdsWhoMeMutingRenotes.has(note.userId)) return false; | ||||
| 					if (isInstanceMuted(note, userMutedInstances)) return false; | ||||
| 
 | ||||
|  |  | |||
|  | @ -77,9 +77,51 @@ export class QueryService { | |||
| 		return q; | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * ミュートやブロックのようにすべてのタイムラインで共通に使用するフィルターを定義します。 | ||||
| 	 * | ||||
| 	 * 特別な事情がない限り、各タイムラインはこの関数を呼び出してフィルターを適用してください。 | ||||
| 	 * | ||||
| 	 * Notes for future maintainers: | ||||
| 	 * 1) この関数で生成するクエリと同等の処理が FanoutTimelineEndpointService にあります。 | ||||
| 	 *    この関数を変更した場合、FanoutTimelineEndpointService の方も変更する必要があります。 | ||||
| 	 * 2) 以下のエンドポイントでは特別な事情があるため queryService のそれぞれの関数を呼び出しています。 | ||||
| 	 *    この関数を変更した場合、以下のエンドポイントの方も変更する必要があることがあります。 | ||||
| 	 *    - packages/backend/src/server/api/endpoints/clips/notes.ts | ||||
| 	 */ | ||||
| 	@bindThis | ||||
| 	public generateBaseNoteFilteringQuery( | ||||
| 		query: SelectQueryBuilder<any>, | ||||
| 		me: { id: MiUser['id'] } | null, | ||||
| 		{ | ||||
| 			excludeUserFromMute, | ||||
| 			excludeAuthor, | ||||
| 		}: { | ||||
| 			excludeUserFromMute?: MiUser['id'], | ||||
| 			excludeAuthor?: boolean, | ||||
| 		} = {}, | ||||
| 	): void { | ||||
| 		this.generateBlockedHostQueryForNote(query, excludeAuthor); | ||||
| 		this.generateSuspendedUserQueryForNote(query, excludeAuthor); | ||||
| 		if (me) { | ||||
| 			this.generateMutedUserQueryForNotes(query, me, { excludeUserFromMute }); | ||||
| 			this.generateBlockedUserQueryForNotes(query, me); | ||||
| 			this.generateMutedUserQueryForNotes(query, me, { noteColumn: 'renote', excludeUserFromMute }); | ||||
| 			this.generateBlockedUserQueryForNotes(query, me, { noteColumn: 'renote' }); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// ここでいうBlockedは被Blockedの意
 | ||||
| 	@bindThis | ||||
| 	public generateBlockedUserQueryForNotes(q: SelectQueryBuilder<any>, me: { id: MiUser['id'] }): void { | ||||
| 	public generateBlockedUserQueryForNotes( | ||||
| 		q: SelectQueryBuilder<any>, | ||||
| 		me: { id: MiUser['id'] }, | ||||
| 		{ | ||||
| 			noteColumn = 'note', | ||||
| 		}: { | ||||
| 			noteColumn?: string, | ||||
| 		} = {}, | ||||
| 	): void { | ||||
| 		const blockingQuery = this.blockingsRepository.createQueryBuilder('blocking') | ||||
| 			.select('blocking.blockerId') | ||||
| 			.where('blocking.blockeeId = :blockeeId', { blockeeId: me.id }); | ||||
|  | @ -88,16 +130,20 @@ export class QueryService { | |||
| 		// 投稿の返信先の作者にブロックされていない かつ
 | ||||
| 		// 投稿の引用元の作者にブロックされていない
 | ||||
| 		q | ||||
| 			.andWhere(`note.userId NOT IN (${ blockingQuery.getQuery() })`) | ||||
| 			.andWhere(new Brackets(qb => { | ||||
| 				qb | ||||
| 					.where('note.replyUserId IS NULL') | ||||
| 					.orWhere(`note.replyUserId NOT IN (${ blockingQuery.getQuery() })`); | ||||
| 					.where(`${noteColumn}.userId IS NULL`) | ||||
| 					.orWhere(`${noteColumn}.userId NOT IN (${ blockingQuery.getQuery() })`); | ||||
| 			})) | ||||
| 			.andWhere(new Brackets(qb => { | ||||
| 				qb | ||||
| 					.where('note.renoteUserId IS NULL') | ||||
| 					.orWhere(`note.renoteUserId NOT IN (${ blockingQuery.getQuery() })`); | ||||
| 					.where(`${noteColumn}.replyUserId IS NULL`) | ||||
| 					.orWhere(`${noteColumn}.replyUserId NOT IN (${ blockingQuery.getQuery() })`); | ||||
| 			})) | ||||
| 			.andWhere(new Brackets(qb => { | ||||
| 				qb | ||||
| 					.where(`${noteColumn}.renoteUserId IS NULL`) | ||||
| 					.orWhere(`${noteColumn}.renoteUserId NOT IN (${ blockingQuery.getQuery() })`); | ||||
| 			})); | ||||
| 
 | ||||
| 		q.setParameters(blockingQuery.getParameters()); | ||||
|  | @ -137,13 +183,23 @@ export class QueryService { | |||
| 	} | ||||
| 
 | ||||
| 	@bindThis | ||||
| 	public generateMutedUserQueryForNotes(q: SelectQueryBuilder<any>, me: { id: MiUser['id'] }, exclude?: { id: MiUser['id'] }): void { | ||||
| 	public generateMutedUserQueryForNotes( | ||||
| 		q: SelectQueryBuilder<any>, | ||||
| 		me: { id: MiUser['id'] }, | ||||
| 		{ | ||||
| 			excludeUserFromMute, | ||||
| 			noteColumn = 'note', | ||||
| 		}: { | ||||
| 			excludeUserFromMute?: MiUser['id'], | ||||
| 			noteColumn?: string, | ||||
| 		} = {}, | ||||
| 	): void { | ||||
| 		const mutingQuery = this.mutingsRepository.createQueryBuilder('muting') | ||||
| 			.select('muting.muteeId') | ||||
| 			.where('muting.muterId = :muterId', { muterId: me.id }); | ||||
| 
 | ||||
| 		if (exclude) { | ||||
| 			mutingQuery.andWhere('muting.muteeId != :excludeId', { excludeId: exclude.id }); | ||||
| 		if (excludeUserFromMute) { | ||||
| 			mutingQuery.andWhere('muting.muteeId != :excludeId', { excludeId: excludeUserFromMute }); | ||||
| 		} | ||||
| 
 | ||||
| 		const mutingInstanceQuery = this.userProfilesRepository.createQueryBuilder('user_profile') | ||||
|  | @ -154,32 +210,36 @@ export class QueryService { | |||
| 		// 投稿の返信先の作者をミュートしていない かつ
 | ||||
| 		// 投稿の引用元の作者をミュートしていない
 | ||||
| 		q | ||||
| 			.andWhere(`note.userId NOT IN (${ mutingQuery.getQuery() })`) | ||||
| 			.andWhere(new Brackets(qb => { | ||||
| 				qb | ||||
| 					.where('note.replyUserId IS NULL') | ||||
| 					.orWhere(`note.replyUserId NOT IN (${ mutingQuery.getQuery() })`); | ||||
| 					.where(`${noteColumn}.userId IS NULL`) | ||||
| 					.orWhere(`${noteColumn}.userId NOT IN (${ mutingQuery.getQuery() })`); | ||||
| 			})) | ||||
| 			.andWhere(new Brackets(qb => { | ||||
| 				qb | ||||
| 					.where('note.renoteUserId IS NULL') | ||||
| 					.orWhere(`note.renoteUserId NOT IN (${ mutingQuery.getQuery() })`); | ||||
| 					.where(`${noteColumn}.replyUserId IS NULL`) | ||||
| 					.orWhere(`${noteColumn}.replyUserId NOT IN (${ mutingQuery.getQuery() })`); | ||||
| 			})) | ||||
| 			.andWhere(new Brackets(qb => { | ||||
| 				qb | ||||
| 					.where(`${noteColumn}.renoteUserId IS NULL`) | ||||
| 					.orWhere(`${noteColumn}.renoteUserId NOT IN (${ mutingQuery.getQuery() })`); | ||||
| 			})) | ||||
| 			// mute instances
 | ||||
| 			.andWhere(new Brackets(qb => { | ||||
| 				qb | ||||
| 					.andWhere('note.userHost IS NULL') | ||||
| 					.orWhere(`NOT ((${ mutingInstanceQuery.getQuery() })::jsonb ? note.userHost)`); | ||||
| 					.andWhere(`${noteColumn}.userHost IS NULL`) | ||||
| 					.orWhere(`NOT ((${ mutingInstanceQuery.getQuery() })::jsonb ? ${noteColumn}.userHost)`); | ||||
| 			})) | ||||
| 			.andWhere(new Brackets(qb => { | ||||
| 				qb | ||||
| 					.where('note.replyUserHost IS NULL') | ||||
| 					.orWhere(`NOT ((${ mutingInstanceQuery.getQuery() })::jsonb ? note.replyUserHost)`); | ||||
| 					.where(`${noteColumn}.replyUserHost IS NULL`) | ||||
| 					.orWhere(`NOT ((${ mutingInstanceQuery.getQuery() })::jsonb ? ${noteColumn}.replyUserHost)`); | ||||
| 			})) | ||||
| 			.andWhere(new Brackets(qb => { | ||||
| 				qb | ||||
| 					.where('note.renoteUserHost IS NULL') | ||||
| 					.orWhere(`NOT ((${ mutingInstanceQuery.getQuery() })::jsonb ? note.renoteUserHost)`); | ||||
| 					.where(`${noteColumn}.renoteUserHost IS NULL`) | ||||
| 					.orWhere(`NOT ((${ mutingInstanceQuery.getQuery() })::jsonb ? ${noteColumn}.renoteUserHost)`); | ||||
| 			})); | ||||
| 
 | ||||
| 		q.setParameters(mutingQuery.getParameters()); | ||||
|  |  | |||
|  | @ -234,10 +234,7 @@ export class SearchService { | |||
| 		} | ||||
| 
 | ||||
| 		this.queryService.generateVisibilityQuery(query, me); | ||||
| 		this.queryService.generateBlockedHostQueryForNote(query); | ||||
| 		this.queryService.generateSuspendedUserQueryForNote(query); | ||||
| 		if (me) this.queryService.generateMutedUserQueryForNotes(query, me); | ||||
| 		if (me) this.queryService.generateBlockedUserQueryForNotes(query, me); | ||||
| 		this.queryService.generateBaseNoteFilteringQuery(query, me); | ||||
| 
 | ||||
| 		return query.limit(pagination.limit).getMany(); | ||||
| 	} | ||||
|  |  | |||
|  | @ -3,7 +3,17 @@ | |||
|  * SPDX-License-Identifier: AGPL-3.0-only | ||||
|  */ | ||||
| 
 | ||||
| export function isUserRelated(note: any, userIds: Set<string>, ignoreAuthor = false): boolean { | ||||
| import type { MiUser } from '@/models/_.js'; | ||||
| 
 | ||||
| interface NoteLike { | ||||
| 	userId: MiUser['id']; | ||||
| 	reply?: NoteLike | null; | ||||
| 	renote?: NoteLike | null; | ||||
| 	replyUserId?: MiUser['id'] | null; | ||||
| 	renoteUserId?: MiUser['id'] | null; | ||||
| } | ||||
| 
 | ||||
| export function isUserRelated(note: NoteLike | null | undefined, userIds: Set<string>, ignoreAuthor = false): boolean { | ||||
| 	if (!note) { | ||||
| 		return false; | ||||
| 	} | ||||
|  | @ -12,13 +22,16 @@ export function isUserRelated(note: any, userIds: Set<string>, ignoreAuthor = fa | |||
| 		return true; | ||||
| 	} | ||||
| 
 | ||||
| 	if (note.reply != null && note.reply.userId !== note.userId && userIds.has(note.reply.userId)) { | ||||
| 	const replyUserId = note.replyUserId ?? note.reply?.userId; | ||||
| 	if (replyUserId != null && replyUserId !== note.userId && userIds.has(replyUserId)) { | ||||
| 		return true; | ||||
| 	} | ||||
| 
 | ||||
| 	if (note.renote != null && note.renote.userId !== note.userId && userIds.has(note.renote.userId)) { | ||||
| 	const renoteUserId = note.renoteUserId ?? note.renote?.userId; | ||||
| 	if (renoteUserId != null && renoteUserId !== note.userId && userIds.has(renoteUserId)) { | ||||
| 		return true; | ||||
| 	} | ||||
| 
 | ||||
| 	return false; | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -111,11 +111,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | |||
| 			// NOTE: センシティブ除外の設定はこのエンドポイントでは無視する。
 | ||||
| 			// https://github.com/misskey-dev/misskey/pull/15346#discussion_r1929950255
 | ||||
| 
 | ||||
| 			this.queryService.generateBlockedHostQueryForNote(query); | ||||
| 			this.queryService.generateSuspendedUserQueryForNote(query); | ||||
| 			this.queryService.generateVisibilityQuery(query, me); | ||||
| 			this.queryService.generateMutedUserQueryForNotes(query, me); | ||||
| 			this.queryService.generateBlockedUserQueryForNotes(query, me); | ||||
| 			this.queryService.generateBaseNoteFilteringQuery(query, me); | ||||
| 
 | ||||
| 			const notes = await query.getMany(); | ||||
| 			if (sinceId != null && untilId == null) { | ||||
|  |  | |||
|  | @ -121,12 +121,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | |||
| 			.leftJoinAndSelect('renote.user', 'renoteUser') | ||||
| 			.leftJoinAndSelect('note.channel', 'channel'); | ||||
| 
 | ||||
| 		this.queryService.generateBlockedHostQueryForNote(query); | ||||
| 		this.queryService.generateSuspendedUserQueryForNote(query); | ||||
| 		if (me) { | ||||
| 			this.queryService.generateMutedUserQueryForNotes(query, me); | ||||
| 			this.queryService.generateBlockedUserQueryForNotes(query, me); | ||||
| 		} | ||||
| 		this.queryService.generateBaseNoteFilteringQuery(query, me); | ||||
| 		//#endregion
 | ||||
| 
 | ||||
| 		return await query.limit(ps.limit).getMany(); | ||||
|  |  | |||
|  | @ -91,6 +91,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | |||
| 			if (me) { | ||||
| 				this.queryService.generateMutedUserQueryForNotes(query, me); | ||||
| 				this.queryService.generateBlockedUserQueryForNotes(query, me); | ||||
| 				this.queryService.generateMutedUserQueryForNotes(query, me, { noteColumn: 'renote' }); | ||||
| 				this.queryService.generateBlockedUserQueryForNotes(query, me, { noteColumn: 'renote' }); | ||||
| 			} | ||||
| 
 | ||||
| 			const notes = await query | ||||
|  |  | |||
|  | @ -70,12 +70,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | |||
| 				.leftJoinAndSelect('renote.user', 'renoteUser'); | ||||
| 
 | ||||
| 			this.queryService.generateVisibilityQuery(query, me); | ||||
| 			this.queryService.generateBlockedHostQueryForNote(query); | ||||
| 			this.queryService.generateSuspendedUserQueryForNote(query); | ||||
| 			if (me) { | ||||
| 				this.queryService.generateMutedUserQueryForNotes(query, me); | ||||
| 				this.queryService.generateBlockedUserQueryForNotes(query, me); | ||||
| 			} | ||||
| 			this.queryService.generateBaseNoteFilteringQuery(query, me); | ||||
| 
 | ||||
| 			const notes = await query.limit(ps.limit).getMany(); | ||||
| 
 | ||||
|  |  | |||
|  | @ -78,11 +78,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | |||
| 				.leftJoinAndSelect('reply.user', 'replyUser') | ||||
| 				.leftJoinAndSelect('renote.user', 'renoteUser'); | ||||
| 
 | ||||
| 			if (me) { | ||||
| 				this.queryService.generateMutedUserQueryForNotes(query, me); | ||||
| 				this.queryService.generateBlockedUserQueryForNotes(query, me); | ||||
| 				this.queryService.generateMutedUserRenotesQueryForNotes(query, me); | ||||
| 			} | ||||
| 			this.queryService.generateBaseNoteFilteringQuery(query, me); | ||||
| 			if (me) this.queryService.generateMutedUserRenotesQueryForNotes(query, me); | ||||
| 
 | ||||
| 			if (ps.withFiles) { | ||||
| 				query.andWhere('note.fileIds != \'{}\''); | ||||
|  |  | |||
|  | @ -243,10 +243,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | |||
| 		} | ||||
| 
 | ||||
| 		this.queryService.generateVisibilityQuery(query, me); | ||||
| 		this.queryService.generateBlockedHostQueryForNote(query); | ||||
| 		this.queryService.generateSuspendedUserQueryForNote(query); | ||||
| 		this.queryService.generateMutedUserQueryForNotes(query, me); | ||||
| 		this.queryService.generateBlockedUserQueryForNotes(query, me); | ||||
| 		this.queryService.generateBaseNoteFilteringQuery(query, me); | ||||
| 		this.queryService.generateMutedUserRenotesQueryForNotes(query, me); | ||||
| 
 | ||||
| 		if (ps.includeMyRenotes === false) { | ||||
|  |  | |||
|  | @ -156,10 +156,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | |||
| 			.leftJoinAndSelect('renote.user', 'renoteUser'); | ||||
| 
 | ||||
| 		this.queryService.generateVisibilityQuery(query, me); | ||||
| 		this.queryService.generateBlockedHostQueryForNote(query); | ||||
| 		this.queryService.generateSuspendedUserQueryForNote(query); | ||||
| 		if (me) this.queryService.generateMutedUserQueryForNotes(query, me); | ||||
| 		if (me) this.queryService.generateBlockedUserQueryForNotes(query, me); | ||||
| 		this.queryService.generateBaseNoteFilteringQuery(query, me); | ||||
| 		if (me) this.queryService.generateMutedUserRenotesQueryForNotes(query, me); | ||||
| 
 | ||||
| 		if (ps.withFiles) { | ||||
|  |  | |||
|  | @ -72,11 +72,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | |||
| 				.leftJoinAndSelect('renote.user', 'renoteUser'); | ||||
| 
 | ||||
| 			this.queryService.generateVisibilityQuery(query, me); | ||||
| 			this.queryService.generateBlockedHostQueryForNote(query); | ||||
| 			this.queryService.generateSuspendedUserQueryForNote(query); | ||||
| 			this.queryService.generateMutedUserQueryForNotes(query, me); | ||||
| 			this.queryService.generateBaseNoteFilteringQuery(query, me); | ||||
| 			this.queryService.generateMutedNoteThreadQuery(query, me); | ||||
| 			this.queryService.generateBlockedUserQueryForNotes(query, me); | ||||
| 
 | ||||
| 			if (ps.visibility) { | ||||
| 				query.andWhere('note.visibility = :visibility', { visibility: ps.visibility }); | ||||
|  |  | |||
|  | @ -72,10 +72,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | |||
| 				.leftJoinAndSelect('renote.user', 'renoteUser'); | ||||
| 
 | ||||
| 			this.queryService.generateVisibilityQuery(query, me); | ||||
| 			this.queryService.generateBlockedHostQueryForNote(query); | ||||
| 			this.queryService.generateSuspendedUserQueryForNote(query); | ||||
| 			if (me) this.queryService.generateMutedUserQueryForNotes(query, me); | ||||
| 			if (me) this.queryService.generateBlockedUserQueryForNotes(query, me); | ||||
| 			this.queryService.generateBaseNoteFilteringQuery(query, me); | ||||
| 
 | ||||
| 			const renotes = await query.limit(ps.limit).getMany(); | ||||
| 
 | ||||
|  |  | |||
|  | @ -56,10 +56,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | |||
| 				.leftJoinAndSelect('renote.user', 'renoteUser'); | ||||
| 
 | ||||
| 			this.queryService.generateVisibilityQuery(query, me); | ||||
| 			this.queryService.generateBlockedHostQueryForNote(query); | ||||
| 			this.queryService.generateSuspendedUserQueryForNote(query); | ||||
| 			if (me) this.queryService.generateMutedUserQueryForNotes(query, me); | ||||
| 			if (me) this.queryService.generateBlockedUserQueryForNotes(query, me); | ||||
| 			this.queryService.generateBaseNoteFilteringQuery(query, me); | ||||
| 
 | ||||
| 			const timeline = await query.limit(ps.limit).getMany(); | ||||
| 
 | ||||
|  |  | |||
|  | @ -81,10 +81,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | |||
| 				.leftJoinAndSelect('renote.user', 'renoteUser'); | ||||
| 
 | ||||
| 			this.queryService.generateVisibilityQuery(query, me); | ||||
| 			this.queryService.generateBlockedHostQueryForNote(query); | ||||
| 			this.queryService.generateSuspendedUserQueryForNote(query); | ||||
| 			if (me) this.queryService.generateMutedUserQueryForNotes(query, me); | ||||
| 			if (me) this.queryService.generateBlockedUserQueryForNotes(query, me); | ||||
| 			this.queryService.generateBaseNoteFilteringQuery(query, me); | ||||
| 
 | ||||
| 			try { | ||||
| 				if (ps.tag) { | ||||
|  |  | |||
|  | @ -199,10 +199,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | |||
| 		})); | ||||
| 
 | ||||
| 		this.queryService.generateVisibilityQuery(query, me); | ||||
| 		this.queryService.generateBlockedHostQueryForNote(query); | ||||
| 		this.queryService.generateSuspendedUserQueryForNote(query); | ||||
| 		this.queryService.generateMutedUserQueryForNotes(query, me); | ||||
| 		this.queryService.generateBlockedUserQueryForNotes(query, me); | ||||
| 		this.queryService.generateBaseNoteFilteringQuery(query, me); | ||||
| 		this.queryService.generateMutedUserRenotesQueryForNotes(query, me); | ||||
| 
 | ||||
| 		if (ps.includeMyRenotes === false) { | ||||
|  |  | |||
|  | @ -184,10 +184,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | |||
| 			})); | ||||
| 
 | ||||
| 		this.queryService.generateVisibilityQuery(query, me); | ||||
| 		this.queryService.generateBlockedHostQueryForNote(query); | ||||
| 		this.queryService.generateSuspendedUserQueryForNote(query); | ||||
| 		this.queryService.generateMutedUserQueryForNotes(query, me); | ||||
| 		this.queryService.generateBlockedUserQueryForNotes(query, me); | ||||
| 		this.queryService.generateBaseNoteFilteringQuery(query, me); | ||||
| 		this.queryService.generateMutedUserRenotesQueryForNotes(query, me); | ||||
| 
 | ||||
| 		if (ps.includeMyRenotes === false) { | ||||
|  |  | |||
|  | @ -102,10 +102,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | |||
| 				.leftJoinAndSelect('renote.user', 'renoteUser'); | ||||
| 
 | ||||
| 			this.queryService.generateVisibilityQuery(query, me); | ||||
| 			this.queryService.generateBlockedHostQueryForNote(query); | ||||
| 			this.queryService.generateSuspendedUserQueryForNote(query); | ||||
| 			this.queryService.generateMutedUserQueryForNotes(query, me); | ||||
| 			this.queryService.generateBlockedUserQueryForNotes(query, me); | ||||
| 			this.queryService.generateBaseNoteFilteringQuery(query, me); | ||||
| 
 | ||||
| 			const notes = await query.getMany(); | ||||
| 			notes.sort((a, b) => a.id > b.id ? -1 : 1); | ||||
|  |  | |||
|  | @ -186,12 +186,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | |||
| 		} | ||||
| 
 | ||||
| 		this.queryService.generateVisibilityQuery(query, me); | ||||
| 		this.queryService.generateBlockedHostQueryForNote(query, true); | ||||
| 		this.queryService.generateSuspendedUserQueryForNote(query, true); | ||||
| 		if (me) { | ||||
| 			this.queryService.generateMutedUserQueryForNotes(query, me, { id: ps.userId }); | ||||
| 			this.queryService.generateBlockedUserQueryForNotes(query, me); | ||||
| 		} | ||||
| 		this.queryService.generateBaseNoteFilteringQuery(query, me, { | ||||
| 			excludeAuthor: true, | ||||
| 			excludeUserFromMute: ps.userId, | ||||
| 		}); | ||||
| 
 | ||||
| 		if (ps.withFiles) { | ||||
| 			query.andWhere('note.fileIds != \'{}\''); | ||||
|  |  | |||
|  | @ -64,6 +64,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | |||
| 			this.queryService.generateMutedUserQueryForUsers(query, me); | ||||
| 			this.queryService.generateBlockQueryForUsers(query, me); | ||||
| 			this.queryService.generateBlockedUserQueryForNotes(query, me); | ||||
| 			this.queryService.generateBlockedUserQueryForNotes(query, me, { noteColumn: 'renote' }); | ||||
| 
 | ||||
| 			const followingQuery = this.followingsRepository.createQueryBuilder('following') | ||||
| 				.select('following.followeeId') | ||||
|  |  | |||
|  | @ -345,6 +345,44 @@ describe('Timelines', () => { | |||
| 			assert.strictEqual(res.body.some(note => note.id === carolNote.id), false); | ||||
| 		}); | ||||
| 
 | ||||
| 		test.concurrent('ミュートしているユーザーのノートの、関係のないユーザによる引用ノートの、フォローしているユーザーによるリノートが含まれない', async () => { | ||||
| 			const [alice, bob, carol, dave] = await Promise.all([signup(), signup(), signup(), signup()]); | ||||
| 
 | ||||
| 			await api('following/create', { userId: bob.id }, alice); | ||||
| 			await api('mute/create', { userId: carol.id }, alice); | ||||
| 			await setTimeout(1000); | ||||
| 			const carolNote = await post(carol, { text: 'hi' }); | ||||
| 			const daveNote = await post(dave, { text: 'quote hi', renoteId: carolNote.id }); | ||||
| 			const bobNote = await post(bob, { renoteId: daveNote.id }); | ||||
| 
 | ||||
| 			await waitForPushToTl(); | ||||
| 
 | ||||
| 			const res = await api('notes/timeline', { limit: 100 }, alice); | ||||
| 
 | ||||
| 			assert.strictEqual(res.body.some(note => note.id === carolNote.id), false); | ||||
| 			assert.strictEqual(res.body.some(note => note.id === daveNote.id), false); | ||||
| 			assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); | ||||
| 		}); | ||||
| 
 | ||||
| 		test.concurrent('ミュートしているユーザーのノートの、関係のないユーザによるリプライの、フォローしているユーザーによるリノートが含まれない', async () => { | ||||
| 			const [alice, bob, carol, dave] = await Promise.all([signup(), signup(), signup(), signup()]); | ||||
| 
 | ||||
| 			await api('following/create', { userId: bob.id }, alice); | ||||
| 			await api('mute/create', { userId: carol.id }, alice); | ||||
| 			await setTimeout(1000); | ||||
| 			const carolNote = await post(carol, { text: 'hi' }); | ||||
| 			const daveNote = await post(dave, { text: 'quote hi', replyId: carolNote.id }); | ||||
| 			const bobNote = await post(bob, { renoteId: daveNote.id }); | ||||
| 
 | ||||
| 			await waitForPushToTl(); | ||||
| 
 | ||||
| 			const res = await api('notes/timeline', { limit: 100 }, alice); | ||||
| 
 | ||||
| 			assert.strictEqual(res.body.some(note => note.id === carolNote.id), false); | ||||
| 			assert.strictEqual(res.body.some(note => note.id === daveNote.id), false); | ||||
| 			assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); | ||||
| 		}); | ||||
| 
 | ||||
| 		test.concurrent('フォローしているリモートユーザーのノートが含まれる', async () => { | ||||
| 			const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]); | ||||
| 
 | ||||
|  | @ -687,6 +725,42 @@ describe('Timelines', () => { | |||
| 			assert.strictEqual(res.body.some(note => note.id === carolNote.id), false); | ||||
| 		}); | ||||
| 
 | ||||
| 		test.concurrent('ミュートしているユーザーのノートの、関係のないユーザによる引用ノートの、リノートが含まれない', async () => { | ||||
| 			const [alice, bob, carol, dave] = await Promise.all([signup(), signup(), signup(), signup()]); | ||||
| 
 | ||||
| 			await api('mute/create', { userId: carol.id }, alice); | ||||
| 			await setTimeout(1000); | ||||
| 			const carolNote = await post(carol, { text: 'hi' }); | ||||
| 			const daveNote = await post(dave, { text: 'quote hi', renoteId: carolNote.id }); | ||||
| 			const bobNote = await post(bob, { renoteId: daveNote.id }); | ||||
| 
 | ||||
| 			await waitForPushToTl(); | ||||
| 
 | ||||
| 			const res = await api('notes/local-timeline', { limit: 100 }, alice); | ||||
| 
 | ||||
| 			assert.strictEqual(res.body.some(note => note.id === carolNote.id), false); | ||||
| 			assert.strictEqual(res.body.some(note => note.id === daveNote.id), false); | ||||
| 			assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); | ||||
| 		}); | ||||
| 
 | ||||
| 		test.concurrent('ミュートしているユーザーのノートの、関係のないユーザによるリプライの、リノートが含まれない', async () => { | ||||
| 			const [alice, bob, carol, dave] = await Promise.all([signup(), signup(), signup(), signup()]); | ||||
| 
 | ||||
| 			await api('mute/create', { userId: carol.id }, alice); | ||||
| 			await setTimeout(1000); | ||||
| 			const carolNote = await post(carol, { text: 'hi' }); | ||||
| 			const daveNote = await post(dave, { text: 'quote hi', replyId: carolNote.id }); | ||||
| 			const bobNote = await post(bob, { renoteId: daveNote.id }); | ||||
| 
 | ||||
| 			await waitForPushToTl(); | ||||
| 
 | ||||
| 			const res = await api('notes/local-timeline', { limit: 100 }, alice); | ||||
| 
 | ||||
| 			assert.strictEqual(res.body.some(note => note.id === carolNote.id), false); | ||||
| 			assert.strictEqual(res.body.some(note => note.id === daveNote.id), false); | ||||
| 			assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); | ||||
| 		}); | ||||
| 
 | ||||
| 		test.concurrent('withReplies: false でフォローしているユーザーからの自分への返信が含まれる', async () => { | ||||
| 			const [alice, bob] = await Promise.all([signup(), signup()]); | ||||
| 
 | ||||
|  | @ -1383,6 +1457,39 @@ describe('Timelines', () => { | |||
| 			assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); | ||||
| 		}); | ||||
| 
 | ||||
| 		test.concurrent('ミュートしているユーザーのノートの、関係のないユーザによる引用ノートの、リノートが含まれない', async () => { | ||||
| 			const [alice, bob, carol, dave] = await Promise.all([signup(), signup(), signup(), signup()]); | ||||
| 
 | ||||
| 			await api('mute/create', { userId: carol.id }, alice); | ||||
| 			await setTimeout(1000); | ||||
| 			const carolNote = await post(carol, { text: 'hi' }); | ||||
| 			const daveNote = await post(dave, { text: 'quote hi', renoteId: carolNote.id }); | ||||
| 			const bobNote = await post(bob, { renoteId: daveNote.id }); | ||||
| 
 | ||||
| 			await waitForPushToTl(); | ||||
| 
 | ||||
| 			const res = await api('users/notes', { userId: bob.id, limit: 100 }, alice); | ||||
| 
 | ||||
| 			assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); | ||||
| 		}); | ||||
| 
 | ||||
| 		test.concurrent('ミュートしているユーザーのノートの、関係のないユーザによるリプライの、リノートが含まれない', async () => { | ||||
| 			const [alice, bob, carol, dave] = await Promise.all([signup(), signup(), signup(), signup()]); | ||||
| 
 | ||||
| 			await api('following/create', { userId: bob.id }, alice); | ||||
| 			await api('mute/create', { userId: carol.id }, alice); | ||||
| 			await setTimeout(1000); | ||||
| 			const carolNote = await post(carol, { text: 'hi' }); | ||||
| 			const daveNote = await post(dave, { text: 'quote hi', replyId: carolNote.id }); | ||||
| 			const bobNote = await post(bob, { renoteId: daveNote.id }); | ||||
| 
 | ||||
| 			await waitForPushToTl(); | ||||
| 
 | ||||
| 			const res = await api('users/notes', { userId: bob.id, limit: 100 }, alice); | ||||
| 
 | ||||
| 			assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); | ||||
| 		}); | ||||
| 
 | ||||
| 		test.concurrent('ミュートしていても userId に指定したユーザーの投稿が含まれる', async () => { | ||||
| 			const [alice, bob] = await Promise.all([signup(), signup()]); | ||||
| 
 | ||||
|  | @ -1391,6 +1498,8 @@ describe('Timelines', () => { | |||
| 			const bobNote1 = await post(bob, { text: 'hi' }); | ||||
| 			const bobNote2 = await post(bob, { text: 'hi', replyId: bobNote1.id }); | ||||
| 			const bobNote3 = await post(bob, { text: 'hi', renoteId: bobNote1.id }); | ||||
| 			const bobNote4 = await post(bob, { renoteId: bobNote2.id }); | ||||
| 			const bobNote5 = await post(bob, { renoteId: bobNote3.id }); | ||||
| 
 | ||||
| 			await waitForPushToTl(); | ||||
| 
 | ||||
|  | @ -1399,6 +1508,8 @@ describe('Timelines', () => { | |||
| 			assert.strictEqual(res.body.some(note => note.id === bobNote1.id), true); | ||||
| 			assert.strictEqual(res.body.some(note => note.id === bobNote2.id), true); | ||||
| 			assert.strictEqual(res.body.some(note => note.id === bobNote3.id), true); | ||||
| 			assert.strictEqual(res.body.some(note => note.id === bobNote4.id), true); | ||||
| 			assert.strictEqual(res.body.some(note => note.id === bobNote5.id), true); | ||||
| 		}); | ||||
| 
 | ||||
| 		test.concurrent('自身の visibility: specified なノートが含まれる', async () => { | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue