@atproto/api 0.12.5 → 0.12.7

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 (40) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/bsky-agent.d.ts +19 -0
  3. package/dist/bsky-agent.d.ts.map +1 -1
  4. package/dist/bsky-agent.js +179 -0
  5. package/dist/bsky-agent.js.map +1 -1
  6. package/dist/client/lexicons.d.ts +68 -0
  7. package/dist/client/lexicons.d.ts.map +1 -1
  8. package/dist/client/lexicons.js +78 -0
  9. package/dist/client/lexicons.js.map +1 -1
  10. package/dist/client/types/app/bsky/actor/defs.d.ts +16 -1
  11. package/dist/client/types/app/bsky/actor/defs.d.ts.map +1 -1
  12. package/dist/client/types/app/bsky/actor/defs.js +21 -1
  13. package/dist/client/types/app/bsky/actor/defs.js.map +1 -1
  14. package/dist/client/types/tools/ozone/moderation/defs.d.ts +22 -2
  15. package/dist/client/types/tools/ozone/moderation/defs.d.ts.map +1 -1
  16. package/dist/client/types/tools/ozone/moderation/defs.js +22 -2
  17. package/dist/client/types/tools/ozone/moderation/defs.js.map +1 -1
  18. package/dist/client/types/tools/ozone/moderation/emitEvent.d.ts +1 -1
  19. package/dist/client/types/tools/ozone/moderation/emitEvent.d.ts.map +1 -1
  20. package/dist/client/types/tools/ozone/moderation/emitEvent.js.map +1 -1
  21. package/dist/client/types/tools/ozone/moderation/queryStatuses.d.ts +2 -0
  22. package/dist/client/types/tools/ozone/moderation/queryStatuses.d.ts.map +1 -1
  23. package/dist/client/types/tools/ozone/moderation/queryStatuses.js.map +1 -1
  24. package/dist/types.d.ts +5 -0
  25. package/dist/types.d.ts.map +1 -1
  26. package/dist/util.d.ts +13 -0
  27. package/dist/util.d.ts.map +1 -1
  28. package/dist/util.js +55 -1
  29. package/dist/util.js.map +1 -1
  30. package/package.json +1 -1
  31. package/src/bsky-agent.ts +222 -1
  32. package/src/client/lexicons.ts +80 -0
  33. package/src/client/types/app/bsky/actor/defs.ts +38 -0
  34. package/src/client/types/tools/ozone/moderation/defs.ts +56 -0
  35. package/src/client/types/tools/ozone/moderation/emitEvent.ts +3 -0
  36. package/src/client/types/tools/ozone/moderation/queryStatuses.ts +2 -0
  37. package/src/types.ts +4 -0
  38. package/src/util.ts +72 -0
  39. package/tests/bsky-agent.test.ts +1021 -2
  40. package/tests/moderation-prefs.test.ts +4 -0
package/src/bsky-agent.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { AtUri, ensureValidDid } from '@atproto/syntax'
2
+ import { TID } from '@atproto/common-web'
2
3
  import { AtpAgent } from './agent'
3
4
  import {
4
5
  AppBskyFeedPost,
@@ -19,7 +20,12 @@ import {
19
20
  ModerationPrefs,
20
21
  } from './moderation/types'
21
22
  import { DEFAULT_LABEL_SETTINGS } from './moderation/const/labels'
22
- import { sanitizeMutedWordValue } from './util'
23
+ import {
24
+ sanitizeMutedWordValue,
25
+ validateSavedFeed,
26
+ savedFeedsToUriArrays,
27
+ getSavedFeedType,
28
+ } from './util'
23
29
  import { interpretLabelValueDefinitions } from './moderation'
24
30
 
25
31
  const FEED_VIEW_PREF_DEFAULTS = {
@@ -365,6 +371,8 @@ export class BskyAgent extends AtpAgent {
365
371
  saved: undefined,
366
372
  pinned: undefined,
367
373
  },
374
+ // @ts-ignore populating below
375
+ savedFeeds: undefined,
368
376
  feedViewPrefs: {
369
377
  home: {
370
378
  ...FEED_VIEW_PREF_DEFAULTS,
@@ -412,6 +420,11 @@ export class BskyAgent extends AtpAgent {
412
420
  labels: {},
413
421
  })),
414
422
  )
423
+ } else if (
424
+ AppBskyActorDefs.isSavedFeedsPrefV2(pref) &&
425
+ AppBskyActorDefs.validateSavedFeedsPrefV2(pref).success
426
+ ) {
427
+ prefs.savedFeeds = pref.items
415
428
  } else if (
416
429
  AppBskyActorDefs.isSavedFeedsPref(pref) &&
417
430
  AppBskyActorDefs.validateSavedFeedsPref(pref).success
@@ -467,6 +480,75 @@ export class BskyAgent extends AtpAgent {
467
480
  }
468
481
  }
