@atcute/bluesky-moderation 2.0.4 → 3.0.1

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 (60) hide show
  1. package/README.md +126 -128
  2. package/dist/_test-util/mock.d.ts +58 -0
  3. package/dist/_test-util/mock.d.ts.map +1 -0
  4. package/dist/_test-util/mock.js +109 -0
  5. package/dist/_test-util/mock.js.map +1 -0
  6. package/dist/_test-util/moderation-behavior.d.ts +57 -0
  7. package/dist/_test-util/moderation-behavior.d.ts.map +1 -0
  8. package/dist/_test-util/moderation-behavior.js +158 -0
  9. package/dist/_test-util/moderation-behavior.js.map +1 -0
  10. package/dist/behaviors.d.ts +21 -18
  11. package/dist/behaviors.d.ts.map +1 -1
  12. package/dist/behaviors.js +18 -21
  13. package/dist/behaviors.js.map +1 -1
  14. package/dist/decision.d.ts +21 -20
  15. package/dist/decision.d.ts.map +1 -1
  16. package/dist/decision.js +14 -16
  17. package/dist/decision.js.map +1 -1
  18. package/dist/index.d.ts +11 -11
  19. package/dist/internal/keyword-filter.d.ts +1 -1
  20. package/dist/internal/keyword-filter.d.ts.map +1 -1
  21. package/dist/keyword-filter.d.ts +6 -5
  22. package/dist/keyword-filter.d.ts.map +1 -1
  23. package/dist/keyword-filter.js +8 -7
  24. package/dist/keyword-filter.js.map +1 -1
  25. package/dist/label.d.ts +29 -25
  26. package/dist/label.d.ts.map +1 -1
  27. package/dist/label.js +24 -28
  28. package/dist/label.js.map +1 -1
  29. package/dist/subjects/feed-generator.d.ts +2 -2
  30. package/dist/subjects/feed-generator.d.ts.map +1 -1
  31. package/dist/subjects/list.d.ts +2 -2
  32. package/dist/subjects/list.d.ts.map +1 -1
  33. package/dist/subjects/notification.d.ts +2 -2
  34. package/dist/subjects/notification.d.ts.map +1 -1
  35. package/dist/subjects/post.d.ts +2 -2
  36. package/dist/subjects/post.d.ts.map +1 -1
  37. package/dist/subjects/post.js.map +1 -1
  38. package/dist/subjects/profile.d.ts +2 -2
  39. package/dist/subjects/profile.d.ts.map +1 -1
  40. package/dist/types.d.ts +2 -2
  41. package/dist/ui.d.ts +2 -2
  42. package/dist/ui.d.ts.map +1 -1
  43. package/dist/ui.js +4 -4
  44. package/dist/ui.js.map +1 -1
  45. package/lib/_test-util/mock.ts +214 -0
  46. package/lib/_test-util/moderation-behavior.ts +259 -0
  47. package/lib/behaviors.ts +21 -18
  48. package/lib/decision.ts +26 -26
  49. package/lib/index.ts +11 -11
  50. package/lib/internal/keyword-filter.ts +1 -1
  51. package/lib/keyword-filter.ts +9 -6
  52. package/lib/label.ts +32 -28
  53. package/lib/subjects/feed-generator.ts +4 -4
  54. package/lib/subjects/list.ts +4 -4
  55. package/lib/subjects/notification.ts +4 -4
  56. package/lib/subjects/post.ts +6 -7
  57. package/lib/subjects/profile.ts +3 -3
  58. package/lib/types.ts +2 -2
  59. package/lib/ui.ts +7 -7
  60. package/package.json +17 -13
