From 5b7c3a1d5e68765825111eb675929378bcef71a3 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 1 Jan 2024 12:43:53 -0600 Subject: [PATCH 1/3] Support GET /api/v1/blocks --- src/app.ts | 3 ++- src/controllers/api/accounts.ts | 12 +++--------- src/controllers/api/blocks.ts | 22 ++++++++++++++++++++++ src/views.ts | 13 ++++++++++++- 4 files changed, 39 insertions(+), 11 deletions(-) create mode 100644 src/controllers/api/blocks.ts diff --git a/src/app.ts b/src/app.ts index e637cb4f..59ee717e 100644 --- a/src/app.ts +++ b/src/app.ts @@ -34,6 +34,7 @@ import { verifyCredentialsController, } from './controllers/api/accounts.ts'; import { appCredentialsController, createAppController } from './controllers/api/apps.ts'; +import { blocksController } from './controllers/api/blocks.ts'; import { emptyArrayController, emptyObjectController, notImplementedController } from './controllers/api/fallback.ts'; import { instanceController } from './controllers/api/instance.ts'; import { mediaController } from './controllers/api/media.ts'; @@ -168,6 +169,7 @@ app.get('/api/v1/trends', cache({ cacheName: 'web', expires: Time.minutes(15) }) app.get('/api/v1/notifications', requirePubkey, notificationsController); app.get('/api/v1/favourites', requirePubkey, favouritesController); +app.get('/api/v1/blocks', requirePubkey, blocksController); app.post('/api/v1/pleroma/admin/config', requireRole('admin'), updateConfigController); @@ -175,7 +177,6 @@ app.post('/api/v1/pleroma/admin/config', requireRole('admin'), updateConfigContr app.get('/api/v1/bookmarks', emptyArrayController); app.get('/api/v1/custom_emojis', emptyArrayController); app.get('/api/v1/filters', emptyArrayController); -app.get('/api/v1/blocks', emptyArrayController); app.get('/api/v1/mutes', emptyArrayController); app.get('/api/v1/domain_blocks', emptyArrayController); app.get('/api/v1/markers', emptyObjectController); diff --git a/src/controllers/api/accounts.ts b/src/controllers/api/accounts.ts index 9e1acc80..7486b029 100644 --- a/src/controllers/api/accounts.ts +++ b/src/controllers/api/accounts.ts @@ -12,7 +12,7 @@ import { uploadFile } from '@/upload.ts'; import { lookupAccount, nostrNow } from '@/utils.ts'; import { paginated, paginationSchema, parseBody, updateListEvent } from '@/utils/web.ts'; import { createEvent } from '@/utils/web.ts'; -import { renderEventAccounts } from '@/views.ts'; +import { renderAccounts, renderEventAccounts } from '@/views.ts'; import { accountFromPubkey, renderAccount } from '@/views/mastodon/accounts.ts'; import { renderRelationship } from '@/views/mastodon/relationships.ts'; import { renderStatus } from '@/views/mastodon/statuses.ts'; @@ -236,16 +236,10 @@ const followersController: AppController = (c) => { const followingController: AppController = async (c) => { const pubkey = c.req.param('pubkey'); const pubkeys = await getFollowedPubkeys(pubkey); - - // TODO: pagination by offset. - const accounts = await Promise.all(pubkeys.map(async (pubkey) => { - const event = await getAuthor(pubkey); - return event ? await renderAccount(event) : undefined; - })); - - return c.json(accounts.filter(Boolean)); + return renderAccounts(c, pubkeys); }; +/** https://docs.joinmastodon.org/methods/accounts/#block */ const blockController: AppController = async (c) => { const sourcePubkey = c.get('pubkey')!; const targetPubkey = c.req.param('pubkey'); diff --git a/src/controllers/api/blocks.ts b/src/controllers/api/blocks.ts new file mode 100644 index 00000000..2ff4f3e4 --- /dev/null +++ b/src/controllers/api/blocks.ts @@ -0,0 +1,22 @@ +import { type AppController } from '@/app.ts'; +import { eventsDB } from '@/db/events.ts'; +import { getTagSet } from '@/tags.ts'; +import { renderAccounts } from '@/views.ts'; + +/** https://docs.joinmastodon.org/methods/blocks/#get */ +const blocksController: AppController = async (c) => { + const pubkey = c.get('pubkey')!; + + const [event10000] = await eventsDB.getEvents([ + { kinds: [10000], authors: [pubkey], limit: 1 }, + ]); + + if (event10000) { + const pubkeys = getTagSet(event10000.tags, 'p'); + return renderAccounts(c, [...pubkeys].reverse()); + } else { + return c.json([]); + } +}; + +export { blocksController }; diff --git a/src/views.ts b/src/views.ts index 95a998e7..59886899 100644 --- a/src/views.ts +++ b/src/views.ts @@ -24,4 +24,15 @@ async function renderEventAccounts(c: AppContext, filters: Filter[]) { return paginated(c, events, accounts); } -export { renderEventAccounts }; +async function renderAccounts(c: AppContext, pubkeys: string[]) { + // TODO: pagination by offset. + // FIXME: this is very inefficient! + const accounts = await Promise.all(pubkeys.map(async (pubkey) => { + const event = await getAuthor(pubkey); + return event ? await renderAccount(event) : undefined; + })); + + return c.json(accounts.filter(Boolean)); +} + +export { renderAccounts, renderEventAccounts }; From 3807ca175f2541c36b6f07b6c2ff4690bd2672b2 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 1 Jan 2024 12:50:09 -0600 Subject: [PATCH 2/3] Add unblock and unfollow endpoints --- src/app.ts | 8 ++++++-- src/controllers/api/accounts.ts | 35 ++++++++++++++++++++++++++++++++- 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/src/app.ts b/src/app.ts index 59ee717e..2c733691 100644 --- a/src/app.ts +++ b/src/app.ts @@ -30,6 +30,8 @@ import { followersController, followingController, relationshipsController, + unblockController, + unfollowController, updateCredentialsController, verifyCredentialsController, } from './controllers/api/accounts.ts'; @@ -137,8 +139,10 @@ app.patch( app.get('/api/v1/accounts/search', accountSearchController); app.get('/api/v1/accounts/lookup', accountLookupController); app.get('/api/v1/accounts/relationships', relationshipsController); -app.post('/api/v1/accounts/:pubkey{[0-9a-f]{64}}/block', blockController); -app.post('/api/v1/accounts/:pubkey{[0-9a-f]{64}}/follow', followController); +app.post('/api/v1/accounts/:pubkey{[0-9a-f]{64}}/block', requirePubkey, blockController); +app.post('/api/v1/accounts/:pubkey{[0-9a-f]{64}}/unblock', requirePubkey, unblockController); +app.post('/api/v1/accounts/:pubkey{[0-9a-f]{64}}/follow', requirePubkey, followController); +app.post('/api/v1/accounts/:pubkey{[0-9a-f]{64}}/unfollow', requirePubkey, unfollowController); app.get('/api/v1/accounts/:pubkey{[0-9a-f]{64}}/followers', followersController); app.get('/api/v1/accounts/:pubkey{[0-9a-f]{64}}/following', followingController); app.get('/api/v1/accounts/:pubkey{[0-9a-f]{64}}/statuses', accountStatusesController); diff --git a/src/controllers/api/accounts.ts b/src/controllers/api/accounts.ts index 7486b029..b25dbe45 100644 --- a/src/controllers/api/accounts.ts +++ b/src/controllers/api/accounts.ts @@ -7,7 +7,7 @@ import { type DittoFilter } from '@/filter.ts'; import { getAuthor, getFollowedPubkeys } from '@/queries.ts'; import { booleanParamSchema, fileSchema } from '@/schema.ts'; import { jsonMetaContentSchema } from '@/schemas/nostr.ts'; -import { addTag } from '@/tags.ts'; +import { addTag, deleteTag } from '@/tags.ts'; import { uploadFile } from '@/upload.ts'; import { lookupAccount, nostrNow } from '@/utils.ts'; import { paginated, paginationSchema, parseBody, updateListEvent } from '@/utils/web.ts'; @@ -213,6 +213,7 @@ const updateCredentialsController: AppController = async (c) => { return c.json(account); }; +/** https://docs.joinmastodon.org/methods/accounts/#follow */ const followController: AppController = async (c) => { const sourcePubkey = c.get('pubkey')!; const targetPubkey = c.req.param('pubkey'); @@ -227,6 +228,21 @@ const followController: AppController = async (c) => { return c.json(relationship); }; +/** https://docs.joinmastodon.org/methods/accounts/#unfollow */ +const unfollowController: AppController = async (c) => { + const sourcePubkey = c.get('pubkey')!; + const targetPubkey = c.req.param('pubkey'); + + await updateListEvent( + { kinds: [3], authors: [sourcePubkey] }, + (tags) => deleteTag(tags, ['p', targetPubkey]), + c, + ); + + const relationship = await renderRelationship(sourcePubkey, targetPubkey); + return c.json(relationship); +}; + const followersController: AppController = (c) => { const pubkey = c.req.param('pubkey'); const params = paginationSchema.parse(c.req.query()); @@ -254,6 +270,21 @@ const blockController: AppController = async (c) => { return c.json(relationship); }; +/** https://docs.joinmastodon.org/methods/accounts/#unblock */ +const unblockController: AppController = async (c) => { + const sourcePubkey = c.get('pubkey')!; + const targetPubkey = c.req.param('pubkey'); + + await updateListEvent( + { kinds: [10000], authors: [sourcePubkey] }, + (tags) => deleteTag(tags, ['p', targetPubkey]), + c, + ); + + const relationship = await renderRelationship(sourcePubkey, targetPubkey); + return c.json(relationship); +}; + const favouritesController: AppController = async (c) => { const pubkey = c.get('pubkey')!; const params = paginationSchema.parse(c.req.query()); @@ -290,6 +321,8 @@ export { followersController, followingController, relationshipsController, + unblockController, + unfollowController, updateCredentialsController, verifyCredentialsController, }; From 38241d011d935bce6829eb2e811e44b0232aa1da Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 1 Jan 2024 13:08:22 -0600 Subject: [PATCH 3/3] Add missing newline --- src/utils/web.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/utils/web.ts b/src/utils/web.ts index 1e7be076..837c4bed 100644 --- a/src/utils/web.ts +++ b/src/utils/web.ts @@ -64,6 +64,7 @@ function updateListEvent( tags: fn(prev?.tags ?? []), }), c); } + /** Publish an admin event through the pipeline. */ async function createAdminEvent(t: EventStub, c: AppContext): Promise> { const event = await signAdminEvent({