@atproto/ozone 0.1.91 → 0.1.93

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/api/index.d.ts.map +1 -1
  3. package/dist/api/index.js +2 -0
  4. package/dist/api/index.js.map +1 -1
  5. package/dist/api/moderation/getSubjects.d.ts +4 -0
  6. package/dist/api/moderation/getSubjects.d.ts.map +1 -0
  7. package/dist/api/moderation/getSubjects.js +81 -0
  8. package/dist/api/moderation/getSubjects.js.map +1 -0
  9. package/dist/api/util.js +1 -1
  10. package/dist/api/util.js.map +1 -1
  11. package/dist/lexicon/index.d.ts +2 -0
  12. package/dist/lexicon/index.d.ts.map +1 -1
  13. package/dist/lexicon/index.js +4 -0
  14. package/dist/lexicon/index.js.map +1 -1
  15. package/dist/lexicon/lexicons.d.ts +144 -10
  16. package/dist/lexicon/lexicons.d.ts.map +1 -1
  17. package/dist/lexicon/lexicons.js +73 -5
  18. package/dist/lexicon/lexicons.js.map +1 -1
  19. package/dist/lexicon/types/chat/bsky/convo/defs.d.ts +1 -1
  20. package/dist/lexicon/types/chat/bsky/convo/defs.d.ts.map +1 -1
  21. package/dist/lexicon/types/chat/bsky/convo/defs.js.map +1 -1
  22. package/dist/lexicon/types/tools/ozone/moderation/defs.d.ts +14 -0
  23. package/dist/lexicon/types/tools/ozone/moderation/defs.d.ts.map +1 -1
  24. package/dist/lexicon/types/tools/ozone/moderation/defs.js +9 -0
  25. package/dist/lexicon/types/tools/ozone/moderation/defs.js.map +1 -1
  26. package/dist/lexicon/types/tools/ozone/moderation/getSubjects.d.ts +36 -0
  27. package/dist/lexicon/types/tools/ozone/moderation/getSubjects.d.ts.map +1 -0
  28. package/dist/lexicon/types/tools/ozone/moderation/getSubjects.js +7 -0
  29. package/dist/lexicon/types/tools/ozone/moderation/getSubjects.js.map +1 -0
  30. package/dist/mod-service/views.d.ts +2 -1
  31. package/dist/mod-service/views.d.ts.map +1 -1
  32. package/dist/mod-service/views.js +23 -4
  33. package/dist/mod-service/views.js.map +1 -1
  34. package/package.json +3 -3
  35. package/src/api/index.ts +2 -0
  36. package/src/api/moderation/getSubjects.ts +101 -0
  37. package/src/api/util.ts +1 -1
  38. package/src/lexicon/index.ts +12 -0
  39. package/src/lexicon/lexicons.ts +75 -5
  40. package/src/lexicon/types/chat/bsky/convo/defs.ts +1 -1
  41. package/src/lexicon/types/tools/ozone/moderation/defs.ts +21 -0
  42. package/src/lexicon/types/tools/ozone/moderation/getSubjects.ts +54 -0
  43. package/src/mod-service/views.ts +31 -7
  44. package/tests/__snapshots__/get-subjects.test.ts.snap +517 -0
  45. package/tests/get-subjects.test.ts +81 -0
  46. package/tsconfig.build.tsbuildinfo +1 -1
  47. package/tsconfig.tests.tsbuildinfo +1 -1
