@atproto/api 0.12.1 → 0.12.3

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 (64) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/client/index.d.ts +17 -0
  3. package/dist/client/index.d.ts.map +1 -1
  4. package/dist/client/index.js +26 -2
  5. package/dist/client/index.js.map +1 -1
  6. package/dist/client/lexicons.d.ts +244 -0
  7. package/dist/client/lexicons.d.ts.map +1 -1
  8. package/dist/client/lexicons.js +258 -1
  9. package/dist/client/lexicons.js.map +1 -1
  10. package/dist/client/types/app/bsky/embed/record.d.ts +3 -0
  11. package/dist/client/types/app/bsky/embed/record.d.ts.map +1 -1
  12. package/dist/client/types/app/bsky/embed/record.js.map +1 -1
  13. package/dist/client/types/app/bsky/feed/defs.d.ts +38 -0
  14. package/dist/client/types/app/bsky/feed/defs.d.ts.map +1 -1
  15. package/dist/client/types/app/bsky/feed/defs.js +35 -1
  16. package/dist/client/types/app/bsky/feed/defs.js.map +1 -1
  17. package/dist/client/types/app/bsky/feed/generator.d.ts +2 -0
  18. package/dist/client/types/app/bsky/feed/generator.d.ts.map +1 -1
  19. package/dist/client/types/app/bsky/feed/generator.js.map +1 -1
  20. package/dist/client/types/app/bsky/feed/searchPosts.d.ts +18 -0
  21. package/dist/client/types/app/bsky/feed/searchPosts.d.ts.map +1 -1
  22. package/dist/client/types/app/bsky/feed/searchPosts.js.map +1 -1
  23. package/dist/client/types/app/bsky/feed/sendInteractions.d.ts +26 -0
  24. package/dist/client/types/app/bsky/feed/sendInteractions.d.ts.map +1 -0
  25. package/dist/client/types/app/bsky/feed/sendInteractions.js +14 -0
  26. package/dist/client/types/app/bsky/feed/sendInteractions.js.map +1 -0
  27. package/dist/client/types/app/bsky/unspecced/searchActorsSkeleton.d.ts +2 -0
  28. package/dist/client/types/app/bsky/unspecced/searchActorsSkeleton.d.ts.map +1 -1
  29. package/dist/client/types/app/bsky/unspecced/searchActorsSkeleton.js.map +1 -1
  30. package/dist/client/types/app/bsky/unspecced/searchPostsSkeleton.d.ts +20 -0
  31. package/dist/client/types/app/bsky/unspecced/searchPostsSkeleton.d.ts.map +1 -1
  32. package/dist/client/types/app/bsky/unspecced/searchPostsSkeleton.js.map +1 -1
  33. package/dist/client/types/com/atproto/sync/getRecord.d.ts +1 -1
  34. package/dist/client/types/com/atproto/sync/getRecord.d.ts.map +1 -1
  35. package/dist/mocker.d.ts +21 -21
  36. package/dist/mocker.d.ts.map +1 -1
  37. package/dist/moderation/mutewords.d.ts.map +1 -1
  38. package/dist/moderation/mutewords.js +9 -23
  39. package/dist/moderation/mutewords.js.map +1 -1
  40. package/dist/moderation/ui.d.ts.map +1 -1
  41. package/dist/moderation/ui.js.map +1 -1
  42. package/dist/moderation/util.js.map +1 -1
  43. package/dist/rich-text/rich-text.d.ts.map +1 -1
  44. package/dist/rich-text/rich-text.js.map +1 -1
  45. package/jest.setup.ts +1 -1
  46. package/package.json +2 -1
  47. package/scripts/code/labels.mjs +1 -1
  48. package/src/client/index.ts +27 -0
  49. package/src/client/lexicons.ts +285 -1
  50. package/src/client/types/app/bsky/embed/record.ts +3 -0
  51. package/src/client/types/app/bsky/feed/defs.ts +63 -0
  52. package/src/client/types/app/bsky/feed/generator.ts +2 -0
  53. package/src/client/types/app/bsky/feed/searchPosts.ts +18 -0
  54. package/src/client/types/app/bsky/feed/sendInteractions.ts +38 -0
  55. package/src/client/types/app/bsky/unspecced/searchActorsSkeleton.ts +2 -0
  56. package/src/client/types/app/bsky/unspecced/searchPostsSkeleton.ts +20 -0
  57. package/src/client/types/com/atproto/sync/getRecord.ts +1 -1
  58. package/src/moderation/mutewords.ts +8 -30
  59. package/src/moderation/ui.ts +1 -1
  60. package/src/moderation/util.ts +2 -2
  61. package/src/rich-text/rich-text.ts +4 -1
  62. package/tests/bsky-agent.test.ts +7 -0
  63. package/tests/moderation-mutewords.test.ts +80 -2
  64. package/tests/util/moderation-behavior.ts +1 -1
