summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorzyoshoka <107108195+zyoshoka@users.noreply.github.com>2025-02-26 09:29:12 +0900
committerGitHub <noreply@github.com>2025-02-26 00:29:12 +0000
commit389ec6350be1b5f980eb19db4c98ba2ebf22ca38 (patch)
treef84d47c043cc6cc8036f3a409186fd7bbebf0edb
parentfeat: google analytics (#15451) (diff)
downloadsharkey-389ec6350be1b5f980eb19db4c98ba2ebf22ca38.tar.gz
sharkey-389ec6350be1b5f980eb19db4c98ba2ebf22ca38.tar.bz2
sharkey-389ec6350be1b5f980eb19db4c98ba2ebf22ca38.zip
fix(backend): send Delete activity of a note to users who renoted or replied to it (#15554)
* fix(backend): send Delete activity of a note to users who renoted or replied to it * Update CHANGELOG.md
-rw-r--r--CHANGELOG.md1
-rw-r--r--packages/backend/src/core/NoteDeleteService.ts24
-rw-r--r--packages/backend/src/core/activitypub/ApDeliverManagerService.ts19
-rw-r--r--packages/backend/test-federation/test/note.test.ts106
4 files changed, 127 insertions, 23 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2399ce8a52..8ce0cc6b36 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -27,6 +27,7 @@
- Fix: pgroongaでの検索時にはじめのキーワードのみが検索に使用される問題を修正
(Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/886)
- Fix: メールアドレスの形式が正しくなければ以降の処理を行わないように
+- Fix: フォロワーではないユーザーにリノートもしくは返信された場合にノートのDeleteアクティビティが送られていない問題を修正
## 2025.2.0
diff --git a/packages/backend/src/core/NoteDeleteService.ts b/packages/backend/src/core/NoteDeleteService.ts
index 4ecd2592b2..e394506a44 100644
--- a/packages/backend/src/core/NoteDeleteService.ts
+++ b/packages/backend/src/core/NoteDeleteService.ts
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { Brackets, In } from 'typeorm';
+import { Brackets, In, IsNull, Not } from 'typeorm';
import { Injectable, Inject } from '@nestjs/common';
import type { MiUser, MiLocalUser, MiRemoteUser } from '@/models/User.js';
import type { MiNote, IMentionedRemoteUsers } from '@/models/Note.js';
@@ -190,12 +190,26 @@ export class NoteDeleteService {
}
@bindThis
+ private async getRenotedOrRepliedRemoteUsers(note: MiNote) {
+ const query = this.notesRepository.createQueryBuilder('note')
+ .leftJoinAndSelect('note.user', 'user')
+ .where(new Brackets(qb => {
+ qb.orWhere('note.renoteId = :renoteId', { renoteId: note.id });
+ qb.orWhere('note.replyId = :replyId', { replyId: note.id });
+ }))
+ .andWhere({ userHost: Not(IsNull()) });
+ const notes = await query.getMany() as (MiNote & { user: MiRemoteUser })[];
+ const remoteUsers = notes.map(({ user }) => user);
+ return remoteUsers;
+ }
+
+ @bindThis
private async deliverToConcerned(user: { id: MiLocalUser['id']; host: null; }, note: MiNote, content: any) {
this.apDeliverManagerService.deliverToFollowers(user, content);
this.relayService.deliverToRelays(user, content);
- const remoteUsers = await this.getMentionedRemoteUsers(note);
- for (const remoteUser of remoteUsers) {
- this.apDeliverManagerService.deliverToUser(user, content, remoteUser);
- }
+ this.apDeliverManagerService.deliverToUsers(user, content, [
+ ...await this.getMentionedRemoteUsers(note),
+ ...await this.getRenotedOrRepliedRemoteUsers(note),
+ ]);
}
}
diff --git a/packages/backend/src/core/activitypub/ApDeliverManagerService.ts b/packages/backend/src/core/activitypub/ApDeliverManagerService.ts
index 5d07cd8e8f..0140ce9fd6 100644
--- a/packages/backend/src/core/activitypub/ApDeliverManagerService.ts
+++ b/packages/backend/src/core/activitypub/ApDeliverManagerService.ts
@@ -196,6 +196,25 @@ export class ApDeliverManagerService {
await manager.execute();
}
+ /**
+ * Deliver activity to users
+ * @param actor
+ * @param activity Activity
+ * @param targets Target users
+ */
+ @bindThis
+ public async deliverToUsers(actor: { id: MiLocalUser['id']; host: null; }, activity: IActivity, targets: MiRemoteUser[]): Promise<void> {
+ const manager = new DeliverManager(
+ this.userEntityService,
+ this.followingsRepository,
+ this.queueService,
+ actor,
+ activity,
+ );
+ for (const to of targets) manager.addDirectRecipe(to);
+ await manager.execute();
+ }
+
@bindThis
public createDeliverManager(actor: { id: MiUser['id']; host: null; }, activity: IActivity | null): DeliverManager {
return new DeliverManager(
diff --git a/packages/backend/test-federation/test/note.test.ts b/packages/backend/test-federation/test/note.test.ts
index 220c22e198..1584f9587e 100644
--- a/packages/backend/test-federation/test/note.test.ts
+++ b/packages/backend/test-federation/test/note.test.ts
@@ -139,29 +139,99 @@ describe('Note', () => {
});
describe('Deletion', () => {
- describe('Check Delete consistency', () => {
- let carol: LoginUser;
+ describe('Check Delete is delivered', () => {
+ describe('To followers', () => {
+ let carol: LoginUser;
- beforeAll(async () => {
- carol = await createAccount('a.test');
+ beforeAll(async () => {
+ carol = await createAccount('a.test');
- await carol.client.request('following/create', { userId: bobInA.id });
- await sleep();
+ await carol.client.request('following/create', { userId: bobInA.id });
+ await sleep();
+ });
+
+ test('Check', async () => {
+ const note = (await bob.client.request('notes/create', { text: 'I\'m Bob.' })).createdNote;
+ const noteInA = await resolveRemoteNote('b.test', note.id, carol);
+ await bob.client.request('notes/delete', { noteId: note.id });
+ await sleep();
+
+ await rejects(
+ async () => await carol.client.request('notes/show', { noteId: noteInA.id }),
+ (err: any) => {
+ strictEqual(err.code, 'NO_SUCH_NOTE');
+ return true;
+ },
+ );
+ });
+
+ afterAll(async () => {
+ await carol.client.request('following/delete', { userId: bobInA.id });
+ await sleep();
+ });
});
- test('Delete is derivered to followers', async () => {
- const note = (await bob.client.request('notes/create', { text: 'I\'m Bob.' })).createdNote;
- const noteInA = await resolveRemoteNote('b.test', note.id, carol);
- await bob.client.request('notes/delete', { noteId: note.id });
- await sleep();
+ describe('To renoted and not followed user', () => {
+ test('Check', async () => {
+ const note = (await bob.client.request('notes/create', { text: 'I\'m Bob.' })).createdNote;
+ const noteInA = await resolveRemoteNote('b.test', note.id, alice);
+ await alice.client.request('notes/create', { renoteId: noteInA.id });
+ await sleep();
- await rejects(
- async () => await carol.client.request('notes/show', { noteId: noteInA.id }),
- (err: any) => {
- strictEqual(err.code, 'NO_SUCH_NOTE');
- return true;
- },
- );
+ await bob.client.request('notes/delete', { noteId: note.id });
+ await sleep();
+
+ await rejects(
+ async () => await alice.client.request('notes/show', { noteId: noteInA.id }),
+ (err: any) => {
+ strictEqual(err.code, 'NO_SUCH_NOTE');
+ return true;
+ },
+ );
+ });
+ });
+
+ describe('To replied and not followed user', () => {
+ test('Check', async () => {
+ const note = (await bob.client.request('notes/create', { text: 'I\'m Bob.' })).createdNote;
+ const noteInA = await resolveRemoteNote('b.test', note.id, alice);
+ await alice.client.request('notes/create', { text: 'Hello Bob!', replyId: noteInA.id });
+ await sleep();
+
+ await bob.client.request('notes/delete', { noteId: note.id });
+ await sleep();
+
+ await rejects(
+ async () => await alice.client.request('notes/show', { noteId: noteInA.id }),
+ (err: any) => {
+ strictEqual(err.code, 'NO_SUCH_NOTE');
+ return true;
+ },
+ );
+ });
+ });
+
+ /**
+ * FIXME: not delivered
+ * @see https://github.com/misskey-dev/misskey/issues/15548
+ */
+ describe('To only resolved and not followed user', () => {
+ test.failing('Check', async () => {
+ const note = (await bob.client.request('notes/create', { text: 'I\'m Bob.' })).createdNote;
+ const noteInA = await resolveRemoteNote('b.test', note.id, alice);
+ await sleep();
+
+ await bob.client.request('notes/delete', { noteId: note.id });
+ await sleep();
+
+ await rejects(
+ async () => await alice.client.request('notes/show', { noteId: noteInA.id }),
+ (err: any) => {
+ strictEqual(err.code, 'NO_SUCH_NOTE');
+ return true;
+ },
+ );
+ });
});
});