about summary refs log tree commit diff
path: root/src/state/queries
diff options
context:
space:
mode:
Diffstat (limited to 'src/state/queries')
-rw-r--r--src/state/queries/actor-autocomplete.ts19
-rw-r--r--src/state/queries/labeler.ts89
-rw-r--r--src/state/queries/notifications/util.ts30
-rw-r--r--src/state/queries/post-feed.ts26
-rw-r--r--src/state/queries/post-liked-by.ts4
-rw-r--r--src/state/queries/preferences/const.ts18
-rw-r--r--src/state/queries/preferences/index.ts100
-rw-r--r--src/state/queries/preferences/moderation.ts218
-rw-r--r--src/state/queries/preferences/types.ts33
-rw-r--r--src/state/queries/preferences/util.ts16
-rw-r--r--src/state/queries/profile-extra-info.ts34
-rw-r--r--src/state/queries/suggested-follows.ts3
12 files changed, 227 insertions, 363 deletions
diff --git a/src/state/queries/actor-autocomplete.ts b/src/state/queries/actor-autocomplete.ts
index 3159ad7aa..f14b3d65f 100644
--- a/src/state/queries/actor-autocomplete.ts
+++ b/src/state/queries/actor-autocomplete.ts
@@ -6,17 +6,14 @@ import {logger} from '#/logger'
 import {getAgent} from '#/state/session'
 import {useMyFollowsQuery} from '#/state/queries/my-follows'
 import {STALE} from '#/state/queries'
-import {
-  DEFAULT_LOGGED_OUT_PREFERENCES,
-  getModerationOpts,
-  useModerationOpts,
-} from './preferences'
+import {DEFAULT_LOGGED_OUT_PREFERENCES, useModerationOpts} from './preferences'
 import {isInvalidHandle} from '#/lib/strings/handles'
+import {isJustAMute} from '#/lib/moderation'
 
-const DEFAULT_MOD_OPTS = getModerationOpts({
-  userDid: '',
-  preferences: DEFAULT_LOGGED_OUT_PREFERENCES,
-})
+const DEFAULT_MOD_OPTS = {
+  userDid: undefined,
+  prefs: DEFAULT_LOGGED_OUT_PREFERENCES.moderationPrefs,
+}
 
 export const RQKEY = (prefix: string) => ['actor-autocomplete', prefix]
 
@@ -114,8 +111,8 @@ function computeSuggestions(
     }
   }
   return items.filter(profile => {
-    const mod = moderateProfile(profile, moderationOpts)
-    return !mod.account.filter && mod.account.cause?.type !== 'muted'
+    const modui = moderateProfile(profile, moderationOpts).ui('profileList')
+    return !modui.filter || isJustAMute(modui)
   })
 }
 
