diff --git a/src/components/ModerationLabelPref/index.tsx b/src/components/ModerationLabelPref/index.tsx index 0407704889..96c219f083 100644 --- a/src/components/ModerationLabelPref/index.tsx +++ b/src/components/ModerationLabelPref/index.tsx @@ -2,25 +2,29 @@ import React from 'react' import {View} from 'react-native' import {useLingui} from '@lingui/react' import {msg} from '@lingui/macro' -import {LABEL_GROUPS} from '@atproto/api' -import {useLabelGroupStrings} from '#/lib/moderation/useLabelGroupStrings' +import {useLabelStrings} from '#/lib/moderation/useLabelStrings' import {useTheme, atoms as a} from '#/alf' import {Text} from '#/components/Typography' import * as ToggleButton from '#/components/forms/ToggleButton' export function ModerationLabelPref({ - labelGroup, + label, disabled, }: { - labelGroup: keyof typeof LABEL_GROUPS + label: string disabled?: boolean }) { const t = useTheme() const {_} = useLingui() - const labelGroupStrings = useLabelGroupStrings() - const groupInfoStrings = labelGroupStrings[labelGroup] + const allLabelStrings = useLabelStrings() + const labelStrings = allLabelStrings[label] || { + general: { + name: label, + description: `Labeled "${label}"`, + }, + } // TODO add onChange behavior when mod prefs are updated @@ -42,16 +46,16 @@ export function ModerationLabelPref({ a.align_center, ]}> - {groupInfoStrings.name} + {labelStrings.general.name} - {groupInfoStrings.description} + {labelStrings.general.description} {!disabled && ( {}}> diff --git a/src/components/ReportDialog/index.tsx b/src/components/ReportDialog/index.tsx index f222d49a3f..fbbe3b7269 100644 --- a/src/components/ReportDialog/index.tsx +++ b/src/components/ReportDialog/index.tsx @@ -32,7 +32,6 @@ import { getModerationServiceTitle, useConfigurableContentLabelGroups, useConfigurableProfileLabelGroups, - getLabelGroupToLabelerMap, } from '#/lib/moderation' import {DMCA_LINK} from '#/components/ReportDialog/const' import {Link} from '#/components/Link' @@ -165,12 +164,10 @@ function SubmitView({ selectedLabelGroup, goBack, onSubmitComplete, - labelGroupToLabelerMap, }: ReportDialogProps & { selectedLabelGroup: ReportDialogLabelIds goBack: () => void onSubmitComplete: () => void - labelGroupToLabelerMap: ReturnType }) { const t = useTheme() const {_} = useLingui() @@ -178,7 +175,7 @@ function SubmitView({ const groupInfoStrings = labelGroupStrings[selectedLabelGroup] const [details, setDetails] = React.useState('') const [submitting, setSubmitting] = React.useState(false) - const supportedLabelers = labelGroupToLabelerMap[selectedLabelGroup] + const supportedLabelers = [] //labelGroupToLabelerMap[selectedLabelGroup] const [selectedServices, setSelectedServices] = React.useState( supportedLabelers?.map(labeler => labeler.creator.did) || [], ) diff --git a/src/lib/moderation.ts b/src/lib/moderation.ts index fd22d7de8a..065bad4cc4 100644 --- a/src/lib/moderation.ts +++ b/src/lib/moderation.ts @@ -2,8 +2,6 @@ import React from 'react' import { ModerationCause, ModerationUI, - LABEL_GROUPS, - LabelGroupDefinition, AppBskyModerationDefs, } from '@atproto/api' @@ -27,54 +25,19 @@ export function isJustAMute(modui: ModerationUI): boolean { return modui.filters.length === 1 && modui.filters[0].type === 'muted' } -export function getLabelGroupsFromLabels(labels: string[]) { - const groups: LabelGroupDefinition[] = [] - - for (const label of labels) { - for (const group in LABEL_GROUPS) { - const def = LABEL_GROUPS[group as LabelGroupDefinition['id']] - if (def.labels.find(l => l.id === label)) { - groups.push(def) - } - } - } - - return Array.from(groups) -} - -export function getConfigurableLabelGroups() { - return Object.values(LABEL_GROUPS).filter(group => group.configurable) -} - -export function useConfigurableLabelGroups() { - return React.useMemo(() => getConfigurableLabelGroups(), []) -} - export function useConfigurableContentLabelGroups() { - return React.useMemo(() => { - const groups = getConfigurableLabelGroups() - return groups.filter(group => { - return group.labels.every(l => l.targets.includes('content')) - }) - }, []) + // TODO removeme + return [] } export function useConfigurableProfileLabelGroups() { - return React.useMemo(() => { - const groups = getConfigurableLabelGroups() - return groups.filter(group => { - return group.labels.every(l => l.targets.includes('profile')) - }) - }, []) + // TODO removeme + return [] } export function useConfigurableAccountLabelGroups() { - return React.useMemo(() => { - const groups = getConfigurableLabelGroups() - return groups.filter(group => { - return group.labels.every(l => l.targets.includes('account')) - }) - }, []) + // TODO removeme + return [] } export function getModerationServiceTitle({ @@ -88,31 +51,3 @@ export function getModerationServiceTitle({ ? sanitizeDisplayName(displayName) : sanitizeHandle(handle, '@') } - -export function getLabelGroupToLabelerMap( - labelers: AppBskyModerationDefs.ModServiceViewDetailed[], -) { - if (!labelers) return {} - - const groups: Partial< - Record< - LabelGroupDefinition['id'] | 'other', - AppBskyModerationDefs.ModServiceViewDetailed[] - > - > = { - // `other` reports go to all labelers TODO confirm this - other: labelers, - } - - for (const modservice of labelers) { - const labelGroups = getLabelGroupsFromLabels( - modservice.policies.labelValues, - ) - for (const group of labelGroups) { - const g = (groups[group.id] = groups[group.id] || []) - g.push(modservice) - } - } - - return groups -} diff --git a/src/lib/moderation/useLabelStrings.ts b/src/lib/moderation/useLabelStrings.ts index e6a376976f..5542f53d3f 100644 --- a/src/lib/moderation/useLabelStrings.ts +++ b/src/lib/moderation/useLabelStrings.ts @@ -1,10 +1,9 @@ -import {LABELS} from '@atproto/api' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' import {useMemo} from 'react' export type LabelStrings = Record< - keyof typeof LABELS, + string, { general: {name: string; description: string} account: {name: string; description: string} diff --git a/src/lib/moderation/useModerationCauseDescription.ts b/src/lib/moderation/useModerationCauseDescription.ts index 2df72a3d55..17283566a7 100644 --- a/src/lib/moderation/useModerationCauseDescription.ts +++ b/src/lib/moderation/useModerationCauseDescription.ts @@ -1,4 +1,4 @@ -import {ModerationCause} from '@atproto/api' +import {ModerationCause, LABELS} from '@atproto/api' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' import {useLabelStrings} from './useLabelStrings' @@ -76,8 +76,9 @@ export function useModerationCauseDescription( } } if (cause.type === 'label') { - if (cause.labelDef.id in labelStrings) { - const strings = labelStrings[cause.labelDef.id] + if (cause.labelDef.identifier in labelStrings) { + const strings = + labelStrings[cause.labelDef.identifier as keyof typeof LABELS] return { name: context === 'account' ? strings.account.name : strings.content.name, @@ -88,8 +89,8 @@ export function useModerationCauseDescription( } } return { - name: cause.labelDef.id, - description: _(msg`Labeled ${cause.labelDef.id}`), + name: cause.labelDef.identifier, + description: _(msg`Labeled ${cause.labelDef.identifier}`), } } // should never happen diff --git a/src/screens/Moderation/index.tsx b/src/screens/Moderation/index.tsx index c13a37098f..633447b739 100644 --- a/src/screens/Moderation/index.tsx +++ b/src/screens/Moderation/index.tsx @@ -86,7 +86,7 @@ export function ModerationScreen( data: modservices, error: modservicesError, } = useModServicesDetailedInfoQuery({ - dids: preferences ? preferences.moderationOpts.mods.map(m => m.did) : [], + dids: preferences ? preferences.moderationPrefs.mods.map(m => m.did) : [], }) const {gtMobile} = useBreakpoints() const {height} = useSafeAreaFrame() @@ -149,7 +149,7 @@ export function ModerationScreenInner({ usePreferencesSetAdultContentMutation() const adultContentEnabled = !!( (optimisticAdultContent && optimisticAdultContent.enabled) || - (!optimisticAdultContent && preferences.moderationOpts.adultContentEnabled) + (!optimisticAdultContent && preferences.moderationPrefs.adultContentEnabled) ) const onToggleAdultContentEnabled = React.useCallback( @@ -323,7 +323,9 @@ export function ModerationScreenInner({ modservice={{ uri: '', cid: '', - policies: {}, + policies: { + labelValues: [], + }, creator: { did: '', handle: 'safety.bsky.app', @@ -360,7 +362,7 @@ export function ModerationScreenInner({ handle: mod.creator.handle, })} handle={mod.creator.handle} - description={mod.description} + description={mod.creator.description || ''} /> diff --git a/src/screens/Profile/Header/ProfileHeaderModerator.tsx b/src/screens/Profile/Header/ProfileHeaderModerator.tsx index eb2499a082..773178bcc1 100644 --- a/src/screens/Profile/Header/ProfileHeaderModerator.tsx +++ b/src/screens/Profile/Header/ProfileHeaderModerator.tsx @@ -69,7 +69,7 @@ let ProfileHeaderModerator = ({ useModServiceSubscriptionMutation() const isSubscribed = variables?.subscribe ?? - preferences?.moderationOpts.mods.find(mod => mod.did === profile.did) + preferences?.moderationPrefs.mods.find(mod => mod.did === profile.did) const {mutateAsync: likeMod, isPending: isLikePending} = useLikeMutation() const {mutateAsync: unlikeMod, isPending: isUnlikePending} = useUnlikeMutation() diff --git a/src/screens/Profile/Sections/ContentFilters.tsx b/src/screens/Profile/Sections/ContentFilters.tsx index 89d307265c..1e6f3547b8 100644 --- a/src/screens/Profile/Sections/ContentFilters.tsx +++ b/src/screens/Profile/Sections/ContentFilters.tsx @@ -6,7 +6,6 @@ import {useLingui} from '@lingui/react' import {useSafeAreaFrame} from 'react-native-safe-area-context' import {useModServiceSubscriptionMutation} from '#/state/queries/modservice' -import {getLabelGroupsFromLabels} from '#/lib/moderation' import {logger} from '#/logger' import {useTheme, atoms as a} from '#/alf' @@ -76,15 +75,8 @@ export function ProfileContentFiltersSectionInner({ }) { const {_} = useLingui() const t = useTheme() - const groups = React.useMemo(() => { - return getLabelGroupsFromLabels(modservice.policies.labelValues).filter( - def => def.configurable, - ) - }, [modservice.policies.labelValues]) const isEnabled = Boolean( - moderationOpts.mods.find( - mod => mod.did === modservice.creator.did && mod.enabled, - ), + moderationOpts.prefs.mods.find(mod => mod.did === modservice.creator.did), ) const hasSession = true // TODO @@ -92,7 +84,7 @@ export function ProfileContentFiltersSectionInner({ useModServiceSubscriptionMutation() const isSubscribed = variables?.subscribe ?? - moderationOpts.mods.find(mod => mod.did === modservice.creator.did) + moderationOpts.prefs.mods.find(mod => mod.did === modservice.creator.did) const onPressSubscribe = React.useCallback(async () => { try { @@ -142,7 +134,8 @@ export function ProfileContentFiltersSectionInner({ a.border, t.atoms.border_contrast_low, ]}> - {groups.map((def, i) => { + { + undefined /* TODO modservice.policies.labelValues.map((def, i) => { return ( {i !== 0 && } @@ -152,7 +145,8 @@ export function ProfileContentFiltersSectionInner({ /> ) - })} + })*/ + } diff --git a/src/state/modals/index.tsx b/src/state/modals/index.tsx index c39d72a8c3..36b2d6ddc9 100644 --- a/src/state/modals/index.tsx +++ b/src/state/modals/index.tsx @@ -119,10 +119,6 @@ export interface AddAppPasswordModal { name: 'add-app-password' } -export interface ContentFilteringSettingsModal { - name: 'content-filtering-settings' -} - export interface ContentLanguagesSettingsModal { name: 'content-languages-settings' } @@ -182,7 +178,6 @@ export type Modal = | SwitchAccountModal // Curation - | ContentFilteringSettingsModal | ContentLanguagesSettingsModal | PostLanguagesSettingsModal diff --git a/src/state/queries/actor-autocomplete.ts b/src/state/queries/actor-autocomplete.ts index 9559d671ea..f14b3d65f1 100644 --- a/src/state/queries/actor-autocomplete.ts +++ b/src/state/queries/actor-autocomplete.ts @@ -10,7 +10,10 @@ import {DEFAULT_LOGGED_OUT_PREFERENCES, useModerationOpts} from './preferences' import {isInvalidHandle} from '#/lib/strings/handles' import {isJustAMute} from '#/lib/moderation' -const DEFAULT_MOD_OPTS = DEFAULT_LOGGED_OUT_PREFERENCES.moderationOpts +const DEFAULT_MOD_OPTS = { + userDid: undefined, + prefs: DEFAULT_LOGGED_OUT_PREFERENCES.moderationPrefs, +} export const RQKEY = (prefix: string) => ['actor-autocomplete', prefix] diff --git a/src/state/queries/modservice.ts b/src/state/queries/modservice.ts index 3a2e708cc7..15fc6ae84e 100644 --- a/src/state/queries/modservice.ts +++ b/src/state/queries/modservice.ts @@ -26,10 +26,11 @@ export function useModServiceInfoQuery({ enabled: !!did && enabled !== false, queryKey: modServiceInfoQueryKey(did as string), queryFn: async () => { - const res = await getAgent().app.bsky.moderation.getService({ - did: did as string, + const res = await getAgent().app.bsky.moderation.getServices({ + dids: [did as string], + detailed: true, }) - return res.data + return res.data.views[0] as AppBskyModerationDefs.ModServiceViewDetailed }, }) } @@ -40,7 +41,7 @@ export function useModServicesInfoQuery({dids}: {dids: string[]}) { queryKey: modServicesInfoQueryKey(dids), queryFn: async () => { const res = await getAgent().app.bsky.moderation.getServices({dids}) - return res.data.views + return res.data.views as AppBskyModerationDefs.ModServiceView[] }, }) } @@ -50,23 +51,11 @@ export function useModServicesDetailedInfoQuery({dids}: {dids: string[]}) { enabled: !!dids.length, queryKey: modServicesDetailedInfoQueryKey(dids), queryFn: async () => { - const views: AppBskyModerationDefs.ModServiceViewDetailed[] = [] - - await Promise.all( - dids.map(did => { - return getAgent() - .app.bsky.moderation.getService({did}) - .then(res => { - views.push(res.data) - }) - .catch(e => { - console.error(e) - return null - }) - }), - ) - - return views + const res = await getAgent().app.bsky.moderation.getServices({ + dids, + detailed: true, + }) + return res.data.views as AppBskyModerationDefs.ModServiceViewDetailed[] }, }) } @@ -95,50 +84,3 @@ export function useModServiceSubscriptionMutation() { }, }) } - -export function useModServiceEnableMutation() { - const queryClient = useQueryClient() - - return useMutation({ - async mutationFn({did, enabled}: {did: string; enabled: boolean}) { - // TODO - z.object({ - did: z.string(), - enabled: z.boolean(), - }).parse({did, enabled}) - await getAgent().setModServiceEnabled(did, enabled) - await queryClient.invalidateQueries({ - queryKey: preferencesQueryKey, - }) - }, - }) -} - -export function useModServiceLabelGroupEnableMutation() { - const queryClient = useQueryClient() - - return useMutation({ - async mutationFn({ - did, - group, - enabled, - }: { - did: string - group: string - enabled: boolean - }) { - // TODO - z.object({ - did: z.string(), - group: z.string(), - enabled: z.boolean(), - }).parse({did, group, enabled}) - await getAgent().setModServiceLabelGroupEnabled(did, group, enabled) - }, - onSuccess() { - queryClient.invalidateQueries({ - queryKey: preferencesQueryKey, - }) - }, - }) -} diff --git a/src/state/queries/post-feed.ts b/src/state/queries/post-feed.ts index 51aa941a57..4899e53ff7 100644 --- a/src/state/queries/post-feed.ts +++ b/src/state/queries/post-feed.ts @@ -423,10 +423,10 @@ function assertSomePostsPassModeration(feed: AppBskyFeedDefs.FeedViewPost[]) { let somePostsPassModeration = false for (const item of feed) { - const moderation = moderatePost( - item.post, - DEFAULT_LOGGED_OUT_PREFERENCES.moderationOpts, - ) + const moderation = moderatePost(item.post, { + userDid: undefined, + prefs: DEFAULT_LOGGED_OUT_PREFERENCES.moderationPrefs, + }) if (!moderation.ui('contentList').filter) { // we have a sfw post diff --git a/src/state/queries/preferences/const.ts b/src/state/queries/preferences/const.ts index 84db427a95..f03103581c 100644 --- a/src/state/queries/preferences/const.ts +++ b/src/state/queries/preferences/const.ts @@ -34,14 +34,15 @@ export const DEFAULT_LOGGED_OUT_PREFERENCES: UsePreferencesQueryResponse = { pinned: [], unpinned: [], }, - moderationOpts: { - userDid: '', + moderationPrefs: { adultContentEnabled: false, - labelGroups: DEFAULT_LOGGED_OUT_LABEL_PREFERENCES, + labels: DEFAULT_LOGGED_OUT_LABEL_PREFERENCES, mods: [], }, feedViewPrefs: DEFAULT_HOME_FEED_PREFS, threadViewPrefs: DEFAULT_THREAD_VIEW_PREFS, userAge: 13, // TODO(pwi) interests: {tags: []}, + mutedWords: [], + hiddenPosts: [], } diff --git a/src/state/queries/preferences/index.ts b/src/state/queries/preferences/index.ts index 101713f80e..76fff47ab7 100644 --- a/src/state/queries/preferences/index.ts +++ b/src/state/queries/preferences/index.ts @@ -4,7 +4,6 @@ import { LabelPreference, BskyFeedViewPreference, ModerationOpts, - LabelGroupDefinition, } from '@atproto/api' import {track} from '#/lib/analytics/analytics' @@ -77,17 +76,20 @@ export function useModerationOpts() { const override = useContext(moderationOptsOverrideContext) const prefs = usePreferencesQuery() const hiddenPosts = useHiddenPosts() - const opts = useMemo(() => { + const opts = useMemo(() => { if (override) { return override } if (!prefs.data) { return } - const moderationOpts = prefs.data.moderationOpts + const moderationPrefs = prefs.data.moderationPrefs return { - ...moderationOpts, - hiddenPosts, + userDid: '', // TODO + prefs: { + ...moderationPrefs, + hiddenPosts, + }, } }, [override, prefs.data, hiddenPosts]) return opts @@ -130,13 +132,15 @@ export function useSetContentLabelMutation() { return useMutation({ mutationFn: async ({ - labelGroup, + label, visibility, + labelerDid, }: { - labelGroup: LabelGroupDefinition['id'] + label: string visibility: LabelPreference + labelerDid?: string }) => { - await getAgent().setContentLabelPref(labelGroup, visibility) + await getAgent().setContentLabelPref(label, visibility, labelerDid) // 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 f5d3367018..c26d25fef2 100644 --- a/src/state/queries/preferences/moderation.ts +++ b/src/state/queries/preferences/moderation.ts @@ -1,4 +1,4 @@ -import {ComAtprotoLabelDefs, DEFAULT_LABEL_GROUP_SETTINGS} from '@atproto/api' +import {ComAtprotoLabelDefs, DEFAULT_LABEL_SETTINGS} from '@atproto/api' import { LabelGroup, @@ -21,12 +21,9 @@ export type LabelGroupConfig = { * * TODO(pwi) */ -export const DEFAULT_LOGGED_OUT_LABEL_PREFERENCES: typeof DEFAULT_LABEL_GROUP_SETTINGS = +export const DEFAULT_LOGGED_OUT_LABEL_PREFERENCES: typeof DEFAULT_LABEL_SETTINGS = Object.fromEntries( - Object.entries(DEFAULT_LABEL_GROUP_SETTINGS).map(([key, _pref]) => [ - key, - 'hide', - ]), + Object.entries(DEFAULT_LABEL_SETTINGS).map(([key, _pref]) => [key, 'hide']), ) export const CONFIGURABLE_LABEL_GROUPS: Record< diff --git a/src/view/com/modals/ContentFilteringSettings.tsx b/src/view/com/modals/ContentFilteringSettings.tsx deleted file mode 100644 index 77f0082b31..0000000000 --- a/src/view/com/modals/ContentFilteringSettings.tsx +++ /dev/null @@ -1,406 +0,0 @@ -import React from 'react' -import {LabelPreference} from '@atproto/api' -import {StyleSheet, Pressable, View, Linking} from 'react-native' -import LinearGradient from 'react-native-linear-gradient' -import {ScrollView} from './util' -import {s, colors, gradients} from 'lib/styles' -import {Text} from '../util/text/Text' -import {TextLink} from '../util/Link' -import {ToggleButton} from '../util/forms/ToggleButton' -import {Button} from '../util/forms/Button' -import {usePalette} from 'lib/hooks/usePalette' -import {isIOS} from 'platform/detection' -import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' -import * as Toast from '../util/Toast' -import {logger} from '#/logger' -import {Trans, msg} from '@lingui/macro' -import {useLingui} from '@lingui/react' -import {useModalControls} from '#/state/modals' -import { - usePreferencesQuery, - usePreferencesSetContentLabelMutation, - usePreferencesSetAdultContentMutation, - ConfigurableLabelGroup, - CONFIGURABLE_LABEL_GROUPS, - UsePreferencesQueryResponse, -} from '#/state/queries/preferences' - -export const snapPoints = ['90%'] - -export function Component({}: {}) { - const {isMobile} = useWebMediaQueries() - const pal = usePalette('default') - const {_} = useLingui() - const {closeModal} = useModalControls() - const {data: preferences} = usePreferencesQuery() - - const onPressDone = React.useCallback(() => { - closeModal() - }, [closeModal]) - - return ( - - - Content Filtering - - - - - - - - - - - - - - - - - - - Done - - - - - - ) -} - -function AdultContentEnabledPref() { - const pal = usePalette('default') - const {_} = useLingui() - const {data: preferences} = usePreferencesQuery() - const {mutate, variables} = usePreferencesSetAdultContentMutation() - const {openModal} = useModalControls() - - const onSetAge = React.useCallback( - () => openModal({name: 'birth-date-settings'}), - [openModal], - ) - - const onToggleAdultContent = React.useCallback(async () => { - if (isIOS) return - - try { - mutate({ - enabled: !( - variables?.enabled ?? preferences?.moderationOpts.adultContentEnabled - ), - }) - } catch (e) { - Toast.show( - _(msg`There was an issue syncing your preferences with the server`), - ) - logger.error('Failed to update preferences with server', {message: e}) - } - }, [variables, preferences, mutate, _]) - - const onAdultContentLinkPress = React.useCallback(() => { - Linking.openURL('https://bsky.app/') - }, []) - - return ( - - {isIOS ? ( - preferences?.moderationOpts.adultContentEnabled ? null : ( - - - Adult content can only be enabled via the Web at{' '} - - . - - - ) - ) : typeof preferences?.birthDate === 'undefined' ? ( - - - Confirm your age to enable adult content. - -