From 93922b3f93b5509fe5f31ee871e99def1795b3da Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 22 May 2024 17:06:02 -0500 Subject: [PATCH 1/2] Add reactionController --- src/controllers/api/reactions.ts | 38 ++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 src/controllers/api/reactions.ts diff --git a/src/controllers/api/reactions.ts b/src/controllers/api/reactions.ts new file mode 100644 index 0000000..4191402 --- /dev/null +++ b/src/controllers/api/reactions.ts @@ -0,0 +1,38 @@ +import { AppController } from '@/app.ts'; +import { Storages } from '@/storages.ts'; +import { createEvent } from '@/utils/api.ts'; +import { renderStatus } from '@/views/mastodon/statuses.ts'; + +/** + * React to a status. + * https://docs.pleroma.social/backend/development/API/pleroma_api/#put-apiv1pleromastatusesidreactionsemoji + */ +const reactionController: AppController = async (c) => { + const id = c.req.param('id'); + const emoji = c.req.param('emoji'); + const signer = c.get('signer')!; + + if (!/^\p{RGI_Emoji}$/v.test(emoji)) { + return c.json({ error: 'Invalid emoji' }, 400); + } + + const store = await Storages.db(); + const [event] = await store.query([{ kinds: [1], ids: [id], limit: 1 }]); + + if (!event) { + return c.json({ error: 'Status not found' }, 404); + } + + await createEvent({ + kind: 7, + content: '', + created_at: Math.floor(Date.now() / 1000), + tags: [['e', id]], + }, c); + + const status = renderStatus(event, { viewerPubkey: await signer.getPublicKey() }); + + return c.json(status); +}; + +export { reactionController }; From b0a53f47891a8c30f9a4e5bbb4521f6799a31420 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 22 May 2024 18:33:00 -0500 Subject: [PATCH 2/2] Add delete reaction controller --- src/app.ts | 4 +++ src/controllers/api/reactions.ts | 47 ++++++++++++++++++++++++++++++-- 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/src/app.ts b/src/app.ts index 86faa24..bd55417 100644 --- a/src/app.ts +++ b/src/app.ts @@ -43,6 +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 { relayController } from '@/controllers/nostr/relay.ts'; import { adminReportController, @@ -210,6 +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.delete('/api/v1/pleroma/statuses/:id{[0-9a-f]{64}}/reactions/:emoji', requireSigner, deleteReactionController); + app.get('/api/v1/admin/accounts', requireRole('admin'), adminAccountsController); app.get('/api/v1/pleroma/admin/config', requireRole('admin'), configController); app.post('/api/v1/pleroma/admin/config', requireRole('admin'), updateConfigController); diff --git a/src/controllers/api/reactions.ts b/src/controllers/api/reactions.ts index 4191402..bac5d33 100644 --- a/src/controllers/api/reactions.ts +++ b/src/controllers/api/reactions.ts @@ -25,7 +25,7 @@ const reactionController: AppController = async (c) => { await createEvent({ kind: 7, - content: '', + content: emoji, created_at: Math.floor(Date.now() / 1000), tags: [['e', id]], }, c); @@ -35,4 +35,47 @@ const reactionController: AppController = async (c) => { return c.json(status); }; -export { reactionController }; +/** + * Delete reactions to a status. + * https://docs.pleroma.social/backend/development/API/pleroma_api/#delete-apiv1pleromastatusesidreactionsemoji + */ +const deleteReactionController: AppController = async (c) => { + const id = c.req.param('id'); + const emoji = c.req.param('emoji'); + const signer = c.get('signer')!; + const pubkey = await signer.getPublicKey(); + const store = await Storages.db(); + + if (!/^\p{RGI_Emoji}$/v.test(emoji)) { + return c.json({ error: 'Invalid emoji' }, 400); + } + + const [event] = await store.query([ + { kinds: [1], ids: [id], limit: 1 }, + ]); + + if (!event) { + return c.json({ error: 'Status not found' }, 404); + } + + const events = await store.query([ + { kinds: [7], authors: [pubkey], '#e': [id] }, + ]); + + const tags = events + .filter((event) => event.content === emoji) + .map((event) => ['e', event.id]); + + await createEvent({ + kind: 5, + content: '', + created_at: Math.floor(Date.now() / 1000), + tags, + }, c); + + const status = renderStatus(event, { viewerPubkey: pubkey }); + + return c.json(status); +}; + +export { deleteReactionController, reactionController };