* Begin external projects moderator database frontend * add copy link button * begin project page permissions settings * MEL database backend routes * include filename in external files * wip: when uploading a version file, fetch its overrides as a list * wip: override license checks * improve FileHost ref counting * file host read capability * scan files when inserting version file * add dependency sha1 field * clean up version files * wip: attributions * update s3 file host * attribution scanning basic works * works * insert attribution info after resolving * add routes * remove dep sha1 stuff * prepr * wip: override file sources * add files_missing_attributions to versions * return extended version info + attributed at/by * hook up frontend to backend (mostly) * expose version date published * withholding version visibility * frontend work * prepr * use api-client for img upload * moar frontend * prepr * Add schema to attribution resolution and Flame project results * sqlx prepare * changes * remove feature flag, fix optional proof images * fix schema * fmt * fix deletion and file fetch * prepare * fix admonition * update frontend stuff to new schema * prepr * attribution on dependencies * fixes * sqlx prepare * fixes * routes * fix routes * Version grandfathering * prepare * wip: bulk routes * pushing what i've got rn * include link in NoPermission * change hash insert to bulk route * query flame even if entry in MEL * delete file with weird name * Prioritise putting override files in existing groups even with ExternalLicense * fix how hex bytes are handled in route * feat: coolbot moderation changes (#6215) * Update moderator checklist * move permissions stage order * Updated nagContext.versions to v3, added nag for permissions * Update permissions.vue default messages * prepr --------- Co-authored-by: coolbot100s <76798835+coolbot100s@users.noreply.github.com> * QA * prepr * should group by project * return attribution resolution correctly * updated by moderator info * Track what moderator reviewed an attribution moderation status * default deser FMA field * new version page * clean up fetching + add a couple missing features * qa items * prepr * provide moderation package stuff with DI * format? * don't redact moderated_at * move supplementary resources * Reorganize moderation messages. * Quick replies for external content permissions. * prepare * QA * allow exempting projects * Ignore Flame projects which 404 * fix ci * fix cross project attribution stuff * Fix permission error * change what files get cscanned * add more logging * QA Jun 22 * fix * idempotency * Expose route for rescanning * update blog link --------- Co-authored-by: aecsocket <aecsocket@tutanota.com> Co-authored-by: coolbot100s <76798835+coolbot100s@users.noreply.github.com> Co-authored-by: aecsocket <43144841+aecsocket@users.noreply.github.com>
@modrinth/moderation
This package contains both the moderation checklist system used by moderators for reviewing projects on Modrinth, and the publishing checklist (nag system) that provides automated feedback to project authors during the submission process.
Structure
The package is organized as follows:
/packages/moderation/
├── data/
│ ├── checklist.ts # Main moderation checklist definition - imports and exports all stages
│ ├── messages/ # Markdown files containing message templates for moderation
│ │ ├── title/ # Messages for the title stage
│ │ ├── description/ # Messages for the description stage
│ │ └── ... # One directory per stage
│ ├── stages/ # Moderation stage definition files
│ │ ├── title.ts # Title stage definition
│ │ ├── description.ts # Description stage definition
│ │ └── ... # One file per stage
│ └── nags/ # Publishing checklist (nag system) files
│ ├── core.ts # Core nags (required fields, basic validation)
│ └── ...
└── types/ # Type definitions
├── actions.ts # Action-related types (moderation)
├── messages.ts # Message-related types (moderation)
├── stage.ts # Stage-related types (moderation)
└── nags.ts # Nag-related types (publishing checklist)
Moderation Checklist System
The moderation checklist provides a structured and transparent way to define moderation stages, actions, and messages that are displayed to moderators during the review process.
Stages
A stage represents a discrete step in the moderation process, like checking a project's title, description, or links. Each stage has:
- A title displayed to moderators
- A link to guidance documentation
- An optional navigation path to direct moderators to the relevant part of the project page
- A list of actions that moderators can take
Stages are defined in individual files in the data/stages directory and are assembled into the complete checklist in data/checklist.ts.
Actions
Actions represent decisions moderators can make for each stage. They can be buttons, dropdowns, toggles, etc. Actions can have:
- Labels displayed to the moderator
- Messages that are included in the final moderation decision
- Suggested moderation status and severity
- Optional text inputs for additional information
- Conditional behavior based on other selected actions
Each action requires a unique id field that is used for conditional logic and action relationships. The suggestedStatus and severity fields help determine the overall moderation outcome.
Messages
Messages are the actual text that will be included in communications to project authors. To promote maintainability and reuse, messages are stored as Markdown files in the data/messages directory, organized by stage.
Variable replacement
You can use variables in your messages that will be replaced with user input:
- Define a variable in the
relevantExtraInputarray of an action:
relevantExtraInput: [
{
label: 'Explanation for the user',
variable: 'MESSAGE',
required: true,
},
],
- Use the variable in your message with
%VARIABLE%syntax:
# Your Message Title
Here is some explanation about the issue.
%MESSAGE%
More text after the variable.
The %MESSAGE% placeholder will be replaced with the text entered by the moderator.
Conditional logic
The moderation system supports conditional behavior that changes based on the selection of other actions.
Conditional messages
You can define different messages for an action based on other selected actions:
{
id: 'my_action',
type: 'button',
label: 'My Action',
weight: 100,
message: async () => (await import('../messages/default-message.md?raw')).default,
conditionalMessages: [
{
conditions: {
requiredActions: ['other_action_id'],
excludedActions: ['another_action_id']
},
message: async () => (await import('../messages/conditional-message.md?raw')).default,
}
]
}
Enabling and disabling actions
Actions can enable or disable other actions when selected:
{
id: 'parent_action',
type: 'button',
label: 'Parent Action',
// This will show these actions when parent_action is selected
enablesActions: [
{
id: 'child_action',
type: 'button',
label: 'Child Action',
// ...other properties
}
],
// This will hide actions with these IDs when parent_action is selected
disablesActions: ['incompatible_action_id']
}
Conditional text inputs
Text inputs can be conditionally shown based on selected actions:
relevantExtraInput: [
{
label: 'Additional Information',
variable: 'INFO',
showWhen: {
requiredActions: ['specific_action_id'],
excludedActions: ['incompatible_action_id'],
},
},
]
Publishing Checklist (Nag System)
The nag system provides automated feedback to project authors during the submission process, helping them improve their projects before they reach moderation. It analyzes project data and provides suggestions, warnings, and requirements.
Nags
A nag represents a specific issue or suggestion for improvement. Each nag has:
- A unique
idfor identification - A
titleanddescriptiondisplayed to the user - A
statusindicating severity:'required','warning', or'suggestion' - A
shouldShowfunction that determines when the nag should be displayed - An optional
linkto help users address the issue
Internationalization
Use vintl's defineMessage syntax.
If you want to use context in the messages, you can do so like this:
description: (context: NagContext) => {
const { formatMessage } = useVIntl()
return formatMessage(defineMessage(...), {
length: context.project.body?.length || 0,
minChars: MIN_DESCRIPTION_CHARS,
})
}
Nag Context
The NagContext type provides access to:
project: Current project dataversions: Project versionstags: Frontend "tags" (generated state)currentRoute: Current page route- and other data...
Adding New Nags
To add a new nag:
- Add the nag definition to the appropriate category file (or make a new category file and add it to
data/nags.ts) - Add corresponding i18n messages to the
.i18n.tsfile - Implement the
shouldShowlogic based on project state - Add appropriate links to help users resolve the issue
- Run
pnpm run fixto fix lint issues & generate the root locale index.json file.
Example:
// In description.ts
{
id: 'new-nag',
title: messages.newNagTitle,
description: messages.newNagDescription,
status: 'warning',
shouldShow: (context: NagContext) => {
// Your validation logic here
return someCondition
},
link: {
path: 'settings/description',
title: messages.editDescriptionTitle,
shouldShow: (context: NagContext) => context.currentRoute !== 'type-project-settings-description',
},
}
// In description.i18n.ts
newNagTitle: {
id: 'nags.new-nag.title',
defaultMessage: 'New Nag Title',
},
newNagDescription: {
id: 'nags.new-nag.description',
defaultMessage: 'Description of the new nag issue.',