Skip to content

Commit 1dbc075

Browse files
committed
fix(backend): handle relay-delivered Announce activities correctly
Relay Announce activities now use the target note URI instead of the Announce URI for federation allowlist checks, dedup locking, and existence lookups. Notes delivered via relay are published directly to the notes stream without creating a renote. Closes #11056
1 parent 4d6256e commit 1dbc075

3 files changed

Lines changed: 30 additions & 5 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
- Fix: `/api-doc` にアクセスできない問題を修正
1414
- Fix: support `alsoKnownAs` from remote actors as either array or unwrapped singleton
1515
- Fix: ローカルに存在しないリモートアカウントに対するアカウント削除リクエストを受信した際に、そのユーザーを新規作成して削除する挙動を修正
16+
- Fix: リレー経由で届いたノートがRenoteとして表示される問題を修正
1617

1718
## 2026.3.2
1819

packages/backend/src/core/RelayService.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,13 +91,27 @@ export class RelayService {
9191
return JSON.stringify(result);
9292
}
9393

94+
@bindThis
95+
public async getAcceptedRelays(): Promise<MiRelay[]> {
96+
return this.relaysCache.fetch(() => this.relaysRepository.findBy({
97+
status: 'accepted',
98+
}));
99+
}
100+
101+
@bindThis
102+
public async isRelayActor(actor: { inbox: string | null; sharedInbox: string | null }): Promise<boolean> {
103+
const relays = await this.getAcceptedRelays();
104+
return relays.some(relay =>
105+
(actor.inbox != null && relay.inbox === actor.inbox)
106+
|| (actor.sharedInbox != null && relay.inbox === actor.sharedInbox),
107+
);
108+
}
109+
94110
@bindThis
95111
public async deliverToRelays(user: { id: MiUser['id']; host: null; }, activity: any): Promise<void> {
96112
if (activity == null) return;
97113

98-
const relays = await this.relaysCache.fetch(() => this.relaysRepository.findBy({
99-
status: 'accepted',
100-
}));
114+
const relays = await this.getAcceptedRelays();
101115
if (relays.length === 0) return;
102116

103117
const copy = deepClone(activity);

packages/backend/src/core/activitypub/ApInboxService.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -302,12 +302,14 @@ export class ApInboxService {
302302

303303
@bindThis
304304
private async announceNote(actor: MiRemoteUser, activity: IAnnounce, target: IPost, resolver?: Resolver): Promise<string | void> {
305-
const uri = getApId(activity);
306-
307305
if (actor.isSuspended) {
308306
return;
309307
}
310308

309+
// リレーからのAnnounceかチェック
310+
const fromRelay = await this.relayService.isRelayActor(actor);
311+
const uri = getApId(fromRelay ? target : activity);
312+
311313
// アナウンス先が許可されているかチェック
312314
if (!this.utilityService.isFederationAllowedUri(uri)) return;
313315

@@ -336,6 +338,14 @@ export class ApInboxService {
336338
throw err;
337339
}
338340

341+
// リレーからのAnnounceはリノートを作成せず、ノートを直接公開する
342+
if (fromRelay) {
343+
this.logger.info(`Publishing relay-delivered note: ${uri}`);
344+
const noteObj = await this.noteEntityService.pack(renote, null, { skipHide: true, withReactionAndUserPairCache: true });
345+
this.globalEventService.publishNotesStream(noteObj);
346+
return;
347+
}
348+
339349
if (!await this.noteEntityService.isVisibleForMe(renote, actor.id)) {
340350
return 'skip: invalid actor for this activity';
341351
}

0 commit comments

Comments
 (0)