@@ -69,6 +69,8 @@ export interface FeedViewPost {
69
69
  post: PostView
70
70
  reply?: ReplyRef
71
71
  reason?: ReasonRepost | { $type: string; [k: string]: unknown }
72
+ /** Context provided by feed generator that may be passed back alongside interactions. */
73
+ feedContext?: string
72
74
  [k: string]: unknown
73
75
  }
74
76
 
@@ -219,6 +221,7 @@ export interface GeneratorView {
219
221
  descriptionFacets?: AppBskyRichtextFacet.Main[]
220
222
  avatar?: string
221
223
  likeCount?: number
224
+ acceptsInteractions?: boolean
222
225
  labels?: ComAtprotoLabelDefs.Label[]
223
226
  viewer?: GeneratorViewerState
224
227
  indexedAt: string
@@ -257,6 +260,8 @@ export function validateGeneratorViewerState(v: unknown): ValidationResult {
257
260
  export interface SkeletonFeedPost {
258
261
  post: string
259
262
  reason?: SkeletonReasonRepost | { $type: string; [k: string]: unknown }
263
+ /** Context that will be passed through to client and may be passed to feed generator back alongside interactions. */
264
+ feedContext?: string
260
265
  [k: string]: unknown
261
266
  }
262
267
 
@@ -308,3 +313,61 @@ export function isThreadgateView(v: unknown): v is ThreadgateView {
308
313
  export function validateThreadgateView(v: unknown): ValidationResult {
309
314
  return lexicons.validate('app.bsky.feed.defs#threadgateView', v)
310
315
  }
316
+
317
+ export interface Interaction {
318
+ item?: string
319
+ event?:
320
+ | 'app.bsky.feed.defs#requestLess'
321
+ | 'app.bsky.feed.defs#requestMore'
322
+ | 'app.bsky.feed.defs#clickthroughItem'
323
+ | 'app.bsky.feed.defs#clickthroughAuthor'
324
+ | 'app.bsky.feed.defs#clickthroughReposter'
325
+ | 'app.bsky.feed.defs#clickthroughEmbed'
326
+ | 'app.bsky.feed.defs#interactionSeen'
327
+ | 'app.bsky.feed.defs#interactionLike'
328
+ | 'app.bsky.feed.defs#interactionRepost'
329
+ | 'app.bsky.feed.defs#interactionReply'
330
+ | 'app.bsky.feed.defs#interactionQuote'
331
+ | 'app.bsky.feed.defs#interactionShare'
332
+ | (string & {})
333
+ /** Context on a feed item that was orginally supplied by the feed generator on getFeedSkeleton. */
334
+ feedContext?: string
335
+ [k: string]: unknown
336
+ }
337
+
338
+ export function isInteraction(v: unknown): v is Interaction {
339
+ return (
340
+ isObj(v) &&
341
+ hasProp(v, '$type') &&
342
+ v.$type === 'app.bsky.feed.defs#interaction'
343
+ )
344
+ }
345
+
346
+ export function validateInteraction(v: unknown): ValidationResult {
347
+ return lexicons.validate('app.bsky.feed.defs#interaction', v)
348
+ }
349
+
350
+ /** Request that less content like the given feed item be shown in the feed */
351
+ export const REQUESTLESS = 'app.bsky.feed.defs#requestLess'
352
+ /** Request that more content like the given feed item be shown in the feed */
353
+ export const REQUESTMORE = 'app.bsky.feed.defs#requestMore'
354
+ /** User clicked through to the feed item */
355
+ export const CLICKTHROUGHITEM = 'app.bsky.feed.defs#clickthroughItem'
356
+ /** User clicked through to the author of the feed item */
357
+ export const CLICKTHROUGHAUTHOR = 'app.bsky.feed.defs#clickthroughAuthor'
358
+ /** User clicked through to the reposter of the feed item */
359
+ export const CLICKTHROUGHREPOSTER = 'app.bsky.feed.defs#clickthroughReposter'
360
+ /** User clicked through to the embedded content of the feed item */
361
+ export const CLICKTHROUGHEMBED = 'app.bsky.feed.defs#clickthroughEmbed'
362
+ /** Feed item was seen by user */
363
+ export const INTERACTIONSEEN = 'app.bsky.feed.defs#interactionSeen'
364
+ /** User liked the feed item */
365
+ export const INTERACTIONLIKE = 'app.bsky.feed.defs#interactionLike'
366
+ /** User reposted the feed item */
367
+ export const INTERACTIONREPOST = 'app.bsky.feed.defs#interactionRepost'
368
+ /** User replied to the feed item */
369
+ export const INTERACTIONREPLY = 'app.bsky.feed.defs#interactionReply'
370
+ /** User quoted the feed item */
371
+ export const INTERACTIONQUOTE = 'app.bsky.feed.defs#interactionQuote'
372
+ /** User shared the feed item */
373
+ export const INTERACTIONSHARE = 'app.bsky.feed.defs#interactionShare'
@@ -14,6 +14,8 @@ export interface Record {
14
14
  description?: string
15
15
  descriptionFacets?: AppBskyRichtextFacet.Main[]
16
16
  avatar?: BlobRef
17
+ /** Declaration that a feed accepts feedback interactions from a client through app.bsky.feed.sendInteractions */
18
+ acceptsInteractions?: boolean
17
19
  labels?:
18
20
  | ComAtprotoLabelDefs.SelfLabels
19
21
  | { $type: string; [k: string]: unknown }
@@ -11,6 +11,24 @@ import * as AppBskyFeedDefs from './defs'
11
11
  export interface QueryParams {
12
12
  /** Search query string; syntax, phrase, boolean, and faceting is unspecified, but Lucene query syntax is recommended. */
13
13
  q: string
14
+ /** Specifies the ranking order of results. */
15
+ sort?: 'top' | 'latest' | (string & {})
16
+ /** Filter results for posts after the indicated datetime (inclusive). Expected to use 'sortAt' timestamp, which may not match 'createdAt'. Can be a datetime, or just an ISO date (YYYY-MM-DD). */
17
+ since?: string
18
+ /** Filter results for posts before the indicated datetime (not inclusive). Expected to use 'sortAt' timestamp, which may not match 'createdAt'. Can be a datetime, or just an ISO date (YYY-MM-DD). */
19
+ until?: string
20
+ /** Filter to posts which mention the given account. Handles are resolved to DID before query-time. Only matches rich-text facet mentions. */
21
+ mentions?: string
22
+ /** Filter to posts by the given account. Handles are resolved to DID before query-time. */
23
+ author?: string
24
+ /** Filter to posts in the given language. Expected to be based on post language field, though server may override language detection. */
25
+ lang?: string
26
+ /** Filter to posts with URLs (facet links or embeds) linking to the given domain (hostname). Server may apply hostname normalization. */
27
+ domain?: string
28
+ /** Filter to posts with links (facet links or embeds) pointing to this URL. Server may apply URL normalization or fuzzy matching. */
29
+ url?: string
30
+ /** Filter to posts with the given tag (hashtag), based on rich-text facet or tag field. Do not include the hash (#) prefix. Multiple tags can be specified, with 'AND' matching. */
31
+ tag?: string[]
14
32
  limit?: number
15
33
  /** Optional pagination mechanism; may not necessarily allow scrolling through entire result set. */
16
34
  cursor?: string
@@ -0,0 +1,38 @@
1
+ /**
2
+ * GENERATED CODE - DO NOT MODIFY
3
+ */
4
+ import { Headers, XRPCError } from '@atproto/xrpc'
5
+ import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+ import { isObj, hasProp } from '../../../../util'
7
+ import { lexicons } from '../../../../lexicons'
8
+ import { CID } from 'multiformats/cid'
9
+ import * as AppBskyFeedDefs from './defs'
10
+
11
+ export interface QueryParams {}
12
+
13
+ export interface InputSchema {
14
+ interactions: AppBskyFeedDefs.Interaction[]
15
+ [k: string]: unknown
16
+ }
17
+
18
+ export interface OutputSchema {
19
+ [k: string]: unknown
20
+ }
21
+
22
+ export interface CallOptions {
23
+ headers?: Headers
24
+ qp?: QueryParams
25
+ encoding: 'application/json'
26
+ }
27
+
28
+ export interface Response {
29
+ success: boolean
30
+ headers: Headers
31
+ data: OutputSchema
32
+ }
33
+
34
+ export function toKnownErr(e: any) {
35
+ if (e instanceof XRPCError) {
36
+ }
37
+ return e
38
+ }
@@ -11,6 +11,8 @@ import * as AppBskyUnspeccedDefs from './defs'
11
11
  export interface QueryParams {
12
12
  /** Search query string; syntax, phrase, boolean, and faceting is unspecified, but Lucene query syntax is recommended. For typeahead search, only simple term match is supported, not full syntax. */
13
13
  q: string
14
+ /** DID of the account making the request (not included for public/unauthenticated queries). Used to boost followed accounts in ranking. */
15
+ viewer?: string
14
16
  /** If true, acts as fast/simple 'typeahead' query. */
15
17
  typeahead?: boolean
16
18
  limit?: number
@@ -11,6 +11,26 @@ import * as AppBskyUnspeccedDefs from './defs'
11
11
  export interface QueryParams {
12
12
  /** Search query string; syntax, phrase, boolean, and faceting is unspecified, but Lucene query syntax is recommended. */
13
13
  q: string
14
+ /** Specifies the ranking order of results. */
15
+ sort?: 'top' | 'latest' | (string & {})
16
+ /** Filter results for posts after the indicated datetime (inclusive). Expected to use 'sortAt' timestamp, which may not match 'createdAt'. Can be a datetime, or just an ISO date (YYYY-MM-DD). */
17
+ since?: string
18
+ /** Filter results for posts before the indicated datetime (not inclusive). Expected to use 'sortAt' timestamp, which may not match 'createdAt'. Can be a datetime, or just an ISO date (YYY-MM-DD). */
19
+ until?: string
20
+ /** Filter to posts which mention the given account. Handles are resolved to DID before query-time. Only matches rich-text facet mentions. */
21
+ mentions?: string
22
+ /** Filter to posts by the given account. Handles are resolved to DID before query-time. */
23
+ author?: string
24
+ /** Filter to posts in the given language. Expected to be based on post language field, though server may override language detection. */
25
+ lang?: string
26
+ /** Filter to posts with URLs (facet links or embeds) linking to the given domain (hostname). Server may apply hostname normalization. */
27
+ domain?: string
28
+ /** Filter to posts with links (facet links or embeds) pointing to this URL. Server may apply URL normalization or fuzzy matching. */
29
+ url?: string
30
+ /** Filter to posts with the given tag (hashtag), based on rich-text facet or tag field. Do not include the hash (#) prefix. Multiple tags can be specified, with 'AND' matching. */
31
+ tag?: string[]
32
+ /** DID of the account making the request (not included for public/unauthenticated queries). Used for 'from:me' queries. */
33
+ viewer?: string
14
34
  limit?: number
15
35
  /** Optional pagination mechanism; may not necessarily allow scrolling through entire result set. */
16
36
  cursor?: string
@@ -13,7 +13,7 @@ export interface QueryParams {
13
13
  collection: string
14
14
  /** Record Key */
15
15
  rkey: string
16
- /** An optional past commit CID. */
16
+ /** DEPRECATED: referenced a repo commit by CID, and retrieved record as of that commit */
17
17
  commit?: string
18
18
  }
19
19
 
@@ -82,38 +82,16 @@ export function hasMutedWord({
82
82
  if (mutedWord === wordTrimmedPunctuation) return true
83
83
  if (mutedWord.length > wordTrimmedPunctuation.length) continue
84
84
 
85
- // handle hyphenated, slash separated words, etc
86
- if (REGEX.SEPARATORS.test(wordTrimmedPunctuation)) {
87
- // check against full normalized phrase
88
- const wordNormalizedSeparators = wordTrimmedPunctuation.replace(
89
- REGEX.SEPARATORS,
90
- ' ',
91
- )
92
- const mutedWordNormalizedSeparators = mutedWord.replace(
93
- REGEX.SEPARATORS,
94
- ' ',
95
- )
96
- // hyphenated (or other sep) to spaced words
97
- if (wordNormalizedSeparators === mutedWordNormalizedSeparators)
98
- return true
85
+ if (/\p{P}+/u.test(wordTrimmedPunctuation)) {
86
+ const spacedWord = wordTrimmedPunctuation.replace(/\p{P}+/gu, ' ')
87
+ if (spacedWord === mutedWord) return true
99
88
 
100
- /* Disabled for now e.g. `super-cool` to `supercool`
101
- const wordNormalizedCompressed = wordNormalizedSeparators.replace(
102
- REGEX.WORD_BOUNDARY,
103
- '',
104
- )
105
- const mutedWordNormalizedCompressed =
106
- mutedWordNormalizedSeparators.replace(/\s+?/g, '')
107
- // hyphenated (or other sep) to non-hyphenated contiguous word
108
- if (mutedWordNormalizedCompressed === wordNormalizedCompressed)
109
- return true
110
- */
89
+ const contiguousWord = spacedWord.replace(/\s/gu, '')
90
+ if (contiguousWord === mutedWord) return true
111
91
 
112
- // then individual parts of separated phrases/words
113
- const wordParts = wordTrimmedPunctuation.split(REGEX.SEPARATORS)
114
- for (const wp of wordParts) {
115
- // still retain internal punctuation
116
- if (wp === mutedWord) return true
92
+ const wordParts = wordTrimmedPunctuation.split(/\p{P}+/u)
93
+ for (const wordPart of wordParts) {
94
+ if (wordPart === mutedWord) return true
117
95
  }
118
96
  }
119
97
  }
@@ -1,7 +1,7 @@
1
1
  import { ModerationCause } from './types'
2
2
 
3
3
  export class ModerationUI {
4
- noOverride: boolean = false
4
+ noOverride = false
5
5
  filters: ModerationCause[] = []
6
6
  blurs: ModerationCause[] = []
7
7
  alerts: ModerationCause[] = []
@@ -38,8 +38,8 @@ export function interpretLabelValueDefinition(
38
38
  def.severity === 'alert'
39
39
  ? 'alert'
40
40
  : def.severity === 'inform'
41
- ? 'inform'
42
- : undefined
41
+ ? 'inform'
42
+ : undefined
43
43
  if (def.blurs === 'content') {
44
44
  // target=account, blurs=content
45
45
  behaviors.account.profileList = alertOrInform
@@ -117,7 +117,10 @@ export interface RichTextOpts {
117
117
  }
118
118
 
119
119
  export class RichTextSegment {
120
- constructor(public text: string, public facet?: Facet) {}
120
+ constructor(
121
+ public text: string,
122
+ public facet?: Facet,
123
+ ) {}
121
124
 
122
125
  get link(): FacetLink | undefined {
123
126
  const link = this.facet?.features.find(AppBskyRichtextFacet.isLink)
@@ -1582,6 +1582,13 @@ describe('agent', () => {
1582
1582
  expect(end.mutedWords.find((m) => m.value === '##️⃣')).toBeFalsy()
1583
1583
  })
1584
1584
 
1585
+ it(`apostrophe: Bluesky's`, async () => {
1586
+ await agent.upsertMutedWords([{ value: `Bluesky's`, targets: [] }])
1587
+ const { mutedWords } = (await agent.getPreferences()).moderationPrefs
1588
+
1589
+ expect(mutedWords.find((m) => m.value === `Bluesky's`)).toBeTruthy()
1590
+ })
1591
+
1585
1592
  describe(`invalid characters`, () => {
1586
1593
  it('zero width space', async () => {
1587
1594
  const prev = (await agent.getPreferences()).moderationPrefs
@@ -89,6 +89,22 @@ describe(`hasMutedWord`, () => {
89
89
  expect(match).toBe(true)
90
90
  })
91
91
 
92
+ it(`match: single char with length > 1 ☠︎`, () => {
93
+ const rt = new RichText({
94
+ text: `Idk why ☠︎ but maybe`,
95
+ })
96
+ rt.detectFacetsWithoutResolution()
97
+
98
+ const match = hasMutedWord({
99
+ mutedWords: [{ value: '☠︎', targets: ['content'] }],
100
+ text: rt.text,
101
+ facets: rt.facets,
102
+ outlineTags: [],
103
+ })
104
+
105
+ expect(match).toBe(true)
106
+ })
107
+
92
108
  it(`no match: long muted word, short post`, () => {
93
109
  const rt = new RichText({
94
110
  text: `hey`,
@@ -248,6 +264,57 @@ describe(`hasMutedWord`, () => {
248
264
  })
249
265
  })
250
266
 
267
+ describe(`apostrophes: Bluesky's`, () => {
268
+ const rt = new RichText({
269
+ text: `Yay, Bluesky's mutewords work`,
270
+ })
271
+ rt.detectFacetsWithoutResolution()
272
+
273
+ it(`match: Bluesky's`, () => {
274
+ const match = hasMutedWord({
275
+ mutedWords: [{ value: `Bluesky's`, targets: ['content'] }],
276
+ text: rt.text,
277
+ facets: rt.facets,
278
+ outlineTags: [],
279
+ })
280
+
281
+ expect(match).toBe(true)
282
+ })
283
+
284
+ it(`match: Bluesky`, () => {
285
+ const match = hasMutedWord({
286
+ mutedWords: [{ value: 'Bluesky', targets: ['content'] }],
287
+ text: rt.text,
288
+ facets: rt.facets,
289
+ outlineTags: [],
290
+ })
291
+
292
+ expect(match).toBe(true)
293
+ })
294
+
295
+ it(`match: bluesky`, () => {
296
+ const match = hasMutedWord({
297
+ mutedWords: [{ value: 'bluesky', targets: ['content'] }],
298
+ text: rt.text,
299
+ facets: rt.facets,
300
+ outlineTags: [],
301
+ })
302
+
303
+ expect(match).toBe(true)
304
+ })
305
+
306
+ it(`match: blueskys`, () => {
307
+ const match = hasMutedWord({
308
+ mutedWords: [{ value: 'blueskys', targets: ['content'] }],
309
+ text: rt.text,
310
+ facets: rt.facets,
311
+ outlineTags: [],
312
+ })
313
+
314
+ expect(match).toBe(true)
315
+ })
316
+ })
317
+
251
318
  describe(`Why so S@assy?`, () => {
252
319
  const rt = new RichText({
253
320
  text: `Why so S@assy?`,
@@ -398,6 +465,17 @@ describe(`hasMutedWord`, () => {
398
465
  expect(match).toBe(true)
399
466
  })
400
467
 
468
+ it(`match: bad`, () => {
469
+ const match = hasMutedWord({
470
+ mutedWords: [{ value: `bad`, targets: ['content'] }],
471
+ text: rt.text,
472
+ facets: rt.facets,
473
+ outlineTags: [],
474
+ })
475
+
476
+ expect(match).toBe(true)
477
+ })
478
+
401
479
  it(`match: super bad`, () => {
402
480
  const match = hasMutedWord({
403
481
  mutedWords: [{ value: `super bad`, targets: ['content'] }],
@@ -417,7 +495,7 @@ describe(`hasMutedWord`, () => {
417
495
  outlineTags: [],
418
496
  })
419
497
 
420
- expect(match).toBe(false)
498
+ expect(match).toBe(true)
421
499
  })
422
500
  })
423
501
 
@@ -474,7 +552,7 @@ describe(`hasMutedWord`, () => {
474
552
  outlineTags: [],
475
553
  })
476
554
 
477
- expect(match).toBe(false)
555
+ expect(match).toBe(true)
478
556
  })
479
557
  })
480
558
 
@@ -63,7 +63,7 @@ expect.extend({
63
63
  toBeModerationResult(
64
64
  actual: ModerationUI,
65
65
  expected: ModerationTestSuiteResultFlag[] | undefined,
66
- context: string = '',
66
+ context = '',
67
67
  stringifiedResult: string | undefined = undefined,
68
68
  _ignoreCause = false,
69
69
  ) {