@atcute/bluesky-moderation 2.0.4 → 3.0.0

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 (65) hide show
  1. package/README.md +126 -128
  2. package/dist/_test-util/mock.d.ts +2735 -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 +25 -24
  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 +3 -3
  20. package/dist/internal/keyword-filter.d.ts.map +1 -1
  21. package/dist/internal/keyword-filter.js.map +1 -1
  22. package/dist/keyword-filter.d.ts +7 -6
  23. package/dist/keyword-filter.d.ts.map +1 -1
  24. package/dist/keyword-filter.js +8 -7
  25. package/dist/keyword-filter.js.map +1 -1
  26. package/dist/label.d.ts +30 -27
  27. package/dist/label.d.ts.map +1 -1
  28. package/dist/label.js +24 -28
  29. package/dist/label.js.map +1 -1
  30. package/dist/subjects/feed-generator.d.ts +3 -3
  31. package/dist/subjects/feed-generator.d.ts.map +1 -1
  32. package/dist/subjects/feed-generator.js.map +1 -1
  33. package/dist/subjects/list.d.ts +2 -2
  34. package/dist/subjects/list.d.ts.map +1 -1
  35. package/dist/subjects/list.js.map +1 -1
  36. package/dist/subjects/notification.d.ts +3 -3
  37. package/dist/subjects/notification.d.ts.map +1 -1
  38. package/dist/subjects/notification.js.map +1 -1
  39. package/dist/subjects/post.d.ts +4 -3
  40. package/dist/subjects/post.d.ts.map +1 -1
  41. package/dist/subjects/post.js.map +1 -1
  42. package/dist/subjects/profile.d.ts +2 -2
  43. package/dist/subjects/profile.d.ts.map +1 -1
  44. package/dist/subjects/profile.js.map +1 -1
  45. package/dist/types.d.ts +2 -2
  46. package/dist/ui.d.ts +2 -2
  47. package/dist/ui.d.ts.map +1 -1
  48. package/dist/ui.js +4 -4
  49. package/dist/ui.js.map +1 -1
  50. package/lib/_test-util/mock.ts +214 -0
  51. package/lib/_test-util/moderation-behavior.ts +259 -0
  52. package/lib/behaviors.ts +21 -18
  53. package/lib/decision.ts +26 -26
  54. package/lib/index.ts +11 -11
  55. package/lib/internal/keyword-filter.ts +1 -1
  56. package/lib/keyword-filter.ts +9 -6
  57. package/lib/label.ts +32 -28
  58. package/lib/subjects/feed-generator.ts +4 -4
  59. package/lib/subjects/list.ts +4 -4
  60. package/lib/subjects/notification.ts +4 -4
  61. package/lib/subjects/post.ts +6 -7
  62. package/lib/subjects/profile.ts +3 -3
  63. package/lib/types.ts +2 -2
  64. package/lib/ui.ts +7 -7
  65. package/package.json +14 -12
package/dist/ui.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { DisplayContext } from './behaviors.js';
2
- import { type ModerationCause, type ModerationDecision } from './decision.js';
1
+ import { DisplayContext } from './behaviors.ts';
2
+ import { type ModerationCause, type ModerationDecision } from './decision.ts';
3
3
  export interface DisplayRestrictions {
4
4
  noOverride: boolean;
5
5
  filters: ModerationCause[];
package/dist/ui.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"ui.d.ts","sourceRoot":"","sources":["../lib/ui.ts"],"names":[],"mappings":"AAAA,OAAO,EAEN,cAAc,EAMd,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAuB,KAAK,eAAe,EAAE,KAAK,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAGnG,MAAM,WAAW,mBAAmB;IACnC,UAAU,EAAE,OAAO,CAAC;IACpB,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B,KAAK,EAAE,eAAe,EAAE,CAAC;IACzB,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,OAAO,EAAE,eAAe,EAAE,CAAC;CAC3B;AAED,eAAO,MAAM,sBAAsB,GAClC,UAAU,kBAAkB,EAC5B,SAAS,cAAc,KACrB,mBA2JF,CAAC"}
1
+ {"version":3,"file":"ui.d.ts","sourceRoot":"","sources":["../lib/ui.ts"],"names":[],"mappings":"AAAA,OAAO,EAEN,cAAc,EAMd,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAuB,KAAK,eAAe,EAAE,KAAK,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAGnG,MAAM,WAAW,mBAAmB;IACnC,UAAU,EAAE,OAAO,CAAC;IACpB,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B,KAAK,EAAE,eAAe,EAAE,CAAC;IACzB,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,OAAO,EAAE,eAAe,EAAE,CAAC;CAC3B;AAED,eAAO,MAAM,sBAAsB,gFA8JlC,CAAC"}
package/dist/ui.js CHANGED
@@ -133,10 +133,10 @@ export const getDisplayRestrictions = (decision, context) => {
133
133
  }
