@atproto/ozone 0.1.149 → 0.1.151
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 +16 -0
- package/dist/api/moderation/queryEvents.d.ts.map +1 -1
- package/dist/api/moderation/queryEvents.js +2 -1
- package/dist/api/moderation/queryEvents.js.map +1 -1
- package/dist/context.d.ts +3 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +7 -1
- package/dist/context.js.map +1 -1
- package/dist/daemon/context.d.ts +3 -0
- package/dist/daemon/context.d.ts.map +1 -1
- package/dist/daemon/context.js +11 -1
- package/dist/daemon/context.js.map +1 -1
- package/dist/daemon/index.d.ts +1 -0
- package/dist/daemon/index.d.ts.map +1 -1
- package/dist/daemon/index.js +3 -1
- package/dist/daemon/index.js.map +1 -1
- package/dist/daemon/strike-expiry-processor.d.ts +18 -0
- package/dist/daemon/strike-expiry-processor.d.ts.map +1 -0
- package/dist/daemon/strike-expiry-processor.js +111 -0
- package/dist/daemon/strike-expiry-processor.js.map +1 -0
- package/dist/db/migrations/20251008T120000000Z-add-strike-system.d.ts +4 -0
- package/dist/db/migrations/20251008T120000000Z-add-strike-system.d.ts.map +1 -0
- package/dist/db/migrations/20251008T120000000Z-add-strike-system.js +75 -0
- package/dist/db/migrations/20251008T120000000Z-add-strike-system.js.map +1 -0
- package/dist/db/migrations/index.d.ts +1 -0
- package/dist/db/migrations/index.d.ts.map +1 -1
- package/dist/db/migrations/index.js +2 -1
- package/dist/db/migrations/index.js.map +1 -1
- package/dist/db/schema/account_strike.d.ts +12 -0
- package/dist/db/schema/account_strike.d.ts.map +1 -0
- package/dist/db/schema/account_strike.js +5 -0
- package/dist/db/schema/account_strike.js.map +1 -0
- package/dist/db/schema/index.d.ts +3 -1
- package/dist/db/schema/index.d.ts.map +1 -1
- package/dist/db/schema/index.js.map +1 -1
- package/dist/db/schema/job_cursor.d.ts +11 -0
- package/dist/db/schema/job_cursor.d.ts.map +1 -0
- package/dist/db/schema/job_cursor.js +5 -0
- package/dist/db/schema/job_cursor.js.map +1 -0
- package/dist/db/schema/moderation_event.d.ts +3 -0
- package/dist/db/schema/moderation_event.d.ts.map +1 -1
- package/dist/db/schema/moderation_event.js.map +1 -1
- package/dist/lexicon/lexicons.d.ts +176 -0
- package/dist/lexicon/lexicons.d.ts.map +1 -1
- package/dist/lexicon/lexicons.js +88 -0
- package/dist/lexicon/lexicons.js.map +1 -1
- package/dist/lexicon/types/app/bsky/actor/profile.d.ts +4 -3
- package/dist/lexicon/types/app/bsky/actor/profile.d.ts.map +1 -1
- package/dist/lexicon/types/app/bsky/actor/profile.js +9 -7
- package/dist/lexicon/types/app/bsky/actor/profile.js.map +1 -1
- package/dist/lexicon/types/app/bsky/actor/status.d.ts +4 -3
- package/dist/lexicon/types/app/bsky/actor/status.d.ts.map +1 -1
- package/dist/lexicon/types/app/bsky/actor/status.js +9 -7
- package/dist/lexicon/types/app/bsky/actor/status.js.map +1 -1
- package/dist/lexicon/types/app/bsky/feed/generator.d.ts +4 -3
- package/dist/lexicon/types/app/bsky/feed/generator.d.ts.map +1 -1
- package/dist/lexicon/types/app/bsky/feed/generator.js +9 -7
- package/dist/lexicon/types/app/bsky/feed/generator.js.map +1 -1
- package/dist/lexicon/types/app/bsky/feed/like.d.ts +4 -3
- package/dist/lexicon/types/app/bsky/feed/like.d.ts.map +1 -1
- package/dist/lexicon/types/app/bsky/feed/like.js +9 -7
- package/dist/lexicon/types/app/bsky/feed/like.js.map +1 -1
- package/dist/lexicon/types/app/bsky/feed/post.d.ts +4 -3
- package/dist/lexicon/types/app/bsky/feed/post.d.ts.map +1 -1
- package/dist/lexicon/types/app/bsky/feed/post.js +9 -7
- package/dist/lexicon/types/app/bsky/feed/post.js.map +1 -1
- package/dist/lexicon/types/app/bsky/feed/postgate.d.ts +4 -3
- package/dist/lexicon/types/app/bsky/feed/postgate.d.ts.map +1 -1
- package/dist/lexicon/types/app/bsky/feed/postgate.js +9 -7
- package/dist/lexicon/types/app/bsky/feed/postgate.js.map +1 -1
- package/dist/lexicon/types/app/bsky/feed/repost.d.ts +4 -3
- package/dist/lexicon/types/app/bsky/feed/repost.d.ts.map +1 -1
- package/dist/lexicon/types/app/bsky/feed/repost.js +9 -7
- package/dist/lexicon/types/app/bsky/feed/repost.js.map +1 -1
- package/dist/lexicon/types/app/bsky/feed/threadgate.d.ts +4 -3
- package/dist/lexicon/types/app/bsky/feed/threadgate.d.ts.map +1 -1
- package/dist/lexicon/types/app/bsky/feed/threadgate.js +9 -7
- package/dist/lexicon/types/app/bsky/feed/threadgate.js.map +1 -1
- package/dist/lexicon/types/app/bsky/graph/block.d.ts +4 -3
- package/dist/lexicon/types/app/bsky/graph/block.d.ts.map +1 -1
- package/dist/lexicon/types/app/bsky/graph/block.js +9 -7
- package/dist/lexicon/types/app/bsky/graph/block.js.map +1 -1
- package/dist/lexicon/types/app/bsky/graph/follow.d.ts +4 -3
- package/dist/lexicon/types/app/bsky/graph/follow.d.ts.map +1 -1
- package/dist/lexicon/types/app/bsky/graph/follow.js +9 -7
- package/dist/lexicon/types/app/bsky/graph/follow.js.map +1 -1
- package/dist/lexicon/types/app/bsky/graph/list.d.ts +4 -3
- package/dist/lexicon/types/app/bsky/graph/list.d.ts.map +1 -1
- package/dist/lexicon/types/app/bsky/graph/list.js +9 -7
- package/dist/lexicon/types/app/bsky/graph/list.js.map +1 -1
- package/dist/lexicon/types/app/bsky/graph/listblock.d.ts +4 -3
- package/dist/lexicon/types/app/bsky/graph/listblock.d.ts.map +1 -1
- package/dist/lexicon/types/app/bsky/graph/listblock.js +9 -7
- package/dist/lexicon/types/app/bsky/graph/listblock.js.map +1 -1
- package/dist/lexicon/types/app/bsky/graph/listitem.d.ts +4 -3
- package/dist/lexicon/types/app/bsky/graph/listitem.d.ts.map +1 -1
- package/dist/lexicon/types/app/bsky/graph/listitem.js +9 -7
- package/dist/lexicon/types/app/bsky/graph/listitem.js.map +1 -1
- package/dist/lexicon/types/app/bsky/graph/starterpack.d.ts +4 -3
- package/dist/lexicon/types/app/bsky/graph/starterpack.d.ts.map +1 -1
- package/dist/lexicon/types/app/bsky/graph/starterpack.js +9 -7
- package/dist/lexicon/types/app/bsky/graph/starterpack.js.map +1 -1
- package/dist/lexicon/types/app/bsky/graph/verification.d.ts +4 -3
- package/dist/lexicon/types/app/bsky/graph/verification.d.ts.map +1 -1
- package/dist/lexicon/types/app/bsky/graph/verification.js +9 -7
- package/dist/lexicon/types/app/bsky/graph/verification.js.map +1 -1
- package/dist/lexicon/types/app/bsky/labeler/service.d.ts +4 -3
- package/dist/lexicon/types/app/bsky/labeler/service.d.ts.map +1 -1
- package/dist/lexicon/types/app/bsky/labeler/service.js +9 -7
- package/dist/lexicon/types/app/bsky/labeler/service.js.map +1 -1
- package/dist/lexicon/types/app/bsky/notification/declaration.d.ts +4 -3
- package/dist/lexicon/types/app/bsky/notification/declaration.d.ts.map +1 -1
- package/dist/lexicon/types/app/bsky/notification/declaration.js +9 -7
- package/dist/lexicon/types/app/bsky/notification/declaration.js.map +1 -1
- package/dist/lexicon/types/chat/bsky/actor/declaration.d.ts +4 -3
- package/dist/lexicon/types/chat/bsky/actor/declaration.d.ts.map +1 -1
- package/dist/lexicon/types/chat/bsky/actor/declaration.js +9 -7
- package/dist/lexicon/types/chat/bsky/actor/declaration.js.map +1 -1
- package/dist/lexicon/types/com/atproto/lexicon/schema.d.ts +4 -3
- package/dist/lexicon/types/com/atproto/lexicon/schema.d.ts.map +1 -1
- package/dist/lexicon/types/com/atproto/lexicon/schema.js +9 -7
- package/dist/lexicon/types/com/atproto/lexicon/schema.js.map +1 -1
- package/dist/lexicon/types/tools/ozone/moderation/defs.d.ts +35 -0
- package/dist/lexicon/types/tools/ozone/moderation/defs.d.ts.map +1 -1
- package/dist/lexicon/types/tools/ozone/moderation/defs.js +9 -0
- package/dist/lexicon/types/tools/ozone/moderation/defs.js.map +1 -1
- package/dist/lexicon/types/tools/ozone/moderation/queryEvents.d.ts +2 -0
- package/dist/lexicon/types/tools/ozone/moderation/queryEvents.d.ts.map +1 -1
- package/dist/lexicon/types/tools/ozone/moderation/queryEvents.js.map +1 -1
- package/dist/lexicon/types/tools/ozone/moderation/queryStatuses.d.ts +2 -0
- package/dist/lexicon/types/tools/ozone/moderation/queryStatuses.d.ts.map +1 -1
- package/dist/lexicon/types/tools/ozone/moderation/queryStatuses.js.map +1 -1
- package/dist/mod-service/index.d.ts +9 -3
- package/dist/mod-service/index.d.ts.map +1 -1
- package/dist/mod-service/index.js +58 -5
- package/dist/mod-service/index.js.map +1 -1
- package/dist/mod-service/status.d.ts +44 -2
- package/dist/mod-service/status.d.ts.map +1 -1
- package/dist/mod-service/status.js +7 -0
- package/dist/mod-service/status.js.map +1 -1
- package/dist/mod-service/strike.d.ts +19 -0
- package/dist/mod-service/strike.d.ts.map +1 -0
- package/dist/mod-service/strike.js +86 -0
- package/dist/mod-service/strike.js.map +1 -0
- package/dist/mod-service/types.d.ts +4 -0
- package/dist/mod-service/types.d.ts.map +1 -1
- package/dist/mod-service/types.js.map +1 -1
- package/dist/mod-service/views.d.ts.map +1 -1
- package/dist/mod-service/views.js +20 -4
- package/dist/mod-service/views.js.map +1 -1
- package/dist/setting/constants.d.ts +1 -0
- package/dist/setting/constants.d.ts.map +1 -1
- package/dist/setting/constants.js +2 -1
- package/dist/setting/constants.js.map +1 -1
- package/dist/setting/validators.d.ts.map +1 -1
- package/dist/setting/validators.js +179 -0
- package/dist/setting/validators.js.map +1 -1
- package/package.json +4 -4
- package/src/api/moderation/queryEvents.ts +2 -0
- package/src/context.ts +20 -11
- package/src/daemon/context.ts +15 -1
- package/src/daemon/index.ts +1 -0
- package/src/daemon/strike-expiry-processor.ts +111 -0
- package/src/db/migrations/20251008T120000000Z-add-strike-system.ts +87 -0
- package/src/db/migrations/index.ts +1 -0
- package/src/db/schema/account_strike.ts +13 -0
- package/src/db/schema/index.ts +4 -0
- package/src/db/schema/job_cursor.ts +13 -0
- package/src/db/schema/moderation_event.ts +3 -0
- package/src/lexicon/lexicons.ts +103 -0
- package/src/lexicon/types/app/bsky/actor/profile.ts +12 -6
- package/src/lexicon/types/app/bsky/actor/status.ts +12 -6
- package/src/lexicon/types/app/bsky/feed/generator.ts +12 -6
- package/src/lexicon/types/app/bsky/feed/like.ts +12 -6
- package/src/lexicon/types/app/bsky/feed/post.ts +12 -6
- package/src/lexicon/types/app/bsky/feed/postgate.ts +12 -6
- package/src/lexicon/types/app/bsky/feed/repost.ts +12 -6
- package/src/lexicon/types/app/bsky/feed/threadgate.ts +12 -6
- package/src/lexicon/types/app/bsky/graph/block.ts +12 -6
- package/src/lexicon/types/app/bsky/graph/follow.ts +12 -6
- package/src/lexicon/types/app/bsky/graph/list.ts +12 -6
- package/src/lexicon/types/app/bsky/graph/listblock.ts +12 -6
- package/src/lexicon/types/app/bsky/graph/listitem.ts +12 -6
- package/src/lexicon/types/app/bsky/graph/starterpack.ts +12 -6
- package/src/lexicon/types/app/bsky/graph/verification.ts +12 -6
- package/src/lexicon/types/app/bsky/labeler/service.ts +12 -6
- package/src/lexicon/types/app/bsky/notification/declaration.ts +12 -6
- package/src/lexicon/types/chat/bsky/actor/declaration.ts +12 -6
- package/src/lexicon/types/com/atproto/lexicon/schema.ts +12 -6
- package/src/lexicon/types/tools/ozone/moderation/defs.ts +44 -0
- package/src/lexicon/types/tools/ozone/moderation/queryEvents.ts +2 -0
- package/src/lexicon/types/tools/ozone/moderation/queryStatuses.ts +2 -0
- package/src/mod-service/index.ts +69 -2
- package/src/mod-service/status.ts +9 -0
- package/src/mod-service/strike.ts +96 -0
- package/src/mod-service/types.ts +6 -0
- package/src/mod-service/views.ts +25 -4
- package/src/setting/constants.ts +1 -0
- package/src/setting/validators.ts +231 -1
- package/tests/__snapshots__/account-strikes.test.ts.snap +159 -0
- package/tests/account-strikes.test.ts +184 -0
- package/tests/query-labels.test.ts +1 -0
- package/tests/strike-expiry-processor.test.ts +299 -0
- package/tsconfig.build.tsbuildinfo +1 -1
- package/tsconfig.tests.tsbuildinfo +1 -1
package/src/context.ts
CHANGED
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
ModerationServiceProfile,
|
|
22
22
|
ModerationServiceProfileCreator,
|
|
23
23
|
} from './mod-service/profile'
|
|
24
|
+
import { StrikeService, StrikeServiceCreator } from './mod-service/strike'
|
|
24
25
|
import {
|
|
25
26
|
SafelinkRuleService,
|
|
26
27
|
SafelinkRuleServiceCreator,
|
|
@@ -59,6 +60,7 @@ export type AppContextOptions = {
|
|
|
59
60
|
scheduledActionService: ScheduledActionServiceCreator
|
|
60
61
|
setService: SetServiceCreator
|
|
61
62
|
settingService: SettingServiceCreator
|
|
63
|
+
strikeService: StrikeServiceCreator
|
|
62
64
|
teamService: TeamServiceCreator
|
|
63
65
|
appviewAgent: AtpAgent
|
|
64
66
|
pdsAgent: AtpAgent | undefined
|
|
@@ -132,17 +134,6 @@ export class AppContext {
|
|
|
132
134
|
appview: cfg.appview.pushEvents ? cfg.appview : undefined,
|
|
133
135
|
pds: cfg.pds ?? undefined,
|
|
134
136
|
})
|
|
135
|
-
const modService = ModerationService.creator(
|
|
136
|
-
signingKey,
|
|
137
|
-
signingKeyId,
|
|
138
|
-
cfg,
|
|
139
|
-
backgroundQueue,
|
|
140
|
-
idResolver,
|
|
141
|
-
eventPusher,
|
|
142
|
-
appviewAgent,
|
|
143
|
-
createAuthHeaders,
|
|
144
|
-
overrides?.imgInvalidator,
|
|
145
|
-
)
|
|
146
137
|
|
|
147
138
|
const communicationTemplateService = CommunicationTemplateService.creator()
|
|
148
139
|
const safelinkRuleService = SafelinkRuleService.creator()
|
|
@@ -154,12 +145,25 @@ export class AppContext {
|
|
|
154
145
|
)
|
|
155
146
|
const setService = SetService.creator()
|
|
156
147
|
const settingService = SettingService.creator()
|
|
148
|
+
const strikeService = StrikeService.creator()
|
|
157
149
|
const verificationService = VerificationService.creator()
|
|
158
150
|
const verificationIssuer = VerificationIssuer.creator()
|
|
159
151
|
const moderationServiceProfile = ModerationServiceProfile.creator(
|
|
160
152
|
cfg,
|
|
161
153
|
appviewAgent,
|
|
162
154
|
)
|
|
155
|
+
const modService = ModerationService.creator(
|
|
156
|
+
signingKey,
|
|
157
|
+
signingKeyId,
|
|
158
|
+
cfg,
|
|
159
|
+
backgroundQueue,
|
|
160
|
+
idResolver,
|
|
161
|
+
eventPusher,
|
|
162
|
+
appviewAgent,
|
|
163
|
+
createAuthHeaders,
|
|
164
|
+
strikeService,
|
|
165
|
+
overrides?.imgInvalidator,
|
|
166
|
+
)
|
|
163
167
|
|
|
164
168
|
const sequencer = new Sequencer(modService(db))
|
|
165
169
|
|
|
@@ -181,6 +185,7 @@ export class AppContext {
|
|
|
181
185
|
teamService,
|
|
182
186
|
setService,
|
|
183
187
|
settingService,
|
|
188
|
+
strikeService,
|
|
184
189
|
appviewAgent,
|
|
185
190
|
pdsAgent,
|
|
186
191
|
chatAgent,
|
|
@@ -248,6 +253,10 @@ export class AppContext {
|
|
|
248
253
|
return this.opts.settingService
|
|
249
254
|
}
|
|
250
255
|
|
|
256
|
+
get strikeService(): StrikeServiceCreator {
|
|
257
|
+
return this.opts.strikeService
|
|
258
|
+
}
|
|
259
|
+
|
|
251
260
|
get verificationService(): VerificationServiceCreator {
|
|
252
261
|
return this.opts.verificationService
|
|
253
262
|
}
|
package/src/daemon/context.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { BackgroundQueue } from '../background'
|
|
|
7
7
|
import { OzoneConfig, OzoneSecrets } from '../config'
|
|
8
8
|
import { Database } from '../db'
|
|
9
9
|
import { ModerationService } from '../mod-service'
|
|
10
|
+
import { StrikeService } from '../mod-service/strike'
|
|
10
11
|
import { ScheduledActionService } from '../scheduled-action/service'
|
|
11
12
|
import { SettingService } from '../setting/service'
|
|
12
13
|
import { TeamService } from '../team'
|
|
@@ -15,6 +16,7 @@ import { EventPusher } from './event-pusher'
|
|
|
15
16
|
import { EventReverser } from './event-reverser'
|
|
16
17
|
import { MaterializedViewRefresher } from './materialized-view-refresher'
|
|
17
18
|
import { ScheduledActionProcessor } from './scheduled-action-processor'
|
|
19
|
+
import { StrikeExpiryProcessor } from './strike-expiry-processor'
|
|
18
20
|
import { TeamProfileSynchronizer } from './team-profile-synchronizer'
|
|
19
21
|
import { VerificationListener } from './verification-listener'
|
|
20
22
|
|
|
@@ -28,6 +30,7 @@ export type DaemonContextOptions = {
|
|
|
28
30
|
materializedViewRefresher: MaterializedViewRefresher
|
|
29
31
|
teamProfileSynchronizer: TeamProfileSynchronizer
|
|
30
32
|
scheduledActionProcessor: ScheduledActionProcessor
|
|
33
|
+
strikeExpiryProcessor: StrikeExpiryProcessor
|
|
31
34
|
verificationListener?: VerificationListener
|
|
32
35
|
}
|
|
33
36
|
|
|
@@ -66,6 +69,8 @@ export class DaemonContext {
|
|
|
66
69
|
|
|
67
70
|
const backgroundQueue = new BackgroundQueue(db)
|
|
68
71
|
|
|
72
|
+
const settingService = SettingService.creator()
|
|
73
|
+
const strikeService = StrikeService.creator()
|
|
69
74
|
const modService = ModerationService.creator(
|
|
70
75
|
signingKey,
|
|
71
76
|
signingKeyId,
|
|
@@ -75,8 +80,8 @@ export class DaemonContext {
|
|
|
75
80
|
eventPusher,
|
|
76
81
|
appviewAgent,
|
|
77
82
|
createAuthHeaders,
|
|
83
|
+
strikeService,
|
|
78
84
|
)
|
|
79
|
-
const settingService = SettingService.creator()
|
|
80
85
|
const scheduledActionService = ScheduledActionService.creator()
|
|
81
86
|
const teamService = TeamService.creator(
|
|
82
87
|
appviewAgent,
|
|
@@ -104,6 +109,8 @@ export class DaemonContext {
|
|
|
104
109
|
scheduledActionService,
|
|
105
110
|
)
|
|
106
111
|
|
|
112
|
+
const strikeExpiryProcessor = new StrikeExpiryProcessor(db, strikeService)
|
|
113
|
+
|
|
107
114
|
// Only spawn the listener if verifier config exists and a jetstream URL is provided
|
|
108
115
|
const verificationListener =
|
|
109
116
|
cfg.verifier && cfg.jetstreamUrl
|
|
@@ -124,6 +131,7 @@ export class DaemonContext {
|
|
|
124
131
|
materializedViewRefresher,
|
|
125
132
|
teamProfileSynchronizer,
|
|
126
133
|
scheduledActionProcessor,
|
|
134
|
+
strikeExpiryProcessor,
|
|
127
135
|
verificationListener,
|
|
128
136
|
...(overrides ?? {}),
|
|
129
137
|
})
|
|
@@ -161,6 +169,10 @@ export class DaemonContext {
|
|
|
161
169
|
return this.opts.scheduledActionProcessor
|
|
162
170
|
}
|
|
163
171
|
|
|
172
|
+
get strikeExpiryProcessor(): StrikeExpiryProcessor {
|
|
173
|
+
return this.opts.strikeExpiryProcessor
|
|
174
|
+
}
|
|
175
|
+
|
|
164
176
|
get verificationListener(): VerificationListener | undefined {
|
|
165
177
|
return this.opts.verificationListener
|
|
166
178
|
}
|
|
@@ -171,6 +183,7 @@ export class DaemonContext {
|
|
|
171
183
|
this.materializedViewRefresher.start()
|
|
172
184
|
this.teamProfileSynchronizer.start()
|
|
173
185
|
this.scheduledActionProcessor.start()
|
|
186
|
+
this.strikeExpiryProcessor.start()
|
|
174
187
|
this.verificationListener?.start()
|
|
175
188
|
}
|
|
176
189
|
|
|
@@ -189,6 +202,7 @@ export class DaemonContext {
|
|
|
189
202
|
this.materializedViewRefresher.destroy(),
|
|
190
203
|
this.teamProfileSynchronizer.destroy(),
|
|
191
204
|
this.scheduledActionProcessor.destroy(),
|
|
205
|
+
this.strikeExpiryProcessor.destroy(),
|
|
192
206
|
this.verificationListener?.stop(),
|
|
193
207
|
])
|
|
194
208
|
} finally {
|
package/src/daemon/index.ts
CHANGED
|
@@ -6,6 +6,7 @@ export { EventPusher } from './event-pusher'
|
|
|
6
6
|
export { BlobDiverter } from './blob-diverter'
|
|
7
7
|
export { EventReverser } from './event-reverser'
|
|
8
8
|
export { ScheduledActionProcessor } from './scheduled-action-processor'
|
|
9
|
+
export { StrikeExpiryProcessor } from './strike-expiry-processor'
|
|
9
10
|
|
|
10
11
|
export class OzoneDaemon {
|
|
11
12
|
constructor(public ctx: DaemonContext) {}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { HOUR } from '@atproto/common'
|
|
2
|
+
import { Database } from '../db'
|
|
3
|
+
import { dbLogger } from '../logger'
|
|
4
|
+
import { StrikeServiceCreator } from '../mod-service/strike'
|
|
5
|
+
|
|
6
|
+
const JOB_NAME = 'strike_expiry'
|
|
7
|
+
|
|
8
|
+
export class StrikeExpiryProcessor {
|
|
9
|
+
destroyed = false
|
|
10
|
+
processingPromise: Promise<void> = Promise.resolve()
|
|
11
|
+
timer?: NodeJS.Timeout
|
|
12
|
+
|
|
13
|
+
constructor(
|
|
14
|
+
private db: Database,
|
|
15
|
+
private strikeServiceCreator: StrikeServiceCreator,
|
|
16
|
+
) {}
|
|
17
|
+
|
|
18
|
+
start() {
|
|
19
|
+
this.initializeCursor().then(() => this.poll())
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
poll() {
|
|
23
|
+
if (this.destroyed) return
|
|
24
|
+
this.processingPromise = this.processExpiredStrikes()
|
|
25
|
+
.catch((err) =>
|
|
26
|
+
dbLogger.error({ err }, 'strike expiry processing errored'),
|
|
27
|
+
)
|
|
28
|
+
.finally(() => {
|
|
29
|
+
this.timer = setTimeout(() => this.poll(), getInterval())
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async destroy() {
|
|
34
|
+
this.destroyed = true
|
|
35
|
+
if (this.timer) {
|
|
36
|
+
clearTimeout(this.timer)
|
|
37
|
+
this.timer = undefined
|
|
38
|
+
}
|
|
39
|
+
await this.processingPromise
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async initializeCursor() {
|
|
43
|
+
await this.db.db
|
|
44
|
+
.insertInto('job_cursor')
|
|
45
|
+
.values({
|
|
46
|
+
job: JOB_NAME,
|
|
47
|
+
cursor: null,
|
|
48
|
+
})
|
|
49
|
+
.onConflict((oc) => oc.doNothing())
|
|
50
|
+
.execute()
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async getCursor(): Promise<string | null> {
|
|
54
|
+
const entry = await this.db.db
|
|
55
|
+
.selectFrom('job_cursor')
|
|
56
|
+
.select('cursor')
|
|
57
|
+
.where('job', '=', JOB_NAME)
|
|
58
|
+
.executeTakeFirst()
|
|
59
|
+
|
|
60
|
+
return entry?.cursor || null
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async updateCursor(cursor: string): Promise<void> {
|
|
64
|
+
await this.db.db
|
|
65
|
+
.updateTable('job_cursor')
|
|
66
|
+
.set({ cursor })
|
|
67
|
+
.where('job', '=', JOB_NAME)
|
|
68
|
+
.execute()
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async processExpiredStrikes() {
|
|
72
|
+
const now = new Date()
|
|
73
|
+
const strikeService = this.strikeServiceCreator(this.db)
|
|
74
|
+
const lastProcessedAt = await this.getCursor()
|
|
75
|
+
const affectedSubjects = await strikeService.getExpiredStrikeSubjects(
|
|
76
|
+
lastProcessedAt || undefined,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
if (!affectedSubjects.length) {
|
|
80
|
+
dbLogger.info('no expired strikes to process')
|
|
81
|
+
await this.updateCursor(now.toISOString())
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
dbLogger.info(
|
|
86
|
+
{ count: affectedSubjects.length },
|
|
87
|
+
'processing subjects with expired strikes',
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
await Promise.all(
|
|
91
|
+
affectedSubjects.map(({ subjectDid }) => {
|
|
92
|
+
return strikeService.updateSubjectStrikeCount(subjectDid)
|
|
93
|
+
}),
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
await this.updateCursor(now.toISOString())
|
|
97
|
+
|
|
98
|
+
dbLogger.info(
|
|
99
|
+
{ processed: affectedSubjects.length },
|
|
100
|
+
'strike expiry processing completed',
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const getInterval = (): number => {
|
|
106
|
+
// Run every hour, synchronized to the hour boundary
|
|
107
|
+
const now = Date.now()
|
|
108
|
+
const intervalMs = HOUR
|
|
109
|
+
const nextIteration = Math.ceil(now / intervalMs)
|
|
110
|
+
return nextIteration * intervalMs - now
|
|
111
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { Kysely, sql } from 'kysely'
|
|
2
|
+
|
|
3
|
+
export async function up(db: Kysely<unknown>): Promise<void> {
|
|
4
|
+
await db.schema
|
|
5
|
+
.alterTable('moderation_event')
|
|
6
|
+
.addColumn('severityLevel', 'varchar')
|
|
7
|
+
.execute()
|
|
8
|
+
await db.schema
|
|
9
|
+
.alterTable('moderation_event')
|
|
10
|
+
.addColumn('strikeCount', 'integer')
|
|
11
|
+
.execute()
|
|
12
|
+
await db.schema
|
|
13
|
+
.alterTable('moderation_event')
|
|
14
|
+
.addColumn('strikeExpiresAt', 'varchar')
|
|
15
|
+
.execute()
|
|
16
|
+
|
|
17
|
+
await db.schema
|
|
18
|
+
.createTable('account_strike')
|
|
19
|
+
.addColumn('did', 'text', (col) => col.primaryKey())
|
|
20
|
+
.addColumn('firstStrikeAt', 'varchar')
|
|
21
|
+
.addColumn('lastStrikeAt', 'varchar')
|
|
22
|
+
.addColumn('activeStrikeCount', 'integer', (col) =>
|
|
23
|
+
col.notNull().defaultTo(0),
|
|
24
|
+
)
|
|
25
|
+
.addColumn('totalStrikeCount', 'integer', (col) =>
|
|
26
|
+
col.notNull().defaultTo(0),
|
|
27
|
+
)
|
|
28
|
+
.execute()
|
|
29
|
+
|
|
30
|
+
await db.schema
|
|
31
|
+
.createTable('job_cursor')
|
|
32
|
+
.addColumn('job', 'text', (col) => col.primaryKey())
|
|
33
|
+
.addColumn('cursor', 'text')
|
|
34
|
+
.addColumn('updatedAt', 'text', (col) =>
|
|
35
|
+
col.defaultTo(sql`now()`).notNull(),
|
|
36
|
+
)
|
|
37
|
+
.execute()
|
|
38
|
+
|
|
39
|
+
// This supports fast look up for background job that aggregates strike data per subjectDid
|
|
40
|
+
await db.schema
|
|
41
|
+
.createIndex('moderation_event_subject_did_strike_count_idx')
|
|
42
|
+
.on('moderation_event')
|
|
43
|
+
.columns(['subjectDid', 'strikeCount'])
|
|
44
|
+
.execute()
|
|
45
|
+
|
|
46
|
+
// This supports fast lookup in the background job that needs to find strikes that have expired
|
|
47
|
+
await sql`
|
|
48
|
+
CREATE INDEX moderation_event_strike_expires_at_strike_count_idx
|
|
49
|
+
ON moderation_event ("strikeExpiresAt", "strikeCount")
|
|
50
|
+
WHERE "strikeExpiresAt" IS NOT NULL AND "strikeCount" IS NOT NULL
|
|
51
|
+
`.execute(db)
|
|
52
|
+
|
|
53
|
+
// for sorting and filtering by active strike count
|
|
54
|
+
await db.schema
|
|
55
|
+
.createIndex('account_strike_active_count_idx')
|
|
56
|
+
.on('account_strike')
|
|
57
|
+
.column('activeStrikeCount')
|
|
58
|
+
.execute()
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function down(db: Kysely<unknown>): Promise<void> {
|
|
62
|
+
await db.schema
|
|
63
|
+
.dropIndex('moderation_event_subject_did_strike_count_idx')
|
|
64
|
+
.execute()
|
|
65
|
+
await db.schema
|
|
66
|
+
.dropIndex('moderation_event_strike_expires_at_strike_count_idx')
|
|
67
|
+
.execute()
|
|
68
|
+
await db.schema.dropIndex('account_strike_active_count_idx').execute()
|
|
69
|
+
|
|
70
|
+
await db.schema.dropTable('account_strike').execute()
|
|
71
|
+
await db.schema.dropTable('job_cursor').execute()
|
|
72
|
+
|
|
73
|
+
await db.schema
|
|
74
|
+
.alterTable('moderation_event')
|
|
75
|
+
.dropColumn('severityLevel')
|
|
76
|
+
.execute()
|
|
77
|
+
|
|
78
|
+
await db.schema
|
|
79
|
+
.alterTable('moderation_event')
|
|
80
|
+
.dropColumn('strikeCount')
|
|
81
|
+
.execute()
|
|
82
|
+
|
|
83
|
+
await db.schema
|
|
84
|
+
.alterTable('moderation_event')
|
|
85
|
+
.dropColumn('strikeExpiresAt')
|
|
86
|
+
.execute()
|
|
87
|
+
}
|
|
@@ -32,3 +32,4 @@ export * as _20250715T000000000Z from './20250715T000000000Z-add-mod-event-exter
|
|
|
32
32
|
export * as _20250718T150931000Z from './20250718T150931000Z-update-appeal-reason-stats'
|
|
33
33
|
export * as _20250813T000000000Z from './20250813T000000000Z-mod-tool-batch-id-index'
|
|
34
34
|
export * as _20250923T000000000Z from './20250923T000000000Z-scheduled-actions'
|
|
35
|
+
export * as _20251008T120000000Z from './20251008T120000000Z-add-strike-system'
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export const accountStrikeTableName = 'account_strike'
|
|
2
|
+
|
|
3
|
+
export interface AccountStrike {
|
|
4
|
+
did: string // Primary key
|
|
5
|
+
firstStrikeAt: string | null
|
|
6
|
+
lastStrikeAt: string | null
|
|
7
|
+
activeStrikeCount: number
|
|
8
|
+
totalStrikeCount: number
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type PartialDB = {
|
|
12
|
+
[accountStrikeTableName]: AccountStrike
|
|
13
|
+
}
|
package/src/db/schema/index.ts
CHANGED
|
@@ -2,9 +2,11 @@ import { Kysely } from 'kysely'
|
|
|
2
2
|
import * as accountEventsStats from './account_events_stats'
|
|
3
3
|
import * as accountRecordEventsStats from './account_record_events_stats'
|
|
4
4
|
import * as accountRecordStatusStats from './account_record_status_stats'
|
|
5
|
+
import * as accountStrike from './account_strike'
|
|
5
6
|
import * as blobPushEvent from './blob_push_event'
|
|
6
7
|
import * as communicationTemplate from './communication_template'
|
|
7
8
|
import * as firehoseCursor from './firehose_cursor'
|
|
9
|
+
import * as jobCursor from './job_cursor'
|
|
8
10
|
import * as label from './label'
|
|
9
11
|
import * as member from './member'
|
|
10
12
|
import * as modEvent from './moderation_event'
|
|
@@ -34,8 +36,10 @@ export type DatabaseSchemaType = modEvent.PartialDB &
|
|
|
34
36
|
recordEventsStats.PartialDB &
|
|
35
37
|
accountRecordEventsStats.PartialDB &
|
|
36
38
|
accountRecordStatusStats.PartialDB &
|
|
39
|
+
accountStrike.PartialDB &
|
|
37
40
|
verification.PartialDB &
|
|
38
41
|
firehoseCursor.PartialDB &
|
|
42
|
+
jobCursor.PartialDB &
|
|
39
43
|
safelink.PartialDB &
|
|
40
44
|
scheduledAction.PartialDB
|
|
41
45
|
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Generated } from 'kysely'
|
|
2
|
+
|
|
3
|
+
export const jobCursorTableName = 'job_cursor'
|
|
4
|
+
|
|
5
|
+
export interface JobCursor {
|
|
6
|
+
job: string
|
|
7
|
+
cursor: string | null
|
|
8
|
+
updatedAt: Generated<string>
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type PartialDB = {
|
|
12
|
+
[jobCursorTableName]: JobCursor
|
|
13
|
+
}
|
|
@@ -48,6 +48,9 @@ export interface ModerationEvent {
|
|
|
48
48
|
legacyRefId: number | null
|
|
49
49
|
modTool: { name: string; meta?: { [_ in string]: unknown } } | null
|
|
50
50
|
externalId: string | null
|
|
51
|
+
severityLevel: string | null
|
|
52
|
+
strikeCount: number | null
|
|
53
|
+
strikeExpiresAt: string | null
|
|
51
54
|
}
|
|
52
55
|
|
|
53
56
|
export type PartialDB = {
|
package/src/lexicon/lexicons.ts
CHANGED
|
@@ -14630,6 +14630,12 @@ export const schemaDict = {
|
|
|
14630
14630
|
type: 'ref',
|
|
14631
14631
|
ref: 'lex:tools.ozone.moderation.defs#recordsStats',
|
|
14632
14632
|
},
|
|
14633
|
+
accountStrike: {
|
|
14634
|
+
description:
|
|
14635
|
+
'Strike information for the account (account-level only)',
|
|
14636
|
+
type: 'ref',
|
|
14637
|
+
ref: 'lex:tools.ozone.moderation.defs#accountStrike',
|
|
14638
|
+
},
|
|
14633
14639
|
ageAssuranceState: {
|
|
14634
14640
|
type: 'string',
|
|
14635
14641
|
description: 'Current age assurance state of the subject.',
|
|
@@ -14742,6 +14748,32 @@ export const schemaDict = {
|
|
|
14742
14748
|
},
|
|
14743
14749
|
},
|
|
14744
14750
|
},
|
|
14751
|
+
accountStrike: {
|
|
14752
|
+
description: 'Strike information for an account',
|
|
14753
|
+
type: 'object',
|
|
14754
|
+
properties: {
|
|
14755
|
+
activeStrikeCount: {
|
|
14756
|
+
description:
|
|
14757
|
+
'Current number of active strikes (excluding expired strikes)',
|
|
14758
|
+
type: 'integer',
|
|
14759
|
+
},
|
|
14760
|
+
totalStrikeCount: {
|
|
14761
|
+
description:
|
|
14762
|
+
'Total number of strikes ever received (including expired strikes)',
|
|
14763
|
+
type: 'integer',
|
|
14764
|
+
},
|
|
14765
|
+
firstStrikeAt: {
|
|
14766
|
+
description: 'Timestamp of the first strike received',
|
|
14767
|
+
type: 'string',
|
|
14768
|
+
format: 'datetime',
|
|
14769
|
+
},
|
|
14770
|
+
lastStrikeAt: {
|
|
14771
|
+
description: 'Timestamp of the most recent strike received',
|
|
14772
|
+
type: 'string',
|
|
14773
|
+
format: 'datetime',
|
|
14774
|
+
},
|
|
14775
|
+
},
|
|
14776
|
+
},
|
|
14745
14777
|
subjectReviewState: {
|
|
14746
14778
|
type: 'string',
|
|
14747
14779
|
knownValues: [
|
|
@@ -14797,6 +14829,22 @@ export const schemaDict = {
|
|
|
14797
14829
|
description:
|
|
14798
14830
|
'Names/Keywords of the policies that drove the decision.',
|
|
14799
14831
|
},
|
|
14832
|
+
severityLevel: {
|
|
14833
|
+
type: 'string',
|
|
14834
|
+
description:
|
|
14835
|
+
"Severity level of the violation (e.g., 'sev-0', 'sev-1', 'sev-2', etc.).",
|
|
14836
|
+
},
|
|
14837
|
+
strikeCount: {
|
|
14838
|
+
type: 'integer',
|
|
14839
|
+
description:
|
|
14840
|
+
'Number of strikes to assign to the user for this violation.',
|
|
14841
|
+
},
|
|
14842
|
+
strikeExpiresAt: {
|
|
14843
|
+
type: 'string',
|
|
14844
|
+
format: 'datetime',
|
|
14845
|
+
description:
|
|
14846
|
+
'When the strike should expire. If not provided, the strike never expires.',
|
|
14847
|
+
},
|
|
14800
14848
|
},
|
|
14801
14849
|
},
|
|
14802
14850
|
modEventReverseTakedown: {
|
|
@@ -14807,6 +14855,25 @@ export const schemaDict = {
|
|
|
14807
14855
|
type: 'string',
|
|
14808
14856
|
description: 'Describe reasoning behind the reversal.',
|
|
14809
14857
|
},
|
|
14858
|
+
policies: {
|
|
14859
|
+
type: 'array',
|
|
14860
|
+
maxLength: 5,
|
|
14861
|
+
items: {
|
|
14862
|
+
type: 'string',
|
|
14863
|
+
},
|
|
14864
|
+
description:
|
|
14865
|
+
'Names/Keywords of the policy infraction for which takedown is being reversed.',
|
|
14866
|
+
},
|
|
14867
|
+
severityLevel: {
|
|
14868
|
+
type: 'string',
|
|
14869
|
+
description:
|
|
14870
|
+
"Severity level of the violation. Usually set from the last policy infraction's severity.",
|
|
14871
|
+
},
|
|
14872
|
+
strikeCount: {
|
|
14873
|
+
type: 'integer',
|
|
14874
|
+
description:
|
|
14875
|
+
"Number of strikes to subtract from the user's strike count. Usually set from the last policy infraction's severity.",
|
|
14876
|
+
},
|
|
14810
14877
|
},
|
|
14811
14878
|
},
|
|
14812
14879
|
modEventResolveAppeal: {
|
|
@@ -15050,6 +15117,31 @@ export const schemaDict = {
|
|
|
15050
15117
|
type: 'string',
|
|
15051
15118
|
description: 'Additional comment about the outgoing comm.',
|
|
15052
15119
|
},
|
|
15120
|
+
policies: {
|
|
15121
|
+
type: 'array',
|
|
15122
|
+
maxLength: 5,
|
|
15123
|
+
items: {
|
|
15124
|
+
type: 'string',
|
|
15125
|
+
},
|
|
15126
|
+
description:
|
|
15127
|
+
'Names/Keywords of the policies that necessitated the email.',
|
|
15128
|
+
},
|
|
15129
|
+
severityLevel: {
|
|
15130
|
+
type: 'string',
|
|
15131
|
+
description:
|
|
15132
|
+
"Severity level of the violation. Normally 'sev-1' that adds strike on repeat offense",
|
|
15133
|
+
},
|
|
15134
|
+
strikeCount: {
|
|
15135
|
+
type: 'integer',
|
|
15136
|
+
description:
|
|
15137
|
+
'Number of strikes to assign to the user for this violation. Normally 0 as an indicator of a warning and only added as a strike on a repeat offense.',
|
|
15138
|
+
},
|
|
15139
|
+
strikeExpiresAt: {
|
|
15140
|
+
type: 'string',
|
|
15141
|
+
format: 'datetime',
|
|
15142
|
+
description:
|
|
15143
|
+
'When the strike should expire. If not provided, the strike never expires.',
|
|
15144
|
+
},
|
|
15053
15145
|
},
|
|
15054
15146
|
},
|
|
15055
15147
|
modEventDivert: {
|
|
@@ -16448,6 +16540,11 @@ export const schemaDict = {
|
|
|
16448
16540
|
'blocked',
|
|
16449
16541
|
],
|
|
16450
16542
|
},
|
|
16543
|
+
withStrike: {
|
|
16544
|
+
type: 'boolean',
|
|
16545
|
+
description:
|
|
16546
|
+
'If specified, only events where strikeCount value is set are returned.',
|
|
16547
|
+
},
|
|
16451
16548
|
cursor: {
|
|
16452
16549
|
type: 'string',
|
|
16453
16550
|
},
|
|
@@ -16678,6 +16775,12 @@ export const schemaDict = {
|
|
|
16678
16775
|
description:
|
|
16679
16776
|
'If specified, only subjects that have priority score value above the given value will be returned.',
|
|
16680
16777
|
},
|
|
16778
|
+
minStrikeCount: {
|
|
16779
|
+
type: 'integer',
|
|
16780
|
+
minimum: 1,
|
|
16781
|
+
description:
|
|
16782
|
+
'If specified, only subjects that belong to an account that has at least this many active strikes will be returned.',
|
|
16783
|
+
},
|
|
16681
16784
|
ageAssuranceState: {
|
|
16682
16785
|
type: 'string',
|
|
16683
16786
|
description:
|
|
@@ -16,7 +16,7 @@ const is$typed = _is$typed,
|
|
|
16
16
|
validate = _validate
|
|
17
17
|
const id = 'app.bsky.actor.profile'
|
|
18
18
|
|
|
19
|
-
export interface
|
|
19
|
+
export interface Main {
|
|
20
20
|
$type: 'app.bsky.actor.profile'
|
|
21
21
|
displayName?: string
|
|
22
22
|
/** Free-form profile description text. */
|
|
@@ -35,12 +35,18 @@ export interface Record {
|
|
|
35
35
|
[k: string]: unknown
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
const
|
|
38
|
+
const hashMain = 'main'
|
|
39
39
|
|
|
40
|
-
export function
|
|
41
|
-
return is$typed(v, id,
|
|
40
|
+
export function isMain<V>(v: V) {
|
|
41
|
+
return is$typed(v, id, hashMain)
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
export function
|
|
45
|
-
return validate<
|
|
44
|
+
export function validateMain<V>(v: V) {
|
|
45
|
+
return validate<Main & V>(v, id, hashMain, true)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export {
|
|
49
|
+
type Main as Record,
|
|
50
|
+
isMain as isRecord,
|
|
51
|
+
validateMain as validateRecord,
|
|
46
52
|
}
|
|
@@ -15,7 +15,7 @@ const is$typed = _is$typed,
|
|
|
15
15
|
validate = _validate
|
|
16
16
|
const id = 'app.bsky.actor.status'
|
|
17
17
|
|
|
18
|
-
export interface
|
|
18
|
+
export interface Main {
|
|
19
19
|
$type: 'app.bsky.actor.status'
|
|
20
20
|
/** The status for the account. */
|
|
21
21
|
status: 'app.bsky.actor.status#live' | (string & {})
|
|
@@ -26,14 +26,20 @@ export interface Record {
|
|
|
26
26
|
[k: string]: unknown
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
const
|
|
29
|
+
const hashMain = 'main'
|
|
30
30
|
|
|
31
|
-
export function
|
|
32
|
-
return is$typed(v, id,
|
|
31
|
+
export function isMain<V>(v: V) {
|
|
32
|
+
return is$typed(v, id, hashMain)
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
export function
|
|
36
|
-
return validate<
|
|
35
|
+
export function validateMain<V>(v: V) {
|
|
36
|
+
return validate<Main & V>(v, id, hashMain, true)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export {
|
|
40
|
+
type Main as Record,
|
|
41
|
+
isMain as isRecord,
|
|
42
|
+
validateMain as validateRecord,
|
|
37
43
|
}
|
|
38
44
|
|
|
39
45
|
/** Advertises an account as currently offering live content. */
|
|
@@ -16,7 +16,7 @@ const is$typed = _is$typed,
|
|
|
16
16
|
validate = _validate
|
|
17
17
|
const id = 'app.bsky.feed.generator'
|
|
18
18
|
|
|
19
|
-
export interface
|
|
19
|
+
export interface Main {
|
|
20
20
|
$type: 'app.bsky.feed.generator'
|
|
21
21
|
did: string
|
|
22
22
|
displayName: string
|
|
@@ -34,12 +34,18 @@ export interface Record {
|
|
|
34
34
|
[k: string]: unknown
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
const
|
|
37
|
+
const hashMain = 'main'
|
|
38
38
|
|
|
39
|
-
export function
|
|
40
|
-
return is$typed(v, id,
|
|
39
|
+
export function isMain<V>(v: V) {
|
|
40
|
+
return is$typed(v, id, hashMain)
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
export function
|
|
44
|
-
return validate<
|
|
43
|
+
export function validateMain<V>(v: V) {
|
|
44
|
+
return validate<Main & V>(v, id, hashMain, true)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export {
|
|
48
|
+
type Main as Record,
|
|
49
|
+
isMain as isRecord,
|
|
50
|
+
validateMain as validateRecord,
|
|
45
51
|
}
|