@atproto/api 0.15.13 → 0.15.15

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 (51) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/client/index.d.ts +6 -0
  3. package/dist/client/index.d.ts.map +1 -1
  4. package/dist/client/index.js +12 -4
  5. package/dist/client/index.js.map +1 -1
  6. package/dist/client/lexicons.d.ts +418 -0
  7. package/dist/client/lexicons.d.ts.map +1 -1
  8. package/dist/client/lexicons.js +225 -0
  9. package/dist/client/lexicons.js.map +1 -1
  10. package/dist/client/types/app/bsky/notification/defs.d.ts +40 -0
  11. package/dist/client/types/app/bsky/notification/defs.d.ts.map +1 -1
  12. package/dist/client/types/app/bsky/notification/defs.js +36 -0
  13. package/dist/client/types/app/bsky/notification/defs.js.map +1 -1
  14. package/dist/client/types/app/bsky/notification/getPreferences.d.ts +22 -0
  15. package/dist/client/types/app/bsky/notification/getPreferences.d.ts.map +1 -0
  16. package/dist/client/types/app/bsky/notification/getPreferences.js +11 -0
  17. package/dist/client/types/app/bsky/notification/getPreferences.js.map +1 -0
  18. package/dist/client/types/app/bsky/notification/putPreferencesV2.d.ts +38 -0
  19. package/dist/client/types/app/bsky/notification/putPreferencesV2.d.ts.map +1 -0
  20. package/dist/client/types/app/bsky/notification/putPreferencesV2.js +11 -0
  21. package/dist/client/types/app/bsky/notification/putPreferencesV2.js.map +1 -0
  22. package/dist/moderation/decision.d.ts +2 -1
  23. package/dist/moderation/decision.d.ts.map +1 -1
  24. package/dist/moderation/decision.js +3 -2
  25. package/dist/moderation/decision.js.map +1 -1
  26. package/dist/moderation/index.d.ts +1 -1
  27. package/dist/moderation/index.d.ts.map +1 -1
  28. package/dist/moderation/index.js +2 -1
  29. package/dist/moderation/index.js.map +1 -1
  30. package/dist/moderation/mutewords.d.ts +22 -2
  31. package/dist/moderation/mutewords.d.ts.map +1 -1
  32. package/dist/moderation/mutewords.js +63 -25
  33. package/dist/moderation/mutewords.js.map +1 -1
  34. package/dist/moderation/subjects/post.js +49 -39
  35. package/dist/moderation/subjects/post.js.map +1 -1
  36. package/dist/moderation/types.d.ts +2 -0
  37. package/dist/moderation/types.d.ts.map +1 -1
  38. package/dist/moderation/types.js.map +1 -1
  39. package/package.json +1 -1
  40. package/src/client/index.ts +28 -0
  41. package/src/client/lexicons.ts +227 -0
  42. package/src/client/types/app/bsky/notification/defs.ts +76 -0
  43. package/src/client/types/app/bsky/notification/getPreferences.ts +40 -0
  44. package/src/client/types/app/bsky/notification/putPreferencesV2.ts +56 -0
  45. package/src/moderation/decision.ts +4 -2
  46. package/src/moderation/index.ts +1 -1
  47. package/src/moderation/mutewords.ts +88 -26
  48. package/src/moderation/subjects/post.ts +104 -115
  49. package/src/moderation/types.ts +2 -0
  50. package/tests/moderation-mutewords.test.ts +153 -112
  51. package/tsconfig.build.tsbuildinfo +1 -1