134
134
  return {
135
135
  noOverride,
136
- filters: filters.sort(sortByPriority),
137
- blurs: blurs.sort(sortByPriority),
138
- alerts: alerts.sort(sortByPriority),
139
- informs: informs.sort(sortByPriority),
136
+ filters: filters.sort(sortByPriority), // oxlint-disable-line unicorn/no-array-sort -- local array
137
+ blurs: blurs.sort(sortByPriority), // oxlint-disable-line unicorn/no-array-sort -- local array
138
+ alerts: alerts.sort(sortByPriority), // oxlint-disable-line unicorn/no-array-sort -- local array
139
+ informs: informs.sort(sortByPriority), // oxlint-disable-line unicorn/no-array-sort -- local array
140
140
  };
141
141
  };
142
142
  const sortByPriority = (a, b) => {
package/dist/ui.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"ui.js","sourceRoot":"","sources":["../lib/ui.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ui.js","sourceRoot":"","sources":["../lib/ui.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,214 @@
1
+ import type { ComAtprotoLabelDefs } from '@atcute/atproto';
2
+ import type {
3
+ AppBskyActorDefs,
4
+ AppBskyEmbedRecord,
5
+ AppBskyFeedDefs,
6
+ AppBskyFeedPost,
7
+ AppBskyGraphDefs,
8
+ AppBskyNotificationListNotifications,
9
+ } from '@atcute/bluesky';
10
+ import type { $type, Did, GenericUri, Handle, ResourceUri } from '@atcute/lexicons';
11
+
12
+ const FAKE_CID = 'bafyreiclp443lavogvhj3d2ob2cxbfuscni2k5jk7bebjzg7khl3esabwq';
13
+
14
+ export const post = ({
15
+ text,
16
+ facets,
17
+ reply,
18
+ embed,
19
+ }: {
20
+ text: string;
21
+ facets?: AppBskyFeedPost.Main['facets'];
22
+ reply?: AppBskyFeedPost.ReplyRef;
23
+ embed?: AppBskyFeedPost.Main['embed'];
24
+ }): AppBskyFeedPost.Main => {
25
+ return {
26
+ $type: 'app.bsky.feed.post',
27
+ text,
28
+ facets,
29
+ reply,
30
+ embed,
31
+ langs: ['en'],
32
+ createdAt: new Date().toISOString(),
33
+ };
34
+ };
35
+
36
+ export const postView = ({
37
+ record,
38
+ author,
39
+ embed,
40
+ replyCount,
41
+ repostCount,
42
+ likeCount,
43
+ viewer,
44
+ labels,
45
+ }: {
46
+ record: AppBskyFeedPost.Main;
47
+ author: AppBskyActorDefs.ProfileViewBasic;
48
+ embed?: AppBskyFeedDefs.PostView['embed'];
49
+ replyCount?: number;
50
+ repostCount?: number;
51
+ likeCount?: number;
52
+ viewer?: AppBskyFeedDefs.ViewerState;
53
+ labels?: ComAtprotoLabelDefs.Label[];
54
+ }): $type.enforce<AppBskyFeedDefs.PostView> => {
55
+ return {
56
+ $type: 'app.bsky.feed.defs#postView',
57
+ uri: `at://${author.did}/app.bsky.feed.post/fake`,
58
+ cid: FAKE_CID,
59
+ author,
60
+ record,
61
+ embed,
62
+ replyCount,
63
+ repostCount,
64
+ likeCount,
65
+ indexedAt: new Date().toISOString(),
66
+ viewer,
67
+ labels,
68
+ };
69
+ };
70
+
71
+ export const embedRecordView = ({
72
+ record,
73
+ author,
74
+ labels,
75
+ }: {
76
+ record: AppBskyFeedPost.Main;
77
+ author: AppBskyActorDefs.ProfileViewBasic;
78
+ labels?: ComAtprotoLabelDefs.Label[];
79
+ }): $type.enforce<AppBskyEmbedRecord.View> => {
80
+ return {
81
+ $type: 'app.bsky.embed.record#view',
82
+ record: {
83
+ $type: 'app.bsky.embed.record#viewRecord',
84
+ uri: `at://${author.did}/app.bsky.feed.post/fake`,
85
+ cid: FAKE_CID,
86
+ author,
87
+ value: record,
88
+ labels,
89
+ indexedAt: new Date().toISOString(),
90
+ },
91
+ };
92
+ };
93
+
94
+ export const profileView = ({
95
+ handle,
96
+ displayName,
97
+ viewer,
98
+ labels,
99
+ }: {
100
+ handle: Handle;
101
+ displayName?: string;
102
+ viewer?: AppBskyActorDefs.ViewerState;
103
+ labels?: ComAtprotoLabelDefs.Label[];
104
+ }): $type.omit<AppBskyActorDefs.ProfileView | AppBskyActorDefs.ProfileViewBasic> => {
105
+ return {
106
+ did: `did:web:${handle}`,
107
+ handle,
108
+ displayName,
109
+ viewer,
110
+ labels,
111
+ };
112
+ };
113
+
114
+ export const actorViewerState = ({
115
+ muted,
116
+ mutedByList,
117
+ blockedBy,
118
+ blocking,
119
+ blockingByList,
120
+ following,
121
+ followedBy,
122
+ }: {
123
+ muted?: boolean;
124
+ mutedByList?: AppBskyGraphDefs.ListViewBasic;
125
+ blockedBy?: boolean;
126
+ blocking?: ResourceUri;
127
+ blockingByList?: AppBskyGraphDefs.ListViewBasic;
128
+ following?: ResourceUri;
129
+ followedBy?: ResourceUri;
130
+ }): AppBskyActorDefs.ViewerState => {
131
+ return {
132
+ muted,
133
+ mutedByList,
134
+ blockedBy,
135
+ blocking,
136
+ blockingByList,
137
+ following,
138
+ followedBy,
139
+ };
140
+ };
141
+
142
+ export const listViewBasic = ({ name }: { name: string }): AppBskyGraphDefs.ListViewBasic => {
143
+ return {
144
+ uri: 'at://did:plc:fake/app.bsky.graph.list/fake',
145
+ cid: FAKE_CID,
146
+ name,
147
+ purpose: 'app.bsky.graph.defs#modlist',
148
+ indexedAt: new Date().toISOString(),
149
+ };
150
+ };
151
+
152
+ export const replyNotification = ({
153
+ author,
154
+ record,
155
+ labels,
156
+ }: {
157
+ record: AppBskyFeedPost.Main;
158
+ author: AppBskyActorDefs.ProfileView;
159
+ labels?: ComAtprotoLabelDefs.Label[];
160
+ }): AppBskyNotificationListNotifications.Notification => {
161
+ return {
162
+ uri: `at://${author.did}/app.bsky.feed.post/fake`,
163
+ cid: FAKE_CID,
164
+ author,
165
+ reason: 'reply',
166
+ reasonSubject: `at://${author.did}/app.bsky.feed.post/fake-parent`,
167
+ record,
168
+ isRead: false,
169
+ indexedAt: new Date().toISOString(),
170
+ labels,
171
+ };
172
+ };
173
+
174
+ export const followNotification = ({
175
+ author,
176
+ subjectDid,
177
+ labels,
178
+ }: {
179
+ author: AppBskyActorDefs.ProfileView;
180
+ subjectDid: string;
181
+ labels?: ComAtprotoLabelDefs.Label[];
182
+ }): AppBskyNotificationListNotifications.Notification => {
183
+ return {
184
+ uri: `at://${author.did}/app.bsky.graph.follow/fake`,
185
+ cid: FAKE_CID,
186
+ author,
187
+ reason: 'follow',
188
+ record: {
189
+ $type: 'app.bsky.graph.follow',
190
+ createdAt: new Date().toISOString(),
191
+ subject: subjectDid,
192
+ },
193
+ isRead: false,
194
+ indexedAt: new Date().toISOString(),
195
+ labels,
196
+ };
197
+ };
198
+
199
+ export const label = ({
200
+ val,
201
+ uri,
202
+ src,
203
+ }: {
204
+ val: string;
205
+ uri: GenericUri;
206
+ src?: Did;
207
+ }): ComAtprotoLabelDefs.Label => {
208
+ return {
209
+ src: src || 'did:plc:fake-labeler',
210
+ uri,
211
+ val,
212
+ cts: new Date().toISOString(),
213
+ };
214
+ };
@@ -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 };