From 7015cc937bd872e9c7ebafb56bdaea4ca138e0df Mon Sep 17 00:00:00 2001
From: anatawa12 <anatawa12@icloud.com>
Date: Mon, 30 Oct 2023 13:48:22 +0900
Subject: [PATCH] fix(backend): We can renote pure renote (#12171)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* chore: make pure renote detection an function

* fix: we can renote pure renote

* docs(changelog): リノートをリノートできるのを修正

* fix: remaining debug log

* chore: move isPureRenote to misc

* chore: make isPureRenote type guard

* chore: use isPureRenote in other places

* fix CHANGELOG

* style: fix lint

---------

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
---
 CHANGELOG.md                                           |  1 +
 packages/backend/src/core/NoteDeleteService.ts         |  5 +++--
 packages/backend/src/misc/is-pure-renote.ts            | 10 ++++++++++
 .../backend/src/server/ActivityPubServerService.ts     |  3 ++-
 .../backend/src/server/api/endpoints/notes/create.ts   |  5 +++--
 5 files changed, 19 insertions(+), 5 deletions(-)
 create mode 100644 packages/backend/src/misc/is-pure-renote.ts

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6dfe43ff0f..66b2e0ae4f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -58,6 +58,7 @@
 - Fix: STLでフォローしていないチャンネルが取得される問題を修正
 - Fix: `hashtags/trend`にてRedisからトレンドの情報が取得できない際にInternal Server Errorになる問題を修正
 - Fix: HTLをリロードまたは遡行したとき、フォローしているチャンネルのノートが含まれない問題を修正 #11765 #12181
+- Fix: リノートをリノートできるのを修正
 
 ## 2023.10.2
 
diff --git a/packages/backend/src/core/NoteDeleteService.ts b/packages/backend/src/core/NoteDeleteService.ts
index 9a817ffd76..632daf991a 100644
--- a/packages/backend/src/core/NoteDeleteService.ts
+++ b/packages/backend/src/core/NoteDeleteService.ts
@@ -24,6 +24,7 @@ import { bindThis } from '@/decorators.js';
 import { MetaService } from '@/core/MetaService.js';
 import { SearchService } from '@/core/SearchService.js';
 import { ModerationLogService } from '@/core/ModerationLogService.js';
+import { isPureRenote } from '@/misc/is-pure-renote.js';
 
 @Injectable()
 export class NoteDeleteService {
@@ -77,8 +78,8 @@ export class NoteDeleteService {
 			if (this.userEntityService.isLocalUser(user) && !note.localOnly) {
 				let renote: MiNote | null = null;
 
-				// if deletd note is renote
-				if (note.renoteId && note.text == null && !note.hasPoll && (note.fileIds == null || note.fileIds.length === 0)) {
+				// if deleted note is renote
+				if (isPureRenote(note)) {
 					renote = await this.notesRepository.findOneBy({
 						id: note.renoteId,
 					});
diff --git a/packages/backend/src/misc/is-pure-renote.ts b/packages/backend/src/misc/is-pure-renote.ts
new file mode 100644
index 0000000000..994d981522
--- /dev/null
+++ b/packages/backend/src/misc/is-pure-renote.ts
@@ -0,0 +1,10 @@
+import type { MiNote } from '@/models/Note.js';
+
+export function isPureRenote(note: MiNote): note is MiNote & { renoteId: NonNullable<MiNote['renoteId']> } {
+	if (!note.renoteId) return false;
+
+	if (note.text) return false; // it's quoted with text
+	if (note.fileIds.length !== 0) return false; // it's quoted with files
+	if (note.hasPoll) return false; // it's quoted with poll
+	return true;
+}
diff --git a/packages/backend/src/server/ActivityPubServerService.ts b/packages/backend/src/server/ActivityPubServerService.ts
index a7f6f82daf..2e64d41c91 100644
--- a/packages/backend/src/server/ActivityPubServerService.ts
+++ b/packages/backend/src/server/ActivityPubServerService.ts
@@ -26,6 +26,7 @@ import { UtilityService } from '@/core/UtilityService.js';
 import { UserEntityService } from '@/core/entities/UserEntityService.js';
 import { bindThis } from '@/decorators.js';
 import { IActivity } from '@/core/activitypub/type.js';
+import { isPureRenote } from '@/misc/is-pure-renote.js';
 import type { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions } from 'fastify';
 import type { FindOptionsWhere } from 'typeorm';
 
@@ -88,7 +89,7 @@ export class ActivityPubServerService {
 	 */
 	@bindThis
 	private async packActivity(note: MiNote): Promise<any> {
-		if (note.renoteId && note.text == null && !note.hasPoll && (note.fileIds == null || note.fileIds.length === 0)) {
+		if (isPureRenote(note)) {
 			const renote = await this.notesRepository.findOneByOrFail({ id: note.renoteId });
 			return this.apRendererService.renderAnnounce(renote.uri ? renote.uri : `${this.config.url}/notes/${renote.id}`, note);
 		}
diff --git a/packages/backend/src/server/api/endpoints/notes/create.ts b/packages/backend/src/server/api/endpoints/notes/create.ts
index 3ae4ac044a..649068fb20 100644
--- a/packages/backend/src/server/api/endpoints/notes/create.ts
+++ b/packages/backend/src/server/api/endpoints/notes/create.ts
@@ -17,6 +17,7 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
 import { NoteCreateService } from '@/core/NoteCreateService.js';
 import { DI } from '@/di-symbols.js';
 import { ApiError } from '../../error.js';
+import { isPureRenote } from '@/misc/is-pure-renote.js';
 
 export const meta = {
 	tags: ['notes'],
@@ -221,7 +222,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 
 				if (renote == null) {
 					throw new ApiError(meta.errors.noSuchRenoteTarget);
-				} else if (renote.renoteId && !renote.text && !renote.fileIds && !renote.hasPoll) {
+				} else if (isPureRenote(renote)) {
 					throw new ApiError(meta.errors.cannotReRenote);
 				}
 
@@ -254,7 +255,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 
 				if (reply == null) {
 					throw new ApiError(meta.errors.noSuchReplyTarget);
-				} else if (reply.renoteId && !reply.text && !reply.fileIds && !reply.hasPoll) {
+				} else if (isPureRenote(reply)) {
 					throw new ApiError(meta.errors.cannotReplyToPureRenote);
 				}