diff --git a/src/remote/activitypub/models/person.ts b/src/remote/activitypub/models/person.ts index 228f1e862e71..492413e1eeaf 100644 --- a/src/remote/activitypub/models/person.ts +++ b/src/remote/activitypub/models/person.ts @@ -621,12 +621,25 @@ export async function fetchOutbox(user: IUser) { } } -function parseSearchableBy(actor: IActor) { - if (actor.searchableBy == null) return null; +/** + * リモートユーザーのsearchable検出 + */ +function parseSearchableBy(actor: IActor): 'public' | 'none' | null { + // indexableで明示的に許可されていればpublic + if (actor.indexable === true) return 'public'; + + // searchableByでpublicならpublic const searchableBy = toArray(actor.searchableBy); if (searchableBy.includes('https://www.w3.org/ns/activitystreams#Public')) return 'public'; - if (searchableBy.includes(getApId(actor.followers))) return 'none'; - return 'none'; + + // indexableで明示的に拒否されていればnone + if (actor.indexable === false) return 'none'; + + // searchableByで明示的に拒否されていればnone (followersは未対応なので拒否扱い) + if (actor.searchableBy != null) return 'none'; + + // default + return null; } export const exportedForTesting = { diff --git a/src/remote/activitypub/renderer/index.ts b/src/remote/activitypub/renderer/index.ts index acd5b012d178..49af18cca7c1 100644 --- a/src/remote/activitypub/renderer/index.ts +++ b/src/remote/activitypub/renderer/index.ts @@ -25,6 +25,7 @@ export const renderActivity = (x: any): IActivity | null => { Emoji: 'toot:Emoji', featured: 'toot:featured', discoverable: 'toot:discoverable', + indexable: 'toot:indexable', // schema schema: 'http://schema.org#', PropertyValue: 'schema:PropertyValue', diff --git a/src/remote/activitypub/renderer/person.ts b/src/remote/activitypub/renderer/person.ts index 16d37360c6cd..b3f100d91f95 100644 --- a/src/remote/activitypub/renderer/person.ts +++ b/src/remote/activitypub/renderer/person.ts @@ -108,6 +108,7 @@ export default async (user: ILocalUser) => { manuallyApprovesFollowers: user.isLocked || user.carefulRemote, discoverable: !!user.isExplorable, searchableBy: user.searchableBy === 'none' ? [] : ['https://www.w3.org/ns/activitystreams#Public'], + indexable: user.searchableBy !== 'none', publicKey: renderKey(user, `#main-key`), isCat: user.isCat, attachment: attachment.length ? attachment : undefined, diff --git a/src/remote/activitypub/type.ts b/src/remote/activitypub/type.ts index 2e97ed4448fa..3b201651fd2c 100644 --- a/src/remote/activitypub/type.ts +++ b/src/remote/activitypub/type.ts @@ -229,6 +229,7 @@ export interface IActor extends IObject { manuallyApprovesFollowers?: boolean; discoverable?: boolean; searchableBy?: string[] | string; + indexable?: boolean; inbox: string; sharedInbox?: string; publicKey?: { diff --git a/test/activitypub.ts b/test/activitypub.ts index 8f0e14f3f3ff..897b269fa6c5 100644 --- a/test/activitypub.ts +++ b/test/activitypub.ts @@ -310,7 +310,7 @@ describe('ActivityPub', () => { describe('parseSearchableBy', () => { const parseSearchableBy = exportedForTesting.parseSearchableBy; - it('parseSearchableBy - public', () => { + it('parseSearchableBy - indexable: undef, public', () => { assert.strictEqual(parseSearchableBy({ type: 'Person', preferredUsername: 'a', inbox: 'b', outbox: 'c', '@context': 'd', followers: 'https://example.com/users/a/followers', @@ -318,7 +318,7 @@ describe('ActivityPub', () => { }), 'public'); }); - it('parseSearchableBy - follower', () => { + it('parseSearchableBy - indexable: undef, followers', () => { assert.strictEqual(parseSearchableBy({ type: 'Person', preferredUsername: 'a', inbox: 'b', outbox: 'c', '@context': 'd', followers: 'https://example.com/users/a/followers', @@ -326,7 +326,7 @@ describe('ActivityPub', () => { }), 'none'); }); - it('parseSearchableBy - reaction', () => { + it('parseSearchableBy - indexable: undef, reaction', () => { assert.strictEqual(parseSearchableBy({ type: 'Person', preferredUsername: 'a', inbox: 'b', outbox: 'c', '@context': 'd', followers: 'https://example.com/users/a/followers', @@ -334,6 +334,76 @@ describe('ActivityPub', () => { }), 'none'); }); + it('parseSearchableBy - indexable: true, public', () => { + assert.strictEqual(parseSearchableBy({ + type: 'Person', preferredUsername: 'a', inbox: 'b', outbox: 'c', '@context': 'd', + followers: 'https://example.com/users/a/followers', + searchableBy: [ 'https://www.w3.org/ns/activitystreams#Public' ], + indexable: true, + }), 'public'); + }); + + it('parseSearchableBy - indexable: true, followers', () => { + assert.strictEqual(parseSearchableBy({ + type: 'Person', preferredUsername: 'a', inbox: 'b', outbox: 'c', '@context': 'd', + followers: 'https://example.com/users/a/followers', + searchableBy: [ 'https://example.com/users/a/followers' ], + indexable: true, + }), 'public'); + }); + + it('parseSearchableBy - indexable: true, reaction', () => { + assert.strictEqual(parseSearchableBy({ + type: 'Person', preferredUsername: 'a', inbox: 'b', outbox: 'c', '@context': 'd', + followers: 'https://example.com/users/a/followers', + searchableBy: [], + indexable: true, + }), 'public'); + }); + + it('parseSearchableBy - indexable: false, public', () => { + assert.strictEqual(parseSearchableBy({ + type: 'Person', preferredUsername: 'a', inbox: 'b', outbox: 'c', '@context': 'd', + followers: 'https://example.com/users/a/followers', + searchableBy: [ 'https://www.w3.org/ns/activitystreams#Public' ], + indexable: false, + }), 'public'); + }); + + it('parseSearchableBy - indexable: false, followers', () => { + assert.strictEqual(parseSearchableBy({ + type: 'Person', preferredUsername: 'a', inbox: 'b', outbox: 'c', '@context': 'd', + followers: 'https://example.com/users/a/followers', + searchableBy: [ 'https://example.com/users/a/followers' ], + indexable: false, + }), 'none'); + }); + + it('parseSearchableBy - indexable: false, reaction', () => { + assert.strictEqual(parseSearchableBy({ + type: 'Person', preferredUsername: 'a', inbox: 'b', outbox: 'c', '@context': 'd', + followers: 'https://example.com/users/a/followers', + searchableBy: [], + indexable: false, + }), 'none'); + }); + + it('parseIndexable - true', () => { + assert.strictEqual(parseSearchableBy({ + type: 'Person', preferredUsername: 'a', inbox: 'b', outbox: 'c', '@context': 'd', + followers: 'https://example.com/users/a/followers', + indexable: true, + }), 'public'); + }); + + it('parseIndexable - false', () => { + assert.strictEqual(parseSearchableBy({ + type: 'Person', preferredUsername: 'a', inbox: 'b', outbox: 'c', '@context': 'd', + followers: 'https://example.com/users/a/followers', + indexable: false, + }), 'none'); + }); + it('parseSearchableBy - undefined', () => { assert.strictEqual(parseSearchableBy({ type: 'Person', preferredUsername: 'a', inbox: 'b', outbox: 'c', '@context': 'd',