@@ -27,3 +27,79 @@ export function isRecordDeleted<V>(v: V) {
27
27
  export function validateRecordDeleted<V>(v: V) {
28
28
  return validate<RecordDeleted & V>(v, id, hashRecordDeleted)
29
29
  }
30
+
31
+ export interface ChatPreference {
32
+ $type?: 'app.bsky.notification.defs#chatPreference'
33
+ filter: 'all' | 'accepted' | (string & {})
34
+ push: boolean
35
+ }
36
+
37
+ const hashChatPreference = 'chatPreference'
38
+
39
+ export function isChatPreference<V>(v: V) {
40
+ return is$typed(v, id, hashChatPreference)
41
+ }
42
+
43
+ export function validateChatPreference<V>(v: V) {
44
+ return validate<ChatPreference & V>(v, id, hashChatPreference)
45
+ }
46
+
47
+ export interface FilterablePreference {
48
+ $type?: 'app.bsky.notification.defs#filterablePreference'
49
+ filter: 'all' | 'follows' | (string & {})
50
+ list: boolean
51
+ push: boolean
52
+ }
53
+
54
+ const hashFilterablePreference = 'filterablePreference'
55
+
56
+ export function isFilterablePreference<V>(v: V) {
57
+ return is$typed(v, id, hashFilterablePreference)
58
+ }
59
+
60
+ export function validateFilterablePreference<V>(v: V) {
61
+ return validate<FilterablePreference & V>(v, id, hashFilterablePreference)
62
+ }
63
+
64
+ export interface Preference {
65
+ $type?: 'app.bsky.notification.defs#preference'
66
+ list: boolean
67
+ push: boolean
68
+ }
69
+
70
+ const hashPreference = 'preference'
71
+
72
+ export function isPreference<V>(v: V) {
73
+ return is$typed(v, id, hashPreference)
74
+ }
75
+
76
+ export function validatePreference<V>(v: V) {
77
+ return validate<Preference & V>(v, id, hashPreference)
78
+ }
79
+
80
+ export interface Preferences {
81
+ $type?: 'app.bsky.notification.defs#preferences'
82
+ chat: ChatPreference
83
+ follow: FilterablePreference
84
+ like: FilterablePreference
85
+ likeViaRepost: FilterablePreference
86
+ mention: FilterablePreference
87
+ quote: FilterablePreference
88
+ reply: FilterablePreference
89
+ repost: FilterablePreference
90
+ repostViaRepost: FilterablePreference
91
+ starterpackJoined: Preference
92
+ subscribedPost: Preference
93
+ unverified: Preference
94
+ verified: Preference
95
+ }
96
+
97
+ const hashPreferences = 'preferences'
98
+
99
+ export function isPreferences<V>(v: V) {
100
+ return is$typed(v, id, hashPreferences)
101
+ }
102
+
103
+ export function validatePreferences<V>(v: V) {
104
+ return validate<Preferences & V>(v, id, hashPreferences)
105
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * GENERATED CODE - DO NOT MODIFY
3
+ */
4
+ import { HeadersMap, XRPCError } from '@atproto/xrpc'
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 type * as AppBskyNotificationDefs from './defs.js'
14
+
15
+ const is$typed = _is$typed,
16
+ validate = _validate
17
+ const id = 'app.bsky.notification.getPreferences'
18
+
19
+ export interface QueryParams {}
20
+
21
+ export type InputSchema = undefined
22
+
23
+ export interface OutputSchema {
24
+ preferences: AppBskyNotificationDefs.Preferences
25
+ }
26
+
27
+ export interface CallOptions {
28
+ signal?: AbortSignal
29
+ headers?: HeadersMap
30
+ }
31
+
32
+ export interface Response {
33
+ success: boolean
34
+ headers: HeadersMap
35
+ data: OutputSchema
36
+ }
37
+
38
+ export function toKnownErr(e: any) {
39
+ return e
40
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * GENERATED CODE - DO NOT MODIFY
3
+ */
4
+ import { HeadersMap, XRPCError } from '@atproto/xrpc'
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 type * as AppBskyNotificationDefs from './defs.js'
14
+
15
+ const is$typed = _is$typed,
16
+ validate = _validate
17
+ const id = 'app.bsky.notification.putPreferencesV2'
18
+
19
+ export interface QueryParams {}
20
+
21
+ export interface InputSchema {
22
+ chat?: AppBskyNotificationDefs.ChatPreference
23
+ follow?: AppBskyNotificationDefs.FilterablePreference
24
+ like?: AppBskyNotificationDefs.FilterablePreference
25
+ likeViaRepost?: AppBskyNotificationDefs.FilterablePreference
26
+ mention?: AppBskyNotificationDefs.FilterablePreference
27
+ quote?: AppBskyNotificationDefs.FilterablePreference
28
+ reply?: AppBskyNotificationDefs.FilterablePreference
29
+ repost?: AppBskyNotificationDefs.FilterablePreference
30
+ repostViaRepost?: AppBskyNotificationDefs.FilterablePreference
31
+ starterpackJoined?: AppBskyNotificationDefs.Preference
32
+ subscribedPost?: AppBskyNotificationDefs.Preference
33
+ unverified?: AppBskyNotificationDefs.Preference
34
+ verified?: AppBskyNotificationDefs.Preference
35
+ }
36
+
37
+ export interface OutputSchema {
38
+ preferences: AppBskyNotificationDefs.Preferences
39
+ }
40
+
41
+ export interface CallOptions {
42
+ signal?: AbortSignal
43
+ headers?: HeadersMap
44
+ qp?: QueryParams
45
+ encoding?: 'application/json'
46
+ }
47
+
48
+ export interface Response {
49
+ success: boolean
50
+ headers: HeadersMap
51
+ data: OutputSchema
52
+ }
53
+
54
+ export function toKnownErr(e: any) {
55
+ return e
56
+ }
@@ -1,5 +1,6 @@
1
1
  import { AppBskyGraphDefs } from '../client/index'
2
2
  import { LABELS } from './const/labels'
3
+ import { MuteWordMatch } from './mutewords'
3
4
  import {
4
5
  BLOCK_BEHAVIOR,
5
6
  CUSTOM_LABEL_VALUE_RE,
@@ -195,12 +196,13 @@ export class ModerationDecision {
195
196
  }
196
197
  }
197
198
 
198
- addMutedWord(mutedWord: boolean) {
199
- if (mutedWord) {
199
+ addMutedWord(matches: MuteWordMatch[] | undefined) {
200
+ if (matches?.length) {
200
201
  this.causes.push({
201
202
  type: 'mute-word',
202
203
  source: { type: 'user' },
203
204
  priority: 6,
205
+ matches,
204
206
  })
205
207
  }
206
208
  }
@@ -16,7 +16,7 @@ import {
16
16
 
17
17
  export { ModerationUI } from './ui'
18
18
  export { ModerationDecision } from './decision'
19
- export { hasMutedWord } from './mutewords'
19
+ export { hasMutedWord, matchMuteWords } from './mutewords'
20
20
  export {
21
21
  interpretLabelValueDefinition,
22
22
  interpretLabelValueDefinitions,
@@ -21,21 +21,38 @@ const LANGUAGE_EXCEPTIONS = [
21
21
  'vi', // Vietnamese
22
22
  ]
23
23
 
24
- export function hasMutedWord({
25
- mutedWords,
26
- text,
27
- facets,
28
- outlineTags,
29
- languages,
30
- actor,
31
- }: {
24
+ export type MuteWordMatch = {
25
+ /**
26
+ * The `AppBskyActorDefs.MutedWord` that matched.
27
+ */
28
+ word: AppBskyActorDefs.MutedWord
29
+ /**
30
+ * The string that matched the muted word.
31
+ */
32
+ predicate: string
33
+ }
34
+
35
+ export type Params = {
32
36
  mutedWords: AppBskyActorDefs.MutedWord[]
33
37
  text: string
34
38
  facets?: AppBskyRichtextFacet.Main[]
35
39
  outlineTags?: string[]
36
40
  languages?: string[]
37
41
  actor?: AppBskyActorDefs.ProfileView | AppBskyActorDefs.ProfileViewBasic
38
- }) {
42
+ }
43
+
44
+ /**
45
+ * Checks if the given text matches any of the muted words, returning an array
46
+ * of matches. If no matches are found, returns `undefined`.
47
+ */
48
+ export function matchMuteWords({
49
+ mutedWords,
50
+ text,
51
+ facets,
52
+ outlineTags,
53
+ languages,
54
+ actor,
55
+ }: Params): MuteWordMatch[] | undefined {
39
56
  const exception = LANGUAGE_EXCEPTIONS.includes(languages?.[0] || '')
40
57
  const tags = ([] as string[])
41
58
  .concat(outlineTags || [])
@@ -46,38 +63,54 @@ export function hasMutedWord({
46
63
  )
47
64
  .map((t) => t.toLowerCase())
48
65
 
49
- for (const mute of mutedWords) {
50
- const mutedWord = mute.value.toLowerCase()
66
+ const matches: MuteWordMatch[] = []
67
+
68
+ outer: for (const muteWord of mutedWords) {
69
+ const mutedWord = muteWord.value.toLowerCase()
51
70
  const postText = text.toLowerCase()
52
71
 
53
72
  // expired, ignore
54
- if (mute.expiresAt && mute.expiresAt < new Date().toISOString()) continue
73
+ if (muteWord.expiresAt && muteWord.expiresAt < new Date().toISOString())
74
+ continue
55
75
 
56
76
  if (
57
- mute.actorTarget === 'exclude-following' &&
77
+ muteWord.actorTarget === 'exclude-following' &&
58
78
  Boolean(actor?.viewer?.following)
59
79
  )
60
80
  continue
61
81
 
62
82
  // `content` applies to tags as well
63
- if (tags.includes(mutedWord)) return true
83
+ if (tags.includes(mutedWord)) {
84
+ matches.push({ word: muteWord, predicate: muteWord.value })
85
+ continue
86
+ }
64
87
  // rest of the checks are for `content` only
65
- if (!mute.targets.includes('content')) continue
88
+ if (!muteWord.targets.includes('content')) continue
66
89
  // single character or other exception, has to use includes
67
- if ((mutedWord.length === 1 || exception) && postText.includes(mutedWord))
68
- return true
90
+ if ((mutedWord.length === 1 || exception) && postText.includes(mutedWord)) {
91
+ matches.push({ word: muteWord, predicate: muteWord.value })
92
+ continue
93
+ }
69
94
  // too long
70
95
  if (mutedWord.length > postText.length) continue
71
96
  // exact match
72
- if (mutedWord === postText) return true
97
+ if (mutedWord === postText) {
98
+ matches.push({ word: muteWord, predicate: muteWord.value })
99
+ continue
100
+ }
73
101
  // any muted phrase with space or punctuation
74
- if (/(?:\s|\p{P})+?/u.test(mutedWord) && postText.includes(mutedWord))
75
- return true
102
+ if (/(?:\s|\p{P})+?/u.test(mutedWord) && postText.includes(mutedWord)) {
103
+ matches.push({ word: muteWord, predicate: muteWord.value })
104
+ continue
105
+ }
76
106
 
77
107
  // check individual character groups
78
108
  const words = postText.split(REGEX.WORD_BOUNDARY)
79
109
  for (const word of words) {
80
- if (word === mutedWord) return true
110
+ if (word === mutedWord) {
111
+ matches.push({ word: muteWord, predicate: word })
112
+ continue outer
113
+ }
81
114
 
82
115
  // compare word without leading/trailing punctuation, but allow internal
83
116
  // punctuation (such as `s@ssy`)
@@ -86,23 +119,52 @@ export function hasMutedWord({
86
119
  '',
87
120
  )
88
121
 
89
- if (mutedWord === wordTrimmedPunctuation) return true
122
+ if (mutedWord === wordTrimmedPunctuation) {
123
+ matches.push({ word: muteWord, predicate: word })
124
+ continue outer
125
+ }
126
+
90
127
  if (mutedWord.length > wordTrimmedPunctuation.length) continue
91
128
 
92
129
  if (/\p{P}+/u.test(wordTrimmedPunctuation)) {
130
+ /**
131
+ * Exit case for any punctuation within the predicate that we _do_
132
+ * allow e.g. `and/or` should not match `Andor`.
133
+ */
134
+ if (/[/]+/.test(wordTrimmedPunctuation)) {
135
+ continue outer
136
+ }
137
+
93
138
  const spacedWord = wordTrimmedPunctuation.replace(/\p{P}+/gu, ' ')
94
- if (spacedWord === mutedWord) return true
139
+ if (spacedWord === mutedWord) {
140
+ matches.push({ word: muteWord, predicate: word })
141
+ continue outer
142
+ }
95
143
 
96
144
  const contiguousWord = spacedWord.replace(/\s/gu, '')
97
- if (contiguousWord === mutedWord) return true
145
+ if (contiguousWord === mutedWord) {
146
+ matches.push({ word: muteWord, predicate: word })
147
+ continue outer
148
+ }
98
149
 
99
150
  const wordParts = wordTrimmedPunctuation.split(/\p{P}+/u)
100
151
  for (const wordPart of wordParts) {
101
- if (wordPart === mutedWord) return true
152
+ if (wordPart === mutedWord) {
153
+ matches.push({ word: muteWord, predicate: word })
154
+ continue outer
155
+ }
102
156
  }
103
157
  }
104
158
  }
105
159
  }
106
160
 
107
- return false
161
+ return matches.length ? matches : undefined
162
+ }
163
+
164
+ /**
165
+ * Checks if the given text matches any of the muted words, returning a boolean
166
+ * if any matches are found.
167
+ */
168
+ export function hasMutedWord(params: Params) {
169
+ return !!matchMuteWords(params)
108
170
  }