469
482
 
483
+ /*
484
+ * If `prefs.savedFeeds` is undefined, no `savedFeedsPrefV2` exists, which
485
+ * means we want to try to migrate if needed.
486
+ *
487
+ * If v1 prefs exist, they will be migrated to v2.
488
+ *
489
+ * If no v1 prefs exist, the user is either new, or could be old and has
490
+ * never edited their feeds.
491
+ */
492
+ if (prefs.savedFeeds === undefined) {
493
+ const { saved, pinned } = prefs.feeds
494
+
495
+ if (saved && pinned) {
496
+ const uniqueMigratedSavedFeeds: Map<
497
+ string,
498
+ AppBskyActorDefs.SavedFeed
499
+ > = new Map()
500
+
501
+ // insert Following feed first
502
+ uniqueMigratedSavedFeeds.set('timeline', {
503
+ id: TID.nextStr(),
504
+ type: 'timeline',
505
+ value: 'following',
506
+ pinned: true,
507
+ })
508
+
509
+ // use pinned as source of truth for feed order
510
+ for (const uri of pinned) {
511
+ const type = getSavedFeedType(uri)
512
+ // only want supported types
513
+ if (type === 'unknown') continue
514
+ uniqueMigratedSavedFeeds.set(uri, {
515
+ id: TID.nextStr(),
516
+ type,
517
+ value: uri,
518
+ pinned: true,
519
+ })
520
+ }
521
+
522
+ for (const uri of saved) {
523
+ if (!uniqueMigratedSavedFeeds.has(uri)) {
524
+ const type = getSavedFeedType(uri)
525
+ // only want supported types
526
+ if (type === 'unknown') continue
527
+ uniqueMigratedSavedFeeds.set(uri, {
528
+ id: TID.nextStr(),
529
+ type,
530
+ value: uri,
531
+ pinned: false,
532
+ })
533
+ }
534
+ }
535
+
536
+ prefs.savedFeeds = Array.from(uniqueMigratedSavedFeeds.values())
537
+ } else {
538
+ prefs.savedFeeds = [
539
+ {
540
+ id: TID.nextStr(),
541
+ type: 'timeline',
542
+ value: 'following',
543
+ pinned: true,
544
+ },
545
+ ]
546
+ }
547
+
548
+ // save to user preferences so this migration doesn't re-occur
549
+ await this.overwriteSavedFeeds(prefs.savedFeeds)
550
+ }
551
+
470
552
  // apply the label prefs
