@helyx/module-moderation 1.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 (65) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/LICENSE +725 -0
  3. package/README.md +107 -0
  4. package/dist/action-support.d.ts +48 -0
  5. package/dist/action-support.js +201 -0
  6. package/dist/case-actions.d.ts +9 -0
  7. package/dist/case-actions.js +311 -0
  8. package/dist/case-naming.d.ts +4 -0
  9. package/dist/case-naming.js +9 -0
  10. package/dist/case-repository.d.ts +29 -0
  11. package/dist/case-repository.js +199 -0
  12. package/dist/channel-actions.d.ts +3 -0
  13. package/dist/channel-actions.js +267 -0
  14. package/dist/commands.d.ts +4 -0
  15. package/dist/commands.js +309 -0
  16. package/dist/components.d.ts +15 -0
  17. package/dist/components.js +381 -0
  18. package/dist/configuration.d.ts +16 -0
  19. package/dist/configuration.js +94 -0
  20. package/dist/constants.d.ts +53 -0
  21. package/dist/constants.js +53 -0
  22. package/dist/contracts.d.ts +109 -0
  23. package/dist/contracts.js +84 -0
  24. package/dist/domain.d.ts +29 -0
  25. package/dist/domain.js +101 -0
  26. package/dist/events.d.ts +3 -0
  27. package/dist/events.js +60 -0
  28. package/dist/health.d.ts +10 -0
  29. package/dist/health.js +56 -0
  30. package/dist/index.d.ts +9 -0
  31. package/dist/index.js +92 -0
  32. package/dist/moderation-cases-resource.d.ts +14 -0
  33. package/dist/moderation-cases-resource.js +226 -0
  34. package/dist/presentation.d.ts +8 -0
  35. package/dist/presentation.js +69 -0
  36. package/dist/privacy.d.ts +8 -0
  37. package/dist/privacy.js +38 -0
  38. package/dist/provider.d.ts +4 -0
  39. package/dist/provider.js +335 -0
  40. package/dist/receipt-repository.d.ts +9 -0
  41. package/dist/receipt-repository.js +36 -0
  42. package/dist/records.d.ts +399 -0
  43. package/dist/records.js +303 -0
  44. package/dist/repository-model.d.ts +131 -0
  45. package/dist/repository-model.js +318 -0
  46. package/dist/repository.d.ts +21 -0
  47. package/dist/repository.js +41 -0
  48. package/dist/service.d.ts +75 -0
  49. package/dist/service.js +329 -0
  50. package/dist/tasks.d.ts +49 -0
  51. package/dist/tasks.js +391 -0
  52. package/dist/thread-controls.d.ts +23 -0
  53. package/dist/thread-controls.js +376 -0
  54. package/dist/thread-deletion-recovery.d.ts +13 -0
  55. package/dist/thread-deletion-recovery.js +46 -0
  56. package/dist/thread-delivery.d.ts +11 -0
  57. package/dist/thread-delivery.js +427 -0
  58. package/dist/thread-reconciliation.d.ts +24 -0
  59. package/dist/thread-reconciliation.js +181 -0
  60. package/dist/thread-repository.d.ts +16 -0
  61. package/dist/thread-repository.js +70 -0
  62. package/manifest.json +999 -0
  63. package/migrations/0001_moderation_foundation.sql +552 -0
  64. package/migrations/0002_staff_attempt_parameters.sql +27 -0
  65. package/package.json +56 -0
