diff --git a/src/remote/activitypub/renderer/index.ts b/src/remote/activitypub/renderer/index.ts index efce8ebadae3..cfdc07cdab88 100644 --- a/src/remote/activitypub/renderer/index.ts +++ b/src/remote/activitypub/renderer/index.ts @@ -4,48 +4,156 @@ import { IActivity } from '../type'; import { LdSignature } from '../misc/ld-signature'; import { ILocalUser } from '../../../models/user'; -export const renderActivity = (x: any): IActivity | null => { +const references = [ + 'https://www.w3.org/ns/activitystreams', + 'https://w3id.org/security/v1', +]; + +// namespaces (null means already defined in references) +const nss: Record = { + as: null, // defined in 'https://www.w3.org/ns/activitystreams' + vcard: null, // defined in 'https://www.w3.org/ns/activitystreams' + sec: null, // defined in 'https://w3id.org/security/v1' + // : + + // Some extra or vendor namespaces + toot: 'http://joinmastodon.org/ns#', // Mastodon + schema: 'http://schema.org#', + misskey: 'https://misskey-hub.net/ns#', + fedibird: 'http://fedibird.com/ns#', +}; + +// Definitions! (required ns => key => value) +const defs: Record> = { + sec: { + Key: 'sec:Key', + }, + as: { // Not defined by the activitystreams, but defined in Mastodon. + manuallyApprovesFollowers: 'as:manuallyApprovesFollowers', + sensitive: 'as:sensitive', + Hashtag: 'as:Hashtag', + }, + toot: { + Emoji: 'toot:Emoji', + featured: 'toot:featured', + discoverable: 'toot:discoverable', + indexable: 'toot:indexable', + }, + schema: { + PropertyValue: 'schema:PropertyValue', + value: 'schema:value', + }, + misskey: { + _misskey_content: 'misskey:_misskey_content', + _misskey_quote: 'misskey:_misskey_quote', + _misskey_reaction: 'misskey:_misskey_reaction', + _misskey_votes: 'misskey:_misskey_votes', + isCat: 'misskey:isCat', + }, + fedibird: { + quoteUri: 'fedibird:quoteUri', + searchableBy: { '@id': 'fedibird:searchableBy', '@type': '@id' }, + }, +}; + +// flatten defs for processing +type Def = { + /*** Key, eg: '_misskey_content' */ + key: string; + /*** Resolved value, eg: 'misskey:_misskey_content' */ + value: string | Object; + /*** Required namaspace, eg: 'misskey' */ + ns: string; +}; + +const fdefs = new Map(); + +for (const [ns, keyval] of Object.entries(defs)) { + for (const [key, value] of Object.entries(keyval)) { + fdefs.set(key, { key, ns, value }) + } +} + +// builds +const buildContext = (extraKeys: string[]) => { + const extraNss = new Map(); + const extraDefs = new Map(); + + for (const key of extraKeys) { + const def = fdefs.get(key); + if (def == null) { + console.warn(`JSON-LD: key=${key} is not in defs, bug?`); + } else { + const nsValue = nss[def.ns]; + if (nsValue === undefined) { + console.warn(`JSON-LD: ns=${key} is not in nss, bug?`); + } else if (nsValue === null) { + // alredy in imported one, so safety ignore it + } else { + extraNss.set(def.ns, nsValue) + } + + extraDefs.set(key, def.value) + } + } + + return [ + ...references, + { + ...Object.fromEntries(extraNss), + ...Object.fromEntries(extraDefs), + } + ]; +}; + +const prebuildContexts = { + Any: buildContext(Array.from(fdefs.keys())), + Actor: buildContext([ + 'Key', + 'manuallyApprovesFollowers', + 'sensitive', + 'Hashtag', + 'Emoji', + 'featured', + 'discoverable', + 'indexable', + 'PropertyValue', + 'value', + 'isCat', + 'searchableBy' + ]), + Like: buildContext([ + 'Emoji', + '_misskey_reaction', + ]), + Note: buildContext([ + 'sensitive', + 'Hashtag', + 'Emoji', + '_misskey_content', + '_misskey_quote', + '_misskey_votes', + 'quoteUri', + ]), +}; + +console.log('prebuildContexts', prebuildContexts); + +export const renderActivity = (x: any, p?: 'Actor' | 'Like' | 'Note' | 'Any'): IActivity | null => { if (x == null) return null; if (x !== null && typeof x === 'object' && x.id == null) { x.id = `${config.url}/${uuid()}`; } + const context = + p === 'Actor' ? prebuildContexts.Actor : + p === 'Like' ? prebuildContexts.Like : + p === 'Note' ? prebuildContexts.Note : + prebuildContexts.Any; + return Object.assign({ - '@context': [ - 'https://www.w3.org/ns/activitystreams', - 'https://w3id.org/security/v1', - { - Key: 'sec:Key', - // as non-standards - manuallyApprovesFollowers: 'as:manuallyApprovesFollowers', - sensitive: 'as:sensitive', - Hashtag: 'as:Hashtag', - // Mastodon - toot: 'http://joinmastodon.org/ns#', - Emoji: 'toot:Emoji', - featured: 'toot:featured', - discoverable: 'toot:discoverable', - indexable: 'toot:indexable', - // schema - schema: 'http://schema.org#', - PropertyValue: 'schema:PropertyValue', - value: 'schema:value', - // Misskey - misskey: 'https://misskey-hub.net/ns#', - '_misskey_content': 'misskey:_misskey_content', - '_misskey_quote': 'misskey:_misskey_quote', - '_misskey_reaction': 'misskey:_misskey_reaction', - '_misskey_votes': 'misskey:_misskey_votes', - 'isCat': 'misskey:isCat', - // vcard - vcard: 'http://www.w3.org/2006/vcard/ns#', - // Fedibird - fedibird: 'http://fedibird.com/ns#', - quoteUri: 'fedibird:quoteUri', - searchableBy: { '@id': 'fedibird:searchableBy', '@type': '@id' }, - } - ] + '@context': context }, x); }; diff --git a/src/server/activitypub.ts b/src/server/activitypub.ts index 42174d4bae6f..7c083bc0c04c 100644 --- a/src/server/activitypub.ts +++ b/src/server/activitypub.ts @@ -481,7 +481,7 @@ router.get('/likes/:like', async ctx => { return; } - ctx.body = renderActivity(await renderLike(reaction, note)); + ctx.body = renderActivity(await renderLike(reaction, note), 'Like'); ctx.set('Cache-Control', 'public, max-age=180'); setResponseType(ctx); }); diff --git a/src/services/note/reaction/create.ts b/src/services/note/reaction/create.ts index 0f74c4ab48da..89de4ff6e6d5 100644 --- a/src/services/note/reaction/create.ts +++ b/src/services/note/reaction/create.ts @@ -102,7 +102,7 @@ export default async (user: IUser, note: INote, reaction?: string, dislike = fal //#region 配信 if (isLocalUser(user) && !note.localOnly && !user.noFederation) { - const content = renderActivity(await renderLike(inserted, note), user); + const content = renderActivity(await renderLike(inserted, note), 'Like'); const dm = new DeliverManager(user, content) diff --git a/src/services/note/reaction/delete.ts b/src/services/note/reaction/delete.ts index 743623b3dead..818d696f49f2 100644 --- a/src/services/note/reaction/delete.ts +++ b/src/services/note/reaction/delete.ts @@ -65,7 +65,7 @@ export default async (user: IUser, note: INote) => { //#region 配信 if (isLocalUser(user) && !note.localOnly && !user.noFederation) { - const content = renderActivity(renderUndo(await renderLike(exist, note), user), user); + const content = renderActivity(renderUndo(await renderLike(exist, note), user), 'Like'); if (isRemoteUser(note._user)) deliverToUser(user, content, note._user); deliverToFollowers(user, content, true); //deliverToRelays(user, content);