diff --git a/features/handle-like.feature b/features/handle-like.feature new file mode 100644 index 00000000..1639e56d --- /dev/null +++ b/features/handle-like.feature @@ -0,0 +1,10 @@ +Feature: Like(Article) + We want to handle Like(Article) activities in the Inbox + + Scenario: We recieve a Like(Article) activity from someone we follow + Given an Actor "Alice" + And an Actor "Bob" + And a "Create(Article)" Activity "A" by "Alice" + And a "Like(A)" Activity "L" by "Bob" + When "Bob" sends "L" to the Inbox + Then "L" is in our Inbox diff --git a/features/step_definitions/stepdefs.js b/features/step_definitions/stepdefs.js index c300da9d..d2bb27e5 100644 --- a/features/step_definitions/stepdefs.js +++ b/features/step_definitions/stepdefs.js @@ -60,6 +60,20 @@ async function createActivity(activityType, object, actor, remote = true) { actor: actor, }; } + + if (activityType === 'Like') { + return { + '@context': [ + 'https://www.w3.org/ns/activitystreams', + 'https://w3id.org/security/data-integrity/v1', + ], + 'type': 'Like', + 'id': 'http://wiremock:8080/like/1', + 'to': 'as:Public', + 'object': object, + actor: actor, + }; + } } async function createActor(name = 'Test', remote = true) { diff --git a/src/app.ts b/src/app.ts index 0298c6f7..dbb99018 100644 --- a/src/app.ts +++ b/src/app.ts @@ -17,6 +17,7 @@ import { Update, Announce, Context, + Like, } from '@fedify/fedify'; import { federation } from '@fedify/fedify/x/hono'; import { Hono, Context as HonoContext } from 'hono'; @@ -47,6 +48,7 @@ import { createDispatcher, updateDispatcher, handleAnnounce, + handleLike } from './dispatchers'; import { followAction, inboxHandler, postPublishedWebhook, siteChangedWebhook } from './handlers'; @@ -115,6 +117,8 @@ inboxListener .on(Create, ensureCorrectContext(handleCreate)) .onError(inboxErrorHandler) .on(Announce, ensureCorrectContext(handleAnnounce)) + .onError(inboxErrorHandler) + .on(Like, ensureCorrectContext(handleLike)) .onError(inboxErrorHandler); fedify diff --git a/src/dispatchers.ts b/src/dispatchers.ts index f78fedc5..2446743d 100644 --- a/src/dispatchers.ts +++ b/src/dispatchers.ts @@ -15,6 +15,7 @@ import { Actor, Object as APObject, Recipient, + Like, } from '@fedify/fedify'; import { v4 as uuidv4 } from 'uuid'; import { addToList } from './kv-helpers'; @@ -229,6 +230,69 @@ export async function handleAnnounce( await addToList(ctx.data.db, ['inbox'], announce.id.href); } +export async function handleLike( + ctx: Context, + like: Like, +) { + console.log('Handling Like'); + + // Validate like + if (!like.id) { + console.log('Invalid Like - no id'); + return; + } + + if (!like.objectId) { + console.log('Invalid Like - no object id'); + return; + } + + // Validate sender + const sender = await like.getActor(ctx); + + if (sender === null || sender.id === null) { + console.log('Sender missing, exit early'); + return; + } + + // Lookup liked object - If not found in globalDb, perform network lookup + let object = null; + let existing = await ctx.data.globaldb.get([like.objectId.href]) ?? null; + + if (!existing) { + console.log('Object not found in globalDb, performing network lookup'); + + object = await like.getObject(); + } + + // Validate object + if (!existing && !object) { + console.log('Invalid Like - could not find object'); + return; + } + + if (object && !object.id) { + console.log('Invalid Like - could not find object id'); + return; + } + + // Persist like + const likeJson = await like.toJsonLd(); + ctx.data.globaldb.set([like.id.href], likeJson); + + // Persist object if not already persisted + if (!existing && object && object.id) { + console.log('Storing object in globalDb'); + + const objectJson = await object.toJsonLd(); + + ctx.data.globaldb.set([object.id.href], objectJson); + } + + // Add to inbox + await addToList(ctx.data.db, ['inbox'], like.id.href); +} + export async function inboxErrorHandler( ctx: Context, error: unknown,