diff --git a/src/ageAssurance/util.ts b/src/ageAssurance/util.ts index 2bceab4d16..fcda8056f1 100644 --- a/src/ageAssurance/util.ts +++ b/src/ageAssurance/util.ts @@ -4,8 +4,8 @@ import { AppBskyAgeassuranceDefs, computeAgeAssuranceRegionAccess, getAgeAssuranceRegionConfig, - type ModerationPrefs, } from '@atproto/api' +import {type ModerationPrefs} from '@bsky.app/sdk/moderation' import {getAge} from '#/lib/strings/time' import {regionName} from '#/locale/helpers' diff --git a/src/components/Autocomplete/useAutocomplete/index.ts b/src/components/Autocomplete/useAutocomplete/index.ts index 1dc3100f37..b310876b84 100644 --- a/src/components/Autocomplete/useAutocomplete/index.ts +++ b/src/components/Autocomplete/useAutocomplete/index.ts @@ -1,8 +1,9 @@ import {useCallback, useMemo} from 'react' -import {moderateProfile, type ModerationOpts} from '@atproto/api' +import {type ModerationOpts} from '@bsky.app/sdk/moderation' import {keepPreviousData, useQuery} from '@tanstack/react-query' import {isJustAMute, moduiContainsHideableOffense} from '#/lib/moderation' +import {moderateProfile} from '#/lib/moderation/subjects' import {useModerationOpts} from '#/state/preferences/moderation-opts' import {STALE} from '#/state/queries' import {DEFAULT_LOGGED_OUT_PREFERENCES} from '#/state/queries/preferences' diff --git a/src/components/AvatarBubbles.tsx b/src/components/AvatarBubbles.tsx index 49ac4d006f..9b59a2555f 100644 --- a/src/components/AvatarBubbles.tsx +++ b/src/components/AvatarBubbles.tsx @@ -8,8 +8,8 @@ import Animated, { withDelay, withTiming, } from 'react-native-reanimated' -import {moderateProfile} from '@atproto/api' +import {moderateProfile} from '#/lib/moderation/subjects' import {useMaybeProfileShadow} from '#/state/cache/profile-shadow' import {useModerationOpts} from '#/state/preferences/moderation-opts' import {useSession} from '#/state/session' diff --git a/src/components/AvatarStack.tsx b/src/components/AvatarStack.tsx index ae42a7470b..f98148f52c 100644 --- a/src/components/AvatarStack.tsx +++ b/src/components/AvatarStack.tsx @@ -1,6 +1,6 @@ import {View} from 'react-native' -import {moderateProfile} from '@atproto/api' +import {moderateProfile} from '#/lib/moderation/subjects' import {logger} from '#/logger' import {useModerationOpts} from '#/state/preferences/moderation-opts' import {useProfilesQuery} from '#/state/queries/profile' diff --git a/src/components/KnownFollowers.tsx b/src/components/KnownFollowers.tsx index 502b08252f..bda482c1c0 100644 --- a/src/components/KnownFollowers.tsx +++ b/src/components/KnownFollowers.tsx @@ -1,12 +1,10 @@ import {useRef} from 'react' import {View} from 'react-native' -import { - type AppBskyActorDefs, - moderateProfile, - type ModerationOpts, -} from '@atproto/api' +import {type AppBskyActorDefs} from '@atproto/api' +import {type ModerationOpts} from '@bsky.app/sdk/moderation' import {Plural, Trans, useLingui} from '@lingui/react/macro' +import {moderateProfile} from '#/lib/moderation/subjects' import {makeProfileLink} from '#/lib/routes/links' import {sanitizeDisplayName} from '#/lib/strings/display-names' import {UserAvatar} from '#/view/com/util/UserAvatar' diff --git a/src/components/LabelingServiceCard/index.tsx b/src/components/LabelingServiceCard/index.tsx index 10d8c97333..dc40181735 100644 --- a/src/components/LabelingServiceCard/index.tsx +++ b/src/components/LabelingServiceCard/index.tsx @@ -1,5 +1,4 @@ import {View} from 'react-native' -import {type AppBskyLabelerDefs} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Plural, Trans} from '@lingui/react/macro' @@ -13,10 +12,11 @@ import {Flag_Stroke2_Corner0_Rounded as Flag} from '#/components/icons/Flag' import {Link as InternalLink, type LinkProps} from '#/components/Link' import {RichText} from '#/components/RichText' import {Text} from '#/components/Typography' +import {type app} from '#/lexicons' import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '../icons/Chevron' type LabelingServiceProps = { - labeler: AppBskyLabelerDefs.LabelerViewDetailed + labeler: app.bsky.labeler.defs.LabelerViewDetailed } export function Outer({ @@ -187,7 +187,7 @@ export function Loader({ loading?: React.ComponentType<{}> error?: React.ComponentType<{error: string}> component: React.ComponentType<{ - labeler: AppBskyLabelerDefs.LabelerViewDetailed + labeler: app.bsky.labeler.defs.LabelerViewDetailed }> }) { const {isLoading, data, error} = useLabelerInfoQuery({did}) diff --git a/src/components/ListCard.tsx b/src/components/ListCard.tsx index 805c365566..021bf05240 100644 --- a/src/components/ListCard.tsx +++ b/src/components/ListCard.tsx @@ -1,16 +1,13 @@ import {useEffect, useMemo} from 'react' import {View} from 'react-native' -import { - type AppBskyGraphDefs, - AtUri, - moderateUserList, - type ModerationUI, -} from '@atproto/api' +import {type AppBskyGraphDefs, AtUri} from '@atproto/api' +import {type ModerationUI} from '@bsky.app/sdk/moderation' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' import {useQueryClient} from '@tanstack/react-query' +import {moderateUserList} from '#/lib/moderation/subjects' import {sanitizeHandle} from '#/lib/strings/handles' import {useModerationOpts} from '#/state/preferences/moderation-opts' import {precacheList} from '#/state/queries/feed' diff --git a/src/components/NewskieDialog.tsx b/src/components/NewskieDialog.tsx index 5b17ec4451..7df67d80dc 100644 --- a/src/components/NewskieDialog.tsx +++ b/src/components/NewskieDialog.tsx @@ -1,6 +1,6 @@ import {useMemo, useState} from 'react' import {View} from 'react-native' -import {type AppBskyActorDefs, moderateProfile} from '@atproto/api' +import {type AppBskyActorDefs} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -8,6 +8,7 @@ import {differenceInSeconds} from 'date-fns' import {HITSLOP_10} from '#/lib/constants' import {useGetTimeAgo} from '#/lib/hooks/useTimeAgo' +import {moderateProfile} from '#/lib/moderation/subjects' import {sanitizeDisplayName} from '#/lib/strings/display-names' import {useModerationOpts} from '#/state/preferences/moderation-opts' import {useSession} from '#/state/session' diff --git a/src/components/Pills.tsx b/src/components/Pills.tsx index e38afb6c87..70b7c98a2c 100644 --- a/src/components/Pills.tsx +++ b/src/components/Pills.tsx @@ -1,6 +1,7 @@ import {useMemo} from 'react' import {View} from 'react-native' -import {BSKY_LABELER_DID, type ModerationCause} from '@atproto/api' +import {BSKY_LABELER_DID} from '@atproto/api' +import {type ModerationCause} from '@bsky.app/sdk/moderation' import {Trans, useLingui} from '@lingui/react/macro' import {useModerationCauseDescription} from '#/lib/moderation/useModerationCauseDescription' diff --git a/src/components/Post/Embed/FeedEmbed.tsx b/src/components/Post/Embed/FeedEmbed.tsx index a726c313e1..8871a63b59 100644 --- a/src/components/Post/Embed/FeedEmbed.tsx +++ b/src/components/Post/Embed/FeedEmbed.tsx @@ -1,6 +1,6 @@ import {useMemo} from 'react' -import {moderateFeedGenerator} from '@atproto/api' +import {moderateFeedGenerator} from '#/lib/moderation/subjects' import {useModerationOpts} from '#/state/preferences/moderation-opts' import {atoms as a, useTheme} from '#/alf' import * as FeedCard from '#/components/FeedCard' diff --git a/src/components/Post/Embed/ListEmbed.tsx b/src/components/Post/Embed/ListEmbed.tsx index 47c5b2c287..d6daca265c 100644 --- a/src/components/Post/Embed/ListEmbed.tsx +++ b/src/components/Post/Embed/ListEmbed.tsx @@ -1,6 +1,6 @@ import {useMemo} from 'react' -import {moderateUserList} from '@atproto/api' +import {moderateUserList} from '#/lib/moderation/subjects' import {useModerationOpts} from '#/state/preferences/moderation-opts' import {atoms as a, useTheme} from '#/alf' import * as ListCard from '#/components/ListCard' diff --git a/src/components/Post/Embed/index.tsx b/src/components/Post/Embed/index.tsx index 0b143a0df3..3c31bd72a4 100644 --- a/src/components/Post/Embed/index.tsx +++ b/src/components/Post/Embed/index.tsx @@ -5,12 +5,12 @@ import { type AppBskyFeedDefs, AppBskyFeedPost, AtUri, - moderatePost, } from '@atproto/api' import {RichText as RichTextAPI} from '@bsky.app/sdk/richtext' import {Trans} from '@lingui/react/macro' import {useQueryClient} from '@tanstack/react-query' +import {moderatePost} from '#/lib/moderation/subjects' import {makeProfileLink} from '#/lib/routes/links' import {asSdkFacets} from '#/lib/strings/rich-text-helpers' import {getChatInviteCodeFromUrl} from '#/lib/strings/url-helpers' diff --git a/src/components/Post/Embed/types.ts b/src/components/Post/Embed/types.ts index 0ef5569c7d..41031fdf05 100644 --- a/src/components/Post/Embed/types.ts +++ b/src/components/Post/Embed/types.ts @@ -1,5 +1,6 @@ import {type StyleProp, type ViewStyle} from 'react-native' -import {type AppBskyFeedDefs, type ModerationDecision} from '@atproto/api' +import {type AppBskyFeedDefs} from '@atproto/api' +import {type ModerationDecision} from '@bsky.app/sdk/moderation' export enum PostEmbedViewContext { ThreadHighlighted = 'ThreadHighlighted', diff --git a/src/components/PostControls/ShareMenu/RecentChats.tsx b/src/components/PostControls/ShareMenu/RecentChats.tsx index bab3cbfded..94086c420d 100644 --- a/src/components/PostControls/ShareMenu/RecentChats.tsx +++ b/src/components/PostControls/ShareMenu/RecentChats.tsx @@ -1,9 +1,6 @@ import {ScrollView, View} from 'react-native' -import { - type ChatBskyActorDefs, - moderateProfile, - type ModerationOpts, -} from '@atproto/api' +import {type ChatBskyActorDefs} from '@atproto/api' +import {type ModerationOpts} from '@bsky.app/sdk/moderation' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -11,6 +8,7 @@ import {useNavigation} from '@react-navigation/native' import {isBlockedOrBlocking, isMuted} from '#/lib/moderation/blocked-and-muted' import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name' +import {moderateProfile} from '#/lib/moderation/subjects' import {type NavigationProp} from '#/lib/routes/types' import {useProfileShadow} from '#/state/cache/profile-shadow' import {useModerationOpts} from '#/state/preferences/moderation-opts' diff --git a/src/components/ProfileCard.tsx b/src/components/ProfileCard.tsx index 8508fecc71..cdc8d194a1 100644 --- a/src/components/ProfileCard.tsx +++ b/src/components/ProfileCard.tsx @@ -6,11 +6,12 @@ import { View, type ViewStyle, } from 'react-native' -import {moderateProfile, type ModerationOpts} from '@atproto/api' +import {type ModerationOpts} from '@bsky.app/sdk/moderation' import {RichText as RichTextApi} from '@bsky.app/sdk/richtext' import {useLingui} from '@lingui/react/macro' import {getModerationCauseKey} from '#/lib/moderation' +import {moderateProfile} from '#/lib/moderation/subjects' import {makeProfileLink} from '#/lib/routes/links' import {forceLTR} from '#/lib/strings/bidi' import {NON_BREAKING_SPACE} from '#/lib/strings/constants' diff --git a/src/components/ProfileHoverCard/index.web.tsx b/src/components/ProfileHoverCard/index.web.tsx index 46dade016b..74dbb33f28 100644 --- a/src/components/ProfileHoverCard/index.web.tsx +++ b/src/components/ProfileHoverCard/index.web.tsx @@ -1,16 +1,14 @@ import {memo, useCallback, useEffect, useMemo, useReducer, useRef} from 'react' import {View} from 'react-native' -import { - type AppBskyActorDefs, - moderateProfile, - type ModerationOpts, -} from '@atproto/api' +import {type AppBskyActorDefs} from '@atproto/api' +import {type ModerationOpts} from '@bsky.app/sdk/moderation' import {flip, offset, shift, size, useFloating} from '@floating-ui/react-dom' import {msg, plural} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {useNavigation} from '@react-navigation/native' import {getModerationCauseKey} from '#/lib/moderation' +import {moderateProfile} from '#/lib/moderation/subjects' import {makeProfileLink} from '#/lib/routes/links' import {type NavigationProp} from '#/lib/routes/types' import {sanitizeDisplayName} from '#/lib/strings/display-names' diff --git a/src/components/ProgressGuide/FollowDialog.tsx b/src/components/ProgressGuide/FollowDialog.tsx index 0ee575fdec..3ff4569964 100644 --- a/src/components/ProgressGuide/FollowDialog.tsx +++ b/src/components/ProgressGuide/FollowDialog.tsx @@ -1,6 +1,6 @@ import {memo, useCallback, useEffect, useMemo, useRef, useState} from 'react' import {TextInput, View, type ViewToken} from 'react-native' -import {type ModerationOpts} from '@atproto/api' +import {type ModerationOpts} from '@bsky.app/sdk/moderation' import {Trans, useLingui} from '@lingui/react/macro' import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' diff --git a/src/components/StarterPack/Main/ProfilesList.tsx b/src/components/StarterPack/Main/ProfilesList.tsx index c087f5fa46..dea87c8443 100644 --- a/src/components/StarterPack/Main/ProfilesList.tsx +++ b/src/components/StarterPack/Main/ProfilesList.tsx @@ -4,8 +4,8 @@ import { type AppBskyActorDefs, type AppBskyGraphGetList, AtUri, - type ModerationOpts, } from '@atproto/api' +import {type ModerationOpts} from '@bsky.app/sdk/moderation' import { type InfiniteData, type UseInfiniteQueryResult, diff --git a/src/components/StarterPack/Wizard/WizardEditListDialog.tsx b/src/components/StarterPack/Wizard/WizardEditListDialog.tsx index d0c5d546db..498300491d 100644 --- a/src/components/StarterPack/Wizard/WizardEditListDialog.tsx +++ b/src/components/StarterPack/Wizard/WizardEditListDialog.tsx @@ -1,11 +1,8 @@ import {useRef} from 'react' import {type ListRenderItemInfo} from 'react-native' import {View} from 'react-native' -import { - type AppBskyActorDefs, - type AppBskyFeedDefs, - type ModerationOpts, -} from '@atproto/api' +import {type AppBskyActorDefs, type AppBskyFeedDefs} from '@atproto/api' +import {type ModerationOpts} from '@bsky.app/sdk/moderation' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' diff --git a/src/components/StarterPack/Wizard/WizardListCard.tsx b/src/components/StarterPack/Wizard/WizardListCard.tsx index b48c656a08..353fd77b13 100644 --- a/src/components/StarterPack/Wizard/WizardListCard.tsx +++ b/src/components/StarterPack/Wizard/WizardListCard.tsx @@ -1,17 +1,12 @@ import {Keyboard, View} from 'react-native' -import { - type AppBskyActorDefs, - type AppBskyFeedDefs, - moderateFeedGenerator, - moderateProfile, - type ModerationOpts, - type ModerationUI, -} from '@atproto/api' +import {type AppBskyActorDefs, type AppBskyFeedDefs} from '@atproto/api' +import {type ModerationOpts, type ModerationUI} from '@bsky.app/sdk/moderation' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' import {DISCOVER_FEED_URI, STARTER_PACK_MAX_SIZE} from '#/lib/constants' +import {moderateFeedGenerator, moderateProfile} from '#/lib/moderation/subjects' import {sanitizeDisplayName} from '#/lib/strings/display-names' import {sanitizeHandle} from '#/lib/strings/handles' import {useSession} from '#/state/session' diff --git a/src/components/VideoPostCard.tsx b/src/components/VideoPostCard.tsx index ba3d83ec16..bb79fa6a78 100644 --- a/src/components/VideoPostCard.tsx +++ b/src/components/VideoPostCard.tsx @@ -7,8 +7,8 @@ import { AppBskyEmbedVideo, type AppBskyFeedDefs, AppBskyFeedPost, - type ModerationDecision, } from '@atproto/api' +import {type ModerationDecision} from '@bsky.app/sdk/moderation' import {useLingui} from '@lingui/react/macro' import {sanitizeHandle} from '#/lib/strings/handles' diff --git a/src/components/activity-notifications/SubscribeProfileButton.tsx b/src/components/activity-notifications/SubscribeProfileButton.tsx index ce9dcd05ae..ed05f8cba0 100644 --- a/src/components/activity-notifications/SubscribeProfileButton.tsx +++ b/src/components/activity-notifications/SubscribeProfileButton.tsx @@ -1,5 +1,5 @@ import {useCallback, useEffect, useState} from 'react' -import {type ModerationOpts} from '@atproto/api' +import {type ModerationOpts} from '@bsky.app/sdk/moderation' import {Trans, useLingui} from '@lingui/react/macro' import {useRequireEmailVerification} from '#/lib/hooks/useRequireEmailVerification' diff --git a/src/components/activity-notifications/SubscribeProfileDialog.tsx b/src/components/activity-notifications/SubscribeProfileDialog.tsx index 6731cb3038..51d6622407 100644 --- a/src/components/activity-notifications/SubscribeProfileDialog.tsx +++ b/src/components/activity-notifications/SubscribeProfileDialog.tsx @@ -1,11 +1,8 @@ import {useMemo, useState} from 'react' import {View} from 'react-native' -import { - type AppBskyNotificationDefs, - type ModerationOpts, - type Un$Typed, -} from '@atproto/api' +import {type AppBskyNotificationDefs, type Un$Typed} from '@atproto/api' import {type DidString} from '@atproto/syntax' +import {type ModerationOpts} from '@bsky.app/sdk/moderation' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' diff --git a/src/components/contacts/screens/ViewMatches.tsx b/src/components/contacts/screens/ViewMatches.tsx index 0ef4711919..a659d8ae87 100644 --- a/src/components/contacts/screens/ViewMatches.tsx +++ b/src/components/contacts/screens/ViewMatches.tsx @@ -2,8 +2,8 @@ import {useCallback, useMemo, useRef, useState} from 'react' import {View} from 'react-native' import {useSafeAreaInsets} from 'react-native-safe-area-context' import * as SMS from 'expo-sms' -import {type ModerationOpts} from '@atproto/api' import {type DidString} from '@atproto/syntax' +import {type ModerationOpts} from '@bsky.app/sdk/moderation' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Plural, Trans} from '@lingui/react/macro' diff --git a/src/components/dialogs/MutedWords.tsx b/src/components/dialogs/MutedWords.tsx index a885f1bf5f..3263113a73 100644 --- a/src/components/dialogs/MutedWords.tsx +++ b/src/components/dialogs/MutedWords.tsx @@ -1,6 +1,7 @@ import {useCallback, useState} from 'react' import {View} from 'react-native' -import {type AppBskyActorDefs, sanitizeMutedWordValue} from '@atproto/api' +import {type DatetimeString, toDatetimeString} from '@atproto/syntax' +import {sanitizeMutedWordValue} from '@bsky.app/sdk/utils' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -35,6 +36,7 @@ import * as Menu from '#/components/Menu' import * as Prompt from '#/components/Prompt' import {Text} from '#/components/Typography' import {IS_NATIVE} from '#/env' +import {type app} from '#/lexicons' const ONE_DAY = 24 * 60 * 60 * 1000 @@ -68,20 +70,20 @@ function MutedWordsInner() { const sanitizedValue = sanitizeMutedWordValue(field) const surfaces = ['tag', targets.includes('content') && 'content'].filter( Boolean, - ) as AppBskyActorDefs.MutedWord['targets'] + ) as app.bsky.actor.defs.MutedWord['targets'] const actorTarget = excludeFollowing ? 'exclude-following' : 'all' const now = Date.now() const rawDuration = durations.at(0) // undefined evaluates to 'forever' - let duration: string | undefined + let duration: DatetimeString | undefined if (rawDuration === '24_hours') { - duration = new Date(now + ONE_DAY).toISOString() + duration = toDatetimeString(new Date(now + ONE_DAY)) } else if (rawDuration === '7_days') { - duration = new Date(now + 7 * ONE_DAY).toISOString() + duration = toDatetimeString(new Date(now + 7 * ONE_DAY)) } else if (rawDuration === '30_days') { - duration = new Date(now + 30 * ONE_DAY).toISOString() + duration = toDatetimeString(new Date(now + 30 * ONE_DAY)) } if (!sanitizedValue || !surfaces.length) { @@ -421,7 +423,7 @@ function MutedWordsInner() { function MutedWordRow({ style, word, -}: ViewStyleProp & {word: AppBskyActorDefs.MutedWord}) { +}: ViewStyleProp & {word: app.bsky.actor.defs.MutedWord}) { const t = useTheme() const {_} = useLingui() const {isPending, mutateAsync: removeMutedWord} = useRemoveMutedWordMutation() @@ -440,7 +442,7 @@ function MutedWordRow({ updateMutedWord({ ...word, expiresAt: days - ? new Date(Date.now() + days * ONE_DAY).toISOString() + ? toDatetimeString(new Date(Date.now() + days * ONE_DAY)) : undefined, }) } diff --git a/src/components/dialogs/SearchablePeopleList.tsx b/src/components/dialogs/SearchablePeopleList.tsx index cf106684a4..221beb9350 100644 --- a/src/components/dialogs/SearchablePeopleList.tsx +++ b/src/components/dialogs/SearchablePeopleList.tsx @@ -7,10 +7,11 @@ import { useState, } from 'react' import {TextInput, View} from 'react-native' -import {moderateProfile, type ModerationOpts} from '@atproto/api' +import {type ModerationOpts} from '@bsky.app/sdk/moderation' import {Plural, Trans, useLingui} from '@lingui/react/macro' import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name' +import {moderateProfile} from '#/lib/moderation/subjects' import {sanitizeHandle} from '#/lib/strings/handles' import {useModerationOpts} from '#/state/preferences/moderation-opts' import {useActorAutocompleteQuery} from '#/state/queries/actor-autocomplete' diff --git a/src/components/dialogs/lists/ListAddRemoveUsersDialog.tsx b/src/components/dialogs/lists/ListAddRemoveUsersDialog.tsx index 8129e9bdb4..a8f04e2857 100644 --- a/src/components/dialogs/lists/ListAddRemoveUsersDialog.tsx +++ b/src/components/dialogs/lists/ListAddRemoveUsersDialog.tsx @@ -1,6 +1,7 @@ import {useCallback, useMemo} from 'react' import {View} from 'react-native' -import {type AppBskyGraphDefs, type ModerationOpts} from '@atproto/api' +import {type AppBskyGraphDefs} from '@atproto/api' +import {type ModerationOpts} from '@bsky.app/sdk/moderation' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' diff --git a/src/components/dms/ActionsWrapper.tsx b/src/components/dms/ActionsWrapper.tsx index ce58dba31c..b9782949b6 100644 --- a/src/components/dms/ActionsWrapper.tsx +++ b/src/components/dms/ActionsWrapper.tsx @@ -1,5 +1,6 @@ import {View} from 'react-native' -import {type ChatBskyConvoDefs, type ModerationOpts} from '@atproto/api' +import {type ChatBskyConvoDefs} from '@atproto/api' +import {type ModerationOpts} from '@bsky.app/sdk/moderation' import {useLingui} from '@lingui/react/macro' import {MessageContextMenu} from '#/components/dms/MessageContextMenu' diff --git a/src/components/dms/ActionsWrapper.web.tsx b/src/components/dms/ActionsWrapper.web.tsx index 8d67051535..0ef5503370 100644 --- a/src/components/dms/ActionsWrapper.web.tsx +++ b/src/components/dms/ActionsWrapper.web.tsx @@ -1,6 +1,7 @@ import {useCallback, useRef, useState} from 'react' import {Pressable, View} from 'react-native' -import {type ChatBskyConvoDefs, type ModerationOpts} from '@atproto/api' +import {type ChatBskyConvoDefs} from '@atproto/api' +import {type ModerationOpts} from '@bsky.app/sdk/moderation' import {plural} from '@lingui/core/macro' import {useLingui} from '@lingui/react/macro' diff --git a/src/components/dms/BlockedByListDialog.tsx b/src/components/dms/BlockedByListDialog.tsx index 2532122087..2521168d79 100644 --- a/src/components/dms/BlockedByListDialog.tsx +++ b/src/components/dms/BlockedByListDialog.tsx @@ -1,6 +1,6 @@ import {Fragment} from 'react' import {View} from 'react-native' -import {type ModerationCause} from '@atproto/api' +import {type ModerationCause} from '@bsky.app/sdk/moderation' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' diff --git a/src/components/dms/ChatProfileTabs.tsx b/src/components/dms/ChatProfileTabs.tsx index bac128c819..341d94392d 100644 --- a/src/components/dms/ChatProfileTabs.tsx +++ b/src/components/dms/ChatProfileTabs.tsx @@ -1,10 +1,10 @@ import {useCallback, useEffect} from 'react' import {type ScrollView, View} from 'react-native' import Animated, {useAnimatedRef, useSharedValue} from 'react-native-reanimated' -import {moderateProfile} from '@atproto/api' import {useLingui} from '@lingui/react/macro' import {HITSLOP_10} from '#/lib/constants' +import {moderateProfile} from '#/lib/moderation/subjects' import {sanitizeDisplayName} from '#/lib/strings/display-names' import {sanitizeHandle} from '#/lib/strings/handles' import {useModerationOpts} from '#/state/preferences/moderation-opts' diff --git a/src/components/dms/ConvoMenu.tsx b/src/components/dms/ConvoMenu.tsx index 2eac8cdb3a..9aab1da8c9 100644 --- a/src/components/dms/ConvoMenu.tsx +++ b/src/components/dms/ConvoMenu.tsx @@ -1,6 +1,6 @@ import {memo, useCallback} from 'react' import {Keyboard, View} from 'react-native' -import {type ModerationCause} from '@atproto/api' +import {type ModerationCause} from '@bsky.app/sdk/moderation' import {Trans, useLingui} from '@lingui/react/macro' import {useNavigation} from '@react-navigation/native' import {useQueryClient} from '@tanstack/react-query' diff --git a/src/components/dms/InitiateChatFlow.tsx b/src/components/dms/InitiateChatFlow.tsx index 4e0e71c2f4..3a93b966b0 100644 --- a/src/components/dms/InitiateChatFlow.tsx +++ b/src/components/dms/InitiateChatFlow.tsx @@ -7,11 +7,12 @@ import { useState, } from 'react' import {LayoutAnimation, type TextInput, View} from 'react-native' -import {moderateProfile, type ModerationOpts} from '@atproto/api' +import {type ModerationOpts} from '@bsky.app/sdk/moderation' import {Plural, Trans, useLingui} from '@lingui/react/macro' import {MAX_GROUP_NAME_GRAPHEME_LENGTH} from '#/lib/constants' import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name' +import {moderateProfile} from '#/lib/moderation/subjects' import {sanitizeDisplayName} from '#/lib/strings/display-names' import {sanitizeHandle} from '#/lib/strings/handles' import {isOverMaxGraphemeCount} from '#/lib/strings/helpers' diff --git a/src/components/dms/MessageContextMenu.tsx b/src/components/dms/MessageContextMenu.tsx index 90b8554b3c..0bfa156eed 100644 --- a/src/components/dms/MessageContextMenu.tsx +++ b/src/components/dms/MessageContextMenu.tsx @@ -2,7 +2,8 @@ import {memo, useCallback} from 'react' import {Platform} from 'react-native' import {type GestureType} from 'react-native-gesture-handler' import * as Clipboard from 'expo-clipboard' -import {type ChatBskyConvoDefs, type ModerationOpts} from '@atproto/api' +import {type ChatBskyConvoDefs} from '@atproto/api' +import {type ModerationOpts} from '@bsky.app/sdk/moderation' import {RichText} from '@bsky.app/sdk/richtext' import {plural} from '@lingui/core/macro' import {useLingui} from '@lingui/react/macro' diff --git a/src/components/dms/MessageItem.tsx b/src/components/dms/MessageItem.tsx index 88a4571b2a..a835230db2 100644 --- a/src/components/dms/MessageItem.tsx +++ b/src/components/dms/MessageItem.tsx @@ -27,7 +27,6 @@ import { type ChatBskyActorDefs, ChatBskyConvoDefs, ChatBskyEmbedJoinLink, - moderateProfile, } from '@atproto/api' import {RichText as RichTextAPI} from '@bsky.app/sdk/richtext' import {plural} from '@lingui/core/macro' @@ -36,6 +35,7 @@ import {useQueryClient} from '@tanstack/react-query' import {isBlockedOrBlocking} from '#/lib/moderation/blocked-and-muted' import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name' +import {moderateProfile} from '#/lib/moderation/subjects' import {sanitizeHandle} from '#/lib/strings/handles' import {asSdkFacets} from '#/lib/strings/rich-text-helpers' import {useMaybeProfileShadow} from '#/state/cache/profile-shadow' diff --git a/src/components/dms/MessagesListBlockedFooter.tsx b/src/components/dms/MessagesListBlockedFooter.tsx index 1fbee421b0..f239a66aa4 100644 --- a/src/components/dms/MessagesListBlockedFooter.tsx +++ b/src/components/dms/MessagesListBlockedFooter.tsx @@ -1,6 +1,6 @@ import {useCallback, useMemo} from 'react' import {View} from 'react-native' -import {type ModerationDecision} from '@atproto/api' +import {type ModerationDecision} from '@bsky.app/sdk/moderation' import {Trans, useLingui} from '@lingui/react/macro' import {useProfileShadow} from '#/state/cache/profile-shadow' diff --git a/src/components/dms/MessagesListHeader.tsx b/src/components/dms/MessagesListHeader.tsx index dc42965dac..75a534b790 100644 --- a/src/components/dms/MessagesListHeader.tsx +++ b/src/components/dms/MessagesListHeader.tsx @@ -1,9 +1,10 @@ import {useMemo} from 'react' import {View} from 'react-native' -import {moderateProfile, type ModerationOpts} from '@atproto/api' +import {type ModerationOpts} from '@bsky.app/sdk/moderation' import {useLingui} from '@lingui/react/macro' import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name' +import {moderateProfile} from '#/lib/moderation/subjects' import {makeProfileLink} from '#/lib/routes/links' import {sanitizeHandle} from '#/lib/strings/handles' import {useProfileShadow} from '#/state/cache/profile-shadow' diff --git a/src/components/dms/components/GroupChatProfileCard.tsx b/src/components/dms/components/GroupChatProfileCard.tsx index 1d704766c2..63fbbbd20c 100644 --- a/src/components/dms/components/GroupChatProfileCard.tsx +++ b/src/components/dms/components/GroupChatProfileCard.tsx @@ -1,7 +1,8 @@ import {View} from 'react-native' -import {moderateProfile, type ModerationOpts} from '@atproto/api' +import {type ModerationOpts} from '@bsky.app/sdk/moderation' import {Trans} from '@lingui/react/macro' +import {moderateProfile} from '#/lib/moderation/subjects' import {sanitizeDisplayName} from '#/lib/strings/display-names' import {sanitizeHandle} from '#/lib/strings/handles' import {atoms as a, useTheme} from '#/alf' diff --git a/src/components/dms/util.ts b/src/components/dms/util.ts index ce0d30a8fd..7347d1512d 100644 --- a/src/components/dms/util.ts +++ b/src/components/dms/util.ts @@ -1,13 +1,9 @@ -import { - type $Typed, - ChatBskyActorDefs, - ChatBskyConvoDefs, - moderateProfile, - type ModerationOpts, -} from '@atproto/api' +import {type $Typed, ChatBskyActorDefs, ChatBskyConvoDefs} from '@atproto/api' +import {type ModerationOpts} from '@bsky.app/sdk/moderation' import {EMOJI_REACTION_LIMIT} from '#/lib/constants' import {isBlockedOrBlocking} from '#/lib/moderation/blocked-and-muted' +import {moderateProfile} from '#/lib/moderation/subjects' import {logger} from '#/logger' import {type Shadow} from '#/state/cache/profile-shadow' import {type ConvoState, ConvoStatus} from '#/state/messages/convo/types' diff --git a/src/components/images/Gallery/maybeApplyGalleryOffsetStyles.ts b/src/components/images/Gallery/maybeApplyGalleryOffsetStyles.ts index eea54b5ed9..5989497c12 100644 --- a/src/components/images/Gallery/maybeApplyGalleryOffsetStyles.ts +++ b/src/components/images/Gallery/maybeApplyGalleryOffsetStyles.ts @@ -4,9 +4,8 @@ import { AppBskyEmbedRecordWithMedia, type AppBskyFeedDefs, AppBskyFeedPost, - type ModerationCause, - type ModerationUI, } from '@atproto/api' +import {type ModerationCause, type ModerationUI} from '@bsky.app/sdk/moderation' import {unique} from '#/lib/moderation' import {type AppModerationCause} from '#/components/Pills' diff --git a/src/components/intents/GroupChatJoinDialog.tsx b/src/components/intents/GroupChatJoinDialog.tsx index 53782ae271..b2ec975503 100644 --- a/src/components/intents/GroupChatJoinDialog.tsx +++ b/src/components/intents/GroupChatJoinDialog.tsx @@ -1,11 +1,12 @@ import {useEffect} from 'react' import {View} from 'react-native' -import {ChatBskyGroupDefs, moderateProfile} from '@atproto/api' +import {ChatBskyGroupDefs} from '@atproto/api' import {Plural, Trans, useLingui} from '@lingui/react/macro' import {useNavigation} from '@react-navigation/native' import {useQueryClient} from '@tanstack/react-query' import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name' +import {moderateProfile} from '#/lib/moderation/subjects' import {useCallOnce} from '#/lib/once' import {makeProfileLink} from '#/lib/routes/links' import {type NavigationProp} from '#/lib/routes/types' diff --git a/src/components/interstitials/FeedTrendingTopics.tsx b/src/components/interstitials/FeedTrendingTopics.tsx index 4d45001f1c..a50b8652d3 100644 --- a/src/components/interstitials/FeedTrendingTopics.tsx +++ b/src/components/interstitials/FeedTrendingTopics.tsx @@ -1,8 +1,9 @@ import {useMemo} from 'react' import {Pressable, View} from 'react-native' -import {type AppBskyUnspeccedDefs, moderateProfile} from '@atproto/api' +import {type AppBskyUnspeccedDefs} from '@atproto/api' import {Plural, Trans, useLingui} from '@lingui/react/macro' +import {moderateProfile} from '#/lib/moderation/subjects' import {useModerationOpts} from '#/state/preferences/moderation-opts' import { useTrendingSettings, diff --git a/src/components/moderation/ContentHider.tsx b/src/components/moderation/ContentHider.tsx index cfdc6c7e12..b0f8dddf01 100644 --- a/src/components/moderation/ContentHider.tsx +++ b/src/components/moderation/ContentHider.tsx @@ -5,7 +5,7 @@ import { View, type ViewStyle, } from 'react-native' -import {type ModerationUI} from '@atproto/api' +import {type ModerationUI} from '@bsky.app/sdk/moderation' import {Trans, useLingui} from '@lingui/react/macro' import { diff --git a/src/components/moderation/Hider.tsx b/src/components/moderation/Hider.tsx index 74cc9bd1c8..85d063f088 100644 --- a/src/components/moderation/Hider.tsx +++ b/src/components/moderation/Hider.tsx @@ -1,5 +1,5 @@ import {createContext, useContext, useState} from 'react' -import {type ModerationUI} from '@atproto/api' +import {type ModerationUI} from '@bsky.app/sdk/moderation' import { type ModerationCauseDescription, diff --git a/src/components/moderation/LabelPreference.tsx b/src/components/moderation/LabelPreference.tsx index a654b46e24..80a83c5e6c 100644 --- a/src/components/moderation/LabelPreference.tsx +++ b/src/components/moderation/LabelPreference.tsx @@ -2,7 +2,7 @@ import {View} from 'react-native' import { type InterpretedLabelValueDefinition, type LabelPreference, -} from '@atproto/api' +} from '@bsky.app/sdk/moderation' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' diff --git a/src/components/moderation/ModerationDetailsDialog.tsx b/src/components/moderation/ModerationDetailsDialog.tsx index bf19dc0363..1b2351432e 100644 --- a/src/components/moderation/ModerationDetailsDialog.tsx +++ b/src/components/moderation/ModerationDetailsDialog.tsx @@ -1,6 +1,6 @@ import {useState} from 'react' import {View} from 'react-native' -import {type ModerationCause} from '@atproto/api' +import {type ModerationCause} from '@bsky.app/sdk/moderation' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' diff --git a/src/components/moderation/PostAlerts.tsx b/src/components/moderation/PostAlerts.tsx index af8329bec5..4aa3c92a6c 100644 --- a/src/components/moderation/PostAlerts.tsx +++ b/src/components/moderation/PostAlerts.tsx @@ -1,10 +1,6 @@ import {type StyleProp, View, type ViewStyle} from 'react-native' -import { - type AppBskyFeedDefs, - type ComAtprotoLabelDefs, - type ModerationCause, - type ModerationUI, -} from '@atproto/api' +import {type AppBskyFeedDefs, type ComAtprotoLabelDefs} from '@atproto/api' +import {type ModerationCause, type ModerationUI} from '@bsky.app/sdk/moderation' import {plural} from '@lingui/core/macro' import {useLingui} from '@lingui/react/macro' diff --git a/src/components/moderation/PostHider.tsx b/src/components/moderation/PostHider.tsx index 5019b7c3e3..54d4acf945 100644 --- a/src/components/moderation/PostHider.tsx +++ b/src/components/moderation/PostHider.tsx @@ -7,11 +7,8 @@ import { View, type ViewStyle, } from 'react-native' -import { - type AppBskyActorDefs, - type ModerationCause, - type ModerationUI, -} from '@atproto/api' +import {type AppBskyActorDefs} from '@atproto/api' +import {type ModerationCause, type ModerationUI} from '@bsky.app/sdk/moderation' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' diff --git a/src/components/moderation/ProfileHeaderAlerts.tsx b/src/components/moderation/ProfileHeaderAlerts.tsx index e6a13e2944..ee8d472b79 100644 --- a/src/components/moderation/ProfileHeaderAlerts.tsx +++ b/src/components/moderation/ProfileHeaderAlerts.tsx @@ -1,5 +1,5 @@ import {type StyleProp, type ViewStyle} from 'react-native' -import {type ModerationDecision} from '@atproto/api' +import {type ModerationDecision} from '@bsky.app/sdk/moderation' import {getModerationCauseKey, unique} from '#/lib/moderation' import * as Pills from '#/components/Pills' diff --git a/src/components/moderation/ReportDialog/action.ts b/src/components/moderation/ReportDialog/action.ts index 12b483fcab..6113a280cb 100644 --- a/src/components/moderation/ReportDialog/action.ts +++ b/src/components/moderation/ReportDialog/action.ts @@ -145,7 +145,7 @@ export function useSubmitReportMutation() { * the proxy target is built per call from that labeler's creator did. */ await client.call(com.atproto.moderation.createReport, report, { - service: `${labeler.creator.did as DidString}#atproto_labeler`, + service: `${labeler.creator.did}#atproto_labeler`, }) } }, diff --git a/src/components/moderation/ReportDialog/index.tsx b/src/components/moderation/ReportDialog/index.tsx index 7c63d04a47..79ac0be690 100644 --- a/src/components/moderation/ReportDialog/index.tsx +++ b/src/components/moderation/ReportDialog/index.tsx @@ -7,7 +7,7 @@ import { useState, } from 'react' import {Pressable, type ScrollView, View} from 'react-native' -import {type AppBskyLabelerDefs, BSKY_LABELER_DID} from '@atproto/api' +import {BSKY_LABELER_DID} from '@atproto/api' import {Trans, useLingui} from '@lingui/react/macro' import {wait} from '#/lib/async/wait' @@ -38,6 +38,7 @@ import {Loader} from '#/components/Loader' import {Text} from '#/components/Typography' import {useAnalytics} from '#/analytics' import {IS_NATIVE} from '#/env' +import {type app} from '#/lexicons' import {useSubmitReportMutation} from './action' import { BSKY_LABELER_ONLY_REPORT_REASONS, @@ -1045,8 +1046,8 @@ function LabelerCard({ labeler, onSelect, }: { - labeler: AppBskyLabelerDefs.LabelerViewDetailed - onSelect?: (option: AppBskyLabelerDefs.LabelerViewDetailed) => void + labeler: app.bsky.labeler.defs.LabelerViewDetailed + onSelect?: (option: app.bsky.labeler.defs.LabelerViewDetailed) => void }) { const t = useTheme() const {t: l} = useLingui() diff --git a/src/components/moderation/ReportDialog/state.test.ts b/src/components/moderation/ReportDialog/state.test.ts index e6f399ddf1..4129cdc9e5 100644 --- a/src/components/moderation/ReportDialog/state.test.ts +++ b/src/components/moderation/ReportDialog/state.test.ts @@ -1,8 +1,6 @@ -import { - type AppBskyLabelerDefs, - ToolsOzoneReportDefs as OzoneReportDefs, -} from '@atproto/api' +import {ToolsOzoneReportDefs as OzoneReportDefs} from '@atproto/api' +import {type app} from '#/lexicons' import { getNciiQualificationOutcome, initialState, @@ -83,7 +81,7 @@ describe('reducer NCII qualification', () => { let state = selectNciiOption() state = reducer(state, { type: 'selectLabeler', - labeler: {} as AppBskyLabelerDefs.LabelerViewDetailed, + labeler: {} as app.bsky.labeler.defs.LabelerViewDetailed, }) expect(state.activeStepIndex1).toBe(2) }) @@ -92,7 +90,7 @@ describe('reducer NCII qualification', () => { let state = selectNciiOption() state = reducer(state, { type: 'selectLabeler', - labeler: {} as AppBskyLabelerDefs.LabelerViewDetailed, + labeler: {} as app.bsky.labeler.defs.LabelerViewDetailed, }) state = reducer(state, { type: 'answerNciiQuestion', @@ -110,7 +108,7 @@ describe('reducer NCII qualification', () => { }) describe('reducer video timestamp', () => { - const labeler = {} as AppBskyLabelerDefs.LabelerViewDetailed + const labeler = {} as app.bsky.labeler.defs.LabelerViewDetailed const opted: ReportState = {...initialState, includeVideoTimestamp: true} it('is off by default', () => { diff --git a/src/components/moderation/ReportDialog/state.ts b/src/components/moderation/ReportDialog/state.ts index 921074952d..2dc55a6638 100644 --- a/src/components/moderation/ReportDialog/state.ts +++ b/src/components/moderation/ReportDialog/state.ts @@ -1,13 +1,11 @@ -import { - type AppBskyLabelerDefs, - ToolsOzoneReportDefs as OzoneReportDefs, -} from '@atproto/api' +import {ToolsOzoneReportDefs as OzoneReportDefs} from '@atproto/api' import {OTHER_REPORT_REASONS} from '#/components/moderation/ReportDialog/const' import { type ReportCategoryConfig, type ReportOption, } from '#/components/moderation/ReportDialog/utils/useReportOptions' +import {type app} from '#/lexicons' export type NciiQualification = { isDepicted?: boolean @@ -16,7 +14,7 @@ export type NciiQualification = { export type ReportState = { selectedCategory?: ReportCategoryConfig selectedOption?: ReportOption - selectedLabeler?: AppBskyLabelerDefs.LabelerViewDetailed + selectedLabeler?: app.bsky.labeler.defs.LabelerViewDetailed details?: string detailsOpen: boolean activeStepIndex1: number @@ -71,7 +69,7 @@ export type ReportAction = } | { type: 'selectLabeler' - labeler: AppBskyLabelerDefs.LabelerViewDetailed + labeler: app.bsky.labeler.defs.LabelerViewDetailed } | { type: 'clearLabeler' diff --git a/src/components/moderation/ScreenHider.tsx b/src/components/moderation/ScreenHider.tsx index 3c0d4584b8..9557121a05 100644 --- a/src/components/moderation/ScreenHider.tsx +++ b/src/components/moderation/ScreenHider.tsx @@ -5,7 +5,7 @@ import { View, type ViewStyle, } from 'react-native' -import {type ModerationUI} from '@atproto/api' +import {type ModerationUI} from '@bsky.app/sdk/moderation' import {Trans, useLingui} from '@lingui/react/macro' import {useNavigation} from '@react-navigation/native' diff --git a/src/features/liveEvents/context.tsx b/src/features/liveEvents/context.tsx index 3de2534a8e..0d333ba3a1 100644 --- a/src/features/liveEvents/context.tsx +++ b/src/features/liveEvents/context.tsx @@ -1,5 +1,5 @@ import {createContext, useContext, useMemo} from 'react' -import {hasMutedWord} from '@atproto/api' +import {hasMutedWord} from '@bsky.app/sdk/moderation' import {QueryClient, useQuery} from '@tanstack/react-query' import {useOnAppStateChange} from '#/lib/appState' diff --git a/src/features/liveNow/components/LiveStatusDialog.tsx b/src/features/liveNow/components/LiveStatusDialog.tsx index 0d3598451c..ba69b35ff9 100644 --- a/src/features/liveNow/components/LiveStatusDialog.tsx +++ b/src/features/liveNow/components/LiveStatusDialog.tsx @@ -1,16 +1,13 @@ import {useCallback, useMemo} from 'react' import {View} from 'react-native' import {Image} from 'expo-image' -import { - type AppBskyActorDefs, - type AppBskyEmbedExternal, - moderateStatus, -} from '@atproto/api' +import {type AppBskyActorDefs, type AppBskyEmbedExternal} from '@atproto/api' import {Trans, useLingui} from '@lingui/react/macro' import {useNavigation} from '@react-navigation/native' import {useQueryClient} from '@tanstack/react-query' import {useOpenLink} from '#/lib/hooks/useOpenLink' +import {moderateStatus} from '#/lib/moderation/subjects' import {type NavigationProp} from '#/lib/routes/types' import {sanitizeHandle} from '#/lib/strings/handles' import {toNiceDomain} from '#/lib/strings/url-helpers' diff --git a/src/features/liveNow/index.tsx b/src/features/liveNow/index.tsx index a9a14fc2d3..ec96777e85 100644 --- a/src/features/liveNow/index.tsx +++ b/src/features/liveNow/index.tsx @@ -3,7 +3,6 @@ import { type $Typed, type AppBskyActorDefs, AppBskyEmbedExternal, - moderateStatus, } from '@atproto/api' import {retry} from '@atproto/common-web' import {type l} from '@atproto/lex' @@ -16,6 +15,7 @@ import {isAfter, parseISO} from 'date-fns' import {uploadBlob} from '#/lib/api' import {imageToThumb} from '#/lib/api/resolve' import {getLinkMeta, type LinkMeta} from '#/lib/link-meta/link-meta' +import {moderateStatus} from '#/lib/moderation/subjects' import {matchXrpcError} from '#/lib/xrpc-error' import {useAppConfig} from '#/state/appConfig' import { diff --git a/src/lib/moderation.ts b/src/lib/moderation.ts index 0d2e3c4dbd..68b424347b 100644 --- a/src/lib/moderation.ts +++ b/src/lib/moderation.ts @@ -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 diff --git a/src/lib/moderation/create-sanitized-display-name.ts b/src/lib/moderation/create-sanitized-display-name.ts index d15564d385..1298afd7ef 100644 --- a/src/lib/moderation/create-sanitized-display-name.ts +++ b/src/lib/moderation/create-sanitized-display-name.ts @@ -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' diff --git a/src/lib/moderation/subjects.ts b/src/lib/moderation/subjects.ts new file mode 100644 index 0000000000..c1621b300c --- /dev/null +++ b/src/lib/moderation/subjects.ts @@ -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, + }) +} diff --git a/src/lib/moderation/useLabelBehaviorDescription.ts b/src/lib/moderation/useLabelBehaviorDescription.ts index cdded25924..e97ecef33c 100644 --- a/src/lib/moderation/useLabelBehaviorDescription.ts +++ b/src/lib/moderation/useLabelBehaviorDescription.ts @@ -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' diff --git a/src/lib/moderation/useLabelInfo.ts b/src/lib/moderation/useLabelInfo.ts index f398406078..fc565c3d45 100644 --- a/src/lib/moderation/useLabelInfo.ts +++ b/src/lib/moderation/useLabelInfo.ts @@ -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 { diff --git a/src/lib/moderation/useModerationCauseDescription.ts b/src/lib/moderation/useModerationCauseDescription.ts index d11d16dca3..3cdbb30961 100644 --- a/src/lib/moderation/useModerationCauseDescription.ts +++ b/src/lib/moderation/useModerationCauseDescription.ts @@ -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 diff --git a/src/lib/strings/display-names.ts b/src/lib/strings/display-names.ts index 612a317eaf..8ba2e1d215 100644 --- a/src/lib/strings/display-names.ts +++ b/src/lib/strings/display-names.ts @@ -1,4 +1,4 @@ -import {type ModerationUI} from '@atproto/api' +import {type ModerationUI} from '@bsky.app/sdk/moderation' // \u2705 = ✅ // \u2713 = ✓ diff --git a/src/screens/Messages/Conversation.tsx b/src/screens/Messages/Conversation.tsx index 1e2f4037a8..503248754c 100644 --- a/src/screens/Messages/Conversation.tsx +++ b/src/screens/Messages/Conversation.tsx @@ -1,7 +1,7 @@ import {useCallback, useEffect, useMemo, useState} from 'react' import {type LayoutChangeEvent, View} from 'react-native' import {useSafeAreaInsets} from 'react-native-safe-area-context' -import {ChatBskyConvoDefs, moderateProfile} from '@atproto/api' +import {ChatBskyConvoDefs} from '@atproto/api' import { ScrollEdgeEffect, ScrollEdgeEffectProvider, @@ -18,6 +18,7 @@ import {type NativeStackScreenProps} from '@react-navigation/native-stack' import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' import {useViewportZoomLock} from '#/lib/hooks/useViewportZoomLock' +import {moderateProfile} from '#/lib/moderation/subjects' import { type CommonNavigatorParams, type NavigationProp, diff --git a/src/screens/Messages/ConversationSettings/Member.tsx b/src/screens/Messages/ConversationSettings/Member.tsx index 20cc0fee52..8fe07978e8 100644 --- a/src/screens/Messages/ConversationSettings/Member.tsx +++ b/src/screens/Messages/ConversationSettings/Member.tsx @@ -1,9 +1,9 @@ import {View} from 'react-native' -import {moderateProfile} from '@atproto/api' import {Trans, useLingui} from '@lingui/react/macro' import {isBlockedOrBlocking} from '#/lib/moderation/blocked-and-muted' import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name' +import {moderateProfile} from '#/lib/moderation/subjects' import {logger} from '#/logger' import {useProfileShadow} from '#/state/cache/profile-shadow' import {useModerationOpts} from '#/state/preferences/moderation-opts' diff --git a/src/screens/Messages/ConversationSettings/index.tsx b/src/screens/Messages/ConversationSettings/index.tsx index 5f76a3f565..80806ae534 100644 --- a/src/screens/Messages/ConversationSettings/index.tsx +++ b/src/screens/Messages/ConversationSettings/index.tsx @@ -1,10 +1,7 @@ import {useEffect, useState} from 'react' import {Pressable, View} from 'react-native' -import { - ChatBskyActorDefs, - ChatBskyConvoDefs, - type ModerationOpts, -} from '@atproto/api' +import {ChatBskyActorDefs, ChatBskyConvoDefs} from '@atproto/api' +import {type ModerationOpts} from '@bsky.app/sdk/moderation' import {Trans, useLingui} from '@lingui/react/macro' import {useNavigation} from '@react-navigation/native' diff --git a/src/screens/Messages/JoinRequest.tsx b/src/screens/Messages/JoinRequest.tsx index 6ce57a151b..06faecaffe 100644 --- a/src/screens/Messages/JoinRequest.tsx +++ b/src/screens/Messages/JoinRequest.tsx @@ -1,11 +1,12 @@ import {useEffect} from 'react' import {View} from 'react-native' import {ImageBackground} from 'expo-image' -import {ChatBskyGroupDefs, moderateProfile} from '@atproto/api' +import {ChatBskyGroupDefs} from '@atproto/api' import {type ThemeName} from '@bsky.app/alf' import {Trans, useLingui} from '@lingui/react/macro' import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name' +import {moderateProfile} from '#/lib/moderation/subjects' import {sanitizeHandle} from '#/lib/strings/handles' import {useModerationOpts} from '#/state/preferences/moderation-opts' import {useJoinLinkPreviewsQuery} from '#/state/queries/join-links' diff --git a/src/screens/Messages/components/ChatListItem.tsx b/src/screens/Messages/components/ChatListItem.tsx index 0acd0c28e7..ad2a1d731c 100644 --- a/src/screens/Messages/components/ChatListItem.tsx +++ b/src/screens/Messages/components/ChatListItem.tsx @@ -1,11 +1,10 @@ import {useCallback, useMemo, useState} from 'react' import {type GestureResponderEvent, View} from 'react-native' +import {ChatBskyConvoDefs} from '@atproto/api' import { - ChatBskyConvoDefs, - moderateProfile, type ModerationDecision, type ModerationOpts, -} from '@atproto/api' +} from '@bsky.app/sdk/moderation' import {plural} from '@lingui/core/macro' import {useLingui} from '@lingui/react/macro' import {useQueryClient} from '@tanstack/react-query' @@ -13,6 +12,7 @@ import {useQueryClient} from '@tanstack/react-query' import {GestureActionView} from '#/lib/custom-animations/GestureActionView' import {useHaptics} from '#/lib/haptics' import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name' +import {moderateProfile} from '#/lib/moderation/subjects' import {decrementBadgeCount} from '#/lib/notifications/notifications' import {sanitizeHandle} from '#/lib/strings/handles' import { diff --git a/src/screens/Messages/components/ChatStatusInfo.tsx b/src/screens/Messages/components/ChatStatusInfo.tsx index e2e9a75faa..4d3ebe2ab8 100644 --- a/src/screens/Messages/components/ChatStatusInfo.tsx +++ b/src/screens/Messages/components/ChatStatusInfo.tsx @@ -1,10 +1,10 @@ import {useCallback, useMemo} from 'react' import {View} from 'react-native' import {LinearGradient} from 'expo-linear-gradient' -import {moderateProfile} from '@atproto/api' import {Trans, useLingui} from '@lingui/react/macro' import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name' +import {moderateProfile} from '#/lib/moderation/subjects' import {sanitizeHandle} from '#/lib/strings/handles' import {useProfileShadow} from '#/state/cache/profile-shadow' import {type ActiveConvoStates} from '#/state/messages/convo' diff --git a/src/screens/Messages/components/InviteLinkDialog.tsx b/src/screens/Messages/components/InviteLinkDialog.tsx index 22acd577b0..a2a32a41c5 100644 --- a/src/screens/Messages/components/InviteLinkDialog.tsx +++ b/src/screens/Messages/components/InviteLinkDialog.tsx @@ -1,15 +1,13 @@ import {useState} from 'react' import {View} from 'react-native' import {Image} from 'expo-image' -import { - type ChatBskyGroupDefs, - moderateProfile, - type ModerationOpts, -} from '@atproto/api' +import {type ChatBskyGroupDefs} from '@atproto/api' +import {type ModerationOpts} from '@bsky.app/sdk/moderation' import {Plural, Trans, useLingui} from '@lingui/react/macro' import {useOpenComposer} from '#/lib/hooks/useOpenComposer' import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name' +import {moderateProfile} from '#/lib/moderation/subjects' import {shareUrl} from '#/lib/sharing' import {useCreateJoinLink} from '#/state/queries/messages/create-join-link' import {useDisableJoinLink} from '#/state/queries/messages/disable-join-link' diff --git a/src/screens/Messages/components/MessageInputEmbed.tsx b/src/screens/Messages/components/MessageInputEmbed.tsx index 9d1e4c32f6..7740c7ea50 100644 --- a/src/screens/Messages/components/MessageInputEmbed.tsx +++ b/src/screens/Messages/components/MessageInputEmbed.tsx @@ -1,11 +1,12 @@ import {useCallback, useEffect, useMemo, useState} from 'react' import {LayoutAnimation, View} from 'react-native' -import {AppBskyFeedPost, AtUri, moderatePost} from '@atproto/api' +import {AppBskyFeedPost, AtUri} from '@atproto/api' import {RichText as RichTextAPI} from '@bsky.app/sdk/richtext' import {Trans, useLingui} from '@lingui/react/macro' import {type RouteProp, useNavigation, useRoute} from '@react-navigation/native' import {HITSLOP_20} from '#/lib/constants' +import {moderatePost} from '#/lib/moderation/subjects' import {makeProfileLink} from '#/lib/routes/links' import { type CommonNavigatorParams, diff --git a/src/screens/Messages/components/MessagesListInfoPanel.tsx b/src/screens/Messages/components/MessagesListInfoPanel.tsx index b5c39a42dc..297ef8632f 100644 --- a/src/screens/Messages/components/MessagesListInfoPanel.tsx +++ b/src/screens/Messages/components/MessagesListInfoPanel.tsx @@ -1,9 +1,9 @@ import {View} from 'react-native' -import {moderateProfile} from '@atproto/api' import {Trans, useLingui} from '@lingui/react/macro' import {useNavigation} from '@react-navigation/native' import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name' +import {moderateProfile} from '#/lib/moderation/subjects' import {type NavigationProp} from '#/lib/routes/types' import {isInvalidHandle, sanitizeHandle} from '#/lib/strings/handles' import {useModerationOpts} from '#/state/preferences/moderation-opts' diff --git a/src/screens/Moderation/index.tsx b/src/screens/Moderation/index.tsx index 9aea34b89d..416390f075 100644 --- a/src/screens/Moderation/index.tsx +++ b/src/screens/Moderation/index.tsx @@ -1,6 +1,6 @@ import {Fragment, useCallback} from 'react' import {Linking, View} from 'react-native' -import {LABELS} from '@atproto/api' +import {LABELS} from '@bsky.app/sdk/moderation' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' diff --git a/src/screens/ModerationInteractionSettings/index.tsx b/src/screens/ModerationInteractionSettings/index.tsx index 12d952c872..9ce833a8e5 100644 --- a/src/screens/ModerationInteractionSettings/index.tsx +++ b/src/screens/ModerationInteractionSettings/index.tsx @@ -23,7 +23,6 @@ import {PostInteractionSettingsForm} from '#/components/dialogs/PostInteractionS import * as Layout from '#/components/Layout' import {Loader} from '#/components/Loader' import * as Toast from '#/components/Toast' -import {type app} from '#/lexicons' export function Screen() { const gutters = useGutters(['base']) @@ -76,15 +75,14 @@ function Inner({preferences}: {preferences: UsePreferencesQueryResponse}) { * Preferences are still typed against the legacy client, so the stored * rules arrive unbranded. Wave B migrates `getPreferences`. */ - allow: preferences.postInteractionSettings - .threadgateAllowRules as app.bsky.feed.threadgate.Main['allow'], + allow: preferences.postInteractionSettings.threadgateAllowRules, }) }, [preferences.postInteractionSettings.threadgateAllowRules]) const postgate = useMemo(() => { return createPostgateRecord({ post: '', - embeddingRules: preferences.postInteractionSettings - .postgateEmbeddingRules as app.bsky.feed.postgate.Main['embeddingRules'], + embeddingRules: + preferences.postInteractionSettings.postgateEmbeddingRules, }) }, [preferences.postInteractionSettings.postgateEmbeddingRules]) diff --git a/src/screens/Onboarding/StepSuggestedAccounts/index.tsx b/src/screens/Onboarding/StepSuggestedAccounts/index.tsx index f43d06cdd4..982ee15611 100644 --- a/src/screens/Onboarding/StepSuggestedAccounts/index.tsx +++ b/src/screens/Onboarding/StepSuggestedAccounts/index.tsx @@ -1,6 +1,6 @@ import {useCallback, useEffect, useMemo, useRef, useState} from 'react' import {View} from 'react-native' -import {type ModerationOpts} from '@atproto/api' +import {type ModerationOpts} from '@bsky.app/sdk/moderation' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' diff --git a/src/screens/PostThread/components/LikesStat.tsx b/src/screens/PostThread/components/LikesStat.tsx index eab32dc274..de219f4743 100644 --- a/src/screens/PostThread/components/LikesStat.tsx +++ b/src/screens/PostThread/components/LikesStat.tsx @@ -1,7 +1,8 @@ import {View} from 'react-native' -import {type AppBskyFeedDefs, AtUri, moderateProfile} from '@atproto/api' +import {type AppBskyFeedDefs, AtUri} from '@atproto/api' import {Plural, Trans, useLingui} from '@lingui/react/macro' +import {moderateProfile} from '#/lib/moderation/subjects' import {makeProfileLink} from '#/lib/routes/links' import {sanitizeDisplayName} from '#/lib/strings/display-names' import {useModerationOpts} from '#/state/preferences/moderation-opts' diff --git a/src/screens/Profile/Header/DisplayName.tsx b/src/screens/Profile/Header/DisplayName.tsx index 8bf50fee2d..ac99ba82b4 100644 --- a/src/screens/Profile/Header/DisplayName.tsx +++ b/src/screens/Profile/Header/DisplayName.tsx @@ -1,5 +1,6 @@ import {View} from 'react-native' -import {type AppBskyActorDefs, type ModerationDecision} from '@atproto/api' +import {type AppBskyActorDefs} from '@atproto/api' +import {type ModerationDecision} from '@bsky.app/sdk/moderation' import {sanitizeDisplayName} from '#/lib/strings/display-names' import {sanitizeHandle} from '#/lib/strings/handles' diff --git a/src/screens/Profile/Header/ProfileHeaderLabeler.tsx b/src/screens/Profile/Header/ProfileHeaderLabeler.tsx index 812e65b6e2..a7979f17ae 100644 --- a/src/screens/Profile/Header/ProfileHeaderLabeler.tsx +++ b/src/screens/Profile/Header/ProfileHeaderLabeler.tsx @@ -1,11 +1,7 @@ import {memo, useCallback, useMemo, useState} from 'react' import {View} from 'react-native' -import { - type AppBskyActorDefs, - type AppBskyLabelerDefs, - moderateProfile, - type ModerationOpts, -} from '@atproto/api' +import {type AppBskyActorDefs} from '@atproto/api' +import {type ModerationOpts} from '@bsky.app/sdk/moderation' import {type RichText as RichTextAPI} from '@bsky.app/sdk/richtext' import {msg, plural} from '@lingui/core/macro' import {useLingui} from '@lingui/react' @@ -15,6 +11,7 @@ import {Plural, Trans} from '@lingui/react/macro' import {MAX_LABELERS} from '#/lib/constants' import {useHaptics} from '#/lib/haptics' import {isAppLabeler} from '#/lib/moderation' +import {moderateProfile} from '#/lib/moderation/subjects' import {useProfileShadow} from '#/state/cache/profile-shadow' import {type Shadow} from '#/state/cache/types' import {useLabelerSubscriptionMutation} from '#/state/queries/labeler' @@ -37,6 +34,7 @@ import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' import {useAnalytics} from '#/analytics' import {IS_IOS} from '#/env' +import {type app} from '#/lexicons' import {ProfileHeaderDisplayName} from './DisplayName' import {EditProfileDialog} from './EditProfileDialog' import {ProfileHeaderHandle} from './Handle' @@ -45,7 +43,7 @@ import {ProfileHeaderShell} from './Shell' interface Props { profile: AppBskyActorDefs.ProfileViewDetailed - labeler: AppBskyLabelerDefs.LabelerViewDetailed + labeler: app.bsky.labeler.defs.LabelerViewDetailed descriptionRT: RichTextAPI | null moderationOpts: ModerationOpts hideBackButton?: boolean diff --git a/src/screens/Profile/Header/ProfileHeaderStandard.tsx b/src/screens/Profile/Header/ProfileHeaderStandard.tsx index 3070997b98..b7cfff7ab9 100644 --- a/src/screens/Profile/Header/ProfileHeaderStandard.tsx +++ b/src/screens/Profile/Header/ProfileHeaderStandard.tsx @@ -1,17 +1,17 @@ import {memo, useMemo, useState} from 'react' import {View} from 'react-native' +import {type AppBskyActorDefs} from '@atproto/api' import { - type AppBskyActorDefs, - moderateProfile, type ModerationDecision, type ModerationOpts, -} from '@atproto/api' +} from '@bsky.app/sdk/moderation' import {type RichText as RichTextAPI} from '@bsky.app/sdk/richtext' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' import {useHaptics} from '#/lib/haptics' +import {moderateProfile} from '#/lib/moderation/subjects' import {sanitizeDisplayName} from '#/lib/strings/display-names' import {logger} from '#/logger' import {type Shadow, useProfileShadow} from '#/state/cache/profile-shadow' diff --git a/src/screens/Profile/Header/Shell.tsx b/src/screens/Profile/Header/Shell.tsx index a142e1139c..4521ba9edf 100644 --- a/src/screens/Profile/Header/Shell.tsx +++ b/src/screens/Profile/Header/Shell.tsx @@ -5,8 +5,9 @@ import Animated, { useAnimatedRef, } from 'react-native-reanimated' import {useSafeAreaInsets} from 'react-native-safe-area-context' -import {type AppBskyActorDefs, type ModerationDecision} from '@atproto/api' +import {type AppBskyActorDefs} from '@atproto/api' import {utils} from '@bsky.app/alf' +import {type ModerationDecision} from '@bsky.app/sdk/moderation' import {useLingui} from '@lingui/react/macro' import {useNavigation} from '@react-navigation/native' diff --git a/src/screens/Profile/Header/index.tsx b/src/screens/Profile/Header/index.tsx index dbd8bafcab..eff65c854d 100644 --- a/src/screens/Profile/Header/index.tsx +++ b/src/screens/Profile/Header/index.tsx @@ -7,15 +7,12 @@ import Animated, { } from 'react-native-reanimated' import {useSafeAreaInsets} from 'react-native-safe-area-context' import {scheduleOnRN} from 'react-native-worklets' -import { - type AppBskyActorDefs, - type AppBskyLabelerDefs, - moderateProfile, - type ModerationOpts, -} from '@atproto/api' +import {type AppBskyActorDefs} from '@atproto/api' +import {type ModerationOpts} from '@bsky.app/sdk/moderation' import {type RichText as RichTextAPI} from '@bsky.app/sdk/richtext' import {useIsFocused} from '@react-navigation/native' +import {moderateProfile} from '#/lib/moderation/subjects' import {sanitizeHandle} from '#/lib/strings/handles' import {useProfileShadow} from '#/state/cache/profile-shadow' import {useModerationOpts} from '#/state/preferences/moderation-opts' @@ -26,6 +23,7 @@ import {atoms as a, useTheme} from '#/alf' import {Header} from '#/components/Layout' import * as ProfileCard from '#/components/ProfileCard' import {IS_NATIVE} from '#/env' +import {type app} from '#/lexicons' import { HeaderLabelerButtons, ProfileHeaderLabeler, @@ -61,7 +59,7 @@ export {ProfileHeaderLoading} interface Props { profile: AppBskyActorDefs.ProfileViewDetailed - labeler: AppBskyLabelerDefs.LabelerViewDetailed | undefined + labeler: app.bsky.labeler.defs.LabelerViewDetailed | undefined descriptionRT: RichTextAPI | null moderationOpts: ModerationOpts hideBackButton?: boolean @@ -106,7 +104,7 @@ const MinimalHeader = memo(function MinimalHeader({ }: { onLayout: (e: LayoutChangeEvent) => void profile: AppBskyActorDefs.ProfileViewDetailed - labeler?: AppBskyLabelerDefs.LabelerViewDetailed + labeler?: app.bsky.labeler.defs.LabelerViewDetailed hideBackButton?: boolean }) { const t = useTheme() diff --git a/src/screens/Profile/Sections/Labels.tsx b/src/screens/Profile/Sections/Labels.tsx index 8c70d40392..649c214acb 100644 --- a/src/screens/Profile/Sections/Labels.tsx +++ b/src/screens/Profile/Sections/Labels.tsx @@ -1,11 +1,10 @@ import {useCallback, useEffect, useImperativeHandle, useMemo} from 'react' import {type ListRenderItemInfo, View} from 'react-native' import { - type AppBskyLabelerDefs, type InterpretedLabelValueDefinition, interpretLabelValueDefinitions, type ModerationOpts, -} from '@atproto/api' +} from '@bsky.app/sdk/moderation' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -21,13 +20,14 @@ import {Loader} from '#/components/Loader' import {LabelerLabelPreference} from '#/components/moderation/LabelPreference' import {Text} from '#/components/Typography' import {IS_IOS, IS_NATIVE} from '#/env' +import {type app} from '#/lexicons' import {ErrorState} from '../ErrorState' import {type SectionRef} from './types' interface LabelsSectionProps { ref: React.Ref isLabelerLoading: boolean - labelerInfo: AppBskyLabelerDefs.LabelerViewDetailed | undefined + labelerInfo: app.bsky.labeler.defs.LabelerViewDetailed | undefined labelerError: Error | null moderationOpts: ModerationOpts scrollElRef: ListRef @@ -161,7 +161,7 @@ export function LabelerListHeader({ }: { isLabelerLoading: boolean labelerError?: Error | null - labelerInfo?: AppBskyLabelerDefs.LabelerViewDetailed + labelerInfo?: app.bsky.labeler.defs.LabelerViewDetailed hasValues: boolean isSubscribed: boolean }) { diff --git a/src/screens/ProfileList/index.tsx b/src/screens/ProfileList/index.tsx index 77af470cd5..ef0221631d 100644 --- a/src/screens/ProfileList/index.tsx +++ b/src/screens/ProfileList/index.tsx @@ -1,12 +1,8 @@ import {useCallback, useMemo, useRef, useState} from 'react' import {View} from 'react-native' import {useAnimatedRef} from 'react-native-reanimated' -import { - AppBskyGraphDefs, - AtUri, - moderateUserList, - type ModerationOpts, -} from '@atproto/api' +import {AppBskyGraphDefs, AtUri} from '@atproto/api' +import {type ModerationOpts} from '@bsky.app/sdk/moderation' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -15,6 +11,7 @@ import {useQueryClient} from '@tanstack/react-query' import {useOpenComposer} from '#/lib/hooks/useOpenComposer' import {useSetTitle} from '#/lib/hooks/useSetTitle' +import {moderateUserList} from '#/lib/moderation/subjects' import { type CommonNavigatorParams, type NativeStackScreenProps, diff --git a/src/screens/Search/components/SearchHistory.tsx b/src/screens/Search/components/SearchHistory.tsx index 31186726b2..b71f5ef312 100644 --- a/src/screens/Search/components/SearchHistory.tsx +++ b/src/screens/Search/components/SearchHistory.tsx @@ -1,8 +1,9 @@ import {ScrollView, View} from 'react-native' -import {moderateProfile, type ModerationOpts} from '@atproto/api' +import {type ModerationOpts} from '@bsky.app/sdk/moderation' import {Plural, Trans, useLingui} from '@lingui/react/macro' import {createHitslop} from '#/lib/constants' +import {moderateProfile} from '#/lib/moderation/subjects' import {makeProfileLink} from '#/lib/routes/links' import {sanitizeDisplayName} from '#/lib/strings/display-names' import {sanitizeHandle} from '#/lib/strings/handles' diff --git a/src/screens/Search/components/SearchProfileCard.tsx b/src/screens/Search/components/SearchProfileCard.tsx index a5ca18ee13..b848739ed3 100644 --- a/src/screens/Search/components/SearchProfileCard.tsx +++ b/src/screens/Search/components/SearchProfileCard.tsx @@ -1,6 +1,6 @@ import {useCallback} from 'react' import {View} from 'react-native' -import {type ModerationOpts} from '@atproto/api' +import {type ModerationOpts} from '@bsky.app/sdk/moderation' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {useQueryClient} from '@tanstack/react-query' diff --git a/src/screens/Search/components/StarterPackCard.tsx b/src/screens/Search/components/StarterPackCard.tsx index 9831746b42..949bdd6ebf 100644 --- a/src/screens/Search/components/StarterPackCard.tsx +++ b/src/screens/Search/components/StarterPackCard.tsx @@ -1,14 +1,11 @@ import {useState} from 'react' import {View} from 'react-native' -import { - type AppBskyGraphDefs, - AppBskyGraphStarterpack, - moderateProfile, -} from '@atproto/api' +import {type AppBskyGraphDefs, AppBskyGraphStarterpack} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' +import {moderateProfile} from '#/lib/moderation/subjects' import {sanitizeHandle} from '#/lib/strings/handles' import {useModerationOpts} from '#/state/preferences/moderation-opts' import {useSession} from '#/state/session' diff --git a/src/screens/Search/modules/ExploreSuggestedAccounts.tsx b/src/screens/Search/modules/ExploreSuggestedAccounts.tsx index c32d4eefd3..ef6dc0cba9 100644 --- a/src/screens/Search/modules/ExploreSuggestedAccounts.tsx +++ b/src/screens/Search/modules/ExploreSuggestedAccounts.tsx @@ -1,6 +1,7 @@ import {memo, useEffect} from 'react' import {View} from 'react-native' -import {type AppBskyActorSearchActors, type ModerationOpts} from '@atproto/api' +import {type AppBskyActorSearchActors} from '@atproto/api' +import {type ModerationOpts} from '@bsky.app/sdk/moderation' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {type InfiniteData} from '@tanstack/react-query' diff --git a/src/screens/Search/modules/ExploreTrendingTopics.tsx b/src/screens/Search/modules/ExploreTrendingTopics.tsx index 60c42323c3..27356efff9 100644 --- a/src/screens/Search/modules/ExploreTrendingTopics.tsx +++ b/src/screens/Search/modules/ExploreTrendingTopics.tsx @@ -1,10 +1,11 @@ import {useMemo} from 'react' import {Pressable, View} from 'react-native' import {Image} from 'expo-image' -import {type AppBskyUnspeccedDefs, moderateProfile} from '@atproto/api' +import {type AppBskyUnspeccedDefs} from '@atproto/api' import {RichText as RichTextApi} from '@bsky.app/sdk/richtext' import {Plural, Trans, useLingui} from '@lingui/react/macro' +import {moderateProfile} from '#/lib/moderation/subjects' import {useModerationOpts} from '#/state/preferences/moderation-opts' import { useTrendingSettings, diff --git a/src/screens/Settings/FindContactsSettings.tsx b/src/screens/Settings/FindContactsSettings.tsx index c8ecdf4102..1fb0f3b2a8 100644 --- a/src/screens/Settings/FindContactsSettings.tsx +++ b/src/screens/Settings/FindContactsSettings.tsx @@ -1,8 +1,9 @@ import {useCallback, useEffect, useState} from 'react' import {type ListRenderItemInfo, View} from 'react-native' import * as Contacts from 'expo-contacts' -import {type AppBskyContactDefs, type ModerationOpts} from '@atproto/api' +import {type AppBskyContactDefs} from '@atproto/api' import {type DidString} from '@atproto/syntax' +import {type ModerationOpts} from '@bsky.app/sdk/moderation' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Plural, Trans} from '@lingui/react/macro' diff --git a/src/screens/Settings/NotificationSettings/ActivityNotificationSettings.tsx b/src/screens/Settings/NotificationSettings/ActivityNotificationSettings.tsx index 3aaf5e10e5..0da3356f5e 100644 --- a/src/screens/Settings/NotificationSettings/ActivityNotificationSettings.tsx +++ b/src/screens/Settings/NotificationSettings/ActivityNotificationSettings.tsx @@ -1,6 +1,6 @@ import {useCallback, useMemo} from 'react' import {type ListRenderItemInfo, Text as RNText, View} from 'react-native' -import {type ModerationOpts} from '@atproto/api' +import {type ModerationOpts} from '@bsky.app/sdk/moderation' import {Trans, useLingui} from '@lingui/react/macro' import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name' diff --git a/src/screens/Settings/Settings.tsx b/src/screens/Settings/Settings.tsx index 0d5b2bcc73..0fff031f87 100644 --- a/src/screens/Settings/Settings.tsx +++ b/src/screens/Settings/Settings.tsx @@ -1,7 +1,7 @@ import {useState} from 'react' import {Alert, LayoutAnimation, Linking, Pressable, View} from 'react-native' import {useReducedMotion} from 'react-native-reanimated' -import {type AppBskyActorDefs, moderateProfile} from '@atproto/api' +import {type AppBskyActorDefs} from '@atproto/api' import {removeNuxs} from '@bsky.app/sdk' import {Trans, useLingui} from '@lingui/react/macro' import {useNavigation} from '@react-navigation/native' @@ -10,6 +10,7 @@ import {type NativeStackScreenProps} from '@react-navigation/native-stack' import {HELP_DESK_URL, HITSLOP_10} from '#/lib/constants' import {useAccountSwitcher} from '#/lib/hooks/useAccountSwitcher' import {useApplyPullRequestOTAUpdate} from '#/lib/hooks/useOTAUpdates' +import {moderateProfile} from '#/lib/moderation/subjects' import { type CommonNavigatorParams, type NavigationProp, diff --git a/src/screens/StarterPack/StarterPackLandingScreen.tsx b/src/screens/StarterPack/StarterPackLandingScreen.tsx index 6184514412..a8ae9ce95f 100644 --- a/src/screens/StarterPack/StarterPackLandingScreen.tsx +++ b/src/screens/StarterPack/StarterPackLandingScreen.tsx @@ -1,12 +1,8 @@ import {useEffect, useState} from 'react' import {Pressable, View} from 'react-native' import Animated, {FadeIn, FadeOut} from 'react-native-reanimated' -import { - AppBskyGraphDefs, - AppBskyGraphStarterpack, - AtUri, - type ModerationOpts, -} from '@atproto/api' +import {AppBskyGraphDefs, AppBskyGraphStarterpack, AtUri} from '@atproto/api' +import {type ModerationOpts} from '@bsky.app/sdk/moderation' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' diff --git a/src/screens/StarterPack/StarterPackScreen.tsx b/src/screens/StarterPack/StarterPackScreen.tsx index c79e39c190..e392f395db 100644 --- a/src/screens/StarterPack/StarterPackScreen.tsx +++ b/src/screens/StarterPack/StarterPackScreen.tsx @@ -1,13 +1,9 @@ import {useCallback, useEffect, useState} from 'react' import {View} from 'react-native' import {Image} from 'expo-image' -import { - AppBskyGraphDefs, - AppBskyGraphStarterpack, - AtUri, - type ModerationOpts, -} from '@atproto/api' +import {AppBskyGraphDefs, AppBskyGraphStarterpack, AtUri} from '@atproto/api' import {type AtUriString} from '@atproto/syntax' +import {type ModerationOpts} from '@bsky.app/sdk/moderation' import {RichText as RichTextAPI} from '@bsky.app/sdk/richtext' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' diff --git a/src/screens/StarterPack/Wizard/StepFeeds.tsx b/src/screens/StarterPack/Wizard/StepFeeds.tsx index 470d5a55ee..183f9fb843 100644 --- a/src/screens/StarterPack/Wizard/StepFeeds.tsx +++ b/src/screens/StarterPack/Wizard/StepFeeds.tsx @@ -1,7 +1,8 @@ import {useState} from 'react' import {type ListRenderItemInfo, View} from 'react-native' import {KeyboardAwareScrollView} from 'react-native-keyboard-controller' -import {type AppBskyFeedDefs, type ModerationOpts} from '@atproto/api' +import {type AppBskyFeedDefs} from '@atproto/api' +import {type ModerationOpts} from '@bsky.app/sdk/moderation' import {Trans} from '@lingui/react/macro' import {DISCOVER_FEED_URI} from '#/lib/constants' diff --git a/src/screens/StarterPack/Wizard/StepProfiles.tsx b/src/screens/StarterPack/Wizard/StepProfiles.tsx index 9f3b7076fd..45e52fa14b 100644 --- a/src/screens/StarterPack/Wizard/StepProfiles.tsx +++ b/src/screens/StarterPack/Wizard/StepProfiles.tsx @@ -1,7 +1,7 @@ import {useState} from 'react' import {type ListRenderItemInfo, View} from 'react-native' import {KeyboardAwareScrollView} from 'react-native-keyboard-controller' -import {type ModerationOpts} from '@atproto/api' +import {type ModerationOpts} from '@bsky.app/sdk/moderation' import {Trans} from '@lingui/react/macro' import {useA11y} from '#/state/a11y' diff --git a/src/screens/StarterPack/Wizard/index.tsx b/src/screens/StarterPack/Wizard/index.tsx index c579f09d67..4f94943729 100644 --- a/src/screens/StarterPack/Wizard/index.tsx +++ b/src/screens/StarterPack/Wizard/index.tsx @@ -8,8 +8,8 @@ import { type AppBskyFeedDefs, type AppBskyGraphDefs, AtUri, - type ModerationOpts, } from '@atproto/api' +import {type ModerationOpts} from '@bsky.app/sdk/moderation' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Plural, Trans} from '@lingui/react/macro' diff --git a/src/screens/VideoFeed/index.tsx b/src/screens/VideoFeed/index.tsx index 38c2e5a89d..9f4771ddce 100644 --- a/src/screens/VideoFeed/index.tsx +++ b/src/screens/VideoFeed/index.tsx @@ -30,8 +30,8 @@ import { type AppBskyFeedDefs, AppBskyFeedPost, AtUri, - type ModerationDecision, } from '@atproto/api' +import {type ModerationDecision} from '@bsky.app/sdk/moderation' import {RichText as RichTextAPI} from '@bsky.app/sdk/richtext' import {Trans, useLingui} from '@lingui/react/macro' import { diff --git a/src/state/preferences/label-defs.tsx b/src/state/preferences/label-defs.tsx index dd1859ecb6..8000bd231c 100644 --- a/src/state/preferences/label-defs.tsx +++ b/src/state/preferences/label-defs.tsx @@ -1,14 +1,12 @@ import {createContext, useContext} from 'react' -import { - type AppBskyLabelerDefs, - type InterpretedLabelValueDefinition, -} from '@atproto/api' +import {type InterpretedLabelValueDefinition} from '@bsky.app/sdk/moderation' +import {type app} from '#/lexicons' import {useLabelDefinitionsQuery} from '../queries/preferences' interface StateContext { labelDefs: Record - labelers: AppBskyLabelerDefs.LabelerViewDetailed[] + labelers: app.bsky.labeler.defs.LabelerViewDetailed[] } const stateContext = createContext({ diff --git a/src/state/preferences/moderation-opts.tsx b/src/state/preferences/moderation-opts.tsx index 119c9008db..77ef89f283 100644 --- a/src/state/preferences/moderation-opts.tsx +++ b/src/state/preferences/moderation-opts.tsx @@ -1,5 +1,6 @@ import {createContext, useContext, useMemo} from 'react' -import {AtpAgent, type ModerationOpts} from '@atproto/api' +import {Client} from '@atproto/lex' +import {type ModerationOpts} from '@bsky.app/sdk/moderation' import {useHiddenPosts, useLabelDefinitions} from '#/state/preferences' import {DEFAULT_LOGGED_OUT_LABEL_PREFERENCES} from '#/state/queries/preferences/const' @@ -38,16 +39,21 @@ export function Provider({children}: React.PropsWithChildren<{}>) { return undefined } return { - userDid, + /* + * `did`/`hiddenPosts` come from persisted storage typed as plain + * `string`, so brand them to the SDK's `DidString`/`AtUriString` slots. + */ + userDid: userDid as ModerationOpts['userDid'], prefs: { ...moderationPrefs, labelers: moderationPrefs.labelers.length ? moderationPrefs.labelers - : AtpAgent.appLabelers.map(did => ({ + : Client.appLabelers.map(did => ({ did, labels: DEFAULT_LOGGED_OUT_LABEL_PREFERENCES, })), - hiddenPosts: hiddenPosts || [], + hiddenPosts: (hiddenPosts || + []) as ModerationOpts['prefs']['hiddenPosts'], }, labelDefs, } diff --git a/src/state/queries/actor-autocomplete.ts b/src/state/queries/actor-autocomplete.ts index 14377c5347..111a8aaab4 100644 --- a/src/state/queries/actor-autocomplete.ts +++ b/src/state/queries/actor-autocomplete.ts @@ -1,12 +1,10 @@ import {useCallback} from 'react' -import { - type AppBskyActorDefs, - moderateProfile, - type ModerationOpts, -} from '@atproto/api' +import {type AppBskyActorDefs} from '@atproto/api' +import {type ModerationOpts} from '@bsky.app/sdk/moderation' import {keepPreviousData, useQuery, useQueryClient} from '@tanstack/react-query' import {isJustAMute, moduiContainsHideableOffense} from '#/lib/moderation' +import {moderateProfile} from '#/lib/moderation/subjects' import {logger} from '#/logger' import {STALE} from '#/state/queries' import {useAppviewClient} from '#/state/session' diff --git a/src/state/queries/explore-feed-previews.tsx b/src/state/queries/explore-feed-previews.tsx index c340078603..884b46d2df 100644 --- a/src/state/queries/explore-feed-previews.tsx +++ b/src/state/queries/explore-feed-previews.tsx @@ -1,10 +1,5 @@ import {useMemo, useRef} from 'react' -import { - type AppBskyActorDefs, - AppBskyFeedDefs, - AtUri, - moderatePost, -} from '@atproto/api' +import {type AppBskyActorDefs, AppBskyFeedDefs, AtUri} from '@atproto/api' import {type AtUriString} from '@atproto/syntax' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' @@ -17,6 +12,7 @@ import { import {CustomFeedAPI} from '#/lib/api/feed/custom' import {aggregateUserInterests} from '#/lib/api/feed/utils' import {FeedTuner} from '#/lib/api/feed-manip' +import {moderatePost} from '#/lib/moderation/subjects' import {cleanError} from '#/lib/strings/errors' import {useModerationOpts} from '#/state/preferences/moderation-opts' import { diff --git a/src/state/queries/feed.ts b/src/state/queries/feed.ts index a3b90de32b..669ec04263 100644 --- a/src/state/queries/feed.ts +++ b/src/state/queries/feed.ts @@ -4,7 +4,6 @@ import { type AppBskyFeedDefs, type AppBskyGraphDefs, AtUri, - moderateFeedGenerator, } from '@atproto/api' import {type AtUriString} from '@atproto/syntax' import {RichText} from '@bsky.app/sdk/richtext' @@ -20,6 +19,7 @@ import { } from '@tanstack/react-query' import {DISCOVER_FEED_URI, DISCOVER_SAVED_FEED} from '#/lib/constants' +import {moderateFeedGenerator} from '#/lib/moderation/subjects' import {sanitizeDisplayName} from '#/lib/strings/display-names' import {sanitizeHandle} from '#/lib/strings/handles' import {asSdkFacets} from '#/lib/strings/rich-text-helpers' diff --git a/src/state/queries/labeler.ts b/src/state/queries/labeler.ts index b132b73ebe..3fd14cc5fc 100644 --- a/src/state/queries/labeler.ts +++ b/src/state/queries/labeler.ts @@ -1,4 +1,3 @@ -import {type AppBskyLabelerDefs} from '@atproto/api' import {type DidString} from '@atproto/syntax' import {addLabeler, removeLabeler} from '@bsky.app/sdk' import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query' @@ -11,7 +10,8 @@ import { usePreferencesQuery, } from '#/state/queries/preferences' import {createQueryKey} from '#/state/queries/util' -import {useAgent, usePdsClient} from '#/state/session' +import {useAppviewClient, usePdsClient} from '#/state/session' +import {app} from '#/lexicons' const labelerInfoQueryKeyRoot = 'labeler-info' export const labelerInfoQueryKey = (did: string) => [ @@ -35,45 +35,47 @@ export function useLabelerInfoQuery({ did?: string enabled?: boolean }) { - const agent = useAgent() + const client = useAppviewClient() return useQuery({ enabled: !!did && enabled !== false, queryKey: labelerInfoQueryKey(did as string), queryFn: async () => { - const res = await agent.app.bsky.labeler.getServices({ - dids: [did!], + const res = await client.call(app.bsky.labeler.getServices, { + dids: [did! as DidString], detailed: true, }) - return res.data.views[0] as AppBskyLabelerDefs.LabelerViewDetailed + return res.views[0] as app.bsky.labeler.defs.LabelerViewDetailed }, }) } export function useLabelersInfoQuery({dids}: {dids: string[]}) { - const agent = useAgent() + const client = useAppviewClient() return useQuery({ enabled: !!dids.length, queryKey: labelersInfoQueryKey(dids), queryFn: async () => { - const res = await agent.app.bsky.labeler.getServices({dids}) - return res.data.views as AppBskyLabelerDefs.LabelerView[] + const res = await client.call(app.bsky.labeler.getServices, { + dids: dids as DidString[], + }) + return res.views as app.bsky.labeler.defs.LabelerView[] }, }) } export function useLabelersDetailedInfoQuery({dids}: {dids: string[]}) { - const agent = useAgent() + const client = useAppviewClient() return useQuery({ enabled: !!dids.length, queryKey: createLabelersDetailedInfoQueryKey(dids), gcTime: GCTIME.INFINITY, staleTime: STALE.MINUTES.ONE, queryFn: async () => { - const res = await agent.app.bsky.labeler.getServices({ - dids, + const res = await client.call(app.bsky.labeler.getServices, { + dids: dids as DidString[], detailed: true, }) - return res.data.views as AppBskyLabelerDefs.LabelerViewDetailed[] + return res.views as app.bsky.labeler.defs.LabelerViewDetailed[] }, }) } @@ -98,7 +100,7 @@ export function useRemoveLabelersMutation() { export function useLabelerSubscriptionMutation() { const queryClient = useQueryClient() - const agent = useAgent() + const appviewClient = useAppviewClient() const pdsClient = usePdsClient() const preferences = usePreferencesQuery() @@ -122,28 +124,30 @@ export function useLabelerSubscriptionMutation() { const labelerDids = ( preferences.data?.moderationPrefs?.labelers ?? [] ).map(l => l.did) - const invalidLabelers: string[] = [] + const invalidLabelers: DidString[] = [] if (labelerDids.length) { - const profiles = await agent.getProfiles({actors: labelerDids}) - if (profiles.data) { - for (const did of labelerDids) { - const exists = profiles.data.profiles.find(p => p.did === did) + const profiles = await appviewClient.call(app.bsky.actor.getProfiles, { + actors: labelerDids, + }) + if (profiles) { + for (const labelerDid of labelerDids) { + const exists = profiles.profiles.find(p => p.did === labelerDid) if (exists) { // profile came back but it's not a valid labeler if (exists.associated && !exists.associated.labeler) { - invalidLabelers.push(did) + invalidLabelers.push(labelerDid) } } else { // no response came back, might be deactivated or takendown - invalidLabelers.push(did) + invalidLabelers.push(labelerDid) } } } } if (invalidLabelers.length) { await Promise.all( - invalidLabelers.map(did => - pdsClient.call(removeLabeler, did as DidString), + invalidLabelers.map(labelerDid => + pdsClient.call(removeLabeler, labelerDid), ), ) } diff --git a/src/state/queries/notifications/feed.ts b/src/state/queries/notifications/feed.ts index 51740e8d50..4dc23db47e 100644 --- a/src/state/queries/notifications/feed.ts +++ b/src/state/queries/notifications/feed.ts @@ -17,12 +17,7 @@ */ import {useCallback, useEffect, useMemo, useRef} from 'react' -import { - AppBskyFeedDefs, - AppBskyFeedPost, - AtUri, - moderatePost, -} from '@atproto/api' +import {AppBskyFeedDefs, AppBskyFeedPost, AtUri} from '@atproto/api' import { type InfiniteData, type QueryClient, @@ -31,6 +26,7 @@ import { useQueryClient, } from '@tanstack/react-query' +import {moderatePost} from '#/lib/moderation/subjects' import {useModerationOpts} from '#/state/preferences/moderation-opts' import {STALE} from '#/state/queries' import {useAppviewClient} from '#/state/session' diff --git a/src/state/queries/notifications/util.ts b/src/state/queries/notifications/util.ts index 4606507700..929517d9fb 100644 --- a/src/state/queries/notifications/util.ts +++ b/src/state/queries/notifications/util.ts @@ -6,16 +6,15 @@ import { type AppBskyGraphDefs, AppBskyGraphStarterpack, type AppBskyNotificationListNotifications, - hasMutedWord, - moderateNotification, - type ModerationOpts, } from '@atproto/api' import {type Client} from '@atproto/lex' import {type AtUriString} from '@atproto/syntax' +import {type ModerationOpts} from '@bsky.app/sdk/moderation' import {type QueryClient} from '@tanstack/react-query' import chunk from 'lodash.chunk' import {labelIsHideableOffense} from '#/lib/moderation' +import {hasMutedWord, moderateNotification} from '#/lib/moderation/subjects' import {app} from '#/lexicons' import * as bsky from '#/types/bsky' import {precacheProfile} from '../profile' diff --git a/src/state/queries/post-feed.ts b/src/state/queries/post-feed.ts index 0d5b48acde..c3d5ada4d2 100644 --- a/src/state/queries/post-feed.ts +++ b/src/state/queries/post-feed.ts @@ -5,12 +5,13 @@ import { AppBskyFeedDefs, type AppBskyFeedPost, AtUri, - moderatePost, - type ModerationDecision, - type ModerationPrefs, } from '@atproto/api' import {type Client} from '@atproto/lex' import {type AtIdentifierString, type AtUriString} from '@atproto/syntax' +import { + type ModerationDecision, + type ModerationPrefs, +} from '@bsky.app/sdk/moderation' import { type InfiniteData, type QueryClient, @@ -31,6 +32,7 @@ import {type FeedAPI, type ReasonFeedSource} from '#/lib/api/feed/types' import {aggregateUserInterests} from '#/lib/api/feed/utils' import {FeedTuner, type FeedTunerFn} from '#/lib/api/feed-manip' import {DISCOVER_FEED_URI} from '#/lib/constants' +import {moderatePost} from '#/lib/moderation/subjects' import {logger} from '#/logger' import {STALE} from '#/state/queries' import {DEFAULT_LOGGED_OUT_PREFERENCES} from '#/state/queries/preferences/const' diff --git a/src/state/queries/preferences/const.ts b/src/state/queries/preferences/const.ts index 74c86d1020..81b50fffd0 100644 --- a/src/state/queries/preferences/const.ts +++ b/src/state/queries/preferences/const.ts @@ -1,4 +1,4 @@ -import {DEFAULT_LABEL_SETTINGS} from '@atproto/api' +import {DEFAULT_LABEL_SETTINGS} from '@bsky.app/sdk/moderation' import { type ThreadViewPreferences, diff --git a/src/state/queries/preferences/index.ts b/src/state/queries/preferences/index.ts index 6358de738c..449458c075 100644 --- a/src/state/queries/preferences/index.ts +++ b/src/state/queries/preferences/index.ts @@ -40,7 +40,7 @@ import { } from '#/state/queries/preferences/types' import {createQueryKey} from '#/state/queries/util' import {useAgent, usePdsClient} from '#/state/session' -import {saveLabelers} from '#/state/session/moderation' +import {applyLabelersToClient, saveLabelers} from '#/state/session/moderation' import {useAgeAssurance} from '#/ageAssurance' import {makeAgeRestrictedModerationPrefs} from '#/ageAssurance/util' import {useAnalytics} from '#/analytics' @@ -81,21 +81,23 @@ export function usePreferencesQuery() { /* * `BskyAgent.getPreferences` used to call `configureLabelers` itself, * so subscribing to a labeler took effect on the very next appview - * read. The sdk action has no such side effect, and appview reads still - * inherit `atproto-accept-labelers` from the agent's fetch handler, so - * apply the subscriptions there rather than on the wrapping client - - * setting them on both would emit the header twice. + * read. The sdk action has no such side effect, so apply the + * subscriptions here - without this, subscribing to or unsubscribing + * from a labeler would not affect server-attached labels until the + * session bundle was rebuilt. + * + * `applyLabelersToClient` writes to the agent, which is what stamps the + * header on the requests the wrapping appview client issues, and it + * drops the Bluesky moderation DID so the globally redacted authority is + * not also listed unredacted. */ - agent.configureLabelers(labelerDids) + applyLabelersToClient(agent, labelerDids) /* - * The sdk's `BskyPreferences` is field-for-field identical to the - * legacy one that `UsePreferencesQueryResponse` is still derived from; - * only its strings are branded (`AtUriString`, `DidString`). Cast at - * this one seam so every downstream consumer of the response - notably - * the `moderationPrefs` readers - keeps its current types. + * `BskyPreferences` is now the sdk's own type, so the assembled + * response types structurally with no cast at this seam. */ - const preferences = { + const preferences: UsePreferencesQueryResponse = { ...res, savedFeeds: res.savedFeeds.filter(f => f.type !== 'unknown'), /** @@ -111,7 +113,7 @@ export function usePreferencesQuery() { ...(res.threadViewPrefs ?? {}), }, userAge: res.birthDate ? getAge(res.birthDate) : undefined, - } as UsePreferencesQueryResponse + } return preferences } }, @@ -381,11 +383,8 @@ export function useUpsertMutedWordsMutation() { const client = usePdsClient() return useMutation({ - mutationFn: async (mutedWords: AppBskyActorDefs.MutedWord[]) => { - await client.call( - upsertMutedWords, - mutedWords as app.bsky.actor.defs.MutedWord[], - ) + mutationFn: async (mutedWords: app.bsky.actor.defs.MutedWord[]) => { + await client.call(upsertMutedWords, mutedWords) // triggers a refetch await queryClient.invalidateQueries({ queryKey: preferencesQueryKey, @@ -399,11 +398,8 @@ export function useUpdateMutedWordMutation() { const client = usePdsClient() return useMutation({ - mutationFn: async (mutedWord: AppBskyActorDefs.MutedWord) => { - await client.call( - updateMutedWord, - mutedWord as app.bsky.actor.defs.MutedWord, - ) + mutationFn: async (mutedWord: app.bsky.actor.defs.MutedWord) => { + await client.call(updateMutedWord, mutedWord) // triggers a refetch await queryClient.invalidateQueries({ queryKey: preferencesQueryKey, @@ -417,11 +413,8 @@ export function useRemoveMutedWordMutation() { const client = usePdsClient() return useMutation({ - mutationFn: async (mutedWord: AppBskyActorDefs.MutedWord) => { - await client.call( - removeMutedWord, - mutedWord as app.bsky.actor.defs.MutedWord, - ) + mutationFn: async (mutedWord: app.bsky.actor.defs.MutedWord) => { + await client.call(removeMutedWord, mutedWord) // triggers a refetch await queryClient.invalidateQueries({ queryKey: preferencesQueryKey, @@ -435,11 +428,8 @@ export function useRemoveMutedWordsMutation() { const client = usePdsClient() return useMutation({ - mutationFn: async (mutedWords: AppBskyActorDefs.MutedWord[]) => { - await client.call( - removeMutedWords, - mutedWords as app.bsky.actor.defs.MutedWord[], - ) + mutationFn: async (mutedWords: app.bsky.actor.defs.MutedWord[]) => { + await client.call(removeMutedWords, mutedWords) // triggers a refetch await queryClient.invalidateQueries({ queryKey: preferencesQueryKey, diff --git a/src/state/queries/preferences/moderation.ts b/src/state/queries/preferences/moderation.ts index 55c155dc5b..246e7596b5 100644 --- a/src/state/queries/preferences/moderation.ts +++ b/src/state/queries/preferences/moderation.ts @@ -1,5 +1,6 @@ import {useMemo} from 'react' -import {AtpAgent, interpretLabelValueDefinitions} from '@atproto/api' +import {AtpAgent} from '@atproto/api' +import {interpretLabelValueDefinitions} from '@bsky.app/sdk/moderation' import {isNonConfigurableModerationAuthority} from '#/state/session/additional-moderation-authorities' import {useLabelersDetailedInfoQuery} from '../labeler' diff --git a/src/state/queries/preferences/types.ts b/src/state/queries/preferences/types.ts index ab0a957522..f93ca955a4 100644 --- a/src/state/queries/preferences/types.ts +++ b/src/state/queries/preferences/types.ts @@ -1,4 +1,4 @@ -import {type BskyFeedViewPreference, type BskyPreferences} from '@atproto/api' +import {type BskyFeedViewPreference, type BskyPreferences} from '@bsky.app/sdk' export type UsePreferencesQueryResponse = Omit< BskyPreferences, diff --git a/src/state/queries/profile-feedgens.ts b/src/state/queries/profile-feedgens.ts index 7bdcd62b64..7a78c0a6c6 100644 --- a/src/state/queries/profile-feedgens.ts +++ b/src/state/queries/profile-feedgens.ts @@ -1,4 +1,3 @@ -import {moderateFeedGenerator} from '@atproto/api' import {type DidString} from '@atproto/syntax' import { type InfiniteData, @@ -6,6 +5,7 @@ import { useInfiniteQuery, } from '@tanstack/react-query' +import {moderateFeedGenerator} from '#/lib/moderation/subjects' import {useAppviewClient} from '#/state/session' import {app} from '#/lexicons' import {useModerationOpts} from '../preferences/moderation-opts' diff --git a/src/state/queries/profile-lists.ts b/src/state/queries/profile-lists.ts index 0237876513..3d795def0d 100644 --- a/src/state/queries/profile-lists.ts +++ b/src/state/queries/profile-lists.ts @@ -1,4 +1,3 @@ -import {moderateUserList} from '@atproto/api' import {type DidString} from '@atproto/syntax' import { type InfiniteData, @@ -6,6 +5,7 @@ import { useInfiniteQuery, } from '@tanstack/react-query' +import {moderateUserList} from '#/lib/moderation/subjects' import {useAppviewClient} from '#/state/session' import {app} from '#/lexicons' import {useModerationOpts} from '../preferences/moderation-opts' diff --git a/src/state/queries/search-posts-v2.ts b/src/state/queries/search-posts-v2.ts index 185212f8ef..9716383654 100644 --- a/src/state/queries/search-posts-v2.ts +++ b/src/state/queries/search-posts-v2.ts @@ -1,5 +1,5 @@ import {useCallback, useMemo, useRef} from 'react' -import {type AppBskyFeedDefs, AtUri, moderatePost} from '@atproto/api' +import {type AppBskyFeedDefs, AtUri} from '@atproto/api' import { type InfiniteData, type QueryClient, @@ -7,6 +7,7 @@ import { useInfiniteQuery, } from '@tanstack/react-query' +import {moderatePost} from '#/lib/moderation/subjects' import {useModerationOpts} from '#/state/preferences/moderation-opts' import {useAppviewClient} from '#/state/session' import {type SearchFilters} from '#/screens/Search/searchParams' diff --git a/src/state/queries/trending/useGetTrendsQuery.ts b/src/state/queries/trending/useGetTrendsQuery.ts index 8fe0ad845e..5ba9b4d4b2 100644 --- a/src/state/queries/trending/useGetTrendsQuery.ts +++ b/src/state/queries/trending/useGetTrendsQuery.ts @@ -1,5 +1,5 @@ import {useCallback, useMemo} from 'react' -import {hasMutedWord} from '@atproto/api' +import {hasMutedWord} from '@bsky.app/sdk/moderation' import {useQuery} from '@tanstack/react-query' import { diff --git a/src/state/queries/usePostThread/traversal.ts b/src/state/queries/usePostThread/traversal.ts index 81abd958fb..2aca5dfa96 100644 --- a/src/state/queries/usePostThread/traversal.ts +++ b/src/state/queries/usePostThread/traversal.ts @@ -1,4 +1,5 @@ -import {AppBskyUnspeccedDefs, type ModerationOpts} from '@atproto/api' +import {AppBskyUnspeccedDefs} from '@atproto/api' +import {type ModerationOpts} from '@bsky.app/sdk/moderation' import { type ApiThreadItem, diff --git a/src/state/queries/usePostThread/types.ts b/src/state/queries/usePostThread/types.ts index 295fd8bd3e..561490c328 100644 --- a/src/state/queries/usePostThread/types.ts +++ b/src/state/queries/usePostThread/types.ts @@ -5,8 +5,8 @@ import { type AppBskyUnspeccedDefs, type AppBskyUnspeccedGetPostThreadOtherV2, type AppBskyUnspeccedGetPostThreadV2, - type ModerationDecision, } from '@atproto/api' +import {type ModerationDecision} from '@bsky.app/sdk/moderation' export type ApiThreadItem = | AppBskyUnspeccedGetPostThreadV2.ThreadItem diff --git a/src/state/queries/usePostThread/views.ts b/src/state/queries/usePostThread/views.ts index 4e7140eab0..af23ee2959 100644 --- a/src/state/queries/usePostThread/views.ts +++ b/src/state/queries/usePostThread/views.ts @@ -5,10 +5,10 @@ import { type AppBskyUnspeccedDefs, type AppBskyUnspeccedGetPostThreadV2, AtUri, - moderatePost, - type ModerationOpts, } from '@atproto/api' +import {type ModerationOpts} from '@bsky.app/sdk/moderation' +import {moderatePost} from '#/lib/moderation/subjects' import {makeProfileLink} from '#/lib/routes/links' import { type ApiThreadItem, diff --git a/src/state/session/__tests__/clients-test.ts b/src/state/session/__tests__/clients-test.ts index 452de11d9d..019281f784 100644 --- a/src/state/session/__tests__/clients-test.ts +++ b/src/state/session/__tests__/clients-test.ts @@ -15,6 +15,7 @@ jest.mock('jwt-decode', () => ({ import {CHAT_PROXY_SERVICE} from '#/lib/constants' import {app, chat, com} from '#/lexicons' +import {configureGlobalAppLabelers} from '../additional-moderation-authorities' import {BskyAppAgent, PasswordSessionManager} from '../bridge-agent' import { agentToAppviewClient, @@ -163,6 +164,32 @@ describe('agentToAppviewClient', () => { expect(entries).toHaveLength(1) }) + it('does not duplicate a global app labeler set on both statics', async () => { + /* + * `configureGlobalAppLabelers` populates the agent AND the lex `Client` + * static, because clients built without a wrapped agent read only the + * latter. On this path both producers are in play for the same request, and + * neither dedupes against the other - the agent joins its list with the + * existing header string while lex collects into a `Set` keyed on the + * `;redact`-suffixed value. The appview client suppresses its `appLabelers` + * so exactly one producer contributes. + */ + configureGlobalAppLabelers(['did:plc:global-labeler']) + const {agent} = setup(fetchMock) + + await agentToAppviewClient(agent).call(app.bsky.actor.getProfile, { + actor: HANDLE, + }) + + const init = initFor(fetchMock, 'app.bsky.actor.getProfile') + const labelers = new Headers(init?.headers).get('atproto-accept-labelers') + const entries = labelers! + .split(',') + .map(l => l.trim()) + .filter(l => l.includes('did:plc:global-labeler')) + expect(entries).toEqual(['did:plc:global-labeler;redact']) + }) + it('sends the session access token', async () => { const {agent} = setup(fetchMock) @@ -238,6 +265,8 @@ describe('agentToPdsClient', () => { const {agent} = setup(fetchMock) agent.configureProxy('did:web:api.bsky.app#bsky_appview') agent.configureLabelersHeader(['did:plc:labeler']) + /* nor the global authorities: a PDS call is not an appview read */ + configureGlobalAppLabelers(['did:plc:global-labeler']) await agentToPdsClient(agent).call(com.atproto.server.getSession, {}) @@ -302,6 +331,8 @@ describe('agentToChatClient', () => { it('does not emit the agent labeler header', async () => { const {agent} = setup(fetchMock) agent.configureLabelersHeader(['did:plc:labeler']) + /* nor the global authorities: a chat call is not an appview read */ + configureGlobalAppLabelers(['did:plc:global-labeler']) await agentToChatClient(agent) .call(chat.bsky.convo.listConvos, {}) diff --git a/src/state/session/__tests__/session-core-test.ts b/src/state/session/__tests__/session-core-test.ts index 9dbc646c9f..52484dcf72 100644 --- a/src/state/session/__tests__/session-core-test.ts +++ b/src/state/session/__tests__/session-core-test.ts @@ -838,8 +838,8 @@ describe('factory account snapshot after preparation', () => { */ let capturedAgent: BskyAppAgent | undefined mockConfigureModerationForAccount.mockImplementationOnce( - (agent: unknown) => { - capturedAgent = agent as BskyAppAgent + (bundle: unknown) => { + capturedAgent = (bundle as {agent: BskyAppAgent}).agent }, ) mockPrefetchAgeAssuranceServerData.mockImplementationOnce(async () => { @@ -904,8 +904,8 @@ describe('a session destroyed or rejected during preparation', () => { */ let capturedAgent: BskyAppAgent | undefined mockConfigureModerationForAccount.mockImplementationOnce( - (agent: unknown) => { - capturedAgent = agent as BskyAppAgent + (bundle: unknown) => { + capturedAgent = (bundle as {agent: BskyAppAgent}).agent }, ) mockPrefetchAgeAssuranceServerData.mockImplementationOnce(async () => { @@ -934,8 +934,8 @@ describe('a session destroyed or rejected during preparation', () => { it('resume: a prep rejection propagates and disposes the bundle', async () => { let capturedAgent: BskyAppAgent | undefined mockConfigureModerationForAccount.mockImplementationOnce( - (agent: unknown) => { - capturedAgent = agent as BskyAppAgent + (bundle: unknown) => { + capturedAgent = (bundle as {agent: BskyAppAgent}).agent }, ) mockPrefetchAgeAssuranceServerData.mockImplementationOnce(() => diff --git a/src/state/session/additional-moderation-authorities.ts b/src/state/session/additional-moderation-authorities.ts index 63ada2cdd8..3f3566c5c6 100644 --- a/src/state/session/additional-moderation-authorities.ts +++ b/src/state/session/additional-moderation-authorities.ts @@ -1,4 +1,5 @@ import {AtpAgent} from '@atproto/api' +import {Client} from '@atproto/lex' import {device} from '#/storage' @@ -86,5 +87,23 @@ export function configureAdditionalModerationAuthorities() { new Set([...AtpAgent.appLabelers, ...additionalLabelers]), ) - AtpAgent.configure({appLabelers}) + configureGlobalAppLabelers(appLabelers) +} + +/** + * Set the global app labelers on BOTH statics, so the agent-backed request path + * and any client built without a wrapped agent emit the same `;redact` + * authorities. + * + * Keeping the two in lockstep is what makes the duplicate-header hazard + * avoidable: the agent joins its own list with whatever the caller already put + * on the request, and lex appends the `Client` static on top of that, so a DID + * present in both would appear twice. The agent-wrapping clients therefore + * suppress their `appLabelers` (see `clients.ts`), which leaves exactly one + * producer per request while both statics stay populated for the paths that + * read only one of them. + */ +export function configureGlobalAppLabelers(dids: string[]) { + AtpAgent.configure({appLabelers: dids}) + Client.configure({appLabelers: dids as `did:${string}:${string}`[]}) } diff --git a/src/state/session/clients.ts b/src/state/session/clients.ts index 4daf5368b3..fab530aba2 100644 --- a/src/state/session/clients.ts +++ b/src/state/session/clients.ts @@ -31,18 +31,30 @@ const chatClients = new WeakMap() * routing. Because the agent already emits both headers, the client is * deliberately built with neither a `service` option nor labelers - setting * either here would emit them a second time. + * + * `appLabelers: null` suppresses the class-wide `Client.appLabelers` for this + * instance specifically. The static is populated (see + * `configureGlobalAppLabelers`) so that clients built without a wrapped agent + * carry the global authorities, but the agent already stamped those same DIDs + * onto the request, and lex would append its own copy on top: the agent joins + * its list with the existing header value while lex collects into a `Set` keyed + * on the suffixed string, so neither dedupes against the other and every global + * authority would appear twice. */ export function agentToAppviewClient(agent: BskyAppAgent): Client { const existing = appviewClients.get(agent) if (existing) { return existing } - const client = createLexClient({ - get did() { - return agent.did + const client = createLexClient( + { + get did() { + return agent.did + }, + fetchHandler: (path, init) => agent.fetchHandler(path, init), }, - fetchHandler: (path, init) => agent.fetchHandler(path, init), - }) + {appLabelers: null}, + ) appviewClients.set(agent, client) return client } @@ -58,7 +70,10 @@ export function agentToAppviewClient(agent: BskyAppAgent): Client { * for `com.atproto.*` repo/server/identity calls. * * No `service` option for the same reason: adding one would reintroduce the - * proxy header this client exists to avoid. + * proxy header this client exists to avoid. `appLabelers: null` is the same + * kind of suppression: a PDS request is not an appview read, so it must carry no + * moderation authorities at all - without this it would start emitting the + * global `Client.appLabelers`. * * The handler is wrapped in a closure rather than passed by reference because * `PasswordSessionManager.fetchHandler` reads `this`. Relative paths are @@ -71,12 +86,16 @@ export function agentToPdsClient(agent: BskyAppAgent): Client { if (existing) { return existing } - const client = createLexClient({ - get did() { - return agent.did + const client = createLexClient( + { + get did() { + return agent.did + }, + fetchHandler: (path, init) => + agent.sessionManager.fetchHandler(path, init), }, - fetchHandler: (path, init) => agent.sessionManager.fetchHandler(path, init), - }) + {appLabelers: null}, + ) pdsClients.set(agent, client) return client } @@ -88,7 +107,8 @@ export function agentToPdsClient(agent: BskyAppAgent): Client { * and PDS routing, no agent-level proxy or labeler headers - but constructed * with {@link CHAT_PROXY_SERVICE} as its `service`, so lex-client emits * `atproto-proxy: ` on every request and `chat.bsky.*` - * calls are proxied to the chat service. + * calls are proxied to the chat service. `appLabelers: null` for the same + * reason as the PDS client: the chat service takes no moderation authorities. */ export function agentToChatClient(agent: BskyAppAgent): Client { const existing = chatClients.get(agent) @@ -103,7 +123,7 @@ export function agentToChatClient(agent: BskyAppAgent): Client { fetchHandler: (path, init) => agent.sessionManager.fetchHandler(path, init), }, - {service: CHAT_PROXY_SERVICE}, + {appLabelers: null, service: CHAT_PROXY_SERVICE}, ) chatClients.set(agent, client) return client @@ -150,12 +170,14 @@ let publicLexClient: Client | undefined * {@link networkAwareFetch} so public reads feed the app's reachability signal * like authenticated ones do. * - * Unlike the public agent it parallels, this client sends neither - * `atproto-proxy` nor `atproto-accept-labelers`. `createPublicAgent` configures - * the app labeler and the proxy header, so a logged-out appview *agent* read - * does carry labelers while the same read through this client does not. A - * consumer that needs moderation labels on public reads must configure labelers - * itself before issuing the request. + * Unlike the agent-wrapping clients, this one does NOT suppress + * `Client.appLabelers`: there is no agent underneath to stamp the header, so the + * class-wide static is the only producer and a logged-out read carries the same + * `;redact` moderation authorities an authenticated one does. + * + * That makes `configureModerationForGuest()` load-bearing rather than + * test-only - it is what populates the static before this client's first + * request. `createPublicSessionBundle` runs it while building the bundle. */ export function getPublicAppviewClient(): Client { return (publicLexClient ??= createLexClient({ diff --git a/src/state/session/create-account.ts b/src/state/session/create-account.ts index 959df45b13..a14f50cff5 100644 --- a/src/state/session/create-account.ts +++ b/src/state/session/create-account.ts @@ -91,7 +91,7 @@ export async function createSessionBundleAndCreateAccount( accountDid = earlyAccount.did const gates = features.refresh({strategy: 'prefer-fresh-gates'}) - configureModerationForAccount(bundle.agent, earlyAccount) + configureModerationForAccount(bundle, earlyAccount) const createdAt = toDatetimeString(new Date()) const birthdate = birthDate.toISOString() diff --git a/src/state/session/moderation.ts b/src/state/session/moderation.ts index 2354fab4fa..b5b3a01af8 100644 --- a/src/state/session/moderation.ts +++ b/src/state/session/moderation.ts @@ -1,8 +1,12 @@ -import {AtpAgent, BSKY_LABELER_DID} from '@atproto/api' +import {type AtpAgent, BSKY_LABELER_DID} from '@atproto/api' +import {type Client} from '@atproto/lex' import {IS_TEST_USER} from '#/lib/constants' import {account as accountStorage} from '#/storage' -import {configureAdditionalModerationAuthorities} from './additional-moderation-authorities' +import { + configureAdditionalModerationAuthorities, + configureGlobalAppLabelers, +} from './additional-moderation-authorities' import {type SessionAccount} from './types' /** @@ -26,6 +30,29 @@ export function readLabelers(did: string): string[] | undefined { } } +/** + * Apply an account's labeler subscriptions without duplicating the globally + * redacted Bluesky moderation authority. + * + * The Bluesky DID is filtered out because it already flows through the global + * `appLabelers`, which lex and the agent both emit with a `;redact` suffix. + * Listing it per-subscription would add a second, non-redacting entry for the + * same authority. + * + * Writes to the agent rather than the client: the agent-level fetch handler is + * what stamps `atproto-accept-labelers` on the requests the wrapping clients + * issue, so setting them here reaches every appview read. The bundle rework + * moves this to `appviewClient.setLabelers` once the agent is gone. + */ +export function applyLabelersToClient( + agent: AtpAgent, + subscribedDids: string[], +) { + agent.configureLabelersHeader( + subscribedDids.filter(did => did !== BSKY_LABELER_DID), + ) +} + export function configureModerationForGuest() { // This global mutation is *only* OK because this code is only relevant for testing. // Don't add any other global behavior here! @@ -39,7 +66,7 @@ export function configureModerationForGuest() { * in the same tick, before any request goes out. */ export function configureModerationForAccount( - agent: AtpAgent, + bundle: {agent: AtpAgent; appviewClient?: Client}, account: SessionAccount, ) { // This global mutation is *only* OK because this code is only relevant for testing. @@ -47,15 +74,13 @@ export function configureModerationForAccount( switchToBskyAppLabeler() if (IS_TEST_USER(account.handle)) { // Test accounts may briefly use the production authority while this resolves. - void trySwitchToTestAppLabeler(agent) + void trySwitchToTestAppLabeler(bundle.agent) } // The code below is actually relevant to production (and isn't global). const labelerDids = readLabelers(account.did) if (labelerDids) { - agent.configureLabelersHeader( - labelerDids.filter(did => did !== BSKY_LABELER_DID), - ) + applyLabelersToClient(bundle.agent, labelerDids) } else { // If there are no headers in the storage, we'll not send them on the initial requests. // If we wanted to fix this, we could block on the preferences query here. @@ -65,7 +90,7 @@ export function configureModerationForAccount( } function switchToBskyAppLabeler() { - AtpAgent.configure({appLabelers: [BSKY_LABELER_DID]}) + configureGlobalAppLabelers([BSKY_LABELER_DID]) } /** Resolve and install the test environment's moderation authority. */ @@ -77,6 +102,6 @@ async function trySwitchToTestAppLabeler(agent: AtpAgent) { )?.data.did if (did) { console.warn('USING TEST ENV MODERATION') - AtpAgent.configure({appLabelers: [did]}) + configureGlobalAppLabelers([did]) } } diff --git a/src/state/session/session-core.ts b/src/state/session/session-core.ts index 759e6afcfa..7a9d1a22cc 100644 --- a/src/state/session/session-core.ts +++ b/src/state/session/session-core.ts @@ -191,7 +191,9 @@ export type PublicSessionBundle = { /** * Build the logged-out bundle. `createPublicAgent` installs the guest - * moderation authorities as part of building the agent. + * moderation authorities as part of building the agent, which is what populates + * the global `Client.appLabelers` that {@link getPublicAppviewClient} relies on + * for its labeler header. */ export function createPublicSessionBundle(): PublicSessionBundle { return { @@ -290,7 +292,7 @@ export async function createSessionBundleAndResume( storedAccount.pdsUrl, ) ?? storedAccount - configureModerationForAccount(bundle.agent, earlyAccount) + configureModerationForAccount(bundle, earlyAccount) const aa = prefetchAgeAssuranceServerData({ appviewClient: agentToAppviewClient(bundle.agent), accountClient: agentToPdsClient(bundle.agent), @@ -358,7 +360,7 @@ export async function createSessionBundleAndLogin( accountDid = earlyAccount.did const gates = features.refresh({strategy: 'prefer-fresh-gates'}) - configureModerationForAccount(bundle.agent, earlyAccount) + configureModerationForAccount(bundle, earlyAccount) const aa = prefetchAgeAssuranceServerData({ appviewClient: agentToAppviewClient(bundle.agent), accountClient: agentToPdsClient(bundle.agent), @@ -400,7 +402,7 @@ export function createSessionBundleFromStoredAccount( ) bundle = buildBundle(session, storedAccount.pdsUrl) registerBundleKillSwitch(bundle, hooks.kill) - configureModerationForAccount(bundle.agent, storedAccount) + configureModerationForAccount(bundle, storedAccount) bundle.agent.configureProxy(BLUESKY_PROXY_HEADER.get()) const account = session.destroyed diff --git a/src/state/shell/composer/index.tsx b/src/state/shell/composer/index.tsx index 9a1e212f48..087a3b348a 100644 --- a/src/state/shell/composer/index.tsx +++ b/src/state/shell/composer/index.tsx @@ -3,8 +3,8 @@ import { type AppBskyActorDefs, type AppBskyFeedDefs, type AppBskyUnspeccedGetPostThreadV2, - type ModerationDecision, } from '@atproto/api' +import {type ModerationDecision} from '@bsky.app/sdk/moderation' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {useQueryClient} from '@tanstack/react-query' diff --git a/src/view/com/composer/text-input/web/Autocomplete.tsx b/src/view/com/composer/text-input/web/Autocomplete.tsx index 8371ad993a..267d4a4829 100644 --- a/src/view/com/composer/text-input/web/Autocomplete.tsx +++ b/src/view/com/composer/text-input/web/Autocomplete.tsx @@ -1,6 +1,7 @@ import {forwardRef, useEffect, useImperativeHandle, useState} from 'react' import {Pressable, View} from 'react-native' -import {type AppBskyActorDefs, type ModerationOpts} from '@atproto/api' +import {type AppBskyActorDefs} from '@atproto/api' +import {type ModerationOpts} from '@bsky.app/sdk/moderation' import {Trans} from '@lingui/react/macro' import {ReactRenderer} from '@tiptap/react' import { diff --git a/src/view/com/composer/threadgate/ThreadgateBtn.tsx b/src/view/com/composer/threadgate/ThreadgateBtn.tsx index 2602b9bc45..9e730b48d8 100644 --- a/src/view/com/composer/threadgate/ThreadgateBtn.tsx +++ b/src/view/com/composer/threadgate/ThreadgateBtn.tsx @@ -90,14 +90,12 @@ export function ThreadgateBtn({ * Preferences are still typed against the legacy client, so the stored * rules arrive unbranded. Wave B migrates `getPreferences`. */ - allow: preferences?.postInteractionSettings - .threadgateAllowRules as app.bsky.feed.threadgate.Main['allow'], + allow: preferences?.postInteractionSettings.threadgateAllowRules, }) const prefPostgate = createPostgateRecord({ post: '', - embeddingRules: (preferences?.postInteractionSettings - ?.postgateEmbeddingRules || - []) as app.bsky.feed.postgate.Main['embeddingRules'], + embeddingRules: + preferences?.postInteractionSettings?.postgateEmbeddingRules || [], }) const isDirty = useMemo(() => { diff --git a/src/view/com/notifications/NotificationFeedItem.tsx b/src/view/com/notifications/NotificationFeedItem.tsx index 2afb3a0f43..a10e4b47ac 100644 --- a/src/view/com/notifications/NotificationFeedItem.tsx +++ b/src/view/com/notifications/NotificationFeedItem.tsx @@ -16,12 +16,13 @@ import { AppBskyGraphFollow, AppBskyGraphStarterpack, AtUri, - moderateProfile, - type ModerationDecision, - type ModerationOpts, } from '@atproto/api' import {TID} from '@atproto/common-web' import {type DidString} from '@atproto/syntax' +import { + type ModerationDecision, + type ModerationOpts, +} from '@bsky.app/sdk/moderation' import {plural} from '@lingui/core/macro' import {Plural, Trans, useLingui} from '@lingui/react/macro' import {useNavigation} from '@react-navigation/native' @@ -29,6 +30,7 @@ import {useQueryClient} from '@tanstack/react-query' import {MAX_POST_LINES} from '#/lib/constants' import {useAnimatedValue} from '#/lib/hooks/useAnimatedValue' +import {moderateProfile} from '#/lib/moderation/subjects' import {makeProfileLink} from '#/lib/routes/links' import {type NavigationProp} from '#/lib/routes/types' import {forceLTR} from '#/lib/strings/bidi' diff --git a/src/view/com/post-thread/PostQuotes.tsx b/src/view/com/post-thread/PostQuotes.tsx index b16281f9e0..0bad7969c0 100644 --- a/src/view/com/post-thread/PostQuotes.tsx +++ b/src/view/com/post-thread/PostQuotes.tsx @@ -1,15 +1,12 @@ import {useCallback, useState} from 'react' -import { - type AppBskyFeedDefs, - AppBskyFeedPost, - moderatePost, - type ModerationDecision, -} from '@atproto/api' +import {type AppBskyFeedDefs, AppBskyFeedPost} from '@atproto/api' +import {type ModerationDecision} from '@bsky.app/sdk/moderation' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender' import {usePostViewTracking} from '#/lib/hooks/usePostViewTracking' +import {moderatePost} from '#/lib/moderation/subjects' import {cleanError} from '#/lib/strings/errors' import {logger} from '#/logger' import {useModerationOpts} from '#/state/preferences/moderation-opts' diff --git a/src/view/com/post/Post.tsx b/src/view/com/post/Post.tsx index b67133df65..98f6cfb9a0 100644 --- a/src/view/com/post/Post.tsx +++ b/src/view/com/post/Post.tsx @@ -1,17 +1,13 @@ import {useCallback, useMemo, useState} from 'react' import {type StyleProp, StyleSheet, View, type ViewStyle} from 'react-native' -import { - type AppBskyFeedDefs, - AppBskyFeedPost, - AtUri, - moderatePost, - type ModerationDecision, -} from '@atproto/api' +import {type AppBskyFeedDefs, AppBskyFeedPost, AtUri} from '@atproto/api' +import {type ModerationDecision} from '@bsky.app/sdk/moderation' import {RichText as RichTextAPI} from '@bsky.app/sdk/richtext' import {useQueryClient} from '@tanstack/react-query' import {MAX_POST_LINES} from '#/lib/constants' import {useOpenComposer} from '#/lib/hooks/useOpenComposer' +import {moderatePost} from '#/lib/moderation/subjects' import {makeProfileLink} from '#/lib/routes/links' import {countLines} from '#/lib/strings/helpers' import {asSdkFacets} from '#/lib/strings/rich-text-helpers' diff --git a/src/view/com/posts/PostFeedItem.tsx b/src/view/com/posts/PostFeedItem.tsx index a95b41635b..aa3fd06976 100644 --- a/src/view/com/posts/PostFeedItem.tsx +++ b/src/view/com/posts/PostFeedItem.tsx @@ -6,8 +6,8 @@ import { AppBskyFeedPost, AppBskyFeedThreadgate, AtUri, - type ModerationDecision, } from '@atproto/api' +import {type ModerationDecision} from '@bsky.app/sdk/moderation' import {RichText as RichTextAPI} from '@bsky.app/sdk/richtext' import {useQueryClient} from '@tanstack/react-query' diff --git a/src/view/com/posts/PostFeedReason.tsx b/src/view/com/posts/PostFeedReason.tsx index fd3ec73470..57ac3b825c 100644 --- a/src/view/com/posts/PostFeedReason.tsx +++ b/src/view/com/posts/PostFeedReason.tsx @@ -1,5 +1,6 @@ import {StyleSheet, View} from 'react-native' -import {AppBskyFeedDefs, type ModerationDecision} from '@atproto/api' +import {AppBskyFeedDefs} from '@atproto/api' +import {type ModerationDecision} from '@bsky.app/sdk/moderation' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' diff --git a/src/view/com/util/PostMeta.tsx b/src/view/com/util/PostMeta.tsx index 29f3cf3fd8..f50e14b18f 100644 --- a/src/view/com/util/PostMeta.tsx +++ b/src/view/com/util/PostMeta.tsx @@ -1,6 +1,7 @@ import {memo, useCallback} from 'react' import {type StyleProp, View, type ViewStyle} from 'react-native' -import {type AppBskyActorDefs, type ModerationDecision} from '@atproto/api' +import {type AppBskyActorDefs} from '@atproto/api' +import {type ModerationDecision} from '@bsky.app/sdk/moderation' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {useQueryClient} from '@tanstack/react-query' diff --git a/src/view/com/util/UserAvatar.tsx b/src/view/com/util/UserAvatar.tsx index 98819bf8d6..06893524f3 100644 --- a/src/view/com/util/UserAvatar.tsx +++ b/src/view/com/util/UserAvatar.tsx @@ -11,7 +11,7 @@ import { } from 'react-native' import Svg, {Circle, Path, Rect} from 'react-native-svg' import {Image as ExpoImage} from 'expo-image' -import {type ModerationUI} from '@atproto/api' +import {type ModerationUI} from '@bsky.app/sdk/moderation' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' diff --git a/src/view/com/util/UserBanner.tsx b/src/view/com/util/UserBanner.tsx index 964cc2ba57..b9d9604d64 100644 --- a/src/view/com/util/UserBanner.tsx +++ b/src/view/com/util/UserBanner.tsx @@ -1,7 +1,7 @@ import {useCallback, useState} from 'react' import {Pressable, StyleSheet, View} from 'react-native' import {Image} from 'expo-image' -import {type ModerationUI} from '@atproto/api' +import {type ModerationUI} from '@bsky.app/sdk/moderation' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' diff --git a/src/view/screens/DebugMod.tsx b/src/view/screens/DebugMod.tsx index d92043966c..ad0124eb49 100644 --- a/src/view/screens/DebugMod.tsx +++ b/src/view/screens/DebugMod.tsx @@ -6,20 +6,21 @@ import { type AppBskyFeedDefs, type AppBskyFeedPost, type ComAtprotoLabelDefs, + mock, +} from '@atproto/api' +import { interpretLabelValueDefinition, type LabelPreference, LABELS, - mock, - moderatePost, - moderateProfile, type ModerationBehavior, type ModerationDecision, type ModerationOpts, -} from '@atproto/api' +} from '@bsky.app/sdk/moderation' import {RichText} from '@bsky.app/sdk/richtext' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' +import {moderatePost, moderateProfile} from '#/lib/moderation/subjects' import {useGlobalLabelStrings} from '#/lib/moderation/useGlobalLabelStrings' import { type CommonNavigatorParams, @@ -242,9 +243,13 @@ export const DebugModScreen = ({}: NativeStackScreenProps< return item }, [profile, currentAccount]) - const modOpts = useMemo(() => { + const modOpts = useMemo(() => { return { - userDid: isLoggedOut ? '' : isTargetMe ? did : 'did:web:alice.test', + userDid: (isLoggedOut + ? '' + : isTargetMe + ? did + : 'did:web:alice.test') as ModerationOpts['userDid'], prefs: { adultContentEnabled: !noAdult, labels: { diff --git a/src/view/screens/Profile.tsx b/src/view/screens/Profile.tsx index c796bf9586..9c4db67b01 100644 --- a/src/view/screens/Profile.tsx +++ b/src/view/screens/Profile.tsx @@ -2,11 +2,8 @@ import {useCallback, useEffect, useMemo, useRef, useState} from 'react' import {StyleSheet} from 'react-native' import {SafeAreaView} from 'react-native-safe-area-context' import {ScrollForwarderView} from 'react-native-scroll-forwarder' -import { - type AppBskyActorDefs, - moderateProfile, - type ModerationOpts, -} from '@atproto/api' +import {type AppBskyActorDefs} from '@atproto/api' +import {type ModerationOpts} from '@bsky.app/sdk/moderation' import {RichText as RichTextAPI} from '@bsky.app/sdk/richtext' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' @@ -17,6 +14,7 @@ import {useQueryClient} from '@tanstack/react-query' import {useOpenComposer} from '#/lib/hooks/useOpenComposer' import {useRequireEmailVerification} from '#/lib/hooks/useRequireEmailVerification' import {useSetTitle} from '#/lib/hooks/useSetTitle' +import {moderateProfile} from '#/lib/moderation/subjects' import { type CommonNavigatorParams, type NativeStackScreenProps,