@@ -0,0 +1,101 @@
1
+ import { ToolsOzoneModerationDefs } from '@atproto/api'
2
+ import { AtUri } from '@atproto/syntax'
3
+ import { AppContext } from '../../context'
4
+ import { Server } from '../../lexicon'
5
+ import { SubjectView } from '../../lexicon/types/tools/ozone/moderation/defs'
6
+ import { addAccountInfoToRepoViewDetail, getPdsAccountInfos } from '../util'
7
+
8
+ export default function (server: Server, ctx: AppContext) {
9
+ server.tools.ozone.moderation.getSubjects({
10
+ auth: ctx.authVerifier.modOrAdminToken,
11
+ handler: async ({ params, auth, req }) => {
12
+ const { subjects } = params
13
+ const db = ctx.db
14
+ const labelers = ctx.reqLabelers(req)
15
+ const uris = new Set<string>()
16
+ const dids = new Set<string>()
17
+
18
+ for (const subject of subjects) {
19
+ if (subject.startsWith('did:')) {
20
+ dids.add(subject)
21
+ }
22
+ if (subject.startsWith('at://')) {
23
+ uris.add(subject)
24
+ dids.add(new AtUri(subject).host)
25
+ }
26
+ }
27
+
28
+ const didsArray = Array.from(dids)
29
+ const modViews = ctx.modService(db).views
30
+ const [partialRepos, accountInfo, recordInfo, profiles] =
31
+ await Promise.all([
32
+ modViews.repoDetails(didsArray, labelers),
33
+ getPdsAccountInfos(ctx, didsArray),
34
+ modViews.recordDetails(
35
+ Array.from(uris).map((uri) => ({ uri })),
36
+ labelers,
37
+ ),
38
+ modViews.getProfiles(didsArray),
39
+ ])
40
+
41
+ const missingSubjects: string[] = []
42
+ const subjectWithDetails = new Map<string, SubjectView>()
43
+
44
+ for (const subject of subjects) {
45
+ const type = subject.startsWith('did:') ? 'account' : 'record'
46
+ const did = type === 'account' ? subject : new AtUri(subject).host
47
+ const partialRepo = partialRepos.get(did)
48
+ const repo = partialRepo
49
+ ? addAccountInfoToRepoViewDetail(
50
+ partialRepo,
51
+ accountInfo.get(did) || null,
52
+ auth.credentials.isModerator,
53
+ )
54
+ : undefined
55
+ const profile = profiles.get(did)
56
+ const record = type === 'record' ? recordInfo.get(subject) : undefined
57
+ const status =
58
+ type === 'record'
59
+ ? record?.moderation.subjectStatus
60
+ : repo?.moderation.subjectStatus
61
+
62
+ subjectWithDetails.set(subject, {
63
+ type,
64
+ repo,
65
+ record,
66
+ profile: profile && {
67
+ $type: 'app.bsky.actor.defs#profileViewDetailed',
68
+ ...profile,
69
+ },
70
+ status,
71
+ subject,
72
+ })
73
+
74
+ if ((type === 'record' && !record) || (type === 'account' && !repo)) {
75
+ missingSubjects.push(subject)
76
+ }
77
+ }
78
+
79
+ // When a subject is repo or record but the repo/record was deleted, we still want to attach moderation status if any exists
80
+ const missingSubjectStatuses =
81
+ await modViews.getSubjectStatus(missingSubjects)
82
+
83
+ for (const [subject, status] of missingSubjectStatuses) {
84
+ const subjectView = subjectWithDetails.get(subject)
85
+ if (subjectView)
86
+ subjectView.status = modViews.formatSubjectStatus(status)
87
+ }
88
+
89
+ const allSubjects: ToolsOzoneModerationDefs.SubjectView[] = []
90
+ for (const subject of subjects) {
91
+ const subjectView = subjectWithDetails.get(subject)
92
+ if (subjectView) allSubjects.push(subjectView)
93
+ }
94
+
95
+ return {
96
+ encoding: 'application/json',
97
+ body: { subjects: allSubjects },
98
+ }
99
+ },
100
+ })
101
+ }
package/src/api/util.ts CHANGED
@@ -36,7 +36,7 @@ export const getPdsAccountInfos = async (
36
36
  const results = new Map<string, AccountView | null>()
37
37
 
38
38
  const agent = ctx.pdsAgent
39
- if (!agent) return results
39
+ if (!agent || !dids.length) return results
40
40
 
41
41
  const auth = await ctx.pdsAuth(ids.ComAtprotoAdminGetAccountInfos)
42
42
  if (!auth) return results
@@ -184,6 +184,7 @@ import * as ToolsOzoneModerationGetRecords from './types/tools/ozone/moderation/
184
184
  import * as ToolsOzoneModerationGetRepo from './types/tools/ozone/moderation/getRepo.js'
185
185
  import * as ToolsOzoneModerationGetReporterStats from './types/tools/ozone/moderation/getReporterStats.js'
186
186
  import * as ToolsOzoneModerationGetRepos from './types/tools/ozone/moderation/getRepos.js'
187
+ import * as ToolsOzoneModerationGetSubjects from './types/tools/ozone/moderation/getSubjects.js'
187
188
  import * as ToolsOzoneModerationQueryEvents from './types/tools/ozone/moderation/queryEvents.js'
188
189
  import * as ToolsOzoneModerationQueryStatuses from './types/tools/ozone/moderation/queryStatuses.js'
189
190
  import * as ToolsOzoneModerationSearchRepos from './types/tools/ozone/moderation/searchRepos.js'
@@ -2507,6 +2508,17 @@ export class ToolsOzoneModerationNS {
2507
2508
  return this._server.xrpc.method(nsid, cfg)
2508
2509
  }
2509
2510
 
2511
+ getSubjects<AV extends AuthVerifier>(
2512
+ cfg: ConfigOf<
2513
+ AV,
2514
+ ToolsOzoneModerationGetSubjects.Handler<ExtractAuth<AV>>,
2515
+ ToolsOzoneModerationGetSubjects.HandlerReqCtx<ExtractAuth<AV>>
2516
+ >,
2517
+ ) {
2518
+ const nsid = 'tools.ozone.moderation.getSubjects' // @ts-ignore
2519
+ return this._server.xrpc.method(nsid, cfg)
2520
+ }
2521
+
2510
2522
  queryEvents<AV extends AuthVerifier>(
2511
2523
  cfg: ConfigOf<
2512
2524
  AV,
@@ -10593,6 +10593,8 @@ export const schemaDict = {
10593
10593
  },
10594
10594
  reactions: {
10595
10595
  type: 'array',
10596
+ description:
10597
+ 'Reactions to this message, in ascending order of creation time.',
10596
10598
  items: {
10597
10599
  type: 'ref',
10598
10600
  ref: 'lex:chat.bsky.convo.defs#reactionView',
@@ -10640,7 +10642,7 @@ export const schemaDict = {
10640
10642
  },
10641
10643
  reactionView: {
10642
10644
  type: 'object',
10643
- required: ['value', 'sender', 'createdAt'],
10645
+ required: ['value', 'sender'],
10644
10646
  properties: {
10645
10647
  value: {
10646
10648
  type: 'string',
@@ -10649,10 +10651,6 @@ export const schemaDict = {
10649
10651
  type: 'ref',
10650
10652
  ref: 'lex:chat.bsky.convo.defs#reactionViewSender',
10651
10653
  },
10652
- createdAt: {
10653
- type: 'string',
10654
- format: 'datetime',
10655
- },
10656
10654
  },
10657
10655
  },
10658
10656
  reactionViewSender: {
@@ -12103,6 +12101,37 @@ export const schemaDict = {
12103
12101
  },
12104
12102
  },
12105
12103
  },
12104
+ subjectView: {
12105
+ description:
12106
+ "Detailed view of a subject. For record subjects, the author's repo and profile will be returned.",
12107
+ type: 'object',
12108
+ required: ['type', 'subject'],
12109
+ properties: {
12110
+ type: {
12111
+ type: 'ref',
12112
+ ref: 'lex:com.atproto.moderation.defs#subjectType',
12113
+ },
12114
+ subject: {
12115
+ type: 'string',
12116
+ },
12117
+ status: {
12118
+ type: 'ref',
12119
+ ref: 'lex:tools.ozone.moderation.defs#subjectStatusView',
12120
+ },
12121
+ repo: {
12122
+ type: 'ref',
12123
+ ref: 'lex:tools.ozone.moderation.defs#repoViewDetail',
12124
+ },
12125
+ profile: {
12126
+ type: 'union',
12127
+ refs: [],
12128
+ },
12129
+ record: {
12130
+ type: 'ref',
12131
+ ref: 'lex:tools.ozone.moderation.defs#recordViewDetail',
12132
+ },
12133
+ },
12134
+ },
12106
12135
  accountStats: {
12107
12136
  description: 'Statistics about a particular account subject',
12108
12137
  type: 'object',
@@ -13265,6 +13294,46 @@ export const schemaDict = {
13265
13294
  },
13266
13295
  },
13267
13296
  },
13297
+ ToolsOzoneModerationGetSubjects: {
13298
+ lexicon: 1,
13299
+ id: 'tools.ozone.moderation.getSubjects',
13300
+ defs: {
13301
+ main: {
13302
+ type: 'query',
13303
+ description: 'Get details about subjects.',
13304
+ parameters: {
13305
+ type: 'params',
13306
+ required: ['subjects'],
13307
+ properties: {
13308
+ subjects: {
13309
+ type: 'array',
13310
+ maxLength: 100,
13311
+ minLength: 1,
13312
+ items: {
13313
+ type: 'string',
13314
+ },
13315
+ },
13316
+ },
13317
+ },
13318
+ output: {
13319
+ encoding: 'application/json',
13320
+ schema: {
13321
+ type: 'object',
13322
+ required: ['subjects'],
13323
+ properties: {
13324
+ subjects: {
13325
+ type: 'array',
13326
+ items: {
13327
+ type: 'ref',
13328
+ ref: 'lex:tools.ozone.moderation.defs#subjectView',
13329
+ },
13330
+ },
13331
+ },
13332
+ },
13333
+ },
13334
+ },
13335
+ },
13336
+ },
13268
13337
  ToolsOzoneModerationQueryEvents: {
13269
13338
  lexicon: 1,
13270
13339
  id: 'tools.ozone.moderation.queryEvents',
@@ -14949,6 +15018,7 @@ export const ids = {
14949
15018
  ToolsOzoneModerationGetReporterStats:
14950
15019
  'tools.ozone.moderation.getReporterStats',
14951
15020
  ToolsOzoneModerationGetRepos: 'tools.ozone.moderation.getRepos',
15021
+ ToolsOzoneModerationGetSubjects: 'tools.ozone.moderation.getSubjects',
14952
15022
  ToolsOzoneModerationQueryEvents: 'tools.ozone.moderation.queryEvents',
14953
15023
  ToolsOzoneModerationQueryStatuses: 'tools.ozone.moderation.queryStatuses',
14954
15024
  ToolsOzoneModerationSearchRepos: 'tools.ozone.moderation.searchRepos',
@@ -60,6 +60,7 @@ export interface MessageView {
60
60
  /** Annotations of text (mentions, URLs, hashtags, etc) */
61
61
  facets?: AppBskyRichtextFacet.Main[]
62
62
  embed?: $Typed<AppBskyEmbedRecord.View> | { $type: string }
63
+ /** Reactions to this message, in ascending order of creation time. */
63
64
  reactions?: ReactionView[]
64
65
  sender: MessageViewSender
65
66
  sentAt: string
@@ -112,7 +113,6 @@ export interface ReactionView {
112
113
  $type?: 'chat.bsky.convo.defs#reactionView'
113
114
  value: string
114
115
  sender: ReactionViewSender
115
- createdAt: string
116
116
  }
117
117
 
118
118
  const hashReactionView = 'reactionView'
@@ -156,6 +156,27 @@ export function validateSubjectStatusView<V>(v: V) {
156
156
  return validate<SubjectStatusView & V>(v, id, hashSubjectStatusView)
157
157
  }
158
158
 
159
+ /** Detailed view of a subject. For record subjects, the author's repo and profile will be returned. */
160
+ export interface SubjectView {
161
+ $type?: 'tools.ozone.moderation.defs#subjectView'
162
+ type: ComAtprotoModerationDefs.SubjectType
163
+ subject: string
164
+ status?: SubjectStatusView
165
+ repo?: RepoViewDetail
166
+ profile?: { $type: string }
167
+ record?: RecordViewDetail
168
+ }
169
+
170
+ const hashSubjectView = 'subjectView'
171
+
172
+ export function isSubjectView<V>(v: V) {
173
+ return is$typed(v, id, hashSubjectView)
174
+ }
175
+
176
+ export function validateSubjectView<V>(v: V) {
177
+ return validate<SubjectView & V>(v, id, hashSubjectView)
178
+ }
179
+
159
180
  /** Statistics about a particular account subject */
160
181
  export interface AccountStats {
161
182
  $type?: 'tools.ozone.moderation.defs#accountStats'
@@ -0,0 +1,54 @@
1
+ /**
2
+ * GENERATED CODE - DO NOT MODIFY
3
+ */
4
+ import express from 'express'
5
+ import { type ValidationResult, BlobRef } from '@atproto/lexicon'
6
+ import { CID } from 'multiformats/cid'
7
+ import { validate as _validate } from '../../../../lexicons'
8
+ import {
9
+ type $Typed,
10
+ is$typed as _is$typed,
11
+ type OmitKey,
12
+ } from '../../../../util'
13
+ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
14
+ import type * as ToolsOzoneModerationDefs from './defs.js'
15
+
16
+ const is$typed = _is$typed,
17
+ validate = _validate
18
+ const id = 'tools.ozone.moderation.getSubjects'
19
+
20
+ export interface QueryParams {
21
+ subjects: string[]
22
+ }
23
+
24
+ export type InputSchema = undefined
25
+
26
+ export interface OutputSchema {
27
+ subjects: ToolsOzoneModerationDefs.SubjectView[]
28
+ }
29
+
30
+ export type HandlerInput = undefined
31
+
32
+ export interface HandlerSuccess {
33
+ encoding: 'application/json'
34
+ body: OutputSchema
35
+ headers?: { [key: string]: string }
36
+ }
37
+
38
+ export interface HandlerError {
39
+ status: number
40
+ message?: string
41
+ }
42
+
43
+ export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
44
+ export type HandlerReqCtx<HA extends HandlerAuth = never> = {
45
+ auth: HA
46
+ params: QueryParams
47
+ input: HandlerInput
48
+ req: express.Request
49
+ res: express.Response
50
+ resetRouteRateLimits: () => Promise<void>
51
+ }
52
+ export type Handler<HA extends HandlerAuth = never> = (
53
+ ctx: HandlerReqCtx<HA>,
54
+ ) => Promise<HandlerOutput> | HandlerOutput
@@ -1,6 +1,6 @@
1
1
  import { sql } from 'kysely'
2
- import { AtpAgent } from '@atproto/api'
3
- import { dedupeStrs } from '@atproto/common'
2
+ import { AppBskyActorDefs, AtpAgent } from '@atproto/api'
3
+ import { chunkArray, dedupeStrs } from '@atproto/common'
4
4
  import { Keypair } from '@atproto/crypto'
5
5
  import { BlobRef } from '@atproto/lexicon'
6
6
  import { AtUri, INVALID_HANDLE, normalizeDatetimeAlways } from '@atproto/syntax'
@@ -79,7 +79,7 @@ export class ModerationViews {
79
79
  const auth = await this.appviewAuth(ids.ComAtprotoAdminGetAccountInfos)
80
80
  if (!auth) return new Map()
81
81
  try {
82
- const res = await this.appviewAgent.api.com.atproto.admin.getAccountInfos(
82
+ const res = await this.appviewAgent.com.atproto.admin.getAccountInfos(
83
83
  {
84
84
  dids: dedupeStrs(dids),
85
85
  },
@@ -266,6 +266,10 @@ export class ModerationViews {
266
266
  labelers?: ParsedLabelers,
267
267
  ): Promise<Map<string, RepoView>> {
268
268
  const results = new Map<string, RepoView>()
269
+ if (!dids.length) {
270
+ return results
271
+ }
272
+
269
273
  const [repos, localLabels, externalLabels] = await Promise.all([
270
274
  this.repos(dids),
271
275
  this.labels(dids),
@@ -373,6 +377,11 @@ export class ModerationViews {
373
377
  subjects: RecordSubject[],
374
378
  labelers?: ParsedLabelers,
375
379
  ): Promise<Map<string, RecordViewDetail>> {
380
+ const results = new Map<string, RecordViewDetail>()
381
+ if (!subjects.length) {
382
+ return results
383
+ }
384
+
376
385
  const subjectUris = subjects.map((s) => s.uri)
377
386
  const [records, subjectStatusesResult, localLabels, externalLabels] =
378
387
  await Promise.all([
@@ -382,8 +391,6 @@ export class ModerationViews {
382
391
  this.getExternalLabels(subjectUris, labelers),
383
392
  ])
384
393
 
385
- const results = new Map<string, RecordViewDetail>()
386
-
387
394
  await Promise.all(
388
395
  Array.from(records.entries()).map(async ([uri, record]) => {
389
396
  const selfLabels = getSelfLabels({
@@ -425,7 +432,7 @@ export class ModerationViews {
425
432
  try {
426
433
  const {
427
434
  data: { labels },
428
- } = await this.appviewAgent.api.com.atproto.label.queryLabels({
435
+ } = await this.appviewAgent.com.atproto.label.queryLabels({
429
436
  uriPatterns: subjects,
430
437
  sources: labelers.dids,
431
438
  })
@@ -695,10 +702,27 @@ export class ModerationViews {
695
702
  if (!auth) return []
696
703
  const {
697
704
  data: { feed },
698
- } = await this.appviewAgent.api.app.bsky.feed.getAuthorFeed({ actor }, auth)
705
+ } = await this.appviewAgent.app.bsky.feed.getAuthorFeed({ actor }, auth)
699
706
 
700
707
  return feed
701
708
  }
709
+
710
+ async getProfiles(dids: string[]) {
711
+ const profiles = new Map<string, AppBskyActorDefs.ProfileViewDetailed>()
712
+
713
+ const auth = await this.appviewAuth(ids.AppBskyActorGetProfiles)
714
+ if (!auth) return profiles
715
+
716
+ for (const actors of chunkArray(dids, 25)) {
717
+ const { data } = await this.appviewAgent.getProfiles({ actors }, auth)
718
+
719
+ data.profiles.forEach((profile) => {
720
+ profiles.set(profile.did, profile)
721
+ })
722
+ }
723
+
724
+ return profiles
725
+ }
702
726
  }
703
727
 
704
728
  type RecordSubject = { uri: string; cid?: string }