[SDK] Swap moderation to @bsky.app/sdk and move labeler config to Client statics (#11383)

This commit is contained in:
Samuel Newman
2026-08-13 22:26:21 +03:00
committed by GitHub
parent dc31e7fd12
commit 59d2bf09d7
138 changed files with 601 additions and 416 deletions
+7 -8
View File
@@ -1,18 +1,17 @@
import {useMemo} from 'react'
import {AtpAgent, type ComAtprotoLabelDefs} from '@atproto/api'
import {
type AppBskyLabelerDefs,
AtpAgent,
type ComAtprotoLabelDefs,
type InterpretedLabelValueDefinition,
LABELS,
type ModerationCause,
type ModerationOpts,
type ModerationUI,
} from '@atproto/api'
} from '@bsky.app/sdk/moderation'
import {sanitizeDisplayName} from '#/lib/strings/display-names'
import {sanitizeHandle} from '#/lib/strings/handles'
import {type AppModerationCause} from '#/components/Pills'
import {type app} from '#/lexicons'
export const ADULT_CONTENT_LABELS = ['sexual', 'nudity', 'porn'] as const
export const OTHER_SELF_LABELS = ['graphic-media'] as const
@@ -102,8 +101,8 @@ export function lookupLabelValueDefinition(
export function isAppLabeler(
labeler:
| string
| AppBskyLabelerDefs.LabelerView
| AppBskyLabelerDefs.LabelerViewDetailed,
| app.bsky.labeler.defs.LabelerView
| app.bsky.labeler.defs.LabelerViewDetailed,
): boolean {
if (typeof labeler === 'string') {
return AtpAgent.appLabelers.includes(labeler)
@@ -114,8 +113,8 @@ export function isAppLabeler(
export function isLabelerSubscribed(
labeler:
| string
| AppBskyLabelerDefs.LabelerView
| AppBskyLabelerDefs.LabelerViewDetailed,
| app.bsky.labeler.defs.LabelerView
| app.bsky.labeler.defs.LabelerViewDetailed,
modOpts: ModerationOpts,
) {
labeler = typeof labeler === 'string' ? labeler : labeler.creator.did
@@ -1,4 +1,4 @@
import {type ModerationUI} from '@atproto/api'
import {type ModerationUI} from '@bsky.app/sdk/moderation'
import {sanitizeDisplayName} from '#/lib/strings/display-names'
import {sanitizeHandle} from '#/lib/strings/handles'
+133
View File
@@ -0,0 +1,133 @@
import {
type AppBskyActorDefs,
type AppBskyFeedDefs,
type AppBskyGraphDefs,
type AppBskyNotificationListNotifications,
type AppBskyRichtextFacet,
type ChatBskyActorDefs,
} from '@atproto/api'
import {
hasMutedWord as sdkHasMutedWord,
moderateFeedGenerator as sdkModerateFeedGenerator,
moderateNotification as sdkModerateNotification,
moderatePost as sdkModeratePost,
moderateProfile as sdkModerateProfile,
moderateStatus as sdkModerateStatus,
moderateUserList as sdkModerateUserList,
type ModerationDecision,
type ModerationOpts,
} from '@bsky.app/sdk/moderation'
import {type app, type chat} from '#/lexicons'
/*
* TRANSITIONAL. The moderation implementation now comes from
* `@bsky.app/sdk/moderation`, whose subject types are the generated
* `#/lexicons` views - so their `did`/`uri`/`cid` fields are branded
* (`DidString`, `AtUriString`). Many read paths still emit the identically
* shaped `@atproto/api` views, whose same fields are plain `string`.
*
* A plain `string` is not assignable to a branded template-literal type, so
* every `moderate*` call taking an unmigrated view fails to typecheck even
* though the value is byte-identical - the runtime only ever reads `.did`,
* `.labels` and `.viewer`, none of which the brand affects.
*
* These wrappers widen each subject parameter to accept a view from either
* world and drop the brand on the way in. Delete this module once every
* producer emits `#/lexicons` views (the `@atproto/api` removal pass) and point
* callers back at `@bsky.app/sdk/moderation` directly.
*/
type AnyProfileSubject =
| app.bsky.actor.defs.ProfileViewBasic
| app.bsky.actor.defs.ProfileView
| app.bsky.actor.defs.ProfileViewDetailed
| chat.bsky.actor.defs.ProfileViewBasic
| AppBskyActorDefs.ProfileViewBasic
| AppBskyActorDefs.ProfileView
| AppBskyActorDefs.ProfileViewDetailed
| ChatBskyActorDefs.ProfileViewBasic
type AnyPostSubject = app.bsky.feed.defs.PostView | AppBskyFeedDefs.PostView
type AnyUserListSubject =
| app.bsky.graph.defs.ListViewBasic
| app.bsky.graph.defs.ListView
| AppBskyGraphDefs.ListViewBasic
| AppBskyGraphDefs.ListView
type AnyFeedGeneratorSubject =
| app.bsky.feed.defs.GeneratorView
| AppBskyFeedDefs.GeneratorView
type AnyNotificationSubject =
| app.bsky.notification.listNotifications.Notification
| AppBskyNotificationListNotifications.Notification
export function moderateProfile(
subject: AnyProfileSubject,
opts: ModerationOpts,
): ModerationDecision {
return sdkModerateProfile(subject as app.bsky.actor.defs.ProfileView, opts)
}
export function moderateStatus(
subject: AnyProfileSubject,
opts: ModerationOpts,
): ModerationDecision {
return sdkModerateStatus(subject as app.bsky.actor.defs.ProfileView, opts)
}
export function moderatePost(
subject: AnyPostSubject,
opts: ModerationOpts,
): ModerationDecision {
return sdkModeratePost(subject as app.bsky.feed.defs.PostView, opts)
}
export function moderateUserList(
subject: AnyUserListSubject,
opts: ModerationOpts,
): ModerationDecision {
return sdkModerateUserList(subject as app.bsky.graph.defs.ListView, opts)
}
export function moderateFeedGenerator(
subject: AnyFeedGeneratorSubject,
opts: ModerationOpts,
): ModerationDecision {
return sdkModerateFeedGenerator(
subject as app.bsky.feed.defs.GeneratorView,
opts,
)
}
export function moderateNotification(
subject: AnyNotificationSubject,
opts: ModerationOpts,
): ModerationDecision {
return sdkModerateNotification(
subject as app.bsky.notification.listNotifications.Notification,
opts,
)
}
/**
* Widens `facets`/`actor` for the same reason the `moderate*` wrappers widen
* their subjects: mute-word matching reads only `text`/`features`/`langs`, none
* of which the brand affects.
*/
export function hasMutedWord(params: {
mutedWords: app.bsky.actor.defs.MutedWord[]
text: string
facets?: app.bsky.richtext.facet.Main[] | AppBskyRichtextFacet.Main[]
outlineTags?: string[]
languages?: string[]
actor?: AnyProfileSubject
}): boolean {
return sdkHasMutedWord({
...params,
facets: params.facets as app.bsky.richtext.facet.Main[] | undefined,
actor: params.actor as app.bsky.actor.defs.ProfileView | undefined,
})
}
@@ -1,7 +1,7 @@
import {
type InterpretedLabelValueDefinition,
type LabelPreference,
} from '@atproto/api'
} from '@bsky.app/sdk/moderation'
import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react'
+4 -4
View File
@@ -1,10 +1,9 @@
import {type ComAtprotoLabelDefs} from '@atproto/api'
import {
type AppBskyLabelerDefs,
type ComAtprotoLabelDefs,
type InterpretedLabelValueDefinition,
interpretLabelValueDefinition,
LABELS,
} from '@atproto/api'
} from '@bsky.app/sdk/moderation'
import {useLingui} from '@lingui/react'
import * as bcp47Match from 'bcp-47-match'
@@ -13,12 +12,13 @@ import {
useGlobalLabelStrings,
} from '#/lib/moderation/useGlobalLabelStrings'
import {useLabelDefinitions} from '#/state/preferences'
import {type app} from '#/lexicons'
export interface LabelInfo {
label: ComAtprotoLabelDefs.Label
def: InterpretedLabelValueDefinition
strings: ComAtprotoLabelDefs.LabelValueDefinitionStrings
labeler: AppBskyLabelerDefs.LabelerViewDetailed | undefined
labeler: app.bsky.labeler.defs.LabelerViewDetailed | undefined
}
export function useLabelInfo(label: ComAtprotoLabelDefs.Label): LabelInfo {
@@ -1,9 +1,6 @@
import {useMemo} from 'react'
import {
BSKY_LABELER_DID,
type ModerationCause,
type ModerationCauseSource,
} from '@atproto/api'
import {BSKY_LABELER_DID} from '@atproto/api'
import {type ModerationCause} from '@bsky.app/sdk/moderation'
import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react'
@@ -25,7 +22,7 @@ export interface ModerationCauseDescription {
description: string
source?: string
sourceDisplayName?: string
sourceType?: ModerationCauseSource['type']
sourceType?: ModerationCause['source']['type']
sourceAvi?: string
sourceDid?: string
isSubjectAccount?: boolean
+1 -1
View File
@@ -1,4 +1,4 @@
import {type ModerationUI} from '@atproto/api'
import {type ModerationUI} from '@bsky.app/sdk/moderation'
// \u2705 = ✅
// \u2713 = ✓