diff --git a/src/app.ts b/src/app.ts index cbfe5b1..0d38e34 100644 --- a/src/app.ts +++ b/src/app.ts @@ -53,9 +53,11 @@ import { createStatusController, favouriteController, favouritedByController, + pinController, rebloggedByController, statusController, unbookmarkController, + unpinController, } from './controllers/api/statuses.ts'; import { streamingController } from './controllers/api/streaming.ts'; import { @@ -158,6 +160,8 @@ app.get('/api/v1/statuses/:id{[0-9a-f]{64}}', statusController); app.post('/api/v1/statuses/:id{[0-9a-f]{64}}/favourite', requirePubkey, favouriteController); app.post('/api/v1/statuses/:id{[0-9a-f]{64}}/bookmark', requirePubkey, bookmarkController); app.post('/api/v1/statuses/:id{[0-9a-f]{64}}/unbookmark', requirePubkey, unbookmarkController); +app.post('/api/v1/statuses/:id{[0-9a-f]{64}}/pin', requirePubkey, pinController); +app.post('/api/v1/statuses/:id{[0-9a-f]{64}}/unpin', requirePubkey, unpinController); app.post('/api/v1/statuses', requirePubkey, createStatusController); app.post('/api/v1/media', requireRole('user', { validatePayload: false }), mediaController); diff --git a/src/controllers/api/statuses.ts b/src/controllers/api/statuses.ts index 1ba82b3..9cb789b 100644 --- a/src/controllers/api/statuses.ts +++ b/src/controllers/api/statuses.ts @@ -207,13 +207,69 @@ const unbookmarkController: AppController = async (c) => { } }; +/** https://docs.joinmastodon.org/methods/statuses/#pin */ +const pinController: AppController = async (c) => { + const pubkey = c.get('pubkey')!; + const eventId = c.req.param('id'); + + const event = await getEvent(eventId, { + kind: 1, + relations: ['author', 'event_stats', 'author_stats'], + }); + + if (event) { + await updateListEvent( + { kinds: [10001], authors: [pubkey] }, + (tags) => addTag(tags, ['e', eventId]), + c, + ); + + const status = await renderStatus(event, pubkey); + if (status) { + status.pinned = true; + } + return c.json(status); + } else { + return c.json({ error: 'Event not found.' }, 404); + } +}; + +/** https://docs.joinmastodon.org/methods/statuses/#unpin */ +const unpinController: AppController = async (c) => { + const pubkey = c.get('pubkey')!; + const eventId = c.req.param('id'); + + const event = await getEvent(eventId, { + kind: 1, + relations: ['author', 'event_stats', 'author_stats'], + }); + + if (event) { + await updateListEvent( + { kinds: [10001], authors: [pubkey] }, + (tags) => deleteTag(tags, ['e', eventId]), + c, + ); + + const status = await renderStatus(event, pubkey); + if (status) { + status.pinned = false; + } + return c.json(status); + } else { + return c.json({ error: 'Event not found.' }, 404); + } +}; + export { bookmarkController, contextController, createStatusController, favouriteController, favouritedByController, + pinController, rebloggedByController, statusController, unbookmarkController, + unpinController, }; diff --git a/src/views/mastodon/statuses.ts b/src/views/mastodon/statuses.ts index d869563..d840c36 100644 --- a/src/views/mastodon/statuses.ts +++ b/src/views/mastodon/statuses.ts @@ -38,6 +38,7 @@ async function renderStatus(event: DittoEvent<1>, viewerPubkey?: string) { ? await eventsDB.getEvents([ { kinds: [6], '#e': [event.id], authors: [viewerPubkey], limit: 1 }, { kinds: [7], '#e': [event.id], authors: [viewerPubkey], limit: 1 }, + { kinds: [10001], '#e': [event.id], authors: [viewerPubkey], limit: 1 }, { kinds: [10003], '#e': [event.id], authors: [viewerPubkey], limit: 1 }, ]) : [], @@ -45,6 +46,7 @@ async function renderStatus(event: DittoEvent<1>, viewerPubkey?: string) { const reactionEvent = relatedEvents.find((event) => event.kind === 6); const repostEvent = relatedEvents.find((event) => event.kind === 7); + const pinEvent = relatedEvents.find((event) => event.kind === 10001); const bookmarkEvent = relatedEvents.find((event) => event.kind === 10003); const content = buildInlineRecipients(mentions) + html; @@ -79,6 +81,7 @@ async function renderStatus(event: DittoEvent<1>, viewerPubkey?: string) { reblogged: Boolean(repostEvent), muted: false, bookmarked: Boolean(bookmarkEvent), + pinned: Boolean(pinEvent), reblog: null, application: null, media_attachments: media.map(renderAttachment),