@@ -0,0 +1,259 @@
1
+ import type { ComAtprotoLabelDefs } from '@atcute/atproto';
2
+
3
+ import { expect } from 'vitest';
4
+
5
+ import { LabelPreference, type DisplayRestrictions, type ModerationOptions } from '../index.ts';
6
+
7
+ import * as m from './mock.ts';
8
+
9
+ export type ModerationTestSuiteResultFlag = 'filter' | 'blur' | 'alert' | 'inform' | 'noOverride';
10
+
11
+ export interface ModerationTestSuiteScenario {
12
+ cfg: string;
13
+ subject: 'post' | 'profile' | 'userlist' | 'feedgen';
14
+ author: string;
15
+ quoteAuthor?: string;
16
+ labels: {
17
+ post?: string[];
18
+ profile?: string[];
19
+ account?: string[];
20
+ quotedPost?: string[];
21
+ quotedAccount?: string[];
22
+ };
23
+ behaviors: {
24
+ profileList?: ModerationTestSuiteResultFlag[];
25
+ profileView?: ModerationTestSuiteResultFlag[];
26
+ profileMedia?: ModerationTestSuiteResultFlag[];
27
+ contentList?: ModerationTestSuiteResultFlag[];
28
+ contentView?: ModerationTestSuiteResultFlag[];
29
+ contentMedia?: ModerationTestSuiteResultFlag[];
30
+ };
31
+ }
32
+
33
+ export type SuiteUsers = Record<
34
+ string,
35
+ {
36
+ blocking: boolean;
37
+ blockingByList: boolean;
38
+ blockedBy: boolean;
39
+ muted: boolean;
40
+ mutedByList: boolean;
41
+ }
42
+ >;
43
+
44
+ export type SuiteConfigurations = Record<
45
+ string,
46
+ {
47
+ authed?: boolean;
48
+ adultContentEnabled?: boolean;
49
+ settings?: Record<string, LabelPreference>;
50
+ }
51
+ >;
52
+
53
+ export type SuiteScenarios = Record<string, ModerationTestSuiteScenario>;
54
+
55
+ expect.extend({
56
+ toBeModerationResult(
57
+ actual: DisplayRestrictions,
58
+ expected: ModerationTestSuiteResultFlag[] | undefined,
59
+ context = '',
60
+ stringifiedResult: string | undefined = undefined,
61
+ _ignoreCause = false,
62
+ ) {
63
+ const fail = (msg: string) => ({
64
+ pass: false,
65
+ message: () => `${msg}.${stringifiedResult ? ` Full result: ${stringifiedResult}` : ''}`,
66
+ });
67
+ // let cause = actual.causes?.type as string
68
+ // if (actual.cause?.type === 'label') {
69
+ // cause = `label:${actual.cause.labelDef.id}`
70
+ // } else if (actual.cause?.type === 'muted') {
71
+ // if (actual.cause.source.type === 'list') {
72
+ // cause = 'muted-by-list'
73
+ // }
74
+ // } else if (actual.cause?.type === 'blocking') {
75
+ // if (actual.cause.source.type === 'list') {
76
+ // cause = 'blocking-by-list'
77
+ // }
78
+ // }
79
+ if (!expected) {
80
+ // if (!ignoreCause && actual.cause) {
81
+ // return fail(`${context} expected to be a no-op, got ${cause}`)
82
+ // }
83
+ if (actual.informs.length > 0) {
84
+ return fail(`${context} expected to be a no-op, got inform=true`);
85
+ }
86
+ if (actual.alerts.length > 0) {
87
+ return fail(`${context} expected to be a no-op, got alert=true`);
88
+ }
89
+ if (actual.blurs.length > 0) {
90
+ return fail(`${context} expected to be a no-op, got blur=true`);
91
+ }
92
+ if (actual.filters.length > 0) {
93
+ return fail(`${context} expected to be a no-op, got filter=true`);
94
+ }
95
+ if (actual.noOverride) {
96
+ return fail(`${context} expected to be a no-op, got noOverride=true`);
97
+ }
98
+ } else {
99
+ // if (!ignoreCause && cause !== expected.cause) {
100
+ // return fail(`${context} expected to be ${expected.cause}, got ${cause}`)
101
+ // }
102
+ const expectedInform = expected.includes('inform');
103
+ if (!!(actual.informs.length > 0) !== expectedInform) {
104
+ return fail(
105
+ `${context} expected to be inform=${expectedInform}, got ${actual.informs.length > 0 || false}`,
106
+ );
107
+ }
108
+ const expectedAlert = expected.includes('alert');
109
+ if (!!(actual.alerts.length > 0) !== expectedAlert) {
110
+ return fail(
111
+ `${context} expected to be alert=${expectedAlert}, got ${actual.alerts.length > 0 || false}`,
112
+ );
113
+ }
114
+ const expectedBlur = expected.includes('blur');
115
+ if (!!(actual.blurs.length > 0) !== expectedBlur) {
116
+ return fail(
117
+ `${context} expected to be blur=${expectedBlur}, got ${actual.blurs.length > 0 || false}`,
118
+ );
119
+ }
120
+ const expectedFilter = expected.includes('filter');
121
+ if (!!(actual.filters.length > 0) !== expectedFilter) {
122
+ return fail(
123
+ `${context} expected to be filter=${expectedFilter}, got ${actual.filters.length > 0 || false}`,
124
+ );
125
+ }
126
+ const expectedNoOverride = expected.includes('noOverride');
127
+ if (!!actual.noOverride !== expectedNoOverride) {
128
+ return fail(
129
+ `${context} expected to be noOverride=${expectedNoOverride}, got ${actual.noOverride || false}`,
130
+ );
131
+ }
132
+ }
133
+ return { pass: true, message: () => '' };
134
+ },
135
+ });
136
+
137
+ declare module 'vitest' {
138
+ // oxlint-disable-next-line no-unused-vars -- required for module augmentation
139
+ interface Assertion<T = any> {
140
+ toBeModerationResult(
141
+ expected?: ModerationTestSuiteResultFlag[],
142
+ context?: string,
143
+ stringifiedResult?: string,
144
+ ignoreCause?: boolean,
145
+ ): void;
146
+ }
147
+
148
+ interface AsymmetricMatchers {
149
+ toBeModerationResult(
150
+ expected?: ModerationTestSuiteResultFlag[],
151
+ context?: string,
152
+ stringifiedResult?: string,
153
+ ignoreCause?: boolean,
154
+ ): any;
155
+ }
156
+ }
157
+
158
+ export class ModerationBehaviorSuiteRunner {
159
+ users: SuiteUsers;
160
+ configurations: SuiteConfigurations;
161
+ scenarios: SuiteScenarios;
162
+
163
+ constructor(users: SuiteUsers, configurations: SuiteConfigurations, scenarios: SuiteScenarios) {
164
+ this.users = users;
165
+ this.configurations = configurations;
166
+ this.scenarios = scenarios;
167
+ }
168
+
169
+ postScenario(scenario: ModerationTestSuiteScenario) {
170
+ if (scenario.subject !== 'post') {
171
+ throw new Error('Scenario subject must be "post"');
172
+ }
173
+ const author = this.profileView(scenario.author, scenario.labels);
174
+ return m.postView({
175
+ record: m.post({
176
+ text: 'Post text',
177
+ }),
178
+ author,
179
+ labels: (scenario.labels.post || []).map((val) =>
180
+ m.label({ val, uri: `at://${author.did}/app.bsky.feed.post/fake` }),
181
+ ),
182
+ embed: scenario.quoteAuthor
183
+ ? m.embedRecordView({
184
+ record: m.post({
185
+ text: 'Quoted post text',
186
+ }),
187
+ labels: (scenario.labels.quotedPost || []).map((val) =>
188
+ m.label({
189
+ val,
190
+ uri: `at://${author.did}/app.bsky.feed.post/fake`,
191
+ }),
192
+ ),
193
+ author: this.profileView(scenario.quoteAuthor, {
194
+ account: scenario.labels.quotedAccount,
195
+ }),
196
+ })
197
+ : undefined,
198
+ });
199
+ }
200
+
201
+ profileScenario(scenario: ModerationTestSuiteScenario) {
202
+ if (scenario.subject !== 'profile') {
203
+ throw new Error('Scenario subject must be "profile"');
204
+ }
205
+ return this.profileView(scenario.author, scenario.labels);
206
+ }
207
+
208
+ profileView(name: string, scenarioLabels: ModerationTestSuiteScenario['labels']) {
209
+ const def = this.users[name];
210
+
211
+ const labels: ComAtprotoLabelDefs.Label[] = [];
212
+ if (scenarioLabels.account) {
213
+ for (const l of scenarioLabels.account) {
214
+ labels.push(m.label({ val: l, uri: `did:web:${name}` }));
215
+ }
216
+ }
217
+ if (scenarioLabels.profile) {
218
+ for (const l of scenarioLabels.profile) {
219
+ labels.push(
220
+ m.label({
221
+ val: l,
222
+ uri: `at://did:web:${name}/app.bsky.actor.profile/self`,
223
+ }),
224
+ );
225
+ }
226
+ }
227
+
228
+ return m.profileView({
229
+ handle: `${name}.test`,
230
+ labels,
231
+ viewer: m.actorViewerState({
232
+ muted: def.muted || def.mutedByList,
233
+ mutedByList: def.mutedByList ? m.listViewBasic({ name: 'Fake List' }) : undefined,
234
+ blockedBy: def.blockedBy,
235
+ blocking:
236
+ def.blocking || def.blockingByList ? 'at://did:web:self.test/app.bsky.graph.block/fake' : undefined,
237
+ blockingByList: def.blockingByList ? m.listViewBasic({ name: 'Fake List' }) : undefined,
238
+ }),
239
+ });
240
+ }
241
+
242
+ moderationOptions(scenario: ModerationTestSuiteScenario): ModerationOptions {
243
+ return {
244
+ viewerDid: this.configurations[scenario.cfg].authed === false ? undefined : 'did:web:self.test',
245
+ prefs: {
246
+ adultContentEnabled: Boolean(this.configurations[scenario.cfg]?.adultContentEnabled),
247
+ globalLabelPrefs: this.configurations[scenario.cfg].settings || {},
248
+ prefsByLabelers: {
249
+ 'did:plc:fake-labeler': {
250
+ labelPrefs: {},
251
+ },
252
+ },
253
+ hiddenPosts: [],
254
+ keywordFilters: [],
255
+ temporaryMutes: [],
256
+ },
257
+ };
258
+ }
259
+ }
package/lib/behaviors.ts CHANGED
@@ -1,36 +1,39 @@
1
- export enum LabelTarget {
1
+ export const LabelTarget = {
2
2
  /** label is intended for account's content */
3
- Content = 'content',
3
+ Content: 'content',
4
4
  /** label is intended for account's profile */
5
- Profile = 'profile',
5
+ Profile: 'profile',
6
6
  /** label is intended for account's content and profile */
7
- Account = 'account',
8
- }
7
+ Account: 'account',
8
+ } as const;
9
+ export type LabelTarget = (typeof LabelTarget)[keyof typeof LabelTarget];
9
10
 
