From f08211e2a1240cb5731fd55094ea8f70cdb5b986 Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Sat, 4 May 2024 10:29:08 -0300 Subject: [PATCH 01/10] refactor(admin-accounts): resolve import specifier via the active import map --- src/views/mastodon/admin-accounts.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/mastodon/admin-accounts.ts b/src/views/mastodon/admin-accounts.ts index 7914776..8023161 100644 --- a/src/views/mastodon/admin-accounts.ts +++ b/src/views/mastodon/admin-accounts.ts @@ -1,7 +1,7 @@ import { type DittoEvent } from '@/interfaces/DittoEvent.ts'; import { nostrDate } from '@/utils.ts'; -import { accountFromPubkey, renderAccount } from './accounts.ts'; +import { accountFromPubkey, renderAccount } from '@/views/mastodon/accounts.ts'; async function renderAdminAccount(event: DittoEvent) { const d = event.tags.find(([name]) => name === 'd')?.[1]!; From b57188943fb05de562fbadeae16bacf784ab760b Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Sat, 4 May 2024 16:29:31 -0300 Subject: [PATCH 02/10] feat: renderAdminAccount() supports both kind 0 & kind 30361 --- src/views/mastodon/admin-accounts.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/views/mastodon/admin-accounts.ts b/src/views/mastodon/admin-accounts.ts index 8023161..90723f4 100644 --- a/src/views/mastodon/admin-accounts.ts +++ b/src/views/mastodon/admin-accounts.ts @@ -3,9 +3,16 @@ import { nostrDate } from '@/utils.ts'; import { accountFromPubkey, renderAccount } from '@/views/mastodon/accounts.ts'; +/** Expects a kind 0 fully hydrated or a kind 30361 hydrated with `d_author` */ async function renderAdminAccount(event: DittoEvent) { - const d = event.tags.find(([name]) => name === 'd')?.[1]!; - const account = event.d_author ? await renderAccount({ ...event.d_author, user: event }) : await accountFromPubkey(d); + let account; + + if (event.kind === 0 && event.user) { + account = await renderAccount(event); + } else { + const d = event.tags.find(([name]) => name === 'd')?.[1]!; + account = event.d_author ? await renderAccount({ ...event.d_author, user: event }) : await accountFromPubkey(d); + } return { id: account.id, From af7b83cf8ac2c87c21f582043c92f435ab0eaefb Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Sat, 4 May 2024 20:10:18 -0300 Subject: [PATCH 03/10] feat: create /api/v1/admin/reports endpoint & controller --- src/app.ts | 3 ++- src/controllers/api/reports.ts | 19 ++++++++++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/app.ts b/src/app.ts index df8919d..73baa18 100644 --- a/src/app.ts +++ b/src/app.ts @@ -43,7 +43,7 @@ import { } from '@/controllers/api/pleroma.ts'; import { preferencesController } from '@/controllers/api/preferences.ts'; import { relayController } from '@/controllers/nostr/relay.ts'; -import { reportsController } from '@/controllers/api/reports.ts'; +import { reportsController, viewAllReportsController } from '@/controllers/api/reports.ts'; import { searchController } from '@/controllers/api/search.ts'; import { bookmarkController, @@ -200,6 +200,7 @@ app.get('/api/v1/admin/ditto/relays', requireRole('admin'), adminRelaysControlle app.put('/api/v1/admin/ditto/relays', requireRole('admin'), adminSetRelaysController); app.post('/api/v1/reports', requirePubkey, reportsController); +app.get('/api/v1/admin/reports', requirePubkey, requireRole('admin'), viewAllReportsController); // Not (yet) implemented. app.get('/api/v1/custom_emojis', emptyArrayController); diff --git a/src/controllers/api/reports.ts b/src/controllers/api/reports.ts index 9e1f933..351c1c2 100644 --- a/src/controllers/api/reports.ts +++ b/src/controllers/api/reports.ts @@ -5,6 +5,7 @@ import { hydrateEvents } from '@/storages/hydrate.ts'; import { NSchema as n } from '@nostrify/nostrify'; import { renderReport } from '@/views/mastodon/reports.ts'; import { z } from 'zod'; +import { renderAdminReport } from '@/views/mastodon/reports.ts'; const reportsSchema = z.object({ account_id: n.id(), @@ -50,4 +51,20 @@ const reportsController: AppController = async (c) => { return c.json(await renderReport(event, profile)); }; -export { reportsController }; +/** https://docs.joinmastodon.org/methods/admin/reports/#get */ +const viewAllReportsController: AppController = async (c) => { + const store = c.get('store'); + const allMastodonReports = []; + + const allReports = await store.query([{ kinds: [1984], '#P': [Conf.pubkey] }]); + + await hydrateEvents({ storage: store, events: allReports, signal: AbortSignal.timeout(2000) }); + + for (const report of allReports) { + allMastodonReports.push(await renderAdminReport(report, { viewerPubkey: c.get('pubkey') })); + } + + return c.json(allMastodonReports); +}; + +export { reportsController, viewAllReportsController }; From b8df95408bb665d473c3064af9724dc20b041f61 Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Sat, 4 May 2024 20:11:29 -0300 Subject: [PATCH 04/10] feat: add target_account & reported_statuses to DittoEvent type --- src/interfaces/DittoEvent.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/interfaces/DittoEvent.ts b/src/interfaces/DittoEvent.ts index 2ef0bb2..f6810d3 100644 --- a/src/interfaces/DittoEvent.ts +++ b/src/interfaces/DittoEvent.ts @@ -25,4 +25,14 @@ export interface DittoEvent extends NostrEvent { repost?: DittoEvent; quote_repost?: DittoEvent; reacted?: DittoEvent; + /** The account being reported. + * Must be a kind 0 hydrated. + * https://github.com/nostr-protocol/nips/blob/master/56.md + */ + target_account?: DittoEvent; + /** The statuses being reported. + * Nostr only support reporting one note, the array of reported notes can be found in the `status_ids` field after JSON.parsing the `content` of a kind 1984. + * https://github.com/nostr-protocol/nips/blob/master/56.md + */ + reported_statuses?: DittoEvent[]; } From 8a7f0892d715416941a6d061a7c0031b7553e939 Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Sat, 4 May 2024 20:12:34 -0300 Subject: [PATCH 05/10] fix: check if event is not undefined in renderAdminAccount --- src/views/mastodon/admin-accounts.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/mastodon/admin-accounts.ts b/src/views/mastodon/admin-accounts.ts index 90723f4..b344608 100644 --- a/src/views/mastodon/admin-accounts.ts +++ b/src/views/mastodon/admin-accounts.ts @@ -7,7 +7,7 @@ import { accountFromPubkey, renderAccount } from '@/views/mastodon/accounts.ts'; async function renderAdminAccount(event: DittoEvent) { let account; - if (event.kind === 0 && event.user) { + if (event && event.kind === 0 && event.user) { account = await renderAccount(event); } else { const d = event.tags.find(([name]) => name === 'd')?.[1]!; From 4d5d4868ce2fa81d95b4d2f674d4c8eaab076f52 Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Sat, 4 May 2024 20:14:39 -0300 Subject: [PATCH 06/10] feat: create renderAdminReport() func --- src/views/mastodon/reports.ts | 44 ++++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/src/views/mastodon/reports.ts b/src/views/mastodon/reports.ts index 03291f7..50fea46 100644 --- a/src/views/mastodon/reports.ts +++ b/src/views/mastodon/reports.ts @@ -1,6 +1,8 @@ import { type DittoEvent } from '@/interfaces/DittoEvent.ts'; import { accountFromPubkey, renderAccount } from '@/views/mastodon/accounts.ts'; import { nostrDate } from '@/utils.ts'; +import { renderAdminAccount } from '@/views/mastodon/admin-accounts.ts'; +import { renderStatus } from '@/views/mastodon/statuses.ts'; /** Expects a `reportEvent` of kind 1984 and a `profile` of kind 0 of the person being reported */ async function renderReport(reportEvent: DittoEvent, profile: DittoEvent) { @@ -26,4 +28,44 @@ async function renderReport(reportEvent: DittoEvent, profile: DittoEvent) { }; } -export { renderReport }; +interface RenderAdminReportOpts { + viewerPubkey?: string; +} + +/** Admin-level information about a filed report. + * Expects an event of kind 1984 fully hydrated. + * https://docs.joinmastodon.org/entities/Admin_Report */ +async function renderAdminReport(reportEvent: DittoEvent, opts: RenderAdminReportOpts) { + const { viewerPubkey } = opts; + + const { + comment, + forward, + category, + } = JSON.parse(reportEvent.content); + + const statuses = []; + if (reportEvent.reported_statuses) { + for (const status of reportEvent.reported_statuses) { + statuses.push(await renderStatus(status, { viewerPubkey })); + } + } + + return { + id: reportEvent.id, + action_taken: false, + action_taken_at: null, + category, + comment, + forwarded: forward, + created_at: nostrDate(reportEvent.created_at).toISOString(), + account: await renderAdminAccount(reportEvent.author as DittoEvent), + target_account: await renderAdminAccount(reportEvent.target_account as DittoEvent), + assigned_account: null, + action_taken_by_account: null, + statuses, + rule: [], + }; +} + +export { renderAdminReport, renderReport }; From 14802dd38a913e34a0a778fb55d952b0f4d2dc06 Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Sat, 4 May 2024 20:18:36 -0300 Subject: [PATCH 07/10] feat(hydrate): create gatherTargetAccounts() & gatherReportedStatuses() --- src/storages/hydrate.ts | 79 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 75 insertions(+), 4 deletions(-) diff --git a/src/storages/hydrate.ts b/src/storages/hydrate.ts index 716f251..383a133 100644 --- a/src/storages/hydrate.ts +++ b/src/storages/hydrate.ts @@ -42,6 +42,14 @@ async function hydrateEvents(opts: HydrateOpts): Promise { cache.push(event); } + for (const event of await gatherTargetAccounts({ events: cache, storage, signal })) { + cache.push(event); + } + + for (const event of await gatherReportedStatuses({ events: cache, storage, signal })) { + cache.push(event); + } + const stats = { authors: await gatherAuthorStats(cache), events: await gatherEventStats(cache), @@ -69,6 +77,13 @@ function assembleEvents( event.author = b.find((e) => matchFilter({ kinds: [0], authors: [event.pubkey] }, e)); event.user = b.find((e) => matchFilter({ kinds: [30361], authors: [admin], '#d': [event.pubkey] }, e)); + if (event.kind === 1) { + const id = event.tags.find(([name]) => name === 'q')?.[1]; + if (id) { + event.quote_repost = b.find((e) => matchFilter({ kinds: [1], ids: [id] }, e)); + } + } + if (event.kind === 6) { const id = event.tags.find(([name]) => name === 'e')?.[1]; if (id) { @@ -76,10 +91,27 @@ function assembleEvents( } } - if (event.kind === 1) { - const id = event.tags.find(([name]) => name === 'q')?.[1]; - if (id) { - event.quote_repost = b.find((e) => matchFilter({ kinds: [1], ids: [id] }, e)); + if (event.kind === 1984) { + const targetAccountId = event.tags.find(([name]) => name === 'p')?.[1]; + if (targetAccountId) { + event.target_account = b.find((e) => matchFilter({ kinds: [0], authors: [targetAccountId] }, e)); + if (event.target_account) { + event.target_account.user = b.find((e) => + matchFilter({ kinds: [30361], authors: [admin], '#d': [event.pubkey] }, e) + ); + } + } + const reportedEvents: DittoEvent[] = []; + + const { status_ids } = JSON.parse(event.content); + if (status_ids && Array.isArray(status_ids)) { + for (const id of status_ids) { + if (typeof id === 'string') { + const reportedEvent = b.find((e) => matchFilter({ kinds: [1], ids: [id] }, e)); + if (reportedEvent) reportedEvents.push(reportedEvent); + } + } + event.reported_statuses = reportedEvents; } } @@ -167,6 +199,45 @@ function gatherUsers({ events, storage, signal }: HydrateOpts): Promise { + const ids = new Set(); + for (const event of events) { + if (event.kind === 1984) { + const { status_ids } = JSON.parse(event.content); + if (status_ids && Array.isArray(status_ids)) { + for (const id of status_ids) { + if (typeof id === 'string') ids.add(id); + } + } + } + } + + return storage.query( + [{ kinds: [1], ids: [...ids], limit: ids.size }], + { signal }, + ); +} + +/** Collect target accounts (the ones being reported) from the events. */ +function gatherTargetAccounts({ events, storage, signal }: HydrateOpts): Promise { + const pubkeys = new Set(); + + for (const event of events) { + if (event.kind === 1984) { + const pubkey = event.tags.find(([name]) => name === 'p')?.[1]; + if (pubkey) { + pubkeys.add(pubkey); + } + } + } + + return storage.query( + [{ kinds: [0], authors: [...pubkeys], limit: pubkeys.size }], + { signal }, + ); +} + /** Collect author stats from the events. */ function gatherAuthorStats(events: DittoEvent[]): Promise { const pubkeys = new Set( From 7890504adda95db8cb61aa0d2d660bab54328319 Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Sun, 5 May 2024 14:20:13 -0300 Subject: [PATCH 08/10] refactor: view info about all reports --- src/controllers/api/reports.ts | 25 ++++++++++--------------- src/interfaces/DittoEvent.ts | 9 ++++----- src/storages/hydrate.ts | 21 ++++++++------------- src/views/mastodon/admin-accounts.ts | 11 ++--------- src/views/mastodon/reports.ts | 6 +++--- 5 files changed, 27 insertions(+), 45 deletions(-) diff --git a/src/controllers/api/reports.ts b/src/controllers/api/reports.ts index 351c1c2..b49486c 100644 --- a/src/controllers/api/reports.ts +++ b/src/controllers/api/reports.ts @@ -1,11 +1,12 @@ -import { type AppController } from '@/app.ts'; -import { createEvent, parseBody } from '@/utils/api.ts'; -import { Conf } from '@/config.ts'; -import { hydrateEvents } from '@/storages/hydrate.ts'; import { NSchema as n } from '@nostrify/nostrify'; -import { renderReport } from '@/views/mastodon/reports.ts'; import { z } from 'zod'; + +import { type AppController } from '@/app.ts'; +import { Conf } from '@/config.ts'; +import { createEvent, parseBody } from '@/utils/api.ts'; +import { hydrateEvents } from '@/storages/hydrate.ts'; import { renderAdminReport } from '@/views/mastodon/reports.ts'; +import { renderReport } from '@/views/mastodon/reports.ts'; const reportsSchema = z.object({ account_id: n.id(), @@ -54,17 +55,11 @@ const reportsController: AppController = async (c) => { /** https://docs.joinmastodon.org/methods/admin/reports/#get */ const viewAllReportsController: AppController = async (c) => { const store = c.get('store'); - const allMastodonReports = []; + const reports = await store.query([{ kinds: [1984], '#P': [Conf.pubkey] }]) + .then((events) => hydrateEvents({ storage: store, events: events, signal: c.req.raw.signal })) + .then((events) => Promise.all(events.map((event) => renderAdminReport(event, { viewerPubkey: c.get('pubkey') })))); - const allReports = await store.query([{ kinds: [1984], '#P': [Conf.pubkey] }]); - - await hydrateEvents({ storage: store, events: allReports, signal: AbortSignal.timeout(2000) }); - - for (const report of allReports) { - allMastodonReports.push(await renderAdminReport(report, { viewerPubkey: c.get('pubkey') })); - } - - return c.json(allMastodonReports); + return c.json(reports); }; export { reportsController, viewAllReportsController }; diff --git a/src/interfaces/DittoEvent.ts b/src/interfaces/DittoEvent.ts index f6810d3..32c6e93 100644 --- a/src/interfaces/DittoEvent.ts +++ b/src/interfaces/DittoEvent.ts @@ -25,14 +25,13 @@ export interface DittoEvent extends NostrEvent { repost?: DittoEvent; quote_repost?: DittoEvent; reacted?: DittoEvent; - /** The account being reported. + /** The profile being reported. * Must be a kind 0 hydrated. * https://github.com/nostr-protocol/nips/blob/master/56.md */ - target_account?: DittoEvent; - /** The statuses being reported. - * Nostr only support reporting one note, the array of reported notes can be found in the `status_ids` field after JSON.parsing the `content` of a kind 1984. + reported_profile?: DittoEvent; + /** The notes being reported. * https://github.com/nostr-protocol/nips/blob/master/56.md */ - reported_statuses?: DittoEvent[]; + reported_notes?: DittoEvent[]; } diff --git a/src/storages/hydrate.ts b/src/storages/hydrate.ts index ca439bd..ced61e6 100644 --- a/src/storages/hydrate.ts +++ b/src/storages/hydrate.ts @@ -42,11 +42,11 @@ async function hydrateEvents(opts: HydrateOpts): Promise { cache.push(event); } - for (const event of await gatherTargetAccounts({ events: cache, storage, signal })) { + for (const event of await gatherReportedProfiles({ events: cache, storage, signal })) { cache.push(event); } - for (const event of await gatherReportedStatuses({ events: cache, storage, signal })) { + for (const event of await gatherReportedNotes({ events: cache, storage, signal })) { cache.push(event); } @@ -101,12 +101,7 @@ function assembleEvents( if (event.kind === 1984) { const targetAccountId = event.tags.find(([name]) => name === 'p')?.[1]; if (targetAccountId) { - event.target_account = b.find((e) => matchFilter({ kinds: [0], authors: [targetAccountId] }, e)); - if (event.target_account) { - event.target_account.user = b.find((e) => - matchFilter({ kinds: [30361], authors: [admin], '#d': [event.pubkey] }, e) - ); - } + event.reported_profile = b.find((e) => matchFilter({ kinds: [0], authors: [targetAccountId] }, e)); } const reportedEvents: DittoEvent[] = []; @@ -118,7 +113,7 @@ function assembleEvents( if (reportedEvent) reportedEvents.push(reportedEvent); } } - event.reported_statuses = reportedEvents; + event.reported_notes = reportedEvents; } } @@ -206,8 +201,8 @@ function gatherUsers({ events, storage, signal }: HydrateOpts): Promise { +/** Collect reported notes from the events. */ +function gatherReportedNotes({ events, storage, signal }: HydrateOpts): Promise { const ids = new Set(); for (const event of events) { if (event.kind === 1984) { @@ -226,8 +221,8 @@ function gatherReportedStatuses({ events, storage, signal }: HydrateOpts): Promi ); } -/** Collect target accounts (the ones being reported) from the events. */ -function gatherTargetAccounts({ events, storage, signal }: HydrateOpts): Promise { +/** Collect reported profiles from the events. */ +function gatherReportedProfiles({ events, storage, signal }: HydrateOpts): Promise { const pubkeys = new Set(); for (const event of events) { diff --git a/src/views/mastodon/admin-accounts.ts b/src/views/mastodon/admin-accounts.ts index b344608..411a655 100644 --- a/src/views/mastodon/admin-accounts.ts +++ b/src/views/mastodon/admin-accounts.ts @@ -1,18 +1,11 @@ import { type DittoEvent } from '@/interfaces/DittoEvent.ts'; import { nostrDate } from '@/utils.ts'; -import { accountFromPubkey, renderAccount } from '@/views/mastodon/accounts.ts'; +import { renderAccount } from '@/views/mastodon/accounts.ts'; /** Expects a kind 0 fully hydrated or a kind 30361 hydrated with `d_author` */ async function renderAdminAccount(event: DittoEvent) { - let account; - - if (event && event.kind === 0 && event.user) { - account = await renderAccount(event); - } else { - const d = event.tags.find(([name]) => name === 'd')?.[1]!; - account = event.d_author ? await renderAccount({ ...event.d_author, user: event }) : await accountFromPubkey(d); - } + const account = await renderAccount(event); return { id: account.id, diff --git a/src/views/mastodon/reports.ts b/src/views/mastodon/reports.ts index 50fea46..453151d 100644 --- a/src/views/mastodon/reports.ts +++ b/src/views/mastodon/reports.ts @@ -45,8 +45,8 @@ async function renderAdminReport(reportEvent: DittoEvent, opts: RenderAdminRepor } = JSON.parse(reportEvent.content); const statuses = []; - if (reportEvent.reported_statuses) { - for (const status of reportEvent.reported_statuses) { + if (reportEvent.reported_notes) { + for (const status of reportEvent.reported_notes) { statuses.push(await renderStatus(status, { viewerPubkey })); } } @@ -60,7 +60,7 @@ async function renderAdminReport(reportEvent: DittoEvent, opts: RenderAdminRepor forwarded: forward, created_at: nostrDate(reportEvent.created_at).toISOString(), account: await renderAdminAccount(reportEvent.author as DittoEvent), - target_account: await renderAdminAccount(reportEvent.target_account as DittoEvent), + target_account: await renderAdminAccount(reportEvent.reported_profile as DittoEvent), assigned_account: null, action_taken_by_account: null, statuses, From 394599734f5750b80bd4dde9fcfd3eeb5e478e46 Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Sun, 5 May 2024 15:45:24 -0300 Subject: [PATCH 09/10] fix(reports): put notes in tag & only let comment in event.content --- src/controllers/api/reports.ts | 18 +++++++++++------- src/storages/hydrate.ts | 16 +++++++--------- src/views/mastodon/reports.ts | 32 ++++++++++++++------------------ 3 files changed, 32 insertions(+), 34 deletions(-) diff --git a/src/controllers/api/reports.ts b/src/controllers/api/reports.ts index b49486c..3486550 100644 --- a/src/controllers/api/reports.ts +++ b/src/controllers/api/reports.ts @@ -12,7 +12,6 @@ const reportsSchema = z.object({ account_id: n.id(), status_ids: n.id().array().default([]), comment: z.string().max(1000).default(''), - forward: z.boolean().default(false), category: z.string().default('other'), // TODO: rules_ids[] is not implemented }); @@ -31,7 +30,6 @@ const reportsController: AppController = async (c) => { account_id, status_ids, comment, - forward, category, } = result.data; @@ -40,13 +38,19 @@ const reportsController: AppController = async (c) => { await hydrateEvents({ events: [profile], storage: store }); } + const tags = [ + ['p', account_id, category], + ['P', Conf.pubkey], + ]; + + for (const status of status_ids) { + tags.push(['e', status, category]); + } + const event = await createEvent({ kind: 1984, - content: JSON.stringify({ account_id, status_ids, comment, forward, category }), - tags: [ - ['p', account_id, category], - ['P', Conf.pubkey], - ], + content: comment, + tags, }, c); return c.json(await renderReport(event, profile)); diff --git a/src/storages/hydrate.ts b/src/storages/hydrate.ts index ced61e6..41670aa 100644 --- a/src/storages/hydrate.ts +++ b/src/storages/hydrate.ts @@ -105,13 +105,11 @@ function assembleEvents( } const reportedEvents: DittoEvent[] = []; - const { status_ids } = JSON.parse(event.content); - if (status_ids && Array.isArray(status_ids)) { + const status_ids = event.tags.filter(([name]) => name === 'e').map((tag) => tag[1]); + if (status_ids.length > 0) { for (const id of status_ids) { - if (typeof id === 'string') { - const reportedEvent = b.find((e) => matchFilter({ kinds: [1], ids: [id] }, e)); - if (reportedEvent) reportedEvents.push(reportedEvent); - } + const reportedEvent = b.find((e) => matchFilter({ kinds: [1], ids: [id] }, e)); + if (reportedEvent) reportedEvents.push(reportedEvent); } event.reported_notes = reportedEvents; } @@ -206,10 +204,10 @@ function gatherReportedNotes({ events, storage, signal }: HydrateOpts): Promise< const ids = new Set(); for (const event of events) { if (event.kind === 1984) { - const { status_ids } = JSON.parse(event.content); - if (status_ids && Array.isArray(status_ids)) { + const status_ids = event.tags.filter(([name]) => name === 'e').map((tag) => tag[1]); + if (status_ids.length > 0) { for (const id of status_ids) { - if (typeof id === 'string') ids.add(id); + ids.add(id); } } } diff --git a/src/views/mastodon/reports.ts b/src/views/mastodon/reports.ts index 453151d..7e3460d 100644 --- a/src/views/mastodon/reports.ts +++ b/src/views/mastodon/reports.ts @@ -6,25 +6,24 @@ import { renderStatus } from '@/views/mastodon/statuses.ts'; /** Expects a `reportEvent` of kind 1984 and a `profile` of kind 0 of the person being reported */ async function renderReport(reportEvent: DittoEvent, profile: DittoEvent) { - const { - account_id, - status_ids, - comment, - forward, - category, - } = JSON.parse(reportEvent.content); + // The category is present in both the 'e' and 'p' tag, however, it is possible to report a user without reporting a note, so it's better to get the category from the 'p' tag + const category = reportEvent.tags.find(([name]) => name === 'p')?.[2] as string; + + const status_ids = reportEvent.tags.filter(([name]) => name === 'e').map((tag) => tag[1]) ?? []; + + const reported_profile_pubkey = reportEvent.tags.find(([name]) => name === 'p')?.[1] as string; return { - id: account_id, + id: reportEvent.id, action_taken: false, action_taken_at: null, category, - comment, - forwarded: forward, + comment: reportEvent.content, + forwarded: false, created_at: nostrDate(reportEvent.created_at).toISOString(), status_ids, rules_ids: null, - target_account: profile ? await renderAccount(profile) : await accountFromPubkey(account_id), + target_account: profile ? await renderAccount(profile) : await accountFromPubkey(reported_profile_pubkey), }; } @@ -38,11 +37,8 @@ interface RenderAdminReportOpts { async function renderAdminReport(reportEvent: DittoEvent, opts: RenderAdminReportOpts) { const { viewerPubkey } = opts; - const { - comment, - forward, - category, - } = JSON.parse(reportEvent.content); + // The category is present in both the 'e' and 'p' tag, however, it is possible to report a user without reporting a note, so it's better to get the category from the 'p' tag + const category = reportEvent.tags.find(([name]) => name === 'p')?.[2] as string; const statuses = []; if (reportEvent.reported_notes) { @@ -56,8 +52,8 @@ async function renderAdminReport(reportEvent: DittoEvent, opts: RenderAdminRepor action_taken: false, action_taken_at: null, category, - comment, - forwarded: forward, + comment: reportEvent.content, + forwarded: false, created_at: nostrDate(reportEvent.created_at).toISOString(), account: await renderAdminAccount(reportEvent.author as DittoEvent), target_account: await renderAdminAccount(reportEvent.reported_profile as DittoEvent), From 24068d12d078356bdc9e7f914ba5701ee4e23a87 Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Mon, 6 May 2024 11:47:05 -0300 Subject: [PATCH 10/10] refactor(reports): rename variables and remove type assertion --- src/app.ts | 4 ++-- src/controllers/api/reports.ts | 4 ++-- src/views/mastodon/reports.ts | 12 ++++++------ 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/app.ts b/src/app.ts index 313ad3c..80e1288 100644 --- a/src/app.ts +++ b/src/app.ts @@ -44,7 +44,7 @@ import { } from '@/controllers/api/pleroma.ts'; import { preferencesController } from '@/controllers/api/preferences.ts'; import { relayController } from '@/controllers/nostr/relay.ts'; -import { reportsController, viewAllReportsController } from '@/controllers/api/reports.ts'; +import { adminReportsController, reportsController } from '@/controllers/api/reports.ts'; import { searchController } from '@/controllers/api/search.ts'; import { bookmarkController, @@ -208,7 +208,7 @@ app.get('/api/v1/admin/ditto/relays', requireRole('admin'), adminRelaysControlle app.put('/api/v1/admin/ditto/relays', requireRole('admin'), adminSetRelaysController); app.post('/api/v1/reports', requirePubkey, reportsController); -app.get('/api/v1/admin/reports', requirePubkey, requireRole('admin'), viewAllReportsController); +app.get('/api/v1/admin/reports', requirePubkey, requireRole('admin'), adminReportsController); // Not (yet) implemented. app.get('/api/v1/custom_emojis', emptyArrayController); diff --git a/src/controllers/api/reports.ts b/src/controllers/api/reports.ts index 3486550..1e27613 100644 --- a/src/controllers/api/reports.ts +++ b/src/controllers/api/reports.ts @@ -57,7 +57,7 @@ const reportsController: AppController = async (c) => { }; /** https://docs.joinmastodon.org/methods/admin/reports/#get */ -const viewAllReportsController: AppController = async (c) => { +const adminReportsController: AppController = async (c) => { const store = c.get('store'); const reports = await store.query([{ kinds: [1984], '#P': [Conf.pubkey] }]) .then((events) => hydrateEvents({ storage: store, events: events, signal: c.req.raw.signal })) @@ -66,4 +66,4 @@ const viewAllReportsController: AppController = async (c) => { return c.json(reports); }; -export { reportsController, viewAllReportsController }; +export { adminReportsController, reportsController }; diff --git a/src/views/mastodon/reports.ts b/src/views/mastodon/reports.ts index 7e3460d..ec0d6c7 100644 --- a/src/views/mastodon/reports.ts +++ b/src/views/mastodon/reports.ts @@ -7,11 +7,11 @@ import { renderStatus } from '@/views/mastodon/statuses.ts'; /** Expects a `reportEvent` of kind 1984 and a `profile` of kind 0 of the person being reported */ async function renderReport(reportEvent: DittoEvent, profile: DittoEvent) { // The category is present in both the 'e' and 'p' tag, however, it is possible to report a user without reporting a note, so it's better to get the category from the 'p' tag - const category = reportEvent.tags.find(([name]) => name === 'p')?.[2] as string; + const category = reportEvent.tags.find(([name]) => name === 'p')?.[2]; - const status_ids = reportEvent.tags.filter(([name]) => name === 'e').map((tag) => tag[1]) ?? []; + const statusIds = reportEvent.tags.filter(([name]) => name === 'e').map((tag) => tag[1]) ?? []; - const reported_profile_pubkey = reportEvent.tags.find(([name]) => name === 'p')?.[1] as string; + const reportedPubkey = reportEvent.tags.find(([name]) => name === 'p')?.[1]!; return { id: reportEvent.id, @@ -21,9 +21,9 @@ async function renderReport(reportEvent: DittoEvent, profile: DittoEvent) { comment: reportEvent.content, forwarded: false, created_at: nostrDate(reportEvent.created_at).toISOString(), - status_ids, + status_ids: statusIds, rules_ids: null, - target_account: profile ? await renderAccount(profile) : await accountFromPubkey(reported_profile_pubkey), + target_account: profile ? await renderAccount(profile) : await accountFromPubkey(reportedPubkey), }; } @@ -38,7 +38,7 @@ async function renderAdminReport(reportEvent: DittoEvent, opts: RenderAdminRepor const { viewerPubkey } = opts; // The category is present in both the 'e' and 'p' tag, however, it is possible to report a user without reporting a note, so it's better to get the category from the 'p' tag - const category = reportEvent.tags.find(([name]) => name === 'p')?.[2] as string; + const category = reportEvent.tags.find(([name]) => name === 'p')?.[2]; const statuses = []; if (reportEvent.reported_notes) {