2023-08-03 10:28:06 -07:00
# Moderation API
Applying the moderation system is a challenging task, but we've done our best to simplify it for you. The Moderation API helps handle a wide range of tasks, including:
2024-03-12 21:10:19 -07:00
- Moderator labeling
2023-08-03 10:28:06 -07:00
- User muting (including mutelists)
- User blocking
2024-03-12 21:10:19 -07:00
- Mutewords
- Hidden posts
2023-08-03 10:28:06 -07:00
## Configuration
Every moderation function takes a set of options which look like this:
```typescript
{
// the logged-in user's DID
userDid: 'did:plc:1234...',
2024-03-12 21:10:19 -07:00
moderationPrefs: {
// is adult content allowed?
adultContentEnabled: true,
2023-08-03 10:28:06 -07:00
2024-03-12 21:10:19 -07:00
// the global label settings (used on self-labels)
labels: {
porn: 'hide',
sexual: 'warn',
nudity: 'ignore',
// ...
},
2023-08-09 16:06:29 -07:00
2024-03-12 21:10:19 -07:00
// the subscribed labelers and their label settings
labelers: [
{
did: 'did:plc:1234...',
labels: {
porn: 'hide',
sexual: 'warn',
nudity: 'ignore',
// ...
}
2023-08-03 10:28:06 -07:00
}
2024-03-12 21:10:19 -07:00
],
mutedWords: [/* ... */],
hiddenPosts: [/* ... */]
},
// custom label definitions
labelDefs: {
// labelerDid => defs[]
'did:plc:1234...': [
/* ... */
]
}
2023-08-03 10:28:06 -07:00
}
```
This should match the following interfaces:
```typescript
2024-03-12 21:10:19 -07:00
export interface ModerationPrefsLabeler {
did: string
labels: Record< string , LabelPreference >
}
export interface ModerationPrefs {
2023-08-03 10:28:06 -07:00
adultContentEnabled: boolean
2023-08-09 16:06:29 -07:00
labels: Record< string , LabelPreference >
2024-03-12 21:10:19 -07:00
labelers: ModerationPrefsLabeler[]
mutedWords: AppBskyActorDefs.MutedWord[]
hiddenPosts: string[]
2023-08-03 10:28:06 -07:00
}
2024-03-12 21:10:19 -07:00
export interface ModerationOpts {
userDid: string | undefined
prefs: ModerationPrefs
/**
* Map of labeler did -> custom definitions
*/
labelDefs?: Record< string , InterpretedLabelValueDefinition [ ] >
2023-08-03 10:28:06 -07:00
}
2024-03-12 21:10:19 -07:00
```
2023-08-03 10:28:06 -07:00
2024-03-12 21:10:19 -07:00
You can quickly grab the `ModerationPrefs` using the `agent.getPreferences()` method:
2023-08-03 10:28:06 -07:00
2024-03-12 21:10:19 -07:00
```typescript
const prefs = await agent.getPreferences()
moderatePost(post, {
userDid: /*...*/,
prefs: prefs.moderationPrefs,
labelDefs: /*...*/
})
2023-08-03 10:28:06 -07:00
```
2024-03-12 21:10:19 -07:00
To gather the label definitions (`labelDefs` ) see the _Labelers_ section below.
2023-08-03 10:28:06 -07:00
2024-03-12 21:10:19 -07:00
## Labelers
Labelers are services that provide moderation labels. Your application will typically have 1+ top-level labelers set with the ability to do "takedowns" on content. This is controlled via this static function, though the default is to use Bluesky's moderation:
2023-08-03 10:28:06 -07:00
```typescript
2024-03-12 21:10:19 -07:00
BskyAgent.configure({
appLabelers: ['did:web:my-labeler.com'],
})
```
2023-08-03 10:28:06 -07:00
2024-03-12 21:10:19 -07:00
Users may also add their own labelers. The active labelers are controlled via an HTTP header which is automatically set by the agent when `getPreferences` is called, or when the labeler preferences are changed.
2023-08-03 10:28:06 -07:00
2024-03-12 21:10:19 -07:00
Labelers publish a `app.bsky.labeler.service` record that looks like this:
```js
{
$type: 'app.bsky.labeler.service',
policies: {
// the list of label values the labeler will publish
labelValues: [
'rude',
],
// any custom definitions the labeler will be using
labelValueDefinitions: [
{
identifier: 'rude',
blurs: 'content',
severity: 'alert',
defaultSetting: 'warn',
adultOnly: false,
locales: [
{
lang: 'en',
name: 'Rude',
description: 'Not keeping things civil.',
},
],
},
],
},
createdAt: '2024-03-12T17:17:17.215Z'
2023-08-03 10:28:06 -07:00
}
```
2024-03-12 21:10:19 -07:00
The label value definition are custom labels which only apply to that labeler. Your client needs to sync those definitions in order to correctly interpret them. To do that, call `app.bsky.labeler.getService()` (or the `getServices` batch variant) periodically to fetch their definitions. We recommend caching the response (at time our writing the official client uses a TTL of 6 hours).
Here is how to do this:
```typescript
2024-08-12 19:57:21 +02:00
import { AtpAgent } from '@atproto/api '
2024-03-12 21:10:19 -07:00
2024-08-12 19:57:21 +02:00
const agent = new AtpAgent({ service: 'https://example.com' })
2024-03-12 21:10:19 -07:00
// assume `agent` is a signed in session
const prefs = await agent.getPreferences()
const labelDefs = await agent.getLabelDefinitions(prefs)
moderatePost(post, {
userDid: agent.session.did,
prefs: prefs.moderationPrefs,
labelDefs,
})
```
## The `moderate*()` APIs
The SDK exports methods to moderate the different kinds of content on the network.
```typescript
import {
moderateProfile,
moderatePost,
moderateNotification,
moderateFeedGen,
moderateUserList,
moderateLabeler,
} from '@atproto/api '
```
Each of these follows the same API signature:
```typescript
const res = moderatePost(post, moderationOptions)
```
2023-08-03 10:28:06 -07:00
2024-03-12 21:10:19 -07:00
The response object provides an API for figuring out what your UI should do in different contexts.
2023-08-03 10:28:06 -07:00
```typescript
2024-08-12 19:57:21 +02:00
res.ui(context) /* =>
2023-08-03 10:28:06 -07:00
2024-03-12 21:10:19 -07:00
ModerationUI {
filter: boolean // should the content be removed from the interface?
blur: boolean // should the content be put behind a cover?
alert: boolean // should an alert be put on the content? (negative)
inform: boolean // should an informational notice be put on the content? (neutral)
noOverride: boolean // if blur=true, should the UI disable opening the cover?
2023-08-03 10:28:06 -07:00
2024-03-12 21:10:19 -07:00
// the reasons for each of the flags:
filters: ModerationCause[]
blurs: ModerationCause[]
alerts: ModerationCause[]
informs: ModerationCause[]
2023-08-03 10:28:06 -07:00
}
2024-03-12 21:10:19 -07:00
*/
```
There are multiple UI contexts available:
- `profileList` A profile being listed, eg in search or a follower list
- `profileView` A profile being viewed directly
- `avatar` The user's avatar in any context
- `banner` The user's banner in any context
- `displayName` The user's display name in any context
- `contentList` Content being listed, eg posts in a feed, posts as replies, a user list list, a feed generator list, etc
- `contentView` Content being viewed direct, eg an opened post, the user list page, the feedgen page, etc
- `contentMedia ` Media inside the content, eg a picture embedded in a post
Here's how a post in a feed would use these tools to make a decision:
```typescript
const mod = moderatePost(post, moderationOptions)
if (mod.ui('contentList').filter) {
// dont show the post
2023-08-03 10:28:06 -07:00
}
2024-03-12 21:10:19 -07:00
if (mod.ui('contentList').blur) {
// cover the post with the explanation from mod.ui('contentList').blurs[0]
if (mod.ui('contentList').noOverride) {
// dont allow the cover to be removed
}
2023-08-03 10:28:06 -07:00
}
2024-03-12 21:10:19 -07:00
if (mod.ui('contentMedia').blur) {
2024-08-05 09:49:25 -07:00
// cover the post's embedded images with the explanation from mod.ui('contentMedia').blurs[0]
2024-03-12 21:10:19 -07:00
if (mod.ui('contentMedia').noOverride) {
// dont allow the cover to be removed
2023-08-03 10:28:06 -07:00
}
}
2024-03-12 21:10:19 -07:00
if (mod.ui('avatar').blur) {
// cover the avatar with the explanation from mod.ui('avatar').blurs[0]
if (mod.ui('avatar').noOverride) {
// dont allow the cover to be removed
}
2023-08-03 10:28:06 -07:00
}
2024-03-12 21:10:19 -07:00
for (const alert of mod.ui('contentList').alerts) {
// render this alert
2023-08-03 10:28:06 -07:00
}
2024-03-12 21:10:19 -07:00
for (const inform of mod.ui('contentList').informs) {
// render this inform
2023-08-03 10:28:06 -07:00
}
2023-09-06 19:27:50 -05:00
```
2024-03-12 21:10:19 -07:00
## Sending moderation reports
Any Labeler is capable of receiving moderation reports. As a result, you need to specify which labeler should receive the report. You do this with the `Atproto-Proxy` header:
```typescript
agent
.withProxy('atproto_labeler', 'did:web:my-labeler.com')
.createModerationReport({
reasonType: 'com.atproto.moderation.defs#reasonViolation ',
reason: 'They were being such a jerk to me!',
subject: { did: 'did:web:bob.com' },
})
```