10
- export enum DisplayContext {
11
+ export const DisplayContext = {
11
12
  /** content in expanded view */
12
- ContentView = 'contentView',
13
+ ContentView: 'contentView',
13
14
  /** images or video contained in content */
14
- ContentMedia = 'contentMedia',
15
+ ContentMedia: 'contentMedia',
15
16
  /** content in a list/feed */
16
- ContentList = 'contentList',
17
+ ContentList: 'contentList',
17
18
 
18
19
  /** profile in expanded view */
19
- ProfileView = 'profileView',
20
+ ProfileView: 'profileView',
20
21
  /** profile's avatar or banner */
21
- ProfileMedia = 'profileMedia',
22
+ ProfileMedia: 'profileMedia',
22
23
  /** profile in a list */
23
- ProfileList = 'profileList',
24
- }
24
+ ProfileList: 'profileList',
25
+ } as const;
26
+ export type DisplayContext = (typeof DisplayContext)[keyof typeof DisplayContext];
25
27
 
26
- export enum ModerationAction {
28
+ export const ModerationAction = {
27
29
  /** should cause blurring */
28
- Blur = 'blur',
30
+ Blur: 'blur',
29
31
  /** should cause an alert */
30
- Alert = 'alert',
32
+ Alert: 'alert',
31
33
  /** should cause a notice */
32
- Inform = 'inform',
33
- }
34
+ Inform: 'inform',
35
+ } as const;
36
+ export type ModerationAction = (typeof ModerationAction)[keyof typeof ModerationAction];
34
37
 