diff --git a/src/state/queries/labeler.ts b/src/state/queries/labeler.ts
new file mode 100644
index 000000000..c405a6b57
--- /dev/null
+++ b/src/state/queries/labeler.ts
@@ -0,0 +1,89 @@
+import {z} from 'zod'
+import {useQuery, useMutation, useQueryClient} from '@tanstack/react-query'
+import {AppBskyLabelerDefs} from '@atproto/api'
+
+import {getAgent} from '#/state/session'
+import {preferencesQueryKey} from '#/state/queries/preferences'
+import {STALE, PUBLIC_BSKY_AGENT} from '#/state/queries'
+
+export const labelerInfoQueryKey = (did: string) => ['labeler-info', did]
+export const labelersInfoQueryKey = (dids: string[]) => [
+  'labelers-info',
+  dids.sort(),
+]
+export const labelersDetailedInfoQueryKey = (dids: string[]) => [
+  'labelers-detailed-info',
+  dids,
+]
+
+export function useLabelerInfoQuery({
+  did,
+  enabled,
+}: {
+  did?: string
+  enabled?: boolean
+}) {
+  return useQuery({
+    enabled: !!did && enabled !== false,
+    queryKey: labelerInfoQueryKey(did as string),
+    queryFn: async () => {
+      const res = await PUBLIC_BSKY_AGENT.app.bsky.labeler.getServices({
+        dids: [did as string],
+        detailed: true,
+      })
+      return res.data.views[0] as AppBskyLabelerDefs.LabelerViewDetailed
+    },
+  })
+}
+
+export function useLabelersInfoQuery({dids}: {dids: string[]}) {
+  return useQuery({
+    enabled: !!dids.length,
+    queryKey: labelersInfoQueryKey(dids),
+    queryFn: async () => {
+      const res = await PUBLIC_BSKY_AGENT.app.bsky.labeler.getServices({dids})
+      return res.data.views as AppBskyLabelerDefs.LabelerView[]
+    },
+  })
+}
+
+export function useLabelersDetailedInfoQuery({dids}: {dids: string[]}) {
+  return useQuery({
+    enabled: !!dids.length,
+    queryKey: labelersDetailedInfoQueryKey(dids),
+    gcTime: 1000 * 60 * 60 * 6, // 6 hours
+    staleTime: STALE.MINUTES.ONE,
+    queryFn: async () => {
+      const res = await PUBLIC_BSKY_AGENT.app.bsky.labeler.getServices({
+        dids,
+        detailed: true,
+      })
+      return res.data.views as AppBskyLabelerDefs.LabelerViewDetailed[]
+    },
+  })
+}
+
+export function useLabelerSubscriptionMutation() {
+  const queryClient = useQueryClient()
+
+  return useMutation({
+    async mutationFn({did, subscribe}: {did: string; subscribe: boolean}) {
+      // TODO
+      z.object({
+        did: z.string(),
+        subscribe: z.boolean(),
+      }).parse({did, subscribe})
+
+      if (subscribe) {
+        await getAgent().addLabeler(did)
+      } else {
+        await getAgent().removeLabeler(did)
+      }
+    },
+    onSuccess() {
+      queryClient.invalidateQueries({
+        queryKey: preferencesQueryKey,
+      })
+    },
+  })
+}
diff --git a/src/state/queries/notifications/util.ts b/src/state/queries/notifications/util.ts
index 626d3e911..97fc57dc1 100644
--- a/src/state/queries/notifications/util.ts
+++ b/src/state/queries/notifications/util.ts
@@ -1,14 +1,13 @@
 import {
   AppBskyNotificationListNotifications,
   ModerationOpts,
-  moderateProfile,
+  moderateNotification,
   AppBskyFeedDefs,
   AppBskyFeedPost,
   AppBskyFeedRepost,
   AppBskyFeedLike,
   AppBskyEmbedRecord,
 } from '@atproto/api'
-import {moderatePost_wrapped as moderatePost} from '#/lib/moderatePost_wrapped'
 import chunk from 'lodash.chunk'
 import {QueryClient} from '@tanstack/react-query'
 import {getAgent} from '../../session'