471
553
  for (const pref of labelPrefs) {
472
554
  if (pref.labelerDid) {
@@ -491,6 +573,63 @@ export class BskyAgent extends AtpAgent {
491
573
  return prefs
492
574
  }
493
575
 
576
+ async overwriteSavedFeeds(savedFeeds: AppBskyActorDefs.SavedFeed[]) {
577
+ savedFeeds.forEach(validateSavedFeed)
578
+ const uniqueSavedFeeds = new Map<string, AppBskyActorDefs.SavedFeed>()
579
+ savedFeeds.forEach((feed) => {
580
+ // remove and re-insert to preserve order
581
+ if (uniqueSavedFeeds.has(feed.id)) {
582
+ uniqueSavedFeeds.delete(feed.id)
583
+ }
584
+ uniqueSavedFeeds.set(feed.id, feed)
585
+ })
586
+ return updateSavedFeedsV2Preferences(this, () =>
587
+ Array.from(uniqueSavedFeeds.values()),
588
+ )
589
+ }
590
+
591
+ async updateSavedFeeds(savedFeedsToUpdate: AppBskyActorDefs.SavedFeed[]) {
592
+ savedFeedsToUpdate.map(validateSavedFeed)
593
+ return updateSavedFeedsV2Preferences(this, (savedFeeds) => {
594
+ return savedFeeds.map((savedFeed) => {
595
+ const updatedVersion = savedFeedsToUpdate.find(
596
+ (updated) => savedFeed.id === updated.id,
597
+ )
598
+ if (updatedVersion) {
599
+ return {
600
+ ...savedFeed,
601
+ // only update pinned
602
+ pinned: updatedVersion.pinned,
603
+ }
604
+ }
605
+ return savedFeed
606
+ })
607
+ })
608
+ }
609
+
610
+ async addSavedFeeds(
611
+ savedFeeds: Pick<AppBskyActorDefs.SavedFeed, 'type' | 'value' | 'pinned'>[],
612
+ ) {
613
+ const toSave: AppBskyActorDefs.SavedFeed[] = savedFeeds.map((f) => ({
614
+ ...f,
615
+ id: TID.nextStr(),
616
+ }))
617
+ toSave.forEach(validateSavedFeed)
618
+ return updateSavedFeedsV2Preferences(this, (savedFeeds) => [
619
+ ...savedFeeds,
620
+ ...toSave,
621
+ ])
622
+ }
623
+
624
+ async removeSavedFeeds(ids: string[]) {
625
+ return updateSavedFeedsV2Preferences(this, (savedFeeds) => [
626
+ ...savedFeeds.filter((feed) => !ids.find((id) => feed.id === id)),
627
+ ])
628
+ }
629
+
630
+ /**
631
+ * @deprecated use `overwriteSavedFeeds`
632
+ */
494
633
  async setSavedFeeds(saved: string[], pinned: string[]) {
495
634
  return updateFeedPreferences(this, () => ({
496
635
  saved,
@@ -498,6 +637,9 @@ export class BskyAgent extends AtpAgent {
498
637
  }))
499
638
  }
500
639
 
640
+ /**
641
+ * @deprecated use `addSavedFeeds`
642
+ */
501
643
  async addSavedFeed(v: string) {
502
644
  return updateFeedPreferences(this, (saved: string[], pinned: string[]) => ({
503
645
  saved: [...saved.filter((uri) => uri !== v), v],
@@ -505,6 +647,9 @@ export class BskyAgent extends AtpAgent {
505
647
  }))
506
648
  }
507
649
 
650
+ /**
651
+ * @deprecated use `removeSavedFeeds`
652
+ */
508
653
  async removeSavedFeed(v: string) {
509
654
  return updateFeedPreferences(this, (saved: string[], pinned: string[]) => ({
510
655
  saved: saved.filter((uri) => uri !== v),
@@ -512,6 +657,9 @@ export class BskyAgent extends AtpAgent {
512
657
  }))
513
658
  }
514
659
 
660
+ /**
661
+ * @deprecated use `addSavedFeeds` or `updateSavedFeeds`
662
+ */
515
663
  async addPinnedFeed(v: string) {
516
664
  return updateFeedPreferences(this, (saved: string[], pinned: string[]) => ({
517
665
  saved: [...saved.filter((uri) => uri !== v), v],
@@ -519,6 +667,9 @@ export class BskyAgent extends AtpAgent {
519
667
  }))
520
668
  }
521
669
 
670
+ /**
671
+ * @deprecated use `updateSavedFeeds` or `removeSavedFeeds`
672
+ */
522
673
  async removePinnedFeed(v: string) {
523
674
  return updateFeedPreferences(this, (saved: string[], pinned: string[]) => ({
524
675
  saved,
@@ -945,6 +1096,76 @@ async function updateFeedPreferences(
945
1096
  return res
946
1097
  }
947
1098
 
1099
+ async function updateSavedFeedsV2Preferences(
1100
+ agent: BskyAgent,
1101
+ cb: (
1102
+ savedFeedsPref: AppBskyActorDefs.SavedFeed[],
1103
+ ) => AppBskyActorDefs.SavedFeed[],
1104
+ ): Promise<AppBskyActorDefs.SavedFeed[]> {
1105
+ let maybeMutatedSavedFeeds: AppBskyActorDefs.SavedFeed[] = []
1106
+
1107
+ await updatePreferences(agent, (prefs: AppBskyActorDefs.Preferences) => {
1108
+ let existingV2Pref = prefs.findLast(
1109
+ (pref) =>
1110
+ AppBskyActorDefs.isSavedFeedsPrefV2(pref) &&
1111
+ AppBskyActorDefs.validateSavedFeedsPrefV2(pref).success,
1112
+ ) as AppBskyActorDefs.SavedFeedsPrefV2 | undefined
1113
+ let existingV1Pref = prefs.findLast(
1114
+ (pref) =>
1115
+ AppBskyActorDefs.isSavedFeedsPref(pref) &&
1116
+ AppBskyActorDefs.validateSavedFeedsPref(pref).success,
1117
+ ) as AppBskyActorDefs.SavedFeedsPref | undefined
1118
+
1119
+ if (existingV2Pref) {
1120
+ maybeMutatedSavedFeeds = cb(existingV2Pref.items)
1121
+ existingV2Pref = {
1122
+ ...existingV2Pref,
1123
+ items: maybeMutatedSavedFeeds,
1124
+ }
1125
+ } else {
1126
+ maybeMutatedSavedFeeds = cb([])
1127
+ existingV2Pref = {
1128
+ $type: 'app.bsky.actor.defs#savedFeedsPrefV2',
1129
+ items: maybeMutatedSavedFeeds,
1130
+ }
1131
+ }
1132
+
1133
+ // enforce ordering, pinned then saved
1134
+ const pinned = existingV2Pref.items.filter((i) => i.pinned)
1135
+ const saved = existingV2Pref.items.filter((i) => !i.pinned)
1136
+ existingV2Pref.items = pinned.concat(saved)
1137
+
1138
+ let updatedPrefs = prefs
1139
+ .filter((pref) => !AppBskyActorDefs.isSavedFeedsPrefV2(pref))
1140
+ .concat(existingV2Pref)
1141
+
1142
+ /*
1143
+ * If there's a v2 pref present, it means this account was migrated from v1
1144
+ * to v2. During the transition period, we double write v2 prefs back to
1145
+ * v1, but NOT the other way around.
1146
+ */
1147
+ if (existingV1Pref) {
1148
+ const { saved, pinned } = existingV1Pref
1149
+ const v2Compat = savedFeedsToUriArrays(
1150
+ // v1 only supports feeds and lists
1151
+ existingV2Pref.items.filter((i) => ['feed', 'list'].includes(i.type)),
1152
+ )
1153
+ existingV1Pref = {
1154
+ ...existingV1Pref,
1155
+ saved: Array.from(new Set([...saved, ...v2Compat.saved])),
1156
+ pinned: Array.from(new Set([...pinned, ...v2Compat.pinned])),
1157
+ }
1158
+ updatedPrefs = updatedPrefs
1159
+ .filter((pref) => !AppBskyActorDefs.isSavedFeedsPref(pref))
1160
+ .concat(existingV1Pref)
1161
+ }
1162
+
1163
+ return updatedPrefs
1164
+ })
1165
+
1166
+ return maybeMutatedSavedFeeds
1167
+ }
1168
+
948
1169
  /**
949
1170
  * Helper to transform the legacy content preferences.
950
1171
  */
@@ -3822,6 +3822,7 @@ export const schemaDict = {
3822
3822
  'lex:app.bsky.actor.defs#adultContentPref',
3823
3823
  'lex:app.bsky.actor.defs#contentLabelPref',
3824
3824
  'lex:app.bsky.actor.defs#savedFeedsPref',
3825
+ 'lex:app.bsky.actor.defs#savedFeedsPrefV2',
3825
3826
  'lex:app.bsky.actor.defs#personalDetailsPref',
3826
3827
  'lex:app.bsky.actor.defs#feedViewPref',
3827
3828
  'lex:app.bsky.actor.defs#threadViewPref',
@@ -3860,6 +3861,38 @@ export const schemaDict = {
3860
3861
  },
3861
3862
  },
3862
3863
  },
3864
+ savedFeed: {
3865
+ type: 'object',
3866
+ required: ['id', 'type', 'value', 'pinned'],
3867
+ properties: {
3868
+ id: {
3869
+ type: 'string',
3870
+ },
3871
+ type: {
3872
+ type: 'string',
3873
+ knownValues: ['feed', 'list', 'timeline'],
3874
+ },
3875
+ value: {
3876
+ type: 'string',
3877
+ },
3878
+ pinned: {
3879
+ type: 'boolean',
3880
+ },
3881
+ },
3882
+ },
3883
+ savedFeedsPrefV2: {
3884
+ type: 'object',
3885
+ required: ['items'],
3886
+ properties: {
3887
+ items: {
3888
+ type: 'array',
3889
+ items: {
3890
+ type: 'ref',
3891
+ ref: 'lex:app.bsky.actor.defs#savedFeed',
3892
+ },
3893
+ },
3894
+ },
3895
+ },
3863
3896
  savedFeedsPref: {
3864
3897
  type: 'object',
3865
3898
  required: ['pinned', 'saved'],
@@ -8375,6 +8408,9 @@ export const schemaDict = {
8375
8408
  'lex:tools.ozone.moderation.defs#modEventAcknowledge',
8376
8409
  'lex:tools.ozone.moderation.defs#modEventEscalate',
8377
8410
  'lex:tools.ozone.moderation.defs#modEventMute',
8411
+ 'lex:tools.ozone.moderation.defs#modEventUnmute',
8412
+ 'lex:tools.ozone.moderation.defs#modEventMuteReporter',
8413
+ 'lex:tools.ozone.moderation.defs#modEventUnmuteReporter',
8378
8414
  'lex:tools.ozone.moderation.defs#modEventEmail',
8379
8415
  'lex:tools.ozone.moderation.defs#modEventResolveAppeal',
8380
8416
  'lex:tools.ozone.moderation.defs#modEventDivert',
@@ -8434,6 +8470,9 @@ export const schemaDict = {
8434
8470
  'lex:tools.ozone.moderation.defs#modEventAcknowledge',
8435
8471
  'lex:tools.ozone.moderation.defs#modEventEscalate',
8436
8472
  'lex:tools.ozone.moderation.defs#modEventMute',
8473
+ 'lex:tools.ozone.moderation.defs#modEventUnmute',
8474
+ 'lex:tools.ozone.moderation.defs#modEventMuteReporter',
8475
+ 'lex:tools.ozone.moderation.defs#modEventUnmuteReporter',
8437
8476
  'lex:tools.ozone.moderation.defs#modEventEmail',
8438
8477
  'lex:tools.ozone.moderation.defs#modEventResolveAppeal',
8439
8478
  'lex:tools.ozone.moderation.defs#modEventDivert',
@@ -8513,6 +8552,10 @@ export const schemaDict = {
8513
8552
  type: 'string',
8514
8553
  format: 'datetime',
8515
8554
  },
8555
+ muteReportingUntil: {
8556
+ type: 'string',
8557
+ format: 'datetime',
8558
+ },
8516
8559
  lastReviewedBy: {
8517
8560
  type: 'string',
8518
8561
  format: 'did',
@@ -8636,6 +8679,11 @@ export const schemaDict = {
8636
8679
  comment: {
8637
8680
  type: 'string',
8638
8681
  },
8682
+ isReporterMuted: {
8683
+ type: 'boolean',
8684
+ description:
8685
+ "Set to true if the reporter was muted from reporting at the time of the event. These reports won't impact the reviewState of the subject.",
8686
+ },
8639
8687
  reportType: {
8640
8688
  type: 'ref',
8641
8689
  ref: 'lex:com.atproto.moderation.defs#reasonType',
@@ -8704,6 +8752,30 @@ export const schemaDict = {
8704
8752
  },
8705
8753
  },
8706
8754
  },
8755
+ modEventMuteReporter: {
8756
+ type: 'object',
8757
+ description: 'Mute incoming reports from an account',
8758
+ required: ['durationInHours'],
8759
+ properties: {
8760
+ comment: {
8761
+ type: 'string',
8762
+ },
8763
+ durationInHours: {
8764
+ type: 'integer',
8765
+ description: 'Indicates how long the account should remain muted.',
8766
+ },
8767
+ },
8768
+ },
8769
+ modEventUnmuteReporter: {
8770
+ type: 'object',
8771
+ description: 'Unmute incoming reports from an account',
8772
+ properties: {
8773
+ comment: {
8774
+ type: 'string',
8775
+ description: 'Describe reasoning behind the reversal.',
8776
+ },
8777
+ },
8778
+ },
8707
8779
  modEventEmail: {
8708
8780
  type: 'object',
8709
8781
  description: 'Keep a log of outgoing email to a user',
@@ -9088,6 +9160,9 @@ export const schemaDict = {
9088
9160
  'lex:tools.ozone.moderation.defs#modEventLabel',
9089
9161
  'lex:tools.ozone.moderation.defs#modEventReport',
9090
9162
  'lex:tools.ozone.moderation.defs#modEventMute',
9163
+ 'lex:tools.ozone.moderation.defs#modEventUnmute',
9164
+ 'lex:tools.ozone.moderation.defs#modEventMuteReporter',
9165
+ 'lex:tools.ozone.moderation.defs#modEventUnmuteReporter',
9091
9166
  'lex:tools.ozone.moderation.defs#modEventReverseTakedown',
9092
9167
  'lex:tools.ozone.moderation.defs#modEventUnmute',
9093
9168
  'lex:tools.ozone.moderation.defs#modEventEmail',
@@ -9396,6 +9471,11 @@ export const schemaDict = {
9396
9471
  description:
9397
9472
  "By default, we don't include muted subjects in the results. Set this to true to include them.",
9398
9473
  },
9474
+ onlyMuted: {
9475
+ type: 'boolean',
9476
+ description:
9477
+ 'When set to true, only muted subjects and reporters will be returned.',
9478
+ },
9399
9479
  reviewState: {
9400
9480
  type: 'string',
9401
9481
  description: 'Specify when fetching subjects in a certain state',
@@ -132,6 +132,7 @@ export type Preferences = (
132
132
  | AdultContentPref
133
133
  | ContentLabelPref
134
134
  | SavedFeedsPref
135
+ | SavedFeedsPrefV2
135
136
  | PersonalDetailsPref
136
137
  | FeedViewPref
137
138
  | ThreadViewPref
@@ -178,6 +179,43 @@ export function validateContentLabelPref(v: unknown): ValidationResult {
178
179
  return lexicons.validate('app.bsky.actor.defs#contentLabelPref', v)
179
180
  }
180
181
 
182
+ export interface SavedFeed {
183
+ id: string
184
+ type: 'feed' | 'list' | 'timeline' | (string & {})
185
+ value: string
186
+ pinned: boolean
187
+ [k: string]: unknown
188
+ }
189
+
190
+ export function isSavedFeed(v: unknown): v is SavedFeed {
191
+ return (
192
+ isObj(v) &&
193
+ hasProp(v, '$type') &&
194
+ v.$type === 'app.bsky.actor.defs#savedFeed'
195
+ )
196
+ }
197
+
198
+ export function validateSavedFeed(v: unknown): ValidationResult {
199
+ return lexicons.validate('app.bsky.actor.defs#savedFeed', v)
200
+ }
201
+
202
+ export interface SavedFeedsPrefV2 {
203
+ items: SavedFeed[]
204
+ [k: string]: unknown
205
+ }
206
+
207
+ export function isSavedFeedsPrefV2(v: unknown): v is SavedFeedsPrefV2 {
208
+ return (
209
+ isObj(v) &&
210
+ hasProp(v, '$type') &&
211
+ v.$type === 'app.bsky.actor.defs#savedFeedsPrefV2'
212
+ )
213
+ }
214
+
215
+ export function validateSavedFeedsPrefV2(v: unknown): ValidationResult {
216
+ return lexicons.validate('app.bsky.actor.defs#savedFeedsPrefV2', v)
217
+ }
218
+
181
219
  export interface SavedFeedsPref {
182
220
  pinned: string[]
183
221
  saved: string[]
@@ -22,6 +22,9 @@ export interface ModEventView {
22
22
  | ModEventAcknowledge
23
23
  | ModEventEscalate
24
24
  | ModEventMute
25
+ | ModEventUnmute
26
+ | ModEventMuteReporter
27
+ | ModEventUnmuteReporter
25
28
  | ModEventEmail
26
29
  | ModEventResolveAppeal
27
30
  | ModEventDivert
@@ -61,6 +64,9 @@ export interface ModEventViewDetail {
61
64
  | ModEventAcknowledge
62
65
  | ModEventEscalate
63
66
  | ModEventMute
67
+ | ModEventUnmute
68
+ | ModEventMuteReporter
69
+ | ModEventUnmuteReporter
64
70
  | ModEventEmail
65
71
  | ModEventResolveAppeal
66
72
  | ModEventDivert
@@ -105,6 +111,7 @@ export interface SubjectStatusView {
105
111
  /** Sticky comment on the subject. */
106
112
  comment?: string
107
113
  muteUntil?: string
114
+ muteReportingUntil?: string
108
115
  lastReviewedBy?: string
109
116
  lastReviewedAt?: string
110
117
  lastReportedAt?: string
@@ -237,6 +244,8 @@ export function validateModEventComment(v: unknown): ValidationResult {
237
244
  /** Report a subject */
238
245
  export interface ModEventReport {
239
246
  comment?: string
247
+ /** Set to true if the reporter was muted from reporting at the time of the event. These reports won't impact the reviewState of the subject. */
248
+ isReporterMuted?: boolean
240
249
  reportType: ComAtprotoModerationDefs.ReasonType
241
250
  [k: string]: unknown
242
251
  }
@@ -346,6 +355,53 @@ export function validateModEventUnmute(v: unknown): ValidationResult {
346
355
  return lexicons.validate('tools.ozone.moderation.defs#modEventUnmute', v)
347
356
  }
348
357
 
358
+ /** Mute incoming reports from an account */
359
+ export interface ModEventMuteReporter {
360
+ comment?: string
361
+ /** Indicates how long the account should remain muted. */
362
+ durationInHours: number
363
+ [k: string]: unknown
364
+ }
365
+
366
+ export function isModEventMuteReporter(v: unknown): v is ModEventMuteReporter {
367
+ return (
368
+ isObj(v) &&
369
+ hasProp(v, '$type') &&
370
+ v.$type === 'tools.ozone.moderation.defs#modEventMuteReporter'
371
+ )
372
+ }
373
+
374
+ export function validateModEventMuteReporter(v: unknown): ValidationResult {
375
+ return lexicons.validate(
376
+ 'tools.ozone.moderation.defs#modEventMuteReporter',
377
+ v,
378
+ )
379
+ }
380
+
381
+ /** Unmute incoming reports from an account */
382
+ export interface ModEventUnmuteReporter {
383
+ /** Describe reasoning behind the reversal. */
384
+ comment?: string
385
+ [k: string]: unknown
386
+ }
387
+
388
+ export function isModEventUnmuteReporter(
389
+ v: unknown,
390
+ ): v is ModEventUnmuteReporter {
391
+ return (
392
+ isObj(v) &&
393
+ hasProp(v, '$type') &&
394
+ v.$type === 'tools.ozone.moderation.defs#modEventUnmuteReporter'
395
+ )
396
+ }
397
+
398
+ export function validateModEventUnmuteReporter(v: unknown): ValidationResult {
399
+ return lexicons.validate(
400
+ 'tools.ozone.moderation.defs#modEventUnmuteReporter',
401
+ v,
402
+ )
403
+ }
404
+
349
405
  /** Keep a log of outgoing email to a user */
350
406
  export interface ModEventEmail {
351
407
  /** The subject line of the email sent to the user. */
@@ -21,6 +21,9 @@ export interface InputSchema {
21
21
  | ToolsOzoneModerationDefs.ModEventLabel
22
22
  | ToolsOzoneModerationDefs.ModEventReport
23
23
  | ToolsOzoneModerationDefs.ModEventMute
24
+ | ToolsOzoneModerationDefs.ModEventUnmute
25
+ | ToolsOzoneModerationDefs.ModEventMuteReporter
26
+ | ToolsOzoneModerationDefs.ModEventUnmuteReporter
24
27
  | ToolsOzoneModerationDefs.ModEventReverseTakedown
25
28
  | ToolsOzoneModerationDefs.ModEventUnmute
26
29
  | ToolsOzoneModerationDefs.ModEventEmail
@@ -22,6 +22,8 @@ export interface QueryParams {
22
22
  reviewedBefore?: string
23
23
  /** By default, we don't include muted subjects in the results. Set this to true to include them. */
24
24
  includeMuted?: boolean
25
+ /** When set to true, only muted subjects and reporters will be returned. */
26
+ onlyMuted?: boolean
25
27
  /** Specify when fetching subjects in a certain state */
26
28
  reviewState?: string
27
29
  ignoreSubjects?: string[]
package/src/types.ts CHANGED
@@ -112,10 +112,14 @@ export interface BskyInterestsPreference {
112
112
  * Bluesky preferences
113
113
  */
114
114
  export interface BskyPreferences {
115
+ /**
116
+ * @deprecated use `savedFeeds`
117
+ */
115
118
  feeds: {
116
119
  saved?: string[]
117
120
  pinned?: string[]
118
121
  }
122
+ savedFeeds: AppBskyActorDefs.SavedFeed[]
119
123
  feedViewPrefs: Record<string, BskyFeedViewPreference>
120
124
  threadViewPrefs: BskyThreadViewPreference
121
125
  moderationPrefs: ModerationPrefs
package/src/util.ts CHANGED
@@ -1,6 +1,78 @@
1
+ import { AtUri } from '@atproto/syntax'
2
+ import { TID } from '@atproto/common-web'
3
+
4
+ import { AppBskyActorDefs } from './client'
5
+
1
6
  export function sanitizeMutedWordValue(value: string) {
2
7
  return value
3
8
  .trim()
4
9
  .replace(/^#(?!\ufe0f)/, '')
5
10
  .replace(/[\r\n\u00AD\u2060\u200D\u200C\u200B]+/, '')
6
11
  }
12
+
13
+ export function savedFeedsToUriArrays(
14
+ savedFeeds: AppBskyActorDefs.SavedFeed[],
15
+ ): {
16
+ pinned: string[]
17
+ saved: string[]
18
+ } {
19
+ const pinned: string[] = []
20
+ const saved: string[] = []
21
+
22
+ for (const feed of savedFeeds) {
23
+ if (feed.pinned) {
24
+ pinned.push(feed.value)
25
+ // saved in v1 includes pinned
26
+ saved.push(feed.value)
27
+ } else {
28
+ saved.push(feed.value)
29
+ }
30
+ }
31
+
32
+ return {
33
+ pinned,
34
+ saved,
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Get the type of a saved feed, used by deprecated methods for backwards
40
+ * compat. Should not be used moving forward. *Invalid URIs will throw.*
41
+ *
42
+ * @param uri - The AT URI of the saved feed
43
+ */
44
+ export function getSavedFeedType(
45
+ uri: string,
46
+ ): AppBskyActorDefs.SavedFeed['type'] {
47
+ const urip = new AtUri(uri)
48
+
49
+ switch (urip.collection) {
50
+ case 'app.bsky.feed.generator':
51
+ return 'feed'
52
+ case 'app.bsky.graph.list':
53
+ return 'list'
54
+ default:
55
+ return 'unknown'
56
+ }
57
+ }
58
+
59
+ export function validateSavedFeed(savedFeed: AppBskyActorDefs.SavedFeed) {
60
+ new TID(savedFeed.id)
61
+
62
+ if (['feed', 'list'].includes(savedFeed.type)) {
63
+ const uri = new AtUri(savedFeed.value)
64
+ const isFeed = uri.collection === 'app.bsky.feed.generator'
65
+ const isList = uri.collection === 'app.bsky.graph.list'
66
+
67
+ if (savedFeed.type === 'feed' && !isFeed) {
68
+ throw new Error(
69
+ `Saved feed of type 'feed' must be a feed, got ${uri.collection}`,
70
+ )
71
+ }
72
+ if (savedFeed.type === 'list' && !isList) {
73
+ throw new Error(
74
+ `Saved feed of type 'list' must be a list, got ${uri.collection}`,
75
+ )
76
+ }
77
+ }
78
+ }