35
38
  export type BehaviorMapping = { [C in DisplayContext]?: ModerationAction };
36
39
  export type LabelBehaviorMatrix = { [T in LabelTarget]: BehaviorMapping };
package/lib/decision.ts CHANGED
@@ -1,43 +1,43 @@
1
1
  import type { AppBskyGraphDefs } from '@atcute/bluesky';
2
2
  import type { Did } from '@atcute/lexicons';
3
3
 
4
- import { DisplayContext, ModerationAction, type BehaviorMapping, type LabelTarget } from './behaviors.js';
5
- import type { Label, LabelerPreference, ModerationOptions } from './types.js';
6
-
7
- import type { KeywordFilter } from './keyword-filter.js';
4
+ import { DisplayContext, ModerationAction, type BehaviorMapping, type LabelTarget } from './behaviors.ts';
5
+ import type { KeywordFilter } from './keyword-filter.ts';
8
6
  import {
9
7
  BUILTIN_LABELS,
10
8
  isCustomLabelValue,
11
9
  LabelFlags,
12
10
  LabelPreference,
13
11
  type InterpretedLabelDefinition,
14
- } from './label.js';
12
+ } from './label.ts';
13
+ import type { Label, LabelerPreference, ModerationOptions } from './types.ts';
15
14
 
