@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.
- package/CHANGELOG.md +14 -0
- package/dist/client/index.d.ts +6 -0
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +12 -4
- package/dist/client/index.js.map +1 -1
- package/dist/client/lexicons.d.ts +418 -0
- package/dist/client/lexicons.d.ts.map +1 -1
- package/dist/client/lexicons.js +225 -0
- package/dist/client/lexicons.js.map +1 -1
- package/dist/client/types/app/bsky/notification/defs.d.ts +40 -0
- package/dist/client/types/app/bsky/notification/defs.d.ts.map +1 -1
- package/dist/client/types/app/bsky/notification/defs.js +36 -0
- package/dist/client/types/app/bsky/notification/defs.js.map +1 -1
- package/dist/client/types/app/bsky/notification/getPreferences.d.ts +22 -0
- package/dist/client/types/app/bsky/notification/getPreferences.d.ts.map +1 -0
- package/dist/client/types/app/bsky/notification/getPreferences.js +11 -0
- package/dist/client/types/app/bsky/notification/getPreferences.js.map +1 -0
- package/dist/client/types/app/bsky/notification/putPreferencesV2.d.ts +38 -0
- package/dist/client/types/app/bsky/notification/putPreferencesV2.d.ts.map +1 -0
- package/dist/client/types/app/bsky/notification/putPreferencesV2.js +11 -0
- package/dist/client/types/app/bsky/notification/putPreferencesV2.js.map +1 -0
- package/dist/moderation/decision.d.ts +2 -1
- package/dist/moderation/decision.d.ts.map +1 -1
- package/dist/moderation/decision.js +3 -2
- package/dist/moderation/decision.js.map +1 -1
- package/dist/moderation/index.d.ts +1 -1
- package/dist/moderation/index.d.ts.map +1 -1
- package/dist/moderation/index.js +2 -1
- package/dist/moderation/index.js.map +1 -1
- package/dist/moderation/mutewords.d.ts +22 -2
- package/dist/moderation/mutewords.d.ts.map +1 -1
- package/dist/moderation/mutewords.js +63 -25
- package/dist/moderation/mutewords.js.map +1 -1
- package/dist/moderation/subjects/post.js +49 -39
- package/dist/moderation/subjects/post.js.map +1 -1
- package/dist/moderation/types.d.ts +2 -0
- package/dist/moderation/types.d.ts.map +1 -1
- package/dist/moderation/types.js.map +1 -1
- package/package.json +1 -1
- package/src/client/index.ts +28 -0
- package/src/client/lexicons.ts +227 -0
- package/src/client/types/app/bsky/notification/defs.ts +76 -0
- package/src/client/types/app/bsky/notification/getPreferences.ts +40 -0
- package/src/client/types/app/bsky/notification/putPreferencesV2.ts +56 -0
- package/src/moderation/decision.ts +4 -2
- package/src/moderation/index.ts +1 -1
- package/src/moderation/mutewords.ts +88 -26
- package/src/moderation/subjects/post.ts +104 -115
- package/src/moderation/types.ts +2 -0
- package/tests/moderation-mutewords.test.ts +153 -112
- 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(
|
|
199
|
-
if (
|
|
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
|
}
|
package/src/moderation/index.ts
CHANGED
|
@@ -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
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
50
|
-
|
|
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 (
|
|
73
|
+
if (muteWord.expiresAt && muteWord.expiresAt < new Date().toISOString())
|
|
74
|
+
continue
|
|
55
75
|
|
|
56
76
|
if (
|
|
57
|
-
|
|
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))
|
|
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 (!
|
|
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
|
-
|
|
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)
|
|
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
|
-
|
|
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)
|
|
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)
|
|
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)
|
|
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)
|
|
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)
|
|
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
|
|
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
|
}
|