From 86a931d725c40625e4b2492529049bb53420522b Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 23 May 2024 13:16:24 -0500 Subject: [PATCH 1/3] Add reactionsController --- src/app.ts | 3 ++- src/controllers/api/reactions.ts | 41 +++++++++++++++++++++++++++++++- 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/src/app.ts b/src/app.ts index bd55417..b3e251e 100644 --- a/src/app.ts +++ b/src/app.ts @@ -43,7 +43,7 @@ import { updateConfigController, } from '@/controllers/api/pleroma.ts'; import { preferencesController } from '@/controllers/api/preferences.ts'; -import { deleteReactionController, reactionController } from '@/controllers/api/reactions.ts'; +import { deleteReactionController, reactionController, reactionsController } from '@/controllers/api/reactions.ts'; import { relayController } from '@/controllers/nostr/relay.ts'; import { adminReportController, @@ -212,6 +212,7 @@ app.get('/api/v1/markers', requireProof(), markersController); app.post('/api/v1/markers', requireProof(), updateMarkersController); app.put('/api/v1/pleroma/statuses/:id{[0-9a-f]{64}}/reactions/:emoji', requireSigner, reactionController); +app.get('/api/v1/pleroma/statuses/:id{[0-9a-f]{64}}/reactions', reactionsController); app.delete('/api/v1/pleroma/statuses/:id{[0-9a-f]{64}}/reactions/:emoji', requireSigner, deleteReactionController); app.get('/api/v1/admin/accounts', requireRole('admin'), adminAccountsController); diff --git a/src/controllers/api/reactions.ts b/src/controllers/api/reactions.ts index bac5d33..52f5319 100644 --- a/src/controllers/api/reactions.ts +++ b/src/controllers/api/reactions.ts @@ -2,6 +2,9 @@ import { AppController } from '@/app.ts'; import { Storages } from '@/storages.ts'; import { createEvent } from '@/utils/api.ts'; import { renderStatus } from '@/views/mastodon/statuses.ts'; +import { hydrateEvents } from '@/storages/hydrate.ts'; +import { DittoEvent } from '@/interfaces/DittoEvent.ts'; +import { accountFromPubkey, renderAccount } from '@/views/mastodon/accounts.ts'; /** * React to a status. @@ -78,4 +81,40 @@ const deleteReactionController: AppController = async (c) => { return c.json(status); }; -export { deleteReactionController, reactionController }; +/** + * Get an object of emoji to account mappings with accounts that reacted to the post. + * https://docs.pleroma.social/backend/development/API/pleroma_api/#get-apiv1pleromastatusesidreactions + */ +const reactionsController: AppController = async (c) => { + const id = c.req.param('id'); + const store = await Storages.db(); + const pubkey = await c.get('signer')?.getPublicKey(); + + const events = await store.query([{ kinds: [7], '#e': [id], limit: 100 }]) + .then((events) => hydrateEvents({ events, store })); + + /** Events grouped by emoji. */ + const byEmoji = events.reduce((acc, event) => { + const emoji = event.content; + acc[emoji] = acc[emoji] || []; + acc[emoji].push(event); + return acc; + }, {} as Record); + + const results = await Promise.all( + Object.entries(byEmoji).map(async ([name, events]) => { + return { + name, + count: events.length, + me: pubkey && events.some((event) => event.pubkey === pubkey), + accounts: await Promise.all( + events.map((event) => event.author ? renderAccount(event.author) : accountFromPubkey(event.pubkey)), + ), + }; + }), + ); + + return c.json(results); +}; + +export { deleteReactionController, reactionController, reactionsController }; From b197b57ea8ec48f0204e926a99978c6ae74d01a0 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 23 May 2024 13:22:04 -0500 Subject: [PATCH 2/3] Support reactionsController with a single emoji --- src/app.ts | 3 ++- src/controllers/api/reactions.ts | 8 +++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/app.ts b/src/app.ts index b3e251e..fbc1ef2 100644 --- a/src/app.ts +++ b/src/app.ts @@ -211,8 +211,9 @@ app.get('/api/v1/mutes', requireSigner, mutesController); app.get('/api/v1/markers', requireProof(), markersController); app.post('/api/v1/markers', requireProof(), updateMarkersController); -app.put('/api/v1/pleroma/statuses/:id{[0-9a-f]{64}}/reactions/:emoji', requireSigner, reactionController); app.get('/api/v1/pleroma/statuses/:id{[0-9a-f]{64}}/reactions', reactionsController); +app.get('/api/v1/pleroma/statuses/:id{[0-9a-f]{64}}/reactions/:emoji', reactionsController); +app.put('/api/v1/pleroma/statuses/:id{[0-9a-f]{64}}/reactions/:emoji', requireSigner, reactionController); app.delete('/api/v1/pleroma/statuses/:id{[0-9a-f]{64}}/reactions/:emoji', requireSigner, deleteReactionController); app.get('/api/v1/admin/accounts', requireRole('admin'), adminAccountsController); diff --git a/src/controllers/api/reactions.ts b/src/controllers/api/reactions.ts index 52f5319..b889b95 100644 --- a/src/controllers/api/reactions.ts +++ b/src/controllers/api/reactions.ts @@ -89,9 +89,15 @@ const reactionsController: AppController = async (c) => { const id = c.req.param('id'); const store = await Storages.db(); const pubkey = await c.get('signer')?.getPublicKey(); + const emoji = c.req.param('emoji') as string | undefined; + + if (typeof emoji === 'string' && !/^\p{RGI_Emoji}$/v.test(emoji)) { + return c.json({ error: 'Invalid emoji' }, 400); + } const events = await store.query([{ kinds: [7], '#e': [id], limit: 100 }]) - .then((events) => hydrateEvents({ events, store })); + .then((events) => hydrateEvents({ events, store })) + .then((events) => events.filter((event) => !emoji || event.content === emoji)); /** Events grouped by emoji. */ const byEmoji = events.reduce((acc, event) => { From 3f3a302002844381db55a36a0e11fcba7a765b0e Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 23 May 2024 13:23:42 -0500 Subject: [PATCH 3/3] Order imports --- src/controllers/api/reactions.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/controllers/api/reactions.ts b/src/controllers/api/reactions.ts index b889b95..995d67d 100644 --- a/src/controllers/api/reactions.ts +++ b/src/controllers/api/reactions.ts @@ -1,10 +1,10 @@ import { AppController } from '@/app.ts'; +import { DittoEvent } from '@/interfaces/DittoEvent.ts'; +import { hydrateEvents } from '@/storages/hydrate.ts'; import { Storages } from '@/storages.ts'; import { createEvent } from '@/utils/api.ts'; -import { renderStatus } from '@/views/mastodon/statuses.ts'; -import { hydrateEvents } from '@/storages/hydrate.ts'; -import { DittoEvent } from '@/interfaces/DittoEvent.ts'; import { accountFromPubkey, renderAccount } from '@/views/mastodon/accounts.ts'; +import { renderStatus } from '@/views/mastodon/statuses.ts'; /** * React to a status.