16
- const enum ModerationSeverity {
17
- High = 1,
18
- Medium = 2,
19
- Low = 3,
20
- }
15
+ const ModerationSeverity = {
16
+ High: 1,
17
+ Medium: 2,
18
+ Low: 3,
19
+ } as const;
21
20
 
22
- export enum ModerationCauseType {
21
+ export const ModerationCauseType = {
23
22
  /** caused by a label */
24
- Label = 1,
23
+ Label: 1,
25
24
  /** caused by viewer blocking the subject */
26
- Blocking = 2,
25
+ Blocking: 2,
27
26
  /** caused by subject blocking the viewer */
28
- BlockedBy = 3,
27
+ BlockedBy: 3,
29
28
  /** caused by viewer having a (permanent) mute on subject */
30
- MutedPermanent = 4,
29
+ MutedPermanent: 4,
31
30
  /** caused by a temporary mute */
32
- MutedTemporary = 5,
31
+ MutedTemporary: 5,
33
32
  /** caused by a keyword mute */
34
- MutedKeyword = 6,
33
+ MutedKeyword: 6,
35
34
  /** caused by a hidden post */
36
- Hidden = 7,
37
- }
35
+ Hidden: 7,
36
+ } as const;
37
+ export type ModerationCauseType = (typeof ModerationCauseType)[keyof typeof ModerationCauseType];
38
38
 
