From b7a4f286b02c8ebb376779569e5564f52496913e Mon Sep 17 00:00:00 2001
From: MeiMei <30769358+mei23@users.noreply.github.com>
Date: Tue, 4 Feb 2020 08:26:00 +0900
Subject: [PATCH] =?UTF-8?q?=E3=83=AA=E3=83=A2=E3=83=BC=E3=83=88=E6=8A=95?=
 =?UTF-8?q?=E7=A8=BF=E3=81=AB=E3=83=AA=E3=83=A2=E3=83=BC=E3=83=88=E3=81=A7?=
 =?UTF-8?q?=E3=81=95=E3=82=8C=E3=81=9F=E3=83=AA=E3=82=A2=E3=82=AF=E3=82=B7?=
 =?UTF-8?q?=E3=83=A7=E3=83=B3=E3=81=8C=E8=A1=A8=E7=A4=BA=E3=81=95=E3=82=8C?=
 =?UTF-8?q?=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=20(#5817)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* 第3インスタンスへのLikeも受け入れるように

* リアクション済みだったらエラーにせずに置き換えるように

* Likeを第3インスタンスにdeliverするように

* fix

* fix

* 同じリアクションがすでにされていたら何もしない

* リモートから自身の投稿へリアクションした場合にエラーにならないように
---
 src/remote/activitypub/kernel/like.ts      | 21 ++++-------
 src/remote/activitypub/kernel/undo/like.ts | 18 ++++------
 src/services/note/reaction/create.ts       | 42 +++++++++++++---------
 src/services/note/reaction/delete.ts       | 17 +++++----
 4 files changed, 50 insertions(+), 48 deletions(-)

diff --git a/src/remote/activitypub/kernel/like.ts b/src/remote/activitypub/kernel/like.ts
index fa8983b38d..b25f80aedc 100644
--- a/src/remote/activitypub/kernel/like.ts
+++ b/src/remote/activitypub/kernel/like.ts
@@ -1,23 +1,16 @@
 import { IRemoteUser } from '../../../models/entities/user';
-import { ILike } from '../type';
+import { ILike, getApId } from '../type';
 import create from '../../../services/note/reaction/create';
-import { Notes } from '../../../models';
-import { apLogger } from '../logger';
+import { fetchNote } from '../models/note';
 
 export default async (actor: IRemoteUser, activity: ILike) => {
-	const id = typeof activity.object == 'string' ? activity.object : activity.object.id;
-	if (id == null) throw new Error('missing id');
+	const targetUri = getApId(activity.object);
 
-	// Transform:
-	// https://misskey.ex/notes/xxxx to
-	// xxxx
-	const noteId = id.split('/').pop();
+	const note = await fetchNote(targetUri);
+	if (!note) return `skip: target note not found ${targetUri}`;
 
-	const note = await Notes.findOne(noteId);
-	if (note == null) {
-		apLogger.warn(`Like activity recivied, but no such note: ${id}`, { id });
-		return;
-	}
+	if (actor.id === note.userId) return `skip: cannot react to my note`;
 
 	await create(actor, note, activity._misskey_reaction || activity.content || activity.name);
+	return `ok`;
 };
diff --git a/src/remote/activitypub/kernel/undo/like.ts b/src/remote/activitypub/kernel/undo/like.ts
index 2678828a9a..bd6930c66b 100644
--- a/src/remote/activitypub/kernel/undo/like.ts
+++ b/src/remote/activitypub/kernel/undo/like.ts
@@ -1,21 +1,17 @@
 import { IRemoteUser } from '../../../../models/entities/user';
-import { ILike } from '../../type';
+import { ILike, getApId } from '../../type';
 import deleteReaction from '../../../../services/note/reaction/delete';
-import { Notes } from '../../../../models';
+import { fetchNote } from '../../models/note';
 
 /**
  * Process Undo.Like activity
  */
-export default async (actor: IRemoteUser, activity: ILike): Promise<void> => {
-	const id = typeof activity.object == 'string' ? activity.object : activity.object.id;
-	if (id == null) throw new Error('missing id');
+export default async (actor: IRemoteUser, activity: ILike) => {
+	const targetUri = getApId(activity.object);
 
-	const noteId = id.split('/').pop();
-
-	const note = await Notes.findOne(noteId);
-	if (note == null) {
-		throw new Error('note not found');
-	}
+	const note = await fetchNote(targetUri);
+	if (!note) return `skip: target note not found ${targetUri}`;
 
 	await deleteReaction(actor, note);
+	return `ok`;
 };
diff --git a/src/services/note/reaction/create.ts b/src/services/note/reaction/create.ts
index a09fbd9c2f..aee31813af 100644
--- a/src/services/note/reaction/create.ts
+++ b/src/services/note/reaction/create.ts
@@ -1,19 +1,18 @@
 import { publishNoteStream } from '../../stream';
 import watch from '../watch';
 import renderLike from '../../../remote/activitypub/renderer/like';
-import { deliver } from '../../../queue';
+import DeliverManager from '../../../remote/activitypub/deliver-manager';
 import { renderActivity } from '../../../remote/activitypub/renderer';
 import { IdentifiableError } from '../../../misc/identifiable-error';
 import { toDbReaction } from '../../../misc/reaction-lib';
-import { User } from '../../../models/entities/user';
+import { User, IRemoteUser } from '../../../models/entities/user';
 import { Note } from '../../../models/entities/note';
 import { NoteReactions, Users, NoteWatchings, Notes, UserProfiles } from '../../../models';
 import { Not } from 'typeorm';
 import { perUserReactionsChart } from '../../chart';
 import { genId } from '../../../misc/gen-id';
-import { NoteReaction } from '../../../models/entities/note-reaction';
 import { createNotification } from '../../create-notification';
-import { isDuplicateKeyValueError } from '../../../misc/is-duplicate-key-value-error';
+import deleteReaction from './delete';
 
 export default async (user: User, note: Note, reaction?: string) => {
 	// Myself
@@ -23,6 +22,21 @@ export default async (user: User, note: Note, reaction?: string) => {
 
 	reaction = await toDbReaction(reaction);
 
+	const exist = await NoteReactions.findOne({
+		noteId: note.id,
+		userId: user.id,
+	});
+
+	if (exist) {
+		if (exist.reaction !== reaction) {
+			// 別のリアクションがすでにされていたら置き換える
+			await deleteReaction(user, note);
+		} else {
+			// 同じリアクションがすでにされていたら何もしない
+			return;
+		}
+	}
+
 	// Create reaction
 	await NoteReactions.save({
 		id: genId(),
@@ -30,13 +44,6 @@ export default async (user: User, note: Note, reaction?: string) => {
 		noteId: note.id,
 		userId: user.id,
 		reaction
-	} as NoteReaction).catch(e => {
-		// duplicate key error
-		if (isDuplicateKeyValueError(e)) {
-			throw new IdentifiableError('51c42bb4-931a-456b-bff7-e5a8a70dd298', 'already reacted');
-		}
-
-		throw e;
 	});
 
 	// Increment reactions count
@@ -86,12 +93,15 @@ export default async (user: User, note: Note, reaction?: string) => {
 	}
 
 	//#region 配信
-	// リアクターがローカルユーザーかつリアクション対象がリモートユーザーの投稿なら配送
-	if (Users.isLocalUser(user) && note.userHost !== null) {
+	if (Users.isLocalUser(user) && !note.localOnly) {
 		const content = renderActivity(renderLike(user, note, reaction));
-		Users.findOne(note.userId).then(u => {
-			deliver(user, content, u!.inbox);
-		});
+		const dm = new DeliverManager(user, content);
+		if (note.userHost !== null) {
+			const reactee = await Users.findOne(note.userId)
+			dm.addDirectRecipe(reactee as IRemoteUser);
+		}
+		dm.addFollowersRecipe();
+		dm.execute();
 	}
 	//#endregion
 };
diff --git a/src/services/note/reaction/delete.ts b/src/services/note/reaction/delete.ts
index 6e9611ca5a..533c3b4062 100644
--- a/src/services/note/reaction/delete.ts
+++ b/src/services/note/reaction/delete.ts
@@ -2,9 +2,9 @@ import { publishNoteStream } from '../../stream';
 import renderLike from '../../../remote/activitypub/renderer/like';
 import renderUndo from '../../../remote/activitypub/renderer/undo';
 import { renderActivity } from '../../../remote/activitypub/renderer';
-import { deliver } from '../../../queue';
+import DeliverManager from '../../../remote/activitypub/deliver-manager';
 import { IdentifiableError } from '../../../misc/identifiable-error';
-import { User } from '../../../models/entities/user';
+import { User, IRemoteUser } from '../../../models/entities/user';
 import { Note } from '../../../models/entities/note';
 import { NoteReactions, Users, Notes } from '../../../models';
 
@@ -39,12 +39,15 @@ export default async (user: User, note: Note) => {
 	});
 
 	//#region 配信
-	// リアクターがローカルユーザーかつリアクション対象がリモートユーザーの投稿なら配送
-	if (Users.isLocalUser(user) && (note.userHost !== null)) {
+	if (Users.isLocalUser(user) && !note.localOnly) {
 		const content = renderActivity(renderUndo(renderLike(user, note, exist.reaction), user));
-		Users.findOne(note.userId).then(u => {
-			deliver(user, content, u!.inbox);
-		});
+		const dm = new DeliverManager(user, content);
+		if (note.userHost !== null) {
+			const reactee = await Users.findOne(note.userId)
+			dm.addDirectRecipe(reactee as IRemoteUser);
+		}
+		dm.addFollowersRecipe();
+		dm.execute();
 	}
 	//#endregion
 };