package/manifest.json ADDED
@@ -0,0 +1,999 @@
1
+ {
2
+ "manifestVersion": "0.5",
3
+ "releaseStatus": "live",
4
+ "deploymentSupport": "both",
5
+ "credentialRisk": "none",
6
+ "deploymentReason": "Hosted and Self-Hosted installations use only Helyx runtime and Discord capabilities available inside the selected installation.",
7
+ "tier": "free",
8
+ "moduleKind": "customer_feature",
9
+ "customerSelectable": true,
10
+ "countsAsInstalledModule": true,
11
+ "healthExposure": "direct",
12
+ "id": "helyx.moderation",
13
+ "name": "Moderation",
14
+ "version": "1.0.1",
15
+ "description": "Apply accountable staff moderation actions and review durable cases.",
16
+ "entry": "./dist/index.js",
17
+ "framework": "^0.1.0",
18
+ "dependencies": [],
19
+ "permissions": [
20
+ "VIEW_CHANNEL",
21
+ "READ_MESSAGE_HISTORY",
22
+ "SEND_MESSAGES_IN_THREADS",
23
+ "CREATE_PRIVATE_THREADS",
24
+ "MANAGE_THREADS",
25
+ "MANAGE_CHANNELS",
26
+ "MANAGE_MESSAGES",
27
+ "MANAGE_ROLES",
28
+ "MODERATE_MEMBERS",
29
+ "KICK_MEMBERS",
30
+ "BAN_MEMBERS"
31
+ ],
32
+ "features": [],
33
+ "permissionOperations": [
34
+ {
35
+ "id": "moderation.cases.timeout",
36
+ "label": "Mute from case",
37
+ "description": "Apply a timeout from a dashboard case.",
38
+ "defaultPreset": "administrators"
39
+ },
40
+ {
41
+ "id": "moderation.cases.timeout-remove",
42
+ "label": "Unmute from case",
43
+ "description": "Remove a timeout from a dashboard case.",
44
+ "defaultPreset": "administrators"
45
+ },
46
+ {
47
+ "id": "moderation.cases.kick",
48
+ "label": "Kick from case",
49
+ "description": "Kick a member from a dashboard case.",
50
+ "defaultPreset": "administrators"
51
+ },
52
+ {
53
+ "id": "moderation.cases.ban",
54
+ "label": "Ban from case",
55
+ "description": "Ban a member from a dashboard case.",
56
+ "defaultPreset": "administrators"
57
+ },
58
+ {
59
+ "id": "moderation.cases.demote",
60
+ "label": "Demote from case",
61
+ "description": "Remove selected roles from a dashboard case.",
62
+ "defaultPreset": "administrators"
63
+ },
64
+ {
65
+ "id": "moderation.cases.resolve",
66
+ "label": "Resolve case without further action",
67
+ "description": "Record No Further Action from a dashboard case.",
68
+ "defaultPreset": "administrators"
69
+ },
70
+ {
71
+ "id": "moderation.cases.close-thread",
72
+ "label": "Close thread from case",
73
+ "description": "Close a resolved violation thread from its dashboard case.",
74
+ "defaultPreset": "administrators"
75
+ },
76
+ {
77
+ "id": "moderation.cases.close-and-delete-thread",
78
+ "label": "Close and delete thread from case",
79
+ "description": "Delete the resolved case's private thread while retaining moderation records.",
80
+ "defaultPreset": "administrators"
81
+ },
82
+ {
83
+ "id": "moderation.cases.read",
84
+ "label": "Read moderation cases",
85
+ "description": "List and inspect moderation cases and their safe outcomes.",
86
+ "defaultPreset": "administrators"
87
+ },
88
+ {
89
+ "id": "moderation.cases.reconcile",
90
+ "label": "Reconcile moderation cases",
91
+ "description": "Retry or settle an ambiguous moderation action or thread delivery.",
92
+ "defaultPreset": "administrators"
93
+ },
94
+ {
95
+ "id": "moderation.cases.correct",
96
+ "label": "Correct moderation cases",
97
+ "description": "Create a linked correction without rewriting the original action.",
98
+ "defaultPreset": "administrators"
99
+ },
100
+ {
101
+ "id": "moderation.cases.disassociate",
102
+ "label": "Disassociate case users",
103
+ "description": "Detach a case from its subject while preserving anonymous safety history.",
104
+ "defaultPreset": "administrators"
105
+ },
106
+ {
107
+ "id": "moderation.private-notes",
108
+ "label": "Reveal private moderation notes",
109
+ "description": "Reveal a private staff note through an audited case action.",
110
+ "defaultPreset": "administrators"
111
+ },
112
+ {
113
+ "id": "moderation.threads.act",
114
+ "label": "Act in violation threads",
115
+ "description": "Use kick, ban, unmute and no-further-action thread controls.",
116
+ "defaultPreset": "administrators"
117
+ },
118
+ {
119
+ "id": "moderation.threads.close",
120
+ "label": "Close violation threads",
121
+ "description": "Archive and lock a resolved violation thread.",
122
+ "defaultPreset": "administrators"
123
+ },
124
+ {
125
+ "id": "moderation.threads.close-delete",
126
+ "label": "Close and delete violation threads",
127
+ "description": "Delete a resolved case's private thread while retaining moderation records.",
128
+ "defaultPreset": "administrators"
129
+ },
130
+ {
131
+ "id": "moderation.settings.manage",
132
+ "label": "Manage moderation settings",
133
+ "description": "Change retention, warning-message and violation-thread settings.",
134
+ "defaultPreset": "administrators"
135
+ }
136
+ ],
137
+ "privacy": {
138
+ "containsPersonalData": true,
139
+ "providerVersion": 1,
140
+ "collections": [
141
+ {
142
+ "name": "cases",
143
+ "createdAtField": "created_at",
144
+ "subjectFields": [
145
+ {
146
+ "field": "subject_user_id",
147
+ "role": "subject",
148
+ "erasure": "set-null"
149
+ },
150
+ {
151
+ "field": "actor_user_id",
152
+ "role": "actor",
153
+ "erasure": "set-null"
154
+ }
155
+ ],
156
+ "subjectRelationships": [],
157
+ "contentFields": [
158
+ {
159
+ "field": "operation_key",
160
+ "mayIdentifySubject": true,
161
+ "erasure": "manual-review"
162
+ },
163
+ {
164
+ "field": "reason",
165
+ "mayIdentifySubject": true,
166
+ "erasure": "manual-review"
167
+ },
168
+ {
169
+ "field": "private_note",
170
+ "mayIdentifySubject": true,
171
+ "erasure": "manual-review"
172
+ },
173
+ {
174
+ "field": "evidence_channel_id",
175
+ "mayIdentifySubject": true,
176
+ "erasure": "manual-review"
177
+ },
178
+ {
179
+ "field": "evidence_message_id",
180
+ "mayIdentifySubject": true,
181
+ "erasure": "manual-review"
182
+ }
183
+ ]
184
+ },
185
+ {
186
+ "name": "case_operations",
187
+ "createdAtField": "created_at",
188
+ "subjectFields": [],
189
+ "subjectRelationships": [
190
+ {
191
+ "localField": "case_id",
192
+ "targetCollection": "cases",
193
+ "targetField": "case_id",
194
+ "targetSubjectField": "subject_user_id",
195
+ "role": "case-subject",
196
+ "erasure": "retain"
197
+ }
198
+ ],
199
+ "contentFields": [
200
+ {
201
+ "field": "operation_key",
202
+ "mayIdentifySubject": true,
203
+ "erasure": "manual-review"
204
+ },
205
+ {
206
+ "field": "input_hash",
207
+ "mayIdentifySubject": true,
208
+ "erasure": "manual-review"
209
+ }
210
+ ]
211
+ },
212
+ {
213
+ "name": "action_attempts",
214
+ "createdAtField": "created_at",
215
+ "subjectFields": [
216
+ {
217
+ "field": "actor_user_id",
218
+ "role": "attempt-actor",
219
+ "erasure": "set-null"
220
+ }
221
+ ],
222
+ "subjectRelationships": [
223
+ {
224
+ "localField": "case_id",
225
+ "targetCollection": "cases",
226
+ "targetField": "case_id",
227
+ "targetSubjectField": "subject_user_id",
228
+ "role": "case-subject",
229
+ "erasure": "retain"
230
+ }
231
+ ],
232
+ "contentFields": [
233
+ {
234
+ "field": "operation_key",
235
+ "mayIdentifySubject": true,
236
+ "erasure": "manual-review"
237
+ },
238
+ {
239
+ "field": "reason",
240
+ "mayIdentifySubject": true,
241
+ "erasure": "manual-review"
242
+ }
243
+ ]
244
+ },
245
+ {
246
+ "name": "dm_receipts",
247
+ "createdAtField": "created_at",
248
+ "subjectFields": [],
249
+ "subjectRelationships": [
250
+ {
251
+ "localField": "case_id",
252
+ "targetCollection": "cases",
253
+ "targetField": "case_id",
254
+ "targetSubjectField": "subject_user_id",
255
+ "role": "case-subject",
256
+ "erasure": "retain"
257
+ }
258
+ ],
259
+ "contentFields": [
260
+ {
261
+ "field": "message_id",
262
+ "mayIdentifySubject": true,
263
+ "erasure": "manual-review"
264
+ }
265
+ ]
266
+ },
267
+ {
268
+ "name": "thread_deliveries",
269
+ "createdAtField": "created_at",
270
+ "subjectFields": [],
271
+ "subjectRelationships": [
272
+ {
273
+ "localField": "case_id",
274
+ "targetCollection": "cases",
275
+ "targetField": "case_id",
276
+ "targetSubjectField": "subject_user_id",
277
+ "role": "case-subject",
278
+ "erasure": "retain"
279
+ }
280
+ ],
281
+ "contentFields": [
282
+ {
283
+ "field": "operation_key",
284
+ "mayIdentifySubject": true,
285
+ "erasure": "manual-review"
286
+ },
287
+ {
288
+ "field": "parent_channel_id",
289
+ "mayIdentifySubject": true,
290
+ "erasure": "manual-review"
291
+ },
292
+ {
293
+ "field": "access_role_id",
294
+ "mayIdentifySubject": true,
295
+ "erasure": "manual-review"
296
+ },
297
+ {
298
+ "field": "thread_id",
299
+ "mayIdentifySubject": true,
300
+ "erasure": "manual-review"
301
+ },
302
+ {
303
+ "field": "control_token",
304
+ "mayIdentifySubject": true,
305
+ "erasure": "manual-review"
306
+ },
307
+ {
308
+ "field": "control_message_id",
309
+ "mayIdentifySubject": true,
310
+ "erasure": "manual-review"
311
+ }
312
+ ]
313
+ }
314
+ ]
315
+ },
316
+ "dashboard": {
317
+ "category": "safety",
318
+ "icon": "shield",
319
+ "settingsMutationPermissionId": "moderation.settings.manage",
320
+ "settings": [
321
+ {
322
+ "key": "warningDirectMessageEnabled",
323
+ "type": "boolean",
324
+ "label": "Send warning direct messages",
325
+ "description": "Try to send a private notice after a warning case is created.",
326
+ "group": "Warnings",
327
+ "required": true,
328
+ "supportVisibility": "value",
329
+ "default": false
330
+ },
331
+ {
332
+ "key": "warningDirectMessageTemplate",
333
+ "type": "string",
334
+ "label": "Warning direct-message template",
335
+ "description": "Private message used for warnings; the saved value remains when delivery is disabled.",
336
+ "group": "Warnings",
337
+ "required": true,
338
+ "supportVisibility": "hidden",
339
+ "minLength": 0,
340
+ "maxLength": 2000,
341
+ "multiline": true,
342
+ "default": "You have received a moderation warning in {{serverName}}. Case: {{caseNumber}}.",
343
+ "visibleWhen": {
344
+ "field": "warningDirectMessageEnabled",
345
+ "equals": true
346
+ }
347
+ },
348
+ {
349
+ "key": "caseRetentionDays",
350
+ "type": "integer",
351
+ "label": "Settled case retention",
352
+ "description": "Days to retain settled cases; active safety constraints remain protected.",
353
+ "group": "Cases",
354
+ "required": true,
355
+ "supportVisibility": "value",
356
+ "minimum": 30,
357
+ "maximum": 730,
358
+ "default": 365
359
+ },
360
+ {
361
+ "key": "violationThreadChannelId",
362
+ "type": "discord-channel",
363
+ "label": "Violation thread channel",
364
+ "description": "Standard text channel that contains private user-visible violation threads.",
365
+ "group": "Violation threads",
366
+ "required": false,
367
+ "supportVisibility": "value",
368
+ "channelTypes": ["guild_text"],
369
+ "multiple": false
370
+ },
371
+ {
372
+ "key": "violationAccessRoleId",
373
+ "type": "discord-role",
374
+ "label": "Violation access role",
375
+ "description": "Role used to grant the affected user access to the private-thread parent.",
376
+ "group": "Violation threads",
377
+ "required": false,
378
+ "supportVisibility": "value",
379
+ "multiple": false
380
+ },
381
+ {
382
+ "key": "violationModeratorRoleIds",
383
+ "type": "discord-role",
384
+ "label": "Moderator notification roles",
385
+ "description": "Up to ten staff roles mentioned when a violation thread opens. Notification selection does not grant Helyx moderation-action access.",
386
+ "group": "Moderator notifications",
387
+ "required": false,
388
+ "supportVisibility": "value",
389
+ "multiple": true,
390
+ "minItems": 0,
391
+ "maxItems": 10
392
+ },
393
+ {
394
+ "key": "violationModeratorUserIds",
395
+ "type": "discord-user",
396
+ "label": "Moderator notification users",
397
+ "description": "Add up to ten individual staff members by Discord user ID, without requiring a notification role. They are mentioned and invited to new threads; moderation actions still require their own permissions.",
398
+ "group": "Moderator notifications",
399
+ "required": false,
400
+ "supportVisibility": "hidden",
401
+ "multiple": true,
402
+ "minItems": 0,
403
+ "maxItems": 10
404
+ },
405
+ {
406
+ "key": "violationCaseNamePrefix",
407
+ "type": "string",
408
+ "label": "Case name prefix",
409
+ "description": "Name used for new violation threads. Helyx adds the case number, padded to at least three digits: moderation-case-001 or violation-001. Existing threads keep their names.",
410
+ "group": "Violation threads",
411
+ "required": true,
412
+ "supportVisibility": "hidden",
413
+ "minLength": 1,
414
+ "maxLength": 80,
415
+ "default": "moderation-case"
416
+ },
417
+ {
418
+ "key": "violationMessageLayout",
419
+ "type": "select",
420
+ "label": "Thread message layout",
421
+ "description": "Choose the opening card for new cases. Existing threads retain their layout; permissions and staff controls are unchanged.",
422
+ "group": "Thread opening message",
423
+ "required": true,
424
+ "supportVisibility": "value",
425
+ "default": "three_containers",
426
+ "options": [
427
+ { "value": "three_containers", "label": "A · Three containers" },
428
+ { "value": "single_container", "label": "B · Single container" },
429
+ { "value": "compact_incident", "label": "C · Compact incident" },
430
+ {
431
+ "value": "conversation_evidence",
432
+ "label": "D · Conversation + evidence"
433
+ },
434
+ { "value": "spoiler_evidence", "label": "E · Spoiler evidence" }
435
+ ]
436
+ },
437
+ {
438
+ "key": "violationOpeningMessageTemplate",
439
+ "type": "string",
440
+ "label": "Opening message",
441
+ "description": "Configurable text shown to the affected user in the opening message, alongside the staff/user mentions and moderation buttons. Applies to newly opened threads.",
442
+ "group": "Thread opening message",
443
+ "required": true,
444
+ "supportVisibility": "hidden",
445
+ "minLength": 0,
446
+ "maxLength": 2000,
447
+ "multiline": true,
448
+ "default": "A moderation case has been opened so staff can discuss this violation with you."
449
+ }
450
+ ],
451
+ "actions": [
452
+ {
453
+ "id": "check-violation-channel",
454
+ "permissionId": "moderation.settings.manage",
455
+ "label": "Check violation channel",
456
+ "description": "Inspect the selected channel and access role without changing Discord.",
457
+ "buttonLabel": "Check channel",
458
+ "style": "secondary"
459
+ },
460
+ {
461
+ "id": "configure-violation-channel",
462
+ "permissionId": "moderation.settings.manage",
463
+ "label": "Configure violation channel",
464
+ "description": "Apply only the reviewed minimum channel permission changes.",
465
+ "buttonLabel": "Configure channel",
466
+ "style": "primary",
467
+ "confirmation": "Apply the previewed permission changes to this channel?",
468
+ "inspection": "required"
469
+ }
470
+ ],
471
+ "resources": [
472
+ {
473
+ "id": "moderation-cases",
474
+ "name": "Cases / offences",
475
+ "singularName": "Case",
476
+ "description": "Review durable moderation cases, outcomes and recovery state.",
477
+ "operations": ["list", "read", "action"],
478
+ "permissions": {
479
+ "list": "moderation.cases.read",
480
+ "read": "moderation.cases.read"
481
+ },
482
+ "tablePresentation": {
483
+ "primaryColumnLabel": "Case",
484
+ "showStatus": true,
485
+ "showUpdated": true,
486
+ "openAction": "primary",
487
+ "responsive": "stacked"
488
+ },
489
+ "columns": [
490
+ {
491
+ "key": "subjectUserId",
492
+ "label": "Member",
493
+ "format": "text"
494
+ },
495
+ {
496
+ "key": "action",
497
+ "label": "Action",
498
+ "format": "text"
499
+ },
500
+ {
501
+ "key": "repeatOrdinal",
502
+ "label": "Offence",
503
+ "format": "text"
504
+ },
505
+ {
506
+ "key": "actorUserId",
507
+ "label": "Actor",
508
+ "format": "text"
509
+ }
510
+ ],
511
+ "fields": [
512
+ {
513
+ "key": "caseNumber",
514
+ "type": "integer",
515
+ "label": "Case number",
516
+ "description": "Immutable number within this server.",
517
+ "required": true,
518
+ "supportVisibility": "value",
519
+ "minimum": 1,
520
+ "maximum": 2147483647
521
+ },
522
+ {
523
+ "key": "subjectUserId",
524
+ "type": "string",
525
+ "label": "Member",
526
+ "description": "Associated Discord user, or disassociated when corrected.",
527
+ "required": false,
528
+ "supportVisibility": "hidden",
529
+ "minLength": 0,
530
+ "maxLength": 20
531
+ },
532
+ {
533
+ "key": "actorUserId",
534
+ "type": "string",
535
+ "label": "Actor",
536
+ "description": "Staff member who created the case when available.",
537
+ "required": false,
538
+ "supportVisibility": "hidden",
539
+ "minLength": 0,
540
+ "maxLength": 20
541
+ },
542
+ {
543
+ "key": "action",
544
+ "type": "string",
545
+ "label": "Action",
546
+ "description": "Immutable moderation action family.",
547
+ "required": true,
548
+ "supportVisibility": "value",
549
+ "minLength": 1,
550
+ "maxLength": 32
551
+ },
552
+ {
553
+ "key": "reason",
554
+ "type": "string",
555
+ "label": "Reason",
556
+ "description": "Bounded staff reason for this case.",
557
+ "required": true,
558
+ "supportVisibility": "hidden",
559
+ "minLength": 1,
560
+ "maxLength": 500,
561
+ "multiline": true
562
+ },
563
+ {
564
+ "key": "privateNote",
565
+ "type": "string",
566
+ "label": "Private note",
567
+ "description": "Optional note supplied when staff create a case with /moderation. Use Reveal private note for an authorised, audited view; automatic cases normally have no note.",
568
+ "required": false,
569
+ "supportVisibility": "hidden",
570
+ "minLength": 0,
571
+ "maxLength": 1000,
572
+ "multiline": true
573
+ },
574
+ {
575
+ "key": "privateNoteState",
576
+ "type": "string",
577
+ "label": "Private note availability",
578
+ "description": "Whether this case has a concealed private staff note.",
579
+ "required": false,
580
+ "supportVisibility": "value",
581
+ "minLength": 0,
582
+ "maxLength": 100
583
+ },
584
+ {
585
+ "key": "threadState",
586
+ "type": "string",
587
+ "label": "Thread resolution",
588
+ "description": "Current staff resolution and closure state.",
589
+ "required": false,
590
+ "supportVisibility": "value",
591
+ "minLength": 0,
592
+ "maxLength": 100
593
+ }
594
+ ],
595
+ "actions": [
596
+ {
597
+ "id": "mute",
598
+ "permissionId": "moderation.cases.timeout",
599
+ "label": "Mute",
600
+ "description": "Timeout this member and record the staff action.",
601
+ "style": "primary",
602
+ "statuses": ["applied"],
603
+ "confirmationPolicy": "required",
604
+ "confirmation": "Mute this member for the selected duration?",
605
+ "inputs": [
606
+ {
607
+ "key": "durationSeconds",
608
+ "type": "select",
609
+ "label": "Mute duration",
610
+ "description": "How long Discord should timeout this member.",
611
+ "required": true,
612
+ "supportVisibility": "value",
613
+ "options": [
614
+ { "value": "60", "label": "60 seconds" },
615
+ { "value": "300", "label": "5 minutes" },
616
+ { "value": "600", "label": "10 minutes" },
617
+ { "value": "3600", "label": "1 hour" },
618
+ { "value": "86400", "label": "1 day" },
619
+ { "value": "604800", "label": "1 week" }
620
+ ]
621
+ },
622
+ {
623
+ "key": "reason",
624
+ "type": "string",
625
+ "label": "Reason",
626
+ "description": "Private staff reason recorded with this action.",
627
+ "required": true,
628
+ "supportVisibility": "hidden",
629
+ "minLength": 10,
630
+ "maxLength": 500,
631
+ "multiline": true
632
+ }
633
+ ]
634
+ },
635
+ {
636
+ "id": "unmute",
637
+ "permissionId": "moderation.cases.timeout-remove",
638
+ "label": "Unmute",
639
+ "description": "Remove the member timeout and record the staff action.",
640
+ "style": "secondary",
641
+ "statuses": ["applied"],
642
+ "confirmationPolicy": "required",
643
+ "confirmation": "Remove this member's timeout?",
644
+ "inputs": [
645
+ {
646
+ "key": "reason",
647
+ "type": "string",
648
+ "label": "Reason",
649
+ "description": "Private staff reason recorded with this action.",
650
+ "required": true,
651
+ "supportVisibility": "hidden",
652
+ "minLength": 10,
653
+ "maxLength": 500,
654
+ "multiline": true
655
+ }
656
+ ]
657
+ },
658
+ {
659
+ "id": "kick",
660
+ "permissionId": "moderation.cases.kick",
661
+ "label": "Kick",
662
+ "description": "Kick this member and record the staff action.",
663
+ "style": "danger",
664
+ "statuses": ["applied"],
665
+ "confirmationPolicy": "required",
666
+ "confirmation": "Kick this member from the server?",
667
+ "inputs": [
668
+ {
669
+ "key": "reason",
670
+ "type": "string",
671
+ "label": "Reason",
672
+ "description": "Private staff reason recorded with this action.",
673
+ "required": true,
674
+ "supportVisibility": "hidden",
675
+ "minLength": 10,
676
+ "maxLength": 500,
677
+ "multiline": true
678
+ }
679
+ ]
680
+ },
681
+ {
682
+ "id": "ban",
683
+ "permissionId": "moderation.cases.ban",
684
+ "label": "Ban",
685
+ "description": "Ban this member without deleting message history, and record the staff action.",
686
+ "style": "danger",
687
+ "statuses": ["applied"],
688
+ "confirmationPolicy": "required",
689
+ "confirmation": "Ban this member from the server?",
690
+ "inputs": [
691
+ {
692
+ "key": "reason",
693
+ "type": "string",
694
+ "label": "Reason",
695
+ "description": "Private staff reason recorded with this action.",
696
+ "required": true,
697
+ "supportVisibility": "hidden",
698
+ "minLength": 10,
699
+ "maxLength": 500,
700
+ "multiline": true
701
+ }
702
+ ]
703
+ },
704
+ {
705
+ "id": "demote",
706
+ "permissionId": "moderation.cases.demote",
707
+ "label": "Demote",
708
+ "description": "Remove only the selected roles that the member currently has. Protected and managed roles cannot be removed.",
709
+ "style": "danger",
710
+ "statuses": ["applied"],
711
+ "confirmationPolicy": "required",
712
+ "confirmation": "Remove the selected roles from this member?",
713
+ "inputs": [
714
+ {
715
+ "key": "roleIds",
716
+ "type": "discord-role",
717
+ "label": "Roles to remove",
718
+ "description": "Roles to remove, subject to Discord role hierarchy.",
719
+ "required": true,
720
+ "supportVisibility": "value",
721
+ "multiple": true,
722
+ "minItems": 1,
723
+ "maxItems": 10
724
+ },
725
+ {
726
+ "key": "reason",
727
+ "type": "string",
728
+ "label": "Reason",
729
+ "description": "Private staff reason recorded with this action.",
730
+ "required": true,
731
+ "supportVisibility": "hidden",
732
+ "minLength": 10,
733
+ "maxLength": 500,
734
+ "multiline": true
735
+ }
736
+ ]
737
+ },
738
+ {
739
+ "id": "no-further-action",
740
+ "permissionId": "moderation.cases.resolve",
741
+ "label": "No Further Action",
742
+ "description": "Resolve this violation without another punishment. Close becomes available after this succeeds.",
743
+ "style": "secondary",
744
+ "statuses": ["applied"],
745
+ "confirmationPolicy": "none"
746
+ },
747
+ {
748
+ "id": "close-thread",
749
+ "permissionId": "moderation.cases.close-thread",
750
+ "label": "Close thread",
751
+ "description": "Remove the affected user from the resolved thread and archive/lock it. Release only this case's access-role ownership.",
752
+ "style": "secondary",
753
+ "statuses": ["applied"],
754
+ "confirmationPolicy": "none"
755
+ },
756
+ {
757
+ "id": "close-and-delete-thread",
758
+ "permissionId": "moderation.cases.close-and-delete-thread",
759
+ "label": "Close and delete",
760
+ "description": "Close the resolved case's private thread, release this case's access and permanently delete the Discord thread. Case records and action history are retained.",
761
+ "style": "danger",
762
+ "statuses": ["applied"],
763
+ "confirmationPolicy": "required",
764
+ "confirmation": "Permanently delete this case's Discord thread and messages? Moderation records are retained."
765
+ },
766
+ {
767
+ "id": "reconcile",
768
+ "permissionId": "moderation.cases.reconcile",
769
+ "label": "Reconcile case",
770
+ "description": "Inspect current Discord state and safely settle or retry recovery work.",
771
+ "style": "primary",
772
+ "confirmationPolicy": "required",
773
+ "confirmation": "Reconcile this case against current Discord state?",
774
+ "statuses": ["applied", "failed", "review"]
775
+ },
776
+ {
777
+ "id": "correct",
778
+ "permissionId": "moderation.cases.correct",
779
+ "label": "Correct case",
780
+ "description": "Create a linked correction without rewriting the original case.",
781
+ "style": "secondary",
782
+ "confirmationPolicy": "required",
783
+ "confirmation": "Create a linked correction for this case?",
784
+ "statuses": ["applied", "failed", "review", "rejected"],
785
+ "inputs": [
786
+ {
787
+ "key": "correctionReason",
788
+ "type": "string",
789
+ "label": "Correction reason",
790
+ "description": "Reason recorded on the linked correction case.",
791
+ "required": true,
792
+ "supportVisibility": "hidden",
793
+ "minLength": 10,
794
+ "maxLength": 500,
795
+ "multiline": true
796
+ }
797
+ ]
798
+ },
799
+ {
800
+ "id": "disassociate",
801
+ "permissionId": "moderation.cases.disassociate",
802
+ "label": "Disassociate user",
803
+ "description": "Remove the user association while retaining the case number and anonymous history.",
804
+ "style": "secondary",
805
+ "confirmationPolicy": "none",
806
+ "statuses": [
807
+ "applied",
808
+ "failed",
809
+ "review",
810
+ "rejected",
811
+ "corrected",
812
+ "void"
813
+ ]
814
+ },
815
+ {
816
+ "id": "reveal-private-note",
817
+ "permissionId": "moderation.private-notes",
818
+ "label": "Reveal private note",
819
+ "description": "Reveal this case's private staff note through an audited action.",
820
+ "style": "secondary",
821
+ "confirmationPolicy": "default",
822
+ "statuses": [
823
+ "pending",
824
+ "applied",
825
+ "failed",
826
+ "review",
827
+ "rejected",
828
+ "corrected",
829
+ "void"
830
+ ]
831
+ }
832
+ ],
833
+ "query": {
834
+ "search": {
835
+ "label": "Find exact case or member",
836
+ "placeholder": "#123 or Discord user ID"
837
+ },
838
+ "filters": [
839
+ {
840
+ "key": "actorUserId",
841
+ "label": "Actor ID",
842
+ "type": "string",
843
+ "placeholder": "Exact Discord user ID"
844
+ },
845
+ {
846
+ "key": "ruleId",
847
+ "label": "Rule ID",
848
+ "type": "string",
849
+ "placeholder": "Exact rule ID"
850
+ },
851
+ {
852
+ "key": "repeatOrdinal",
853
+ "label": "Offence",
854
+ "type": "select",
855
+ "options": [
856
+ {
857
+ "value": "1",
858
+ "label": "First"
859
+ },
860
+ {
861
+ "value": "2",
862
+ "label": "Second"
863
+ },
864
+ {
865
+ "value": "3",
866
+ "label": "Third or later"
867
+ }
868
+ ]
869
+ },
870
+ {
871
+ "key": "action",
872
+ "label": "Action",
873
+ "type": "select",
874
+ "options": [
875
+ {
876
+ "value": "warn",
877
+ "label": "Warn"
878
+ },
879
+ {
880
+ "value": "timeout",
881
+ "label": "Timeout"
882
+ },
883
+ {
884
+ "value": "timeout_remove",
885
+ "label": "Timeout removed"
886
+ },
887
+ {
888
+ "value": "kick",
889
+ "label": "Kick"
890
+ },
891
+ {
892
+ "value": "ban",
893
+ "label": "Ban"
894
+ },
895
+ {
896
+ "value": "demote",
897
+ "label": "Demote"
898
+ },
899
+ {
900
+ "value": "unban",
901
+ "label": "Unban"
902
+ },
903
+ {
904
+ "value": "delete_message",
905
+ "label": "Delete message"
906
+ },
907
+ {
908
+ "value": "case_only",
909
+ "label": "Case only"
910
+ }
911
+ ]
912
+ },
913
+ {
914
+ "key": "state",
915
+ "label": "State",
916
+ "type": "select",
917
+ "options": [
918
+ {
919
+ "value": "pending",
920
+ "label": "Pending"
921
+ },
922
+ {
923
+ "value": "applied",
924
+ "label": "Applied"
925
+ },
926
+ {
927
+ "value": "failed",
928
+ "label": "Failed"
929
+ },
930
+ {
931
+ "value": "review",
932
+ "label": "Review"
933
+ },
934
+ {
935
+ "value": "rejected",
936
+ "label": "Rejected"
937
+ },
938
+ {
939
+ "value": "corrected",
940
+ "label": "Corrected"
941
+ },
942
+ {
943
+ "value": "void",
944
+ "label": "Void"
945
+ }
946
+ ]
947
+ },
948
+ {
949
+ "key": "from",
950
+ "label": "From",
951
+ "type": "string",
952
+ "placeholder": "ISO date"
953
+ },
954
+ {
955
+ "key": "to",
956
+ "label": "To",
957
+ "type": "string",
958
+ "placeholder": "ISO date"
959
+ }
960
+ ]
961
+ }
962
+ }
963
+ ],
964
+ "workspace": {
965
+ "type": "managed_resources_v1",
966
+ "tabs": [
967
+ {
968
+ "id": "cases",
969
+ "label": "Cases / offences",
970
+ "panels": [
971
+ {
972
+ "resources": ["moderation-cases"],
973
+ "mode": "list-only"
974
+ }
975
+ ]
976
+ }
977
+ ]
978
+ }
979
+ },
980
+ "contributions": {
981
+ "commands": ["mod"],
982
+ "events": [
983
+ "discord.channel-delete",
984
+ "discord.channel-update",
985
+ "discord.guild-role-delete",
986
+ "discord.guild-role-update"
987
+ ],
988
+ "logs": [
989
+ "member-warned",
990
+ "member-timed-out",
991
+ "member-kicked",
992
+ "member-banned",
993
+ "member-demoted",
994
+ "member-unbanned",
995
+ "member-unmuted",
996
+ "case-disassociated"
997
+ ]
998
+ }
999
+ }