39
39
  export interface BlockingModerationCause {
40
- type: ModerationCauseType.Blocking;
40
+ type: typeof ModerationCauseType.Blocking;
41
41
  priority: 3;
42
42
  source: AppBskyGraphDefs.ListViewBasic | null;
43
43
 
@@ -45,21 +45,21 @@ export interface BlockingModerationCause {
45
45
  }
46
46
 
47
47
  export interface BlockedByModerationCause {
48
- type: ModerationCauseType.BlockedBy;
48
+ type: typeof ModerationCauseType.BlockedBy;
49
49
  priority: 4;
50
50
 
51
51
  downgraded: boolean;
52
52
  }
53
53
 
54
54
  export interface HiddenModerationCause {
55
- type: ModerationCauseType.Hidden;
55
+ type: typeof ModerationCauseType.Hidden;
56
56
  priority: 6;
57
57
 
58
58
  downgraded: boolean;
59
59
  }
60
60
 
61
61
  export interface LabelModerationCause {
62
- type: ModerationCauseType.Label;
62
+ type: typeof ModerationCauseType.Label;
63
63
  priority: 1 | 2 | 5 | 7 | 8;
64
64
  source: Did | null;
65
65
 
@@ -75,7 +75,7 @@ export interface LabelModerationCause {
75
75
  }
76
76
 
77
77
  export interface MutedPermanentModerationCause {
78
- type: ModerationCauseType.MutedPermanent;
78
+ type: typeof ModerationCauseType.MutedPermanent;
79
79
  priority: 6;
80
80
  source: AppBskyGraphDefs.ListViewBasic | null;
81
81
 
@@ -83,14 +83,14 @@ export interface MutedPermanentModerationCause {
83
83
  }
84
84
 
85
85
  export interface MutedTemporaryModerationCause {
86
- type: ModerationCauseType.MutedTemporary;
86
+ type: typeof ModerationCauseType.MutedTemporary;
87
87
  priority: 6;
88
88
 
89
89
  downgraded: boolean;
90
90
  }
91
91
 
92
92
  export interface MutedKeywordModerationCause {
93
- type: ModerationCauseType.MutedKeyword;
93
+ type: typeof ModerationCauseType.MutedKeyword;
94
94
  priority: 6;
95
95
  source: KeywordFilter;
96
96
 
package/lib/index.ts CHANGED
@@ -4,7 +4,7 @@ export {
4
4
  ModerationAction,
5
5
  type BehaviorMapping,
6
6
  type LabelBehaviorMatrix,
7
- } from './behaviors.js';
7
+ } from './behaviors.ts';
8
8
  export {
9
9
  ModerationCauseType,
10
10
  type BlockedByModerationCause,
@@ -16,7 +16,7 @@ export {
16
16
  type MutedKeywordModerationCause,
17
17
  type MutedPermanentModerationCause,
18
18
  type MutedTemporaryModerationCause,
19
- } from './decision.js';
19
+ } from './decision.ts';
20
20
 
21
21
  export {
22
22
  createKeywordPattern,
@@ -25,7 +25,7 @@ export {
25
25
  KeywordFilterFlags,
26
26
  type KeywordFilter,
27
27
  type KeywordMatch,
28
- } from './keyword-filter.js';
28
+ } from './keyword-filter.ts';
29
29
  export {
30
30
  BlurLevel,
31
31
  interpretLabelerDefinition,
@@ -38,7 +38,7 @@ export {
38
38
  type InterpretedLabelDefinition,
39
39
  type InterpretedLabelMapping,
40
40
  type LabelLocale,
41
- } from './label.js';
41
+ } from './label.ts';
42
42
 
43
43
  export {
44
44
  type FeedGeneratorSubject,
@@ -50,12 +50,12 @@ export {
50
50
  type NotificationSubject,
51
51
  type PostSubject,
52
52
  type ProfileSubject,
53
- } from './types.js';
53
+ } from './types.ts';
54
54
 
55
- export { getDisplayRestrictions, type DisplayRestrictions } from './ui.js';
55
+ export { getDisplayRestrictions, type DisplayRestrictions } from './ui.ts';
56
56
 
57
- export { moderateFeedGenerator } from './subjects/feed-generator.js';
58
- export { moderateList } from './subjects/list.js';
59
- export { moderateNotification } from './subjects/notification.js';
60
- export { moderatePost } from './subjects/post.js';
61
- export { moderateProfile } from './subjects/profile.js';
57
+ export { moderateFeedGenerator } from './subjects/feed-generator.ts';
58
+ export { moderateList } from './subjects/list.ts';
59
+ export { moderateNotification } from './subjects/notification.ts';
60
+ export { moderatePost } from './subjects/post.ts';
61
+ export { moderateProfile } from './subjects/profile.ts';
@@ -1,6 +1,6 @@
1
1
  import type { AppBskyActorDefs } from '@atcute/bluesky';
2
2
 
3
- import { KeywordFilterFlags, type KeywordFilter } from '../keyword-filter.js';
3
+ import { KeywordFilterFlags, type KeywordFilter } from '../keyword-filter.ts';
4
4
 
5
5
  const EMPTY_ARRAY: never[] = [];
6
6
 
@@ -28,7 +28,9 @@ export const createKeywordPattern = (matchers: KeywordMatch | KeywordMatch[]): R
28
28
  continue;
29
29
  }
30
30
 
31
- re && (re += '|');
31
+ if (re) {
32
+ re += '|';
33
+ }
32
34
 
33
35
  if (whole && WORD_CHAR_RE.test(value.at(0)!)) {
34
36
  re += '\\b';
@@ -49,14 +51,15 @@ const escape = (str: string) => {
49
51
  return str.replace(ESCAPE_RE, '\\$&');
50
52
  };
51
53
 
52
- export enum KeywordFilterFlags {
54
+ export const KeywordFilterFlags = {
53
55
  /** filter applies to content */
54
- ApplyContent = 1 << 0,
56
+ ApplyContent: 1 << 0,
55
57
  /** filter applies to tags */
56
- ApplyTopic = 1 << 1,
58
+ ApplyTopic: 1 << 1,
57
59
  /** filter shouldn't apply to following users */
58
- NoFollowing = 1 << 2,
59
- }
60
+ NoFollowing: 1 << 2,
61
+ } as const;
62
+ export type KeywordFilterFlags = (typeof KeywordFilterFlags)[keyof typeof KeywordFilterFlags];
60
63
 
61
64
  export interface KeywordFilter {
62
65
  /** unique identifier for this filter */
package/lib/label.ts CHANGED
@@ -2,53 +2,57 @@ import type { ComAtprotoLabelDefs } from '@atcute/atproto';
2
2
  import type { AppBskyLabelerDefs } from '@atcute/bluesky';
3
3
  import type { Did } from '@atcute/lexicons';
4
4
 
5
- import { DisplayContext, LabelTarget, ModerationAction, type LabelBehaviorMatrix } from './behaviors.js';
5
+ import { DisplayContext, LabelTarget, ModerationAction, type LabelBehaviorMatrix } from './behaviors.ts';
6
6
 
7
- export enum LabelPreference {
7
+ export const LabelPreference = {
8
8
  /** ignore this label */
9
- Ignore = 'ignore',
9
+ Ignore: 'ignore',
10
10
  /** warn when viewing content or profile with this label */
11
- Warn = 'warn',
11
+ Warn: 'warn',
12
12
  /** hide content or profile containing this label */
13
- Hide = 'hide',
14
- }
13
+ Hide: 'hide',
14
+ } as const;
15
+ export type LabelPreference = (typeof LabelPreference)[keyof typeof LabelPreference];
15
16
 
16
- export enum LabelFlags {
17
+ export const LabelFlags = {
17
18
  /** no flags */
18
- None = 0,
19
+ None: 0,
19
20
 
20
21
  /** unblurring shouldn't be allowed */
21
- NoOverride = 1 << 0,
22
+ NoOverride: 1 << 0,
22
23
  /** label can't be configured */
23
- NoConfigurable = 1 << 1,
24
+ NoConfigurable: 1 << 1,
24
25
  /** label can't be applied as a self-label */
25
- NoSelf = 1 << 2,
26
+ NoSelf: 1 << 2,
26
27
  /** label is adult-only */
27
- AdultOnly = 1 << 3,
28
+ AdultOnly: 1 << 3,
28
29
  /** label can't be applied if authenticated */
29
- UnauthenticatedOnly = 1 << 4,
30
- }
30
+ UnauthenticatedOnly: 1 << 4,
31
+ } as const;
32
+ export type LabelFlags = (typeof LabelFlags)[keyof typeof LabelFlags];
31
33
 
32
- export enum BlurLevel {
34
+ export const BlurLevel = {
33
35
  /** don't blur any parts of the content */
34
- None = 'none',
36
+ None: 'none',
35
37
  /** only blur the media present in the content */
36
- Media = 'media',
38
+ Media: 'media',
37
39
  /** blur the entire content */
38
- Content = 'content',
40
+ Content: 'content',
39
41
 
40
42
  /** special blur value, guaranteed blurring of profile and content */
41
- Forced = 'forced',
42
- }
43
+ Forced: 'forced',
44
+ } as const;
45
+ export type BlurLevel = (typeof BlurLevel)[keyof typeof BlurLevel];
43
46
 
44
- export enum SeverityLevel {
47
+ export const SeverityLevel = {
45
48
  /** don't inform the user */
46
- None = 'none',
49
+ None: 'none',
47
50
  /** lightly inform the user about this label's presence */
48
- Inform = 'inform',
51
+ Inform: 'inform',
49
52
  /** alert the user about this label's presence */
50
- Alert = 'alert',
51
- }
53
+ Alert: 'alert',
54
+ } as const;
55
+ export type SeverityLevel = (typeof SeverityLevel)[keyof typeof SeverityLevel];
52
56
 
53
57
  export interface LabelLocale {
54
58
  /** language code */
@@ -253,9 +257,9 @@ export const isCustomLabelValue = (value: string): boolean => {
253
257
  export const interpretLabelValueDefinition = (
254
258
  def: ComAtprotoLabelDefs.LabelValueDefinition,
255
259
  ): InterpretedLabelDefinition => {
256
- let defaultPref = LabelPreference.Warn;
257
- let blur = BlurLevel.None;
258
- let severity = SeverityLevel.None;
260
+ let defaultPref: LabelPreference = LabelPreference.Warn;
261
+ let blur: BlurLevel = BlurLevel.None;
262
+ let severity: SeverityLevel = SeverityLevel.None;
259
263
  let flags = LabelFlags.NoSelf;
260
264
 
261
265
  switch (def.blurs) {
@@ -1,13 +1,13 @@
1
- import { LabelTarget } from '../behaviors.js';
1
+ import { LabelTarget } from '../behaviors.ts';
2
2
  import {
3
3
  considerLabels,
4
4
  createModerationDecision,
5
5
  mergeModerationDecisions,
6
6
  type ModerationDecision,
7
- } from '../decision.js';
8
- import type { FeedGeneratorSubject, ModerationOptions } from '../types.js';
7
+ } from '../decision.ts';
8
+ import type { FeedGeneratorSubject, ModerationOptions } from '../types.ts';
9
9
 
10
- import { moderateProfile } from './profile.js';
10
+ import { moderateProfile } from './profile.ts';
11
11
 
12
12
  export const moderateFeedGenerator = (
13
13
  subject: FeedGeneratorSubject,
@@ -1,15 +1,15 @@
1
1
  import { parseCanonicalResourceUri } from '@atcute/lexicons';
2
2
 
3
- import { LabelTarget } from '../behaviors.js';
3
+ import { LabelTarget } from '../behaviors.ts';
4
4
  import {
5
5
  considerLabels,
6
6
  createModerationDecision,
7
7
  mergeModerationDecisions,
8
8
  type ModerationDecision,
9
- } from '../decision.js';
10
- import type { ListSubject, ModerationOptions } from '../types.js';
9
+ } from '../decision.ts';
10
+ import type { ListSubject, ModerationOptions } from '../types.ts';
11
11
 
12
- import { moderateProfile } from './profile.js';
12
+ import { moderateProfile } from './profile.ts';
13
13
 
14
14
  export const moderateList = (subject: ListSubject, opts: ModerationOptions): ModerationDecision => {
15
15
  if ('creator' in subject) {
@@ -1,13 +1,13 @@
1
- import { LabelTarget } from '../behaviors.js';
1
+ import { LabelTarget } from '../behaviors.ts';
2
2
  import {
3
3
  considerLabels,
4
4
  createModerationDecision,
5
5
  mergeModerationDecisions,
6
6
  type ModerationDecision,
7
- } from '../decision.js';
8
- import type { ModerationOptions, NotificationSubject } from '../types.js';
7
+ } from '../decision.ts';
8
+ import type { ModerationOptions, NotificationSubject } from '../types.ts';
9
9
 
10
- import { moderateProfile } from './profile.js';
10
+ import { moderateProfile } from './profile.ts';
11
11
 
12
12
  export const moderateNotification = (
13
13
  subject: NotificationSubject,