flip the preferences and labeler reads to sdk types
`UsePreferencesQueryResponse` now derives from the sdk's `BskyPreferences` rather than the legacy one, which removes the cast the previous slice put on the assembled response - the query already returns the sdk action's result, so the two now type structurally. `labeler.ts`'s three service reads move to the appview client and return `app.bsky.labeler.defs` views, because `interpretLabelValueDefinitions` takes the lexicon-typed view. `moderation-opts.tsx` reads `Client.appLabelers` instead of `AtpAgent.appLabelers`: the lex static is already branded, so the fallback labeler list satisfies `ModerationPrefsLabeler` without a cast. `MutedWords` follows the prefs types: `expiresAt` is a `DatetimeString` now, built with `toDatetimeString` rather than `Date.toISOString`, and `sanitizeMutedWordValue` comes from `@bsky.app/sdk/utils`.
This commit is contained in:
@@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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])
|
||||
|
||||
|
||||
@@ -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<string, InterpretedLabelValueDefinition[]>
|
||||
labelers: AppBskyLabelerDefs.LabelerViewDetailed[]
|
||||
labelers: app.bsky.labeler.defs.LabelerViewDetailed[]
|
||||
}
|
||||
|
||||
const stateContext = createContext<StateContext>({
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {DEFAULT_LABEL_SETTINGS} from '@atproto/api'
|
||||
import {DEFAULT_LABEL_SETTINGS} from '@bsky.app/sdk/moderation'
|
||||
|
||||
import {
|
||||
type ThreadViewPreferences,
|
||||
|
||||
@@ -89,13 +89,10 @@ export function usePreferencesQuery() {
|
||||
agent.configureLabelers(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 +108,7 @@ export function usePreferencesQuery() {
|
||||
...(res.threadViewPrefs ?? {}),
|
||||
},
|
||||
userAge: res.birthDate ? getAge(res.birthDate) : undefined,
|
||||
} as UsePreferencesQueryResponse
|
||||
}
|
||||
return preferences
|
||||
}
|
||||
},
|
||||
@@ -381,11 +378,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 +393,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 +408,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 +423,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,
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user