@@ -88,37 +87,20 @@ export async function fetchPage({
 // internal methods
 // =
 
-// TODO this should be in the sdk as moderateNotification -prf
-function shouldFilterNotif(
+export function shouldFilterNotif(
   notif: AppBskyNotificationListNotifications.Notification,
   moderationOpts: ModerationOpts | undefined,
 ): boolean {
   if (!moderationOpts) {
     return false
   }
-  const profile = moderateProfile(notif.author, moderationOpts)
-  if (
-    profile.account.filter ||
-    profile.profile.filter ||
-    notif.author.viewer?.muted
-  ) {
-    return true
-  }
-  if (
-    notif.type === 'reply' ||
-    notif.type === 'quote' ||
-    notif.type === 'mention'
-  ) {
-    // NOTE: the notification overlaps the post enough for this to work
-    const post = moderatePost(notif, moderationOpts)
-    if (post.content.filter) {
-      return true
-    }
+  if (notif.author.viewer?.following) {
+    return false
   }
-  return false
+  return moderateNotification(notif, moderationOpts).ui('contentList').filter
 }
 
-function groupNotifications(
+export function groupNotifications(
   notifs: AppBskyNotificationListNotifications.Notification[],
 ): FeedNotification[] {
   const groupedNotifs: FeedNotification[] = []
diff --git a/src/state/queries/post-feed.ts b/src/state/queries/post-feed.ts
index c295ffcb0..0e6eef52c 100644
--- a/src/state/queries/post-feed.ts
+++ b/src/state/queries/post-feed.ts
@@ -3,8 +3,8 @@ import {AppState} from 'react-native'
 import {
   AppBskyFeedDefs,
   AppBskyFeedPost,
+  ModerationDecision,
   AtUri,
-  PostModeration,
 } from '@atproto/api'
 import {
   useInfiniteQuery,
@@ -29,7 +29,6 @@ import {STALE} from '#/state/queries'
 import {precacheFeedPostProfiles} from './profile'
 import {getAgent} from '#/state/session'
 import {DEFAULT_LOGGED_OUT_PREFERENCES} from '#/state/queries/preferences/const'
-import {getModerationOpts} from '#/state/queries/preferences/moderation'
 import {KnownError} from '#/view/com/posts/FeedErrorMessage'
 import {embedViewRecordToPostView, getEmbeddedPost} from './util'
 import {useModerationOpts} from './preferences'
@@ -69,7 +68,7 @@ export interface FeedPostSliceItem {
   post: AppBskyFeedDefs.PostView
   record: AppBskyFeedPost.Record
   reason?: AppBskyFeedDefs.ReasonRepost | ReasonFeedSource
-  moderation: PostModeration
+  moderation: ModerationDecision
 }
 
 export interface FeedPostSlice {
@@ -250,9 +249,17 @@ export function usePostFeedQuery(
 
                   // apply moderation filter
                   for (let i = 0; i < slice.items.length; i++) {
+                    const ignoreFilter =
+                      slice.items[i].post.author.did === ignoreFilterFor
+                    if (ignoreFilter) {
+                      // remove mutes to avoid confused UIs
+                      moderations[i].causes = moderations[i].causes.filter(
+                        cause => cause.type !== 'muted',
+                      )
+                    }
                     if (
-                      moderations[i]?.content.filter &&
-                      slice.items[i].post.author.did !== ignoreFilterFor
+                      !ignoreFilter &&
+                      moderations[i]?.ui('contentList').filter
                     ) {
                       return undefined
                     }
@@ -435,13 +442,12 @@ function assertSomePostsPassModeration(feed: AppBskyFeedDefs.FeedViewPost[]) {
   let somePostsPassModeration = false
 
   for (const item of feed) {
-    const moderationOpts = getModerationOpts({
-      userDid: '',
-      preferences: DEFAULT_LOGGED_OUT_PREFERENCES,
+    const moderation = moderatePost(item.post, {
+      userDid: undefined,
+      prefs: DEFAULT_LOGGED_OUT_PREFERENCES.moderationPrefs,
     })
-    const moderation = moderatePost(item.post, moderationOpts)
 
-    if (!moderation.content.filter) {
+    if (!moderation.ui('contentList').filter) {
       // we have a sfw post
       somePostsPassModeration = true
     }
diff --git a/src/state/queries/post-liked-by.ts b/src/state/queries/post-liked-by.ts
index 2cde07f28..a0498ada4 100644
--- a/src/state/queries/post-liked-by.ts
+++ b/src/state/queries/post-liked-by.ts
@@ -12,9 +12,9 @@ const PAGE_SIZE = 30
 type RQPageParam = string | undefined
 
 // TODO refactor invalidate on mutate?
-export const RQKEY = (resolvedUri: string) => ['post-liked-by', resolvedUri]
+export const RQKEY = (resolvedUri: string) => ['liked-by', resolvedUri]
 
-export function usePostLikedByQuery(resolvedUri: string | undefined) {
+export function useLikedByQuery(resolvedUri: string | undefined) {
   return useInfiniteQuery<
     AppBskyFeedGetLikes.OutputSchema,
     Error,
diff --git a/src/state/queries/preferences/const.ts b/src/state/queries/preferences/const.ts
index 53c9e482a..4cb4d1e96 100644
--- a/src/state/queries/preferences/const.ts
+++ b/src/state/queries/preferences/const.ts
@@ -29,26 +29,20 @@ export const DEFAULT_PROD_FEEDS = {
 
 export const DEFAULT_LOGGED_OUT_PREFERENCES: UsePreferencesQueryResponse = {
   birthDate: new Date('2022-11-17'), // TODO(pwi)
-  adultContentEnabled: false,
   feeds: {
     saved: [],
     pinned: [],
     unpinned: [],
   },
-  // labels are undefined until set by user
-  contentLabels: {
-    nsfw: DEFAULT_LOGGED_OUT_LABEL_PREFERENCES.nsfw,
-    nudity: DEFAULT_LOGGED_OUT_LABEL_PREFERENCES.nudity,
-    suggestive: DEFAULT_LOGGED_OUT_LABEL_PREFERENCES.suggestive,
-    gore: DEFAULT_LOGGED_OUT_LABEL_PREFERENCES.gore,
-    hate: DEFAULT_LOGGED_OUT_LABEL_PREFERENCES.hate,
-    spam: DEFAULT_LOGGED_OUT_LABEL_PREFERENCES.spam,
-    impersonation: DEFAULT_LOGGED_OUT_LABEL_PREFERENCES.impersonation,
+  moderationPrefs: {
+    adultContentEnabled: false,
+    labels: DEFAULT_LOGGED_OUT_LABEL_PREFERENCES,
+    labelers: [],
+    mutedWords: [],
+    hiddenPosts: [],
   },
   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 37ef10ae0..cfc5c5bbe 100644
--- a/src/state/queries/preferences/index.ts
+++ b/src/state/queries/preferences/index.ts
@@ -1,29 +1,27 @@
-import {useMemo} from 'react'
+import {useMemo, createContext, useContext} from 'react'
 import {useQuery, useMutation, useQueryClient} from '@tanstack/react-query'
 import {
   LabelPreference,
   BskyFeedViewPreference,
+  ModerationOpts,
   AppBskyActorDefs,
 } from '@atproto/api'
 
 import {track} from '#/lib/analytics/analytics'
 import {getAge} from '#/lib/strings/time'
-import {useSession, getAgent} from '#/state/session'
-import {DEFAULT_LABEL_PREFERENCES} from '#/state/queries/preferences/moderation'
+import {getAgent, useSession} from '#/state/session'
 import {
-  ConfigurableLabelGroup,
   UsePreferencesQueryResponse,
   ThreadViewPreferences,
 } from '#/state/queries/preferences/types'
-import {temp__migrateLabelPref} from '#/state/queries/preferences/util'
 import {
   DEFAULT_HOME_FEED_PREFS,
   DEFAULT_THREAD_VIEW_PREFS,
   DEFAULT_LOGGED_OUT_PREFERENCES,
 } from '#/state/queries/preferences/const'
-import {getModerationOpts} from '#/state/queries/preferences/moderation'
 import {STALE} from '#/state/queries'
-import {useHiddenPosts} from '#/state/preferences/hidden-posts'
+import {useHiddenPosts, useLabelDefinitions} from '#/state/preferences'
+import {saveLabelers} from '#/state/session/agent-config'
 
 export * from '#/state/queries/preferences/types'
 export * from '#/state/queries/preferences/moderation'
@@ -44,6 +42,13 @@ export function usePreferencesQuery() {
         return DEFAULT_LOGGED_OUT_PREFERENCES
       } else {
         const res = await agent.getPreferences()
+
+        // save to local storage to ensure there are labels on initial requests
+        saveLabelers(
+          agent.session.did,
+          res.moderationPrefs.labelers.map(l => l.did),
+        )
+
         const preferences: UsePreferencesQueryResponse = {
           ...res,
           feeds: {
@@ -54,32 +59,6 @@ export function usePreferencesQuery() {
                 return !res.feeds.pinned?.includes(f)
               }) || [],
           },
-          // labels are undefined until set by user
-          contentLabels: {
-            nsfw: temp__migrateLabelPref(
-              res.contentLabels?.nsfw || DEFAULT_LABEL_PREFERENCES.nsfw,
-            ),
-            nudity: temp__migrateLabelPref(
-              res.contentLabels?.nudity || DEFAULT_LABEL_PREFERENCES.nudity,
-            ),
-            suggestive: temp__migrateLabelPref(
-              res.contentLabels?.suggestive ||
-                DEFAULT_LABEL_PREFERENCES.suggestive,
-            ),
-            gore: temp__migrateLabelPref(
-              res.contentLabels?.gore || DEFAULT_LABEL_PREFERENCES.gore,
-            ),
-            hate: temp__migrateLabelPref(
-              res.contentLabels?.hate || DEFAULT_LABEL_PREFERENCES.hate,
-            ),
-            spam: temp__migrateLabelPref(
-              res.contentLabels?.spam || DEFAULT_LABEL_PREFERENCES.spam,
-            ),
-            impersonation: temp__migrateLabelPref(
-              res.contentLabels?.impersonation ||
-                DEFAULT_LABEL_PREFERENCES.impersonation,
-            ),
-          },
           feedViewPrefs: {
             ...DEFAULT_HOME_FEED_PREFS,
             ...(res.feedViewPrefs.home || {}),
@@ -96,25 +75,30 @@ export function usePreferencesQuery() {
   })
 }
 
+// used in the moderation state devtool
+export const moderationOptsOverrideContext = createContext<
+  ModerationOpts | undefined
+>(undefined)
+
 export function useModerationOpts() {
+  const override = useContext(moderationOptsOverrideContext)
   const {currentAccount} = useSession()
   const prefs = usePreferencesQuery()
-  const hiddenPosts = useHiddenPosts()
-  const opts = useMemo(() => {
+  const {labelDefs} = useLabelDefinitions()
+  const hiddenPosts = useHiddenPosts() // TODO move this into pds-stored prefs
+  const opts = useMemo<ModerationOpts | undefined>(() => {
+    if (override) {
+      return override
+    }
     if (!prefs.data) {
       return
     }
-    const moderationOpts = getModerationOpts({
-      userDid: currentAccount?.did || '',
-      preferences: prefs.data,
-    })
-
     return {
-      ...moderationOpts,
-      hiddenPosts,
-      mutedWords: prefs.data.mutedWords || [],
+      userDid: currentAccount?.did,
+      prefs: {...prefs.data.moderationPrefs, hiddenPosts: hiddenPosts || []},
+      labelDefs,
     }
-  }, [currentAccount?.did, prefs.data, hiddenPosts])
+  }, [override, currentAccount, labelDefs, prefs.data, hiddenPosts])
   return opts
 }
 
@@ -138,10 +122,32 @@ export function usePreferencesSetContentLabelMutation() {
   return useMutation<
     void,
     unknown,
-    {labelGroup: ConfigurableLabelGroup; visibility: LabelPreference}
+    {label: string; visibility: LabelPreference; labelerDid: string | undefined}
   >({
-    mutationFn: async ({labelGroup, visibility}) => {
-      await getAgent().setContentLabelPref(labelGroup, visibility)
+    mutationFn: async ({label, visibility, labelerDid}) => {
+      await getAgent().setContentLabelPref(label, visibility, labelerDid)
+      // triggers a refetch
+      await queryClient.invalidateQueries({
+        queryKey: preferencesQueryKey,
+      })
+    },
+  })
+}
+
+export function useSetContentLabelMutation() {
+  const queryClient = useQueryClient()
+
+  return useMutation({
+    mutationFn: async ({
+      label,
+      visibility,
+      labelerDid,
+    }: {
+      label: string
+      visibility: LabelPreference
+      labelerDid?: string
+    }) => {
+      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 cdae52937..9cd183e8b 100644
--- a/src/state/queries/preferences/moderation.ts
+++ b/src/state/queries/preferences/moderation.ts
@@ -1,181 +1,53 @@
+import React from 'react'
 import {
-  LabelPreference,
-  ComAtprotoLabelDefs,
-  ModerationOpts,
+  DEFAULT_LABEL_SETTINGS,
+  BskyAgent,
+  interpretLabelValueDefinitions,
 } from '@atproto/api'
 
-import {
-  LabelGroup,
-  ConfigurableLabelGroup,
-  UsePreferencesQueryResponse,
-} from '#/state/queries/preferences/types'
-
-export type Label = ComAtprotoLabelDefs.Label
-
-export type LabelGroupConfig = {
-  id: LabelGroup
-  title: string
-  isAdultImagery?: boolean
-  subtitle?: string
-  warning: string
-  values: string[]
-}
-
-export const DEFAULT_LABEL_PREFERENCES: Record<
-  ConfigurableLabelGroup,
-  LabelPreference
-> = {
-  nsfw: 'hide',
-  nudity: 'warn',
-  suggestive: 'warn',
-  gore: 'warn',
-  hate: 'hide',
-  spam: 'hide',
-  impersonation: 'hide',
-}
+import {usePreferencesQuery} from './index'
+import {useLabelersDetailedInfoQuery} from '../labeler'
 
 /**
  * More strict than our default settings for logged in users.
- *
- * TODO(pwi)
  */
-export const DEFAULT_LOGGED_OUT_LABEL_PREFERENCES: Record<
-  ConfigurableLabelGroup,
-  LabelPreference
-> = {
-  nsfw: 'hide',
-  nudity: 'hide',
-  suggestive: 'hide',
-  gore: 'hide',
-  hate: 'hide',
-  spam: 'hide',
-  impersonation: 'hide',
-}
-
-export const ILLEGAL_LABEL_GROUP: LabelGroupConfig = {
-  id: 'illegal',
-  title: 'Illegal Content',
-  warning: 'Illegal Content',
-  values: ['csam', 'dmca-violation', 'nudity-nonconsensual'],
-}
-
-export const ALWAYS_FILTER_LABEL_GROUP: LabelGroupConfig = {
-  id: 'always-filter',
-  title: 'Content Warning',
-  warning: 'Content Warning',
-  values: ['!filter'],
-}
-
-export const ALWAYS_WARN_LABEL_GROUP: LabelGroupConfig = {
-  id: 'always-warn',
-  title: 'Content Warning',
-  warning: 'Content Warning',
-  values: ['!warn', 'account-security'],
-}
-
-export const UNKNOWN_LABEL_GROUP: LabelGroupConfig = {
-  id: 'unknown',
-  title: 'Unknown Label',
-  warning: 'Content Warning',
-  values: [],
-}
-
-export const CONFIGURABLE_LABEL_GROUPS: Record<
-  ConfigurableLabelGroup,
-  LabelGroupConfig
-> = {
-  nsfw: {
-    id: 'nsfw',
-    title: 'Explicit Sexual Images',
-    subtitle: 'i.e. pornography',
-    warning: 'Sexually Explicit',
-    values: ['porn', 'nsfl'],
-    isAdultImagery: true,
-  },
-  nudity: {
-    id: 'nudity',
-    title: 'Other Nudity',
-    subtitle: 'Including non-sexual and artistic',
-    warning: 'Nudity',
-    values: ['nudity'],
-    isAdultImagery: true,
-  },
-  suggestive: {
-    id: 'suggestive',
-    title: 'Sexually Suggestive',
-    subtitle: 'Does not include nudity',
-    warning: 'Sexually Suggestive',
-    values: ['sexual'],
-    isAdultImagery: true,
-  },
-  gore: {
-    id: 'gore',
-    title: 'Violent / Bloody',
-    subtitle: 'Gore, self-harm, torture',
-    warning: 'Violence',
-    values: ['gore', 'self-harm', 'torture', 'nsfl', 'corpse'],
-    isAdultImagery: true,
-  },
-  hate: {
-    id: 'hate',
-    title: 'Hate Group Iconography',
-    subtitle: 'Images of terror groups, articles covering events, etc.',
-    warning: 'Hate Groups',
-    values: ['icon-kkk', 'icon-nazi', 'icon-intolerant', 'behavior-intolerant'],
-  },
-  spam: {
-    id: 'spam',
-    title: 'Spam',
-    subtitle: 'Excessive unwanted interactions',
-    warning: 'Spam',
-    values: ['spam'],
-  },
-  impersonation: {
-    id: 'impersonation',
-    title: 'Impersonation',
-    subtitle: 'Accounts falsely claiming to be people or orgs',
-    warning: 'Impersonation',
-    values: ['impersonation'],
-  },
-}
-
-export function getModerationOpts({
-  userDid,
-  preferences,
-}: {
-  userDid: string
-  preferences: UsePreferencesQueryResponse
-}): ModerationOpts {
-  return {
-    userDid: userDid,
-    adultContentEnabled: preferences.adultContentEnabled,
-    labels: {
-      porn: preferences.contentLabels.nsfw,
-      sexual: preferences.contentLabels.suggestive,
-      nudity: preferences.contentLabels.nudity,
-      nsfl: preferences.contentLabels.gore,
-      corpse: preferences.contentLabels.gore,
-      gore: preferences.contentLabels.gore,
-      torture: preferences.contentLabels.gore,
-      'self-harm': preferences.contentLabels.gore,
-      'intolerant-race': preferences.contentLabels.hate,
-      'intolerant-gender': preferences.contentLabels.hate,
-      'intolerant-sexual-orientation': preferences.contentLabels.hate,
-      'intolerant-religion': preferences.contentLabels.hate,
-      intolerant: preferences.contentLabels.hate,
-      'icon-intolerant': preferences.contentLabels.hate,
-      spam: preferences.contentLabels.spam,
-      impersonation: preferences.contentLabels.impersonation,
-      scam: 'warn',
-    },
-    labelers: [
-      {
-        labeler: {
-          did: '',
-          displayName: 'Bluesky Social',
-        },
-        labels: {},
-      },
-    ],
-  }
+export const DEFAULT_LOGGED_OUT_LABEL_PREFERENCES: typeof DEFAULT_LABEL_SETTINGS =
+  Object.fromEntries(
+    Object.entries(DEFAULT_LABEL_SETTINGS).map(([key, _pref]) => [key, 'hide']),
+  )
+
+export function useMyLabelersQuery() {
+  const prefs = usePreferencesQuery()
+  const dids = Array.from(
+    new Set(
+      BskyAgent.appLabelers.concat(
+        prefs.data?.moderationPrefs.labelers.map(l => l.did) || [],
+      ),
+    ),
+  )
+  const labelers = useLabelersDetailedInfoQuery({dids})
+  const isLoading = prefs.isLoading || labelers.isLoading
+  const error = prefs.error || labelers.error
+  return React.useMemo(() => {
+    return {
+      isLoading,
+      error,
+      data: labelers.data,
+    }
+  }, [labelers, isLoading, error])
+}
+
+export function useLabelDefinitionsQuery() {
+  const labelers = useMyLabelersQuery()
+  return React.useMemo(() => {
+    return {
+      labelDefs: Object.fromEntries(
+        (labelers.data || []).map(labeler => [
+          labeler.creator.did,
+          interpretLabelValueDefinitions(labeler),
+        ]),
+      ),
+      labelers: labelers.data || [],
+    }
+  }, [labelers])
 }
diff --git a/src/state/queries/preferences/types.ts b/src/state/queries/preferences/types.ts
index 45c9eed7d..96da16f1a 100644
--- a/src/state/queries/preferences/types.ts
+++ b/src/state/queries/preferences/types.ts
@@ -1,46 +1,13 @@
 import {
   BskyPreferences,
-  LabelPreference,
   BskyThreadViewPreference,
   BskyFeedViewPreference,
 } from '@atproto/api'
 
-export const configurableAdultLabelGroups = [
-  'nsfw',
-  'nudity',
-  'suggestive',
-  'gore',
-] as const
-
-export const configurableOtherLabelGroups = [
-  'hate',
-  'spam',
-  'impersonation',
-] as const
-
-export const configurableLabelGroups = [
-  ...configurableAdultLabelGroups,
-  ...configurableOtherLabelGroups,
-] as const
-export type ConfigurableLabelGroup = (typeof configurableLabelGroups)[number]
-
-export type LabelGroup =
-  | ConfigurableLabelGroup
-  | 'illegal'
-  | 'always-filter'
-  | 'always-warn'
-  | 'unknown'
-
 export type UsePreferencesQueryResponse = Omit<
   BskyPreferences,
   'contentLabels' | 'feedViewPrefs' | 'feeds'
 > & {
-  /*
-   * Content labels previously included 'show', which has been deprecated in
-   * favor of 'ignore'. The API can return legacy data from the database, and
-   * we clean up the data in `usePreferencesQuery`.
-   */
-  contentLabels: Record<ConfigurableLabelGroup, LabelPreference>
   feedViewPrefs: BskyFeedViewPreference & {
     lab_mergeFeedEnabled?: boolean
   }
diff --git a/src/state/queries/preferences/util.ts b/src/state/queries/preferences/util.ts
deleted file mode 100644
index 7b8160c28..000000000
--- a/src/state/queries/preferences/util.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import {LabelPreference} from '@atproto/api'
-
-/**
- * Content labels previously included 'show', which has been deprecated in
- * favor of 'ignore'. The API can return legacy data from the database, and
- * we clean up the data in `usePreferencesQuery`.
- *
- * @deprecated
- */
-export function temp__migrateLabelPref(
-  pref: LabelPreference | 'show',
-): LabelPreference {
-  // @ts-ignore
-  if (pref === 'show') return 'ignore'
-  return pref
-}
diff --git a/src/state/queries/profile-extra-info.ts b/src/state/queries/profile-extra-info.ts
deleted file mode 100644
index 8fc32c33e..000000000
--- a/src/state/queries/profile-extra-info.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-import {useQuery} from '@tanstack/react-query'
-
-import {getAgent} from '#/state/session'
-import {STALE} from '#/state/queries'
-
-// TODO refactor invalidate on mutate?
-export const RQKEY = (did: string) => ['profile-extra-info', did]
-
-/**
- * Fetches some additional information for the profile screen which
- * is not available in the API's ProfileView
- */
-export function useProfileExtraInfoQuery(did: string) {
-  return useQuery({
-    staleTime: STALE.MINUTES.ONE,
-    queryKey: RQKEY(did),
-    async queryFn() {
-      const [listsRes, feedsRes] = await Promise.all([
-        getAgent().app.bsky.graph.getLists({
-          actor: did,
-          limit: 1,
-        }),
-        getAgent().app.bsky.feed.getActorFeeds({
-          actor: did,
-          limit: 1,
-        }),
-      ])
-      return {
-        hasLists: listsRes.data.lists.length > 0,
-        hasFeedgens: feedsRes.data.feeds.length > 0,
-      }
-    },
-  })
-}
diff --git a/src/state/queries/suggested-follows.ts b/src/state/queries/suggested-follows.ts
index 932226b75..45b3ebb62 100644
--- a/src/state/queries/suggested-follows.ts
+++ b/src/state/queries/suggested-follows.ts
@@ -46,7 +46,8 @@ export function useSuggestedFollowsQuery() {
 
       res.data.actors = res.data.actors
         .filter(
-          actor => !moderateProfile(actor, moderationOpts!).account.filter,
+          actor =>
+            !moderateProfile(actor, moderationOpts!).ui('profileList').filter,
         )
         .filter(actor => {
           